腾讯云 Redis 集群版配置管理揭秘 ( 下 )

《 腾讯云 Redis 集群版配置管理揭秘 ( 上 )》

接收心跳

​ 配置客户端会通过udp上报[角色,IP,MD5]这样的结构信息,该模块会根据上报信息,更新服务器上的客户端状态列表,以便让服务器及时知道配置客户端的存活与配置情况。[筛选机器]模块会比对客户端状态与服务器配置的MD5,如果不同,将会把相应机器列为待推送的机器。 ​

​
 pstOneVServerClientUpdateInfo->dwServerId = dwServerId;
 pstOneVServerClientUpdateInfo->dwLastUpateTime = tCur;
 pstOneVServerClientUpdateInfo->dwLastUpdatePriority = dwPriority;
 memcpy(pstOneVServerClientUpdateInfo->sMD5Hash,sMd5,sizeof(pstOneVServerClientUpdateInfo->sMD5Hash));

​ ​ 注意:如果是interface上报的心跳,因为interface没有serverId的概念,则只会更新最后心跳时间为当前时间,优先级,md5值等信息。 ​ 配置中心也会给配置客户端回包,表示收到了这个心跳,回包构造过程中有可能有如下异常上报。 ​ "164317 构造状态回应包,序列化错误" "164318 构造状态回应包,包长不够" "164312 构造状态回包错误" "164309 发送状态回应错误" "182798 server状态回包正确量" //这个是与164309相反的上报,表示回包成功,是正常上报 ​ 处理上报过程中,这些异常也得关注。 ​ "164311 cache上报状态,但该cache不在数据库中" cache机往配置中心上报心跳,但配置中心并未记录该cache的IP,这很可能是cache已被移出集群,但cache上的进程还在,导致心跳一直在上报。 ​ "183911 根据serverid获取server信息失败" 一个很底层公共函数,根据serverid,在保存所有VSERVER的服务器配置信息的数组中,寻找该serverid的配置信息。如果寻找不到,就会上报。根据serverid,在保存所有VSERVER的客户端配置信息的数组中,寻找不到该serverid的配置信息,也会进行相应的上报。 ​ "164313 记录cache上报信息,dwServerId为0" 一般是cache刚初始化,从ClientUpdateInfo中,该server_id才会出现还配置为0的情况。 ​ "164315 记录interface上报信息,找到该interface" interface上报心跳时,服务器上记录Interface上报更新信息的列表(InterfaceServerClientUpdateInfoList)中也恰好能找到该IP,进行该上报。属于正常情况。 ​ "646279 不在InerfaceIpList中的IP上报了心跳" 与164315相反,上报的IP不在InerfaceIpList中,这很可能是下线的机器进程未清理,依然在上报心跳信息。 ​ "164307 处理状态上报错误" 配置中心处理配置客户端的心跳上报过程中出现了错误,会有这个上报。 ​ "182903 server更新状态,耗时在0-5" 对配置客户端上报的心跳包进行解包,更新客户端状态列表,以及回包的整个操作耗时多久,单位毫秒。下面几项与此相似。 ​ "182904 server更新状态,耗时在5-10" "182905 server更新状态,耗时在10-20" "182906 server更新状态,耗时在20-50" "182907 server更新状态,耗时在50-100" "182908 server更新状态,耗时在100-200" "182909 server更新状态,耗时在200以上" ​

初始化

​ 1 . 程序启动时必须跟一个配置文件路径参数,路径可以是相对或绝对路径 ​

​
 if (2 != argc)
 {
 printf("\nParam error!\n");
 printf("Usage:%s <CFGFileName> !\n",argv[0]);
 return -1;
 }

 if (argv[1][0] != '/') //绝对路径
 {
 char sCwd[255]={0};
 if (getcwd(sCwd, sizeof(sCwd)) == NULL) 
 {
 printf( "getcwd() failed!\n");
 return -10;
 }
 snprintf(g_szConfFilePath, sizeof(g_szConfFilePath), "%s/%s", sCwd, argv[1]);
 }
 else
 {
 snprintf(g_szConfFilePath, sizeof(g_szConfFilePath), "%s", argv[1]);
 }
 printf("szConfFilePath:%s \n",g_szConfFilePath);
 memset(&g_stConfig, 0, sizeof(g_stConfig));

​ 2 . 变身守护程序 ​

DaemonInit();

​ 3 . 使用信号量集key(0x91032000),来避免 gro_config_server_new 程序重复启动 ​

GROCERY_PROC_CONTROL_SEMKEY_BASE = 0x91000000,
GROCERY_CONFIG_SERVER_SEMKEY_BASE = GROCERY_PROC_CONTROL_SEMKEY_BASE + 0x32000,
iRet = procnum_control_on_start(GROCERY_CONFIG_SERVER_SEMKEY_BASE, 1);
int procnum_control_on_start(key_t semkey, int iProcNum) // 返回0代表没问题,返回其他值代表有重复启动
{
 int semid;
 struct sembuf sbuf;
 /* Get semaphore ID associated with this key. */
 // 如果获取不成功
 if((semid=semget(semkey, 0, S_IRUSR|S_IWUSR)) < 0) 
 {
 /* Semaphore does not exist - Create. */
 // 尝试创建这个信号量
 if((semid=semget(semkey, 1, IPC_CREAT | IPC_EXCL | S_IRUSR |
 S_IWUSR | S_IRGRP | S_IROTH)) >= 0)//创建成功
 {
 /* Initialize the semaphore. */
 memset(&sbuf,0,sizeof(sbuf));
​
 sbuf.sem_num = 0;
 sbuf.sem_op = iProcNum; 
 sbuf.sem_flg = 0;
​
 if(semop(semid, (struct sembuf*)(&sbuf), 1) < 0)//操作失败
 {
 perror("semget IPC_CREAT SUCC semop");
 return -__LINE__;
 }
 }
 else if (errno == EEXIST) //创建失败,但是本来就存在
 {
 //既然说本来就存在,那么再次尝试获取共享key
 if((semid=semget(semkey, 0, S_IRUSR|S_IWUSR)) < 0)//再次尝试,可能是临界状态
 {
 perror("semget IPC_CREAT EEXIST semget again");
 return -__LINE__;
 }
 }
 else //创建失败
 {
 perror("semget IPC_CREAT");
 return -__LINE__;
 }
}

​ 4 .读取配置文件信息,如ServerPort,ClientPort,mysql服务器地址信息等 ​

TLib_Cfg_GetConfig(g_szConfFilePath,
 "ServerPort", CFG_INT, &(g_stConfig.iServerPort), 43100,
 "ClientPort", CFG_INT, &(g_stConfig.iClientPort), 33100,
 "WaterLocalLevel" , CFG_INT , &(g_stConfig.iWaterLogLocalLevel), 0,
 "iTraceUinLogLocalLevel" , CFG_INT , &(g_stConfig.iTraceUinLogLocalLevel), 0,
 "LogFilePath", CFG_STRING, g_stConfig.szLogFilePath, "/home/oicq/log/grocc", sizeof(g_stConfig.szLogFilePath), 
 "MaxLogSize" , CFG_INT , &(g_stConfig.iMaxLogSize), (int)10000000,
 "MaxLogNum" , CFG_INT , &(g_stConfig.iMaxLogNum), (int)5,
 "MaxPkgNum", CFG_INT, &(g_stConfig.iMaxPkgNum), 5000,
 "MYSQL_HOST", CFG_STRING, g_stConfig.sCCDBIP, "localhost", sizeof(g_stConfig.sCCDBIP),
 "MYSQL_PORT", CFG_INT, &g_stConfig.iCCDBPORT, 3306,
 "MYSQL_USER", CFG_STRING, g_stConfig.sCCDBUser, "root", sizeof(g_stConfig.sCCDBUser),
 "MYSQL_PASSWD", CFG_STRING, g_stConfig.sCCDBPass, "grocery_cc", sizeof(g_stConfig.sCCDBPass),
 "GrocerySystemId", CFG_INT, &g_stConfig.iGrocerySystemId, DEFAULT_INVALID_GROCERY_SYSTEM_ID,
 "PRIORITY", CFG_INT, &(g_stConfig.iPriority), 2, 
 "ReportStatusInterval", CFG_INT, &(g_stConfig.iReportStatusInterval), 10,
 "ReportStatusTryTimes", CFG_INT, &(g_stConfig.iReportStatusTryTimes), 180,
 "PushConfigThreadNum", CFG_INT, &(g_stConfig.iPushConfigThreadNum), 10,
 "PushConfigTimeInterval", CFG_INT, &(g_stConfig.iPushConfigTimeInterval), 30,
 (void*)NULL);

​ 摘取一个配置文件示例以及配上说明 ​

ServerPort 43100 // 服务器端口
ClientPort 33100 // 客户端端口
WaterLocalLevel 3 // 日志级别
iTraceUinLogLocalLevel 0 
LogFilePath /home/oicq/grocery/config_center_new/log/ //日志文件路径
MaxLogSize 50000000 //单日志文件最大大小
MaxLogNum 10 //保留最近的多少个文件
MaxPkgNum 5000 
MYSQL_HOST 192.168.1.1 //这个以及下面3项,是配置中心的mysql db连接
MYSQL_PORT 1234 
MYSQL_USER root 
MYSQL_PASSWD pass1111 
GrocerySystemId 10401 //Grocery集群编号
PRIORITY 2 
ReportStatusInterval 1 //心跳上报的间隔(秒)
ReportStatusTryTimes 180 // 配置中心间隔多少次都没有收到客户端的心跳上报,就认为其死机。
PushConfigThreadNum 200 //推送配置的线程数
PushConfigTimeInterval 30 //推送配置的间隔(秒)

​ 5 .获取本地内网IP ​ 会按照bond%,eth1.%,eth%的顺序,尝试获取本地网络接口的IP地址。 ​

static int CloudGetLocalIP(uint32_t *pIP)
{
 char buf[1024];
 struct ifconf ifconf;
 ifconf.ifc_len = sizeof(buf);
 ifconf.ifc_buf = buf;
 int fd = socket(AF_INET, SOCK_DGRAM, 0);
 if(fd < 0)
 {
 return -1;
 }
 if (ioctl(fd, SIOCGIFCONF, &ifconf) < 0)
 {
 close(fd);
 return -1;
 }
 struct ifreq *ifreq = (struct ifreq*)buf;
 for(size_t i = (ifconf.ifc_len/sizeof(struct ifreq)); i > 0; i--)
 {
 if(ifreq->ifr_flags == AF_INET)
 {
 if (strncmp("bond", ifreq->ifr_name, strlen("bond")) == 0)
 {
 memcpy ((void *) pIP, (char *)&ifreq->ifr_addr.sa_data + 2, sizeof(uint32_t));
 }
 else if (strncmp("eth1.", ifreq->ifr_name, strlen("eth1.")) == 0)
 {
 memcpy ((void *) pIP, (char *)&ifreq->ifr_addr.sa_data + 2, sizeof(uint32_t));
 }
 else if (strncmp("eth1", ifreq->ifr_name, strlen("eth1")) == 0)
 {
 memcpy ((void *) pIP, (char *)&ifreq->ifr_addr.sa_data + 2, sizeof(uint32_t));
 }
 }
 ifreq++;
 }
 close(fd);
 if (*pIP > 0)
 return 0;
 else
 return -1;
}

​ ​ 到目前为止,看我们大概取得什么关键信息? ​ g_stConfig.sServerIP --通过ioctl取得。 ​ g_stConfig.iServerPort --通过配置文件取得 ​ g_stConfig.iWaterLogLocalLevel --日志详细级别,通过配置文件取得 ​ g_stConfig.iTraceUinLogLocalLevel --通过配置文件取得 ​ g_stConfig.szLogFilePath--日志文件路径,通过配置文件取得 ​ g_stConfig.iMaxLogSize,g_stConfig.iMaxLogNum--通过配置文件取得 ​ g_stConfig.iPushConfigThreadNum --如果该值大于500,那么我们将设定为500,以免推送线程太多。 ​ iPushConfigTimeInterval --如果该值小于30,我们会让其等于30,意味着这个配置不能小于30秒,以免推送得太频繁。 ​ 6 .创建推送配置信息的消息队列 ​ 创建消息队列(key 11001) int qid=msgget(11001,IPC_CREAT|0666); ​ 创建保存配置信息的服务端共享内存 ​ 创建共享内存key=0x31000 SERVER_CONFIG_SHM_KEY 大概12MB g_stConfig.pstServerConfigsShm 指向这块共享内存 ​ 创建共享内存(key=0x31100) SERVER_CACHE_UPDATE_INFO_SHM_KEY,这块内存用于更新时的比对。 ​ 创建共享内存 SERVER_INTERFACE_UPDATE_INFO_SHM_KEY 0x31200 ​ 创建共享内存 SERVER_COMM_INFO_SHM_KEY 0x31300 ​ 7 .按照配置文件的地址连接mysql,会带上db名去连接,以尝试db的连通性以及数据库是否已建立。 ​ 8 .初始化各个线程互斥锁 ​

 pthread_mutex_init(&g_ServerConfigsShm_Mutex,NULL);
 pthread_mutex_init(&g_ServerCommInfo_Mutex,NULL);
 pthread_mutex_init(&g_PushConfigPkg_Mutex,NULL);
 pthread_mutex_init(&g_Log_Mutex,NULL);
 pthread_mutex_init(&g_Mysql_Mutex,NULL);
 pthread_mutex_init(&g_PushResult_Mutex,NULL);
 pthread_mutex_init(&g_VServerClientUpdateInfoList_Mutex,NULL);
 pthread_mutex_init(&g_InterfaceServerClientUpdateInfoList_Mutex,NULL);

​ 9 .信号注册

 signal(SIGQUIT, SetShutDownFlag); //kill -3 这个是置gs_iShutDwonFlag = 1;
 signal(SIGTERM, SetShutDownFlag); //kill -15 这个是置gs_iShutDwonFlag = 1;
 signal(SIGUSR1, SetShutDownFlag); //kill -10 这个是置gs_iReloadCfgFlag = 1;

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏架构师之旅

轻松搭建分布式文件系统

用户在登录之后可以上传文件,也可以看到所有上传的文件(自己或其他用户上传的文件),并可以下载这些文件。

1414
来自专栏我是攻城师

Nginx入门介绍与安装

2874
来自专栏互联网高可用架构

初识缓存分片框架Redic

1223
来自专栏IT技术精选文摘

高性能配置中心 duic - 设计&实现

配置(Configuration)对于技术人员来说应该都不陌生,通常配置都是以 key-value 的形式存在于配置文件当中。例如线程池大小、数据库连接、逻辑开...

1053
来自专栏Python攻城狮

Python网络爬虫(二)- urllib爬虫案例 urllib的爬虫案例-通过最原始的爬虫方式

代码操作(一)爬取百度贴吧数据(GET方式爬取数据 ,这里爬取的是战狼2贴吧的html)

602
来自专栏xingoo, 一个梦想做发明家的程序员

[logstash-input-redis]插件使用详解

Redis插件参数配置详解 最小化配置 input { redis { data_type => "list" #logstash redis插件工作方式 ...

2518
来自专栏性能与架构

高性能负载均衡软件HAProxy

image.png HAProxy是一个负载均衡软件,开源、高性能,可应用于TCP(第四层)和HTTP(第七层) 借助HAProxy可以快速、可靠地提供基于TC...

3115
来自专栏前端知识分享

第106天:Ajax中同步请求和异步请求

用户填写所有信息后,提交给服务器,等待服务器的回应(检验数据),是一次性的。信息错误又要重新填写!

952
来自专栏Snova云数仓

Greenplum容灾实践

GP集群的 Primary MasterA节点部署一个备份节点,即Slava Master B节点。

3686
来自专栏PHP在线

PHP输入流php://input介绍

在使用xml-rpc的时候,server端获取client数据,主要是通过php输入流input,而不是$_POST数组。所以,这里主要探讨php输入流php:...

3285

扫码关注云+社区