redis sentinel 集群
参照课程:https://www.bilibili.com/video/BV1VJ411B7Kr?p=4
redis sentinel 集群【哨兵 主从复制集群】
* 哨兵要做的事情:
. 将宕机的master下线
. 找slave作为master
. 通知所有的slave来连接新的master
.启动新的master 与 slave
. 全量复制*N + 部分复制*N
***********************************
哨兵来确认master宕机
哨兵选举master
主恢复如何处理,哨兵把主变为从
总结哨兵作用:
监控:
不断的检查master 和 slave是否正常运行。
master 存活检测、master与slave运行情况检测。
通知:
当被监控的服务器出现问题时,向其他(哨兵,客户端)发送通知。
自动故障转移:
断开master 与 slave 连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址。
【注意】
sentinel 之间是通过发布订阅的方式来同步信息。
哨兵也是一台 redis 服务器,只是不提供数据服务
通常哨兵配置数量为单数
1. 主观下线(Subjectively Down, 简称 SDOWN)指的是单个 Sentinel 实例对服务器做出的下线判断。
2. 客观下线(Objectively Down, 简称 ODOWN)指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断。
选举:
sentinel 之间选举出一个负责的sentinel x, 然后 负责人 x 从slave 根据网络响应,数据最新的slave作为master.
*搭建环境 --> 3个哨兵 1 主 4从
查看sentinel.conf内容
cat sentinel.conf | grep -v "#" | grep -v "^$"
修改配置:
* 修改sentinel.conf
port 7000
daemonize yes
pidfile /var/run/redis-sentinel7000.pid
logfile "/mnt/hgfs/linux_share/redis_sentinel/redis_s7000/redis.log"
dir /mnt/hgfs/linux_share/redis_sentinel/redis_s7000
sentinel monitor mymaster 127.0.0.1 7003 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes
将配置文件修改到 7001 7002下:
sed 's/7000/7001/g' redis_s7000/sentinel.conf > redis_s7001/sentinel.conf
sed 's/7000/7002/g' redis_s7000/sentinel.conf > redis_s7002/sentinel.conf
* 修改redis.conf
sed 's/7003/7004/g' redis_m7003/redis.conf > redis_sl7004/redis.conf
sed 's\redis7004\redis_sl7004\g' redis_sl7004/redis.conf > redis_sl7004/redis.conf1 && mv redis_sl7004/redis.conf1 redis_sl7004/redis.conf
同时从机7004追加以下选项:【5.0版本使用REPLICAOF代替了之前版本的SLAVEOF,如果使用5.0及之后版本,则建议新命令REPLICAOF】
slaveof 192.168.153.133 7003
复制从机配置文件:
sed 's\7004\7005\g' redis_sl7004/redis.conf > redis_sl7005/redis.conf
sed 's\7004\7006\g' redis_sl7004/redis.conf > redis_sl7006/redis.conf
sed 's\7004\7007\g' redis_sl7004/redis.conf > redis_sl7007/redis.conf
启动主机从机,建立主从模式。
./redis_exe/redis-server ./redis_m7003/redis.conf
./redis_exe/redis-server ./redis_sl7004/redis.conf
./redis_exe/redis-server ./redis_sl7005/redis.conf
./redis_exe/redis-server ./redis_sl7006/redis.conf
./redis_exe/redis-server ./redis_sl7007/redis.conf
查看信息:
./redis_exe/redis-cli -h 127.0.0.1 -p 7003
info Replication
** 启动哨兵
./redis_exe/redis-sentinel ./redis_s7000/sentinel.conf
./redis_exe/redis-sentinel ./redis_s7001/sentinel.conf
./redis_exe/redis-sentinel ./redis_s7002/sentinel.conf
** 查看搭建情况
./redis_exe/redis-cli -h 127.0.0.1 -p 7000
info sentinel
查看端口:说明sentinel 之间的连接为短连接
测试集群:
查看主机信息:
./redis_exe/redis-cli -h 127.0.0.1 -p 7005
info Replication
7006 作为主机,从机无法写入。
【C++ api】
使用 "sentinel slaves mastername" 命令查找从节点。
#if REDIS_WAIT_TIME_OUT int Command_Connect(redisContext *&con, char ip[][32], int port[], int nHandleIndex) #else int Command_Connect(redisContext *&con, char ip[][32], int port[], int /*nHandleIndex*/) #endif { // my_con = redisConnect(ip, port); struct timeval tv; tv.tv_sec = 2; tv.tv_usec = 0; char msg[1024]; redisContext* my_con = NULL; redisContext* real_con = NULL; int nIPIndex = 0; #if REDIS_WAIT_TIME_OUT int nOutTime = 15; // 取得股票代码的句柄 if (CONNECT_GETCODE_INDEX == nHandleIndex) { nOutTime = g_nPushOutTime; } // 取得行情数据的句柄 else if (CONNECT_PRICE_INDEX == nHandleIndex) { nOutTime = g_nGetPriceOutTime; } else { } #endif do { Sleep(500); // 0-集群模式 if (0 == g_nRedisMode) { if (my_con != NULL) { redisFree(my_con); my_con = NULL; } // 连接sentinel 服务器; 以带有超时的方式链接Redis服务器,同时获取与Redis连接的上下文对象。该对象将用于其后所有与Redis操作的函数。 my_con = redisConnectWithTimeout(ip[nIPIndex], port[nIPIndex], tv); // printf("con = %lld, con->obuf---%lld....con->err---%lld\n", my_con, my_con->obuf, my_con->err); // while ((my_con->err) || (my_con->obuf == NULL)) if (NULL == my_con) { memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "--redis数据库连接失败 IP:%s 端口 %d", ip[nIPIndex], port[nIPIndex]); print_write_log(msg, m_module_name, __LINE__, m_error_log); nIPIndex = (nIPIndex + 1) % g_nRedisCountMax; continue; } if (my_con->err) { memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "--redis数据库连接失败 IP:%s 端口 %d", ip[nIPIndex], port[nIPIndex]); print_write_log(msg, m_module_name, __LINE__, m_error_log); nIPIndex = (nIPIndex + 1) % g_nRedisCountMax; continue; } // 打印日志 memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "sentinel连接成功 IP:%s 端口 %d", ip[nIPIndex], port[nIPIndex]); print_write_log(msg, m_module_name, __LINE__, m_error_log); #if 0 // 连接备机redis redisReply *reply = (redisReply *)redisCommand(my_con, "sentinel get-master-addr-by-name %s", g_chsentinel_master_name); if ((reply == NULL) || (reply->elements < 2)) { memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "--sentinel get-master-addr-by-name %s 失败, IP:%s 端口 %d", g_chsentinel_master_name, ip[nIPIndex], port[nIPIndex]); print_write_log(msg, m_module_name, __LINE__, m_error_log); continue; } // 实际redis连接 real_con = redisConnectWithTimeout(reply->element[0]->str, atoi(reply->element[1]->str), tv); if (real_con == NULL) { continue; } if (real_con->err) { redisFree(real_con); memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "--redis数据库连接失败 IP:%s 端口 %s", reply->element[0]->str, reply->element[1]->str); print_write_log(msg, m_module_name, __LINE__, m_error_log); continue; } memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "++redis数据库连接成功 IP:%s 端口 %s", reply->element[0]->str, reply->element[1]->str); print_write_log(msg, m_module_name, __LINE__, m_error_log); redisFree(my_con); // 释放sentinel连接 my_con = NULL; #else const int array_len = 128; char all_ip[array_len][32]; int all_port[128]; int index = 0; // 连接主节点 redisReply *reply = (redisReply *)redisCommand(my_con, "sentinel masters"); if (reply == NULL) { memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "--sentinel masters 失败, IP:%s 端口 %d", ip[nIPIndex], port[nIPIndex]); print_write_log(msg, m_module_name, __LINE__, m_error_log); } else { if (reply->elements >= 1) { for (int i = 0; i < reply->elements; i++) { if (reply->element[i]->elements != 40) { continue; } //printf("master is : \t %s %s \n", reply->element[i]->element[3]->str, reply->element[i]->element[5]->str); memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "master is : \t %s %s", reply->element[i]->element[3]->str, reply->element[i]->element[5]->str); print_write_log(msg, m_module_name, __LINE__, m_error_log); strcpy_s(all_ip[index], 32, reply->element[i]->element[3]->str); all_port[index] = atoi(reply->element[i]->element[5]->str); ++index; } } freeReplyObject(reply); } // 连接从节点 reply = (redisReply *)redisCommand(my_con, "sentinel slaves %s", g_chsentinel_master_name); if (reply == NULL) { memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "--sentinel slaves %s 失败, IP:%s 端口 %d", g_chsentinel_master_name, ip[nIPIndex], port[nIPIndex]); print_write_log(msg, m_module_name, __LINE__, m_error_log); } else { if (reply->elements >= 1) { for (int i = 0; i < reply->elements; i++) { if (reply->element[i]->elements != 40) { continue; } //printf("slaves is : \t %s %s \n", reply->element[i]->element[3]->str, reply->element[i]->element[5]->str); memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "slaves is : \t %s %s", reply->element[i]->element[3]->str, reply->element[i]->element[5]->str);; print_write_log(msg, m_module_name, __LINE__, m_error_log); strcpy_s(all_ip[index], 32, reply->element[i]->element[3]->str); all_port[index] = atoi(reply->element[i]->element[5]->str); ++index; } } freeReplyObject(reply); } // 如果主从节点都没有取到,则continue int real_index = GetTickCount() % index; // 实际redis连接 real_con = redisConnectWithTimeout(all_ip[real_index], all_port[real_index], tv); if (real_con == NULL) { continue; } if (real_con->err) { redisFree(real_con); memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "--redis数据库连接失败 IP:%s 端口 %d", all_ip[real_index], all_port[real_index]); print_write_log(msg, m_module_name, __LINE__, m_error_log); continue; } memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "++redis数据库连接成功 IP:%s 端口 %d", all_ip[real_index], all_port[real_index]); print_write_log(msg, m_module_name, __LINE__, m_error_log); redisFree(my_con); // 释放sentinel连接 my_con = NULL; #endif } // 1-单机模式 else { // 实际redis连接 real_con = redisConnectWithTimeout(ip[0], port[0], tv); if (real_con->err) { redisFree(real_con); memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "--redis数据库连接失败 IP:%s 端口 %d", ip[0], port[0]); print_write_log(msg, m_module_name, __LINE__, m_error_log); continue; } memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "++redis连接成功 IP:%s 端口 %d", ip[0], port[0]); print_write_log(msg, m_module_name, __LINE__, m_error_log); } con = real_con; char strcmd[256]; sprintf_s(strcmd, sizeof(strcmd), "auth %s", g_chRedisPassWord); redisReply *reply = (redisReply*)redisCommand(con, strcmd); // 操作失败 if ((NULL == reply) || (reply->type == REDIS_REPLY_ERROR)) { if (NULL != reply) { memset(msg, 0x00, sizeof(msg)); sprintf_s(msg, 1024, "redis密码错误:%s ", reply->str); print_write_log(msg, m_module_name, __LINE__, m_error_log); freeReplyObject(reply); } } else { freeReplyObject(reply); } #if REDIS_WAIT_TIME_OUT // 设定超时时间 struct timeval tv; tv.tv_sec = nOutTime * 1000; // tv_sec 的单位ms tv.tv_usec = 0; redisSetTimeout(con, tv); #endif #if REDIS_PING // 取得实际redis 的 ip/端口 if (CONNECT_GETCODE_INDEX == nHandleIndex) { strcpy_s(g_chPingRedisIp[0], 32, ip[0]); //推送股票的redis的IP g_nPingRedisPort[0] = port[0]; // 推送股票的redis的端口 } #endif break; } while (true); return 0; }