2019年12月10日
本文分析了SyncAllThread、HeartBeatThread和TimerThread的关键配置参数和处理流程。
SyncAllThread
功能:回写所有脏数据。
在控制台数据运维指令”syncdata"时启动该线程处理。
文件:
SyncAllThread.h
SyncAllThread.cpp
配置解析
#回写时间(秒),即回写多久以前的数据
SyncTime=300
SyncTime配置成300,意思是回写5分钟以前的“脏”数据到后端(mysql)数据库中。
可以通过修改这个值,来调整内存与后端db的数据一致性频率。
对于一些从关系型db提取数据、且实时性要求较高的系统,如实时大屏,就可以将SyncTime这个参数调低,如SyncTime=1,表示每秒同步一次。
注意:
这个参数在设置时,一定要考虑到数据库的压力,需要在数据库性能和实时性间寻找平衡。
对于那些日报表、月度报表等非实时性应用,就需要根据主机内存的压力情况来设置这个值,以保证(内存)数据不会因过期而丢失。
线程逻辑
1、在控制台数据运维指令”syncdata”时,创建该线程
2、回写SyncTime秒以前的数据
说明:
这个线程不会自动创建,只能由控制台指令触发。
HeartBeatThread
功能:Slave定时上报心跳线程。
线程创建后,调用RouterHandle::getConnectHbAddr(),获取master节点上收集心跳的Servant-ControlAckObj信息;如果是master节点,则禁用上报功能(_enableConHb=false,但该线程还在)。
所在文件:
TimerThread.h
TimerThread.cpp
配置解析
#向Router上报心跳的间隔(毫秒)
RouterHeartbeatInterval=1000
每隔RouterHeartbeatInterval秒,Slave向Router和Master上报心跳。
线程逻辑
1、获取master节点的ControlAckObj对象代理
开始循环:
2、向router服务发送心跳(RouterHandle::getInstance()->heartBeat(),调用router服务的方法_routePrx->heartBeatReport)
3、如果开启了心跳上报功能(_enableConHb==true),即本节点为slave节点,则向master的ControlAckObj对象上报心跳(MKControlAckImp::connectHb)
4、在MKControlAckImp::connectHb中,会增加备机上报心跳的计数(++_slaveHbCount),以供TimeThread判断降级
5、休眠RouterHeartbeatInterval秒后,再次执行2.
说明:
只有Slave会启动这个线程
TimerThread
功能:定时作业线程类。
包括同步路由信息给Router,上报属性信息给Property(用于监控),binlog心跳,上报get命中率,判断是否自动降级等。
相关文件:
TimerThread.h
TimerThread.cpp
配置解析
<Router>
#同步路由的时间间隔(秒)
SyncInterval=1
</Router>
<BinLog>
#active binlog产生的周期(秒)
HeartbeatInterval=600
</BinLog>
<BinLog>
#是否记录binlog
Record=Y
#是否记录key binlog
KeyRecord=N
#binlog日志文件名后缀
LogFile=binlog
</BinLog>
#模块名
ModuleName=Test
<Cache>
#内存块个数
JmemNum=2
#主机自动降级超时时间(秒),即30s无心跳则自动降级
DowngradeTimeout=30
</Cache>
每个配置项的功能见注释信息,比较详细了,不赘述。
初始化
1、读取配置
2、创建属性上报对象,涉及到的属性如下:
CountOfDirtyRecords, 平均值
CacheHitRatio, 平均值
CacheMemSize_MB, 平均值
DataMemUsage, 平均值
ProportionOfDirtyRecords, 平均值
ChunkUsedPerRecord, 平均值
MKV_MainkeyMemUsage, 平均值
TotalCountOfRecords, 平均值
CountOfOnlyKey, 平均值
MaxMemUsageOfJmem, 平均值
JmemNDataUsedRatio, 平均值,其中“N”为Jmem的编号
线程逻辑
1、同步路由
- 判断是否过了同步路由的周期SyncInterval
- 如果处于备机恢复状态(g_app.gstat()->isSlaveCreating()),则不同步路由;如果是主节点,则在控制台和日志中输出错误信息:
server changed from SLAVE to MASTER when in data creating status
- 调用_routerHandle->syncRoute(),同步路由
2、binlog心跳
- 如果已过binlog心跳的时间间隔HeartbeatInterval,则尝试记录”ACTIVE"binlog
- 如果Record=Y/y,则将”ACTIVE"binlog记录到本地磁盘binlog文件中;key binlog同理。文件内容如下:
内容是:SERA+BinlogLength+” “+”Active”。
3、上报get命中率,CacheHitRatio
4、每隔60s,上报一次
- 总内存(CacheMemSize_MB)
- 内存使用率(DataMemUsage)
- Jmem的使用率(JmemNDataUsedRatio)
- Jmem的最大使用量(MaxMemUsageOfJmem)
- CountOfOnlyKey
- MKV_MainkeyMemUsage
- 脏数据比率(ProportionOfDirtyRecords)
- 平均每条记录使用的数据块个数(ChunkUsedPerRecord)
- 脏数据个数(CountOfDirtyRecords)
- 总记录数(TotalCountOfRecords)
5、每隔60s,清除1次 迁移目的服务器数据大小限制(cleanDestTransLimit)
6、主机自动降级判断(master节点才会有的逻辑)
- 当DowngradeTimeout配置大于0、且检测时间已超时,则获取节点的心跳次数(RouterHandle::getInstance()->getSlaveHbCount())
- 与上一次(DowngradeTimeout秒之前)采集的心跳次数相比较,如果相等,则说明在DowngradeTimeout时间间隔内没有收到备机心跳,可能有异常;
- 心跳超时处理(handleConnectHbTimeout),向每个router上报心跳;如果上报都失败,则进行降级处理(RouterHandle::getInstance()->masterDowngrade())
- 将本节点设置为slave节点,然后上报心跳给master(g_app.enableConnectHb(true),这里只是将_enableConHb置位,由HeartBeatThread上报)
7、休眠1s,继续步骤1
案例分享
这里需要说一下,自动降级的判断逻辑并非完美,我就遇到过一种情况,主备没有自动切换,导致业务服务的写操作失败:
1、Slave 无法上报心跳给 Master 了
- 这就导致Master在下一个定时任务周期会进行降级检测:向所有Router发送心跳消息
- 但是 Master 连接不到Router,就触发自动降级,Master变为 “Slave”状态;
2、对于Router服务而言,Master连接到不到Router了,而Router可以正常连接到Master,这就导致
- Master上报心跳超时,Router检测到后,尝试触发主备切换;
- 但是 Router可以正常连接到Master,向Master发送心跳ok,不满足主备切换条件,所以就取消了主备切换操作;导致路由信息没有修改
3、所以,当前的状态为
- 在Router侧,路由信息没有变化,跟异常之前一样
- “Master节点” 和 Slave节点 的实际服务状态都为“Slave”
- 写操作仍会路由到“Master节点”,但是Master记录的自身状态为“Slave”,就拒绝写入。
你可能会有疑问:为什么Slave连接不到Master,Master连接不上Router,而Router却可以连接到Master?
这也是个坑!一般人不会告诉你:这是因为你的注册中心处理线程太少了!
当注册中心处理线程不足时,有一些Tars微服务就拉取不到目标服务的地址列表了。如上面提到的情况,查看日志,发现Slave找不到Maste、Master找不到Router,从而导致主从切换失败。
遇到这样的事情也挺背的,恰巧赶在一起了。
解决方案如下:
1、需要调整Tars注册中心的处理线程数,保证Router模块可以正常上报心跳,成功注册到注册中心,这样一来,CacheServer就可以读取到Router服务的信息了。
理论上,只要注册中心的处理线程足够多,就不会出现上述问题。但实时却总是啪啪打脸...
以防万一:
2、调整 CacheServer 的配置参数,心跳超时时不启用自动降级功能
虽然这样做有风险,但直到目前为止,生产环境没有因此而出现不可用的情况。
总结
本文介绍了SyncAllThread、HeartBeatThread和TimerThread的关键配置参数和处理流程,并分享了一个自动降级失败案例和解决方法,希望后来者可以避开这样的坑。