Redis 监控与优化方案(C++项目)
方案概述
针对 C++ 项目中的 Redis 使用,设计一个完整的监控与优化方案,包括:
- 定期扫描 BigKey 和慢查询日志
- 自动上报到报警系统
- 优化大 Hash 结构
- 设置内存上限和淘汰策略
一、Redis 监控脚本设计(C++实现)
1. 扫描 BigKey
#include <cpp_redis/cpp_redis> |
#include <nlohmann/json.hpp> |
#include <vector> |
#include <string> |
#include <chrono> |
#include <thread> |
|
using json = nlohmann::json; |
|
class RedisMonitor { |
public: |
RedisMonitor(const std::string& host, int port, const std::string& password = "") |
: client(host, port) { |
if (!password.empty()) { |
client.auth(password); |
} |
client.connect(); |
} |
|
// 扫描BigKey |
std::vector<json> scanBigKeys(int threshold_kb = 1024) { |
std::vector<json> big_keys; |
|
// 扫描所有键 |
cpp_redis::reply reply = client.scan("0", "COUNT", "1000"); |
client.sync_commit(); |
|
if (reply.is_array() && reply.as_array().size() >= 2) { |
auto keys = reply.as_array()[1].as_array(); |
|
for (const auto& key_reply : keys) { |
std::string key = key_reply.as_string(); |
|
// 获取键类型 |
auto type_reply = client.type(key); |
client.sync_commit(); |
std::string type = type_reply.as_string(); |
|
// 根据类型获取内存使用情况 |
size_t memory = 0; |
if (type == "string") { |
auto len_reply = client.strlen(key); |
client.sync_commit(); |
memory = len_reply.as_integer(); |
} |
else if (type == "hash") { |
auto len_reply = client.hlen(key); |
client.sync_commit(); |
memory = len_reply.as_integer() * 128; // 估算每个字段128字节 |
} |
// 其他类型类似处理... |
|
// 转换为KB |
size_t memory_kb = memory / 1024; |
if (memory_kb >= threshold_kb) { |
json alert; |
alert["key"] = key; |
alert["type"] = type; |
alert["size_kb"] = memory_kb; |
alert["timestamp"] = std::chrono::system_clock::now().time_since_epoch().count(); |
big_keys.push_back(alert); |
} |
} |
} |
|
return big_keys; |
} |
|
// 扫描慢查询日志 |
std::vector<json> scanSlowLogs(int threshold_ms = 100) { |
std::vector<json> slow_logs; |
|
// 获取慢查询日志 |
auto reply = client.slowlog_get(100); // 获取最近100条 |
client.sync_commit(); |
|
if (reply.is_array()) { |
for (const auto& log_entry : reply.as_array()) { |
if (log_entry.is_array() && log_entry.as_array().size() >= 3) { |
int duration = log_entry.as_array()[2].as_integer(); |
if (duration >= threshold_ms) { |
json alert; |
alert["id"] = log_entry.as_array()[0].as_integer(); |
alert["timestamp"] = log_entry.as_array()[1].as_integer(); |
alert["duration_ms"] = duration; |
alert["command"] = log_entry.as_array()[3].as_string(); |
slow_logs.push_back(alert); |
} |
} |
} |
} |
|
return slow_logs; |
} |
|
// 上报到报警系统 |
void reportToAlarmSystem(const std::vector<json>& alerts, const std::string& system_url) { |
// 这里实现上报逻辑,可以是HTTP请求或其他方式 |
for (const auto& alert : alerts) { |
// 示例:打印到控制台,实际应发送到报警系统 |
std::cout << "ALERT: " << alert.dump() << std::endl; |
|
// 实际实现可能使用libcurl等库发送HTTP请求 |
// curl_easy_setopt(curl, CURLOPT_URL, system_url.c_str()); |
// curl_easy_setopt(curl, CURLOPT_POSTFIELDS, alert.dump().c_str()); |
// ... |
} |
} |
|
private: |
cpp_redis::client client; |
}; |
2. 定时监控任务
#include <functional> |
#include <atomic> |
#include <memory> |
|
class ScheduledTask { |
public: |
using TaskFunc = std::function<void()>; |
|
ScheduledTask(int interval_seconds, TaskFunc task) |
: interval(interval_seconds), task(task), running(false) {} |
|
~ScheduledTask() { |
stop(); |
} |
|
void start() { |
running = true; |
thread = std::thread([this]() { |
while (running) { |
auto start = std::chrono::steady_clock::now(); |
|
try { |
task(); |
} catch (const std::exception& e) { |
std::cerr << "Task error: " << e.what() << std::endl; |
} |
|
auto end = std::chrono::steady_clock::now(); |
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(end - start).count(); |
|
if (elapsed < interval) { |
std::this_thread::sleep_for(std::chrono::seconds(interval - elapsed)); |
} |
} |
}); |
} |
|
void stop() { |
if (running) { |
running = false; |
if (thread.joinable()) { |
thread.join(); |
} |
} |
} |
|
private: |
int interval; |
TaskFunc task; |
std::atomic<bool> running; |
std::thread thread; |
}; |
3. 主监控程序
int main() { |
// 初始化Redis监控 |
RedisMonitor monitor("127.0.0.1", 6379, "yourpassword"); |
|
// BigKey扫描任务(每小时执行一次) |
auto bigkey_task = [&monitor]() { |
auto big_keys = monitor.scanBigKeys(1024); // 扫描大于1MB的key |
if (!big_keys.empty()) { |
monitor.reportToAlarmSystem(big_keys, "http://alarm-system/api/bigkey"); |
} |
}; |
|
// 慢查询扫描任务(每5分钟执行一次) |
auto slowlog_task = [&monitor]() { |
auto slow_logs = monitor.scanSlowLogs(100); // 扫描超过100ms的慢查询 |
if (!slow_logs.empty()) { |
monitor.reportToAlarmSystem(slow_logs, "http://alarm-system/api/slowlog"); |
} |
}; |
|
// 启动定时任务 |
ScheduledTask bigkey_scanner(3600, bigkey_task); // 每小时 |
ScheduledTask slowlog_scanner(300, slowlog_task); // 每5分钟 |
|
bigkey_scanner.start(); |
slowlog_scanner.start(); |
|
// 主线程可以做其他事情或简单等待 |
std::cout << "Redis monitoring started..." << std::endl; |
std::this_thread::sleep_for(std::chrono::hours(24)); // 运行24小时 |
|
// 停止监控 |
bigkey_scanner.stop(); |
slowlog_scanner.stop(); |
|
return 0; |
} |
二、大Hash优化方案
1. 自动拆分大Hash为小Hash
class HashOptimizer { |
public: |
HashOptimizer(RedisMonitor& monitor) : monitor(monitor) {} |
|
// 检查并拆分大Hash |
void checkAndSplitLargeHashes(size_t max_field_count = 5000) { |
auto big_keys = monitor.scanBigKeys(1024); // 1MB以上的Hash |
|
for (const auto& alert : big_keys) { |
if (alert["type"] == "hash") { |
std::string key = alert["key"]; |
|
// 获取Hash大小 |
auto hlen_reply = monitor.getClient().hlen(key); |
monitor.getClient().sync_commit(); |
size_t field_count = hlen_reply.as_integer(); |
|
if (field_count > max_field_count) { |
splitLargeHash(key, field_count, max_field_count); |
} |
} |
} |
} |
|
private: |
RedisMonitor& monitor; |
|
// 拆分大Hash |
void splitLargeHash(const std::string& key, size_t total_fields, size_t max_fields) { |
std::cout << "Splitting large hash: " << key << " (" << total_fields << " fields)" << std::endl; |
|
// 计算需要拆分成多少个小Hash |
size_t split_count = (total_fields + max_fields - 1) / max_fields; |
|
// 使用SCAN命令迭代获取所有字段 |
std::string cursor = "0"; |
size_t current_split = 0; |
std::string new_key_prefix = key + ":part_"; |
|
do { |
auto reply = monitor.getClient().hscan(key, cursor, "COUNT", "100"); |
monitor.getClient().sync_commit(); |
|
if (reply.is_array() && reply.as_array().size() >= 2) { |
cursor = reply.as_array()[0].as_string(); |
auto fields = reply.as_array()[1].as_array(); |
|
// 每max_fields个字段创建一个新Hash |
std::string new_key = new_key_prefix + std::to_string(current_split); |
|
for (size_t i = 0; i < fields.size(); i += 2) { |
std::string field = fields[i].as_string(); |
std::string value = fields[i+1].as_string(); |
|
monitor.getClient().hset(new_key, field, value); |
monitor.getClient().sync_commit(); |
} |
|
// 移动到下一个分片 |
if (++current_split >= split_count) { |
current_split = 0; |
} |
} |
} while (cursor != "0"); |
|
// 原始key可以保留作为指针或删除 |
// monitor.getClient().del(key); |
// monitor.getClient().set(key + ":split_info", "split into " + std::to_string(split_count) + " parts"); |
} |
}; |
2. 在监控主程序中添加Hash优化
int main() { |
// ... 前面的RedisMonitor初始化代码 ... |
|
HashOptimizer optimizer(monitor); |
|
// 大Hash优化任务(每天执行一次) |
auto hash_optimize_task = [&optimizer]() { |
optimizer.checkAndSplitLargeHashes(5000); // 每个小Hash最多5000个字段 |
}; |
|
ScheduledTask hash_optimizer(86400, hash_optimize_task); // 每天 |
hash_optimizer.start(); |
|
// ... 其他任务和主循环 ... |
} |
三、Redis内存管理配置
1. Redis配置建议(redis.conf)
ini
# 内存限制(根据服务器内存调整) |
maxmemory 8gb |
|
# 内存淘汰策略(推荐使用allkeys-lru或volatile-lru) |
maxmemory-policy allkeys-lru |
|
# 设置Hash结构的内存优化参数 |
hash-max-ziplist-entries 512 # 当Hash字段数小于此值时使用ziplist编码 |
hash-max-ziplist-value 64 # 当Hash字段值大小小于此值时使用ziplist编码 |
|
# 启用慢查询日志 |
slowlog-log-slower-than 10000 # 记录超过10ms的查询(单位微秒) |
slowlog-max-len 128 # 最多保留128条慢查询 |
2. 在C++中动态设置配置
void configureRedisMemorySettings(cpp_redis::client& client) { |
// 设置内存限制(如果Redis配置文件中未设置) |
try { |
// 检查是否已设置maxmemory |
auto reply = client.config_get("maxmemory"); |
client.sync_commit(); |
|
if (reply.is_array() && reply.as_array().size() >= 2) { |
auto values = reply.as_array()[1].as_array(); |
if (values.empty() || values[0].as_string() == "0") { |
// 未设置内存限制,动态设置 |
client.config_set("maxmemory", "8589934592"); // 8GB |
client.sync_commit(); |
std::cout << "Set Redis maxmemory to 8GB" << std::endl; |
} |
} |
|
// 设置内存淘汰策略 |
client.config_set("maxmemory-policy", "allkeys-lru"); |
client.sync_commit(); |
|
// 设置Hash优化参数 |
client.config_set("hash-max-ziplist-entries", "512"); |
client.config_set("hash-max-ziplist-value", "64"); |
client.sync_commit(); |
|
} catch (const std::exception& e) { |
std::cerr << "Failed to configure Redis: " << e.what() << std::endl; |
} |
} |
四、完整监控与优化流程
- 初始化阶段:
- 连接Redis并验证配置
- 设置合理的内存限制和淘汰策略
- 启动监控任务
- 运行阶段:
- 定期扫描BigKey并报警
- 定期扫描慢查询并报警
- 定期检查并优化大Hash结构
- 监控内存使用情况,接近上限时提前报警
- 报警处理:
- 收到BigKey报警时,评估是否需要拆分或优化数据结构
- 收到慢查询报警时,优化查询语句或添加适当索引
- 内存接近上限时,考虑扩容或清理不必要数据
五、性能考虑与优化
- 监控脚本性能:
- 使用SCAN而非KEYS命令避免阻塞Redis
- 异步上报报警信息,避免影响监控任务执行
- 合理设置扫描间隔,避免过于频繁
- Hash拆分策略:
- 在业务低峰期执行拆分操作
- 考虑使用管道(pipeline)或Lua脚本减少网络往返
- 拆分后保留原始key作为指针或添加说明信息
- 内存优化:
- 监控内存碎片率(
info memory
中的mem_fragmentation_ratio
) - 碎片率过高时考虑重启Redis或使用
MEMORY PURGE
命令(Redis 4.0+)
六、依赖与部署
- 依赖库:
- cpp_redis: C++ Redis客户端
- nlohmann/json: JSON处理
- libcurl(可选): 用于HTTP报警上报
- 部署建议:
- 将监控程序部署为系统服务
- 配置日志轮转
- 设置合理的资源限制(CPU/内存)
- 示例CMakeLists.txt:
cmake_minimum_required(VERSION 3.10) |
project(RedisMonitor) |
|
set(CMAKE_CXX_STANDARD 17) |
|
find_package(cpp_redis REQUIRED) |
find_package(nlohmann_json REQUIRED) |
|
add_executable(redis_monitor |
src/main.cpp |
src/redis_monitor.cpp |
src/scheduled_task.cpp |
src/hash_optimizer.cpp |
) |
|
target_link_libraries(redis_monitor |
PRIVATE |
cpp_redis |
nlohmann_json::nlohmann_json |
pthread |
) |
这个方案提供了从监控到优化的完整流程,可以根据实际项目需求进行调整和扩展。