为了降低成本,充分利用备DB,很多业务会使用读写分离的架构,由备DB负载处理读请求。但是很难保证主备的严格同步,而且在执行冷备期间还存在主备同步时间差加大的问题。
所以需要使用一定的方法来规避主备不同步时的数据一致性问题。
先看一下Linux消息队列的接口:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
struct mymsg
{
long mtype; /* Message type. */
char mtext[1]; /* Message text. */
};
发送消息时可以指定消息类型字段mtype。
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
The argument msgtyp specifies the type of message requested as follows:
* If msgtyp is 0, the first message on the queue shall be received.
* If msgtyp is greater than 0, the first message of type msgtyp shall be received.
* If msgtyp is less than 0, the first message of the lowest type that is less than or equal to the absolute value of msgtyp shall be received.
如果msgtyp是一个负数,msgrcv将会得到一个mtype小于等于abs(msgtyp)消息。
如果记录每个用户的最后一次写操作时间戳,并在每次msgsnd时将mtype设置为这个时间戳,那么在msgrcv时将msgtyp设置为DB最后一次同步时间*-1,就可以保证得到的用户对应的数据都已经同步到备DB。
另一个进程在msgrcv时将msgtyp设置为0,处理所有的用户请求,从主DB读取数据。保证没有同步到备DB的用户请求也可以得到处理。区别于其他读写完全分离的架构,由于同时在主备DB都有读请求,对于读多写少的系统,可以充分利用主DB空闲时间。
以尽可能低的成本提供尽可能大的容量和尽可能高的性能服务海量用户。
1.设计要点
l按功能和流程划分cache层和db层内部模块,使每个模块的功能清晰、模块间耦合度低、逻辑相对简单。
l主从同步校验、读写自动分离、读负载均衡、读写优先级区分。
lDB读写性能优化,采用读写分离的方法解决机器数量和读写性能的矛盾。动态检测主备DB同步时间差,对于时间差以内有过写操作的用户,通过主DB读取,对于其他用户根据主备DB的负载情况从主DB或备DB读取。
lDB容灾,采用CDB存储用户数据,主备DB的容灾由TGW负责,主备TGW的容灾由DB层服务器根据检测结果自动切换。
lCache层故障转移,cache层使用一致性hash实现负载均衡和故障切换。cache层切换后,未命中的请求量由DB承担。
lCache故障恢复,db层收到cache层请求时记录QQ与cache层机器的对应关系,当QQ发生写操作时通过非当前对应关系的其他cache层机器清除cache。
2.架构设计
2.1.总体架构图
2.2.DbSvr架构图
2.3.架构解析
任务系统后台总共分为四个层次:逻辑层、Cache层、DBSvr层和DB层。
2.3.1.逻辑层
l层对外提供统一的服务接口,实现任务系统的大部分业务逻辑。
l根据当前已配置的任务及其需要的条件对比用户完成任务的记录,提示符合条件的用户完成指定任务。
l逻辑层通过Cache获取用户完成任务的历史记录。
2.3.2.Cache层
l缓存活跃用户的相关数据,为Cache层提供服务。
l从DB或者外部系统(即通OIDB)获取用户数据。
2.3.3.DBSvr层
l下发任务配置信息
l提供DB读写接口,同步用户最新任务积分到积分系统。
l业务优先级控制,优先保证写请求成功写入DB。
l优化的读写分离,主DB读写,备DB只读。
l主备DB差异校验。
2.3.4.DB层
DB层使用CDB,由CBD提供主备同步和故障切换。
3.写请求
3.1.完成任务(增、改)
lmaster_db_svr在完成任务队列为空的情况下,才处理从查询队列中的请求,保证写操作优先处理。
3.2.清除用户(删)
lmaster_db_svr在完成任务队列和查询队列都为空的情况下,才处理从删除用户队列中的请求。
4.读请求
lDBSvr缓存QQ号最后一次写操作的时间戳,并按LRU算法淘汰,对于被淘汰的QQ号或者从来没有记录的QQ号,默认写操作时间记为DBSvr启动时间。
lget_task_svr从user_info缓存中查询QQ号最后一次写操作的时间,并在写消息队列时设置此时间为消息类型。
lslave_db_svr通过db_info查询备DB最后同步时间update_time,从并消息队列中获取消息类型(最后一次写操作时间)小于update_time的请求,从备DB读取用户信息。
lmaster_db_svr在完成任务队列为空的情况下,从查询队列获取任意的请求,从主DB读取用户信息。
5.系统容灾
5.1.Cache/逻辑层容灾
lCache层与逻辑层同机器部署,由TaskInterface对外提供接口,采用一致性Hash算法根据QQ号转发请求到对应的机器。
l当其中一台机器故障时,TaskInterface自动将请求分发给其余的机器。
l当故障机器恢复时,将这部分请求量切回。
l当请求切回恢复后的机器后,如果有写操作,那么故障期间用户在其他机器上产生的cache数据成为脏数据。这时候DBSvr会发现QQ号与Cache层机器的对应关系发生改变,从而通过Cache同步中心通知对应的机器清除此QQ号的Cache数据。
5.2.DBSvr容灾
lDBSvr共两台机器,互为主备,各自承担一半的请求量,DbInterface以固定配置按号段分发对应QQ号的请求。当其中一台DBSvr故障时,DbInterface将所有请求都转发到另一台机器。
lDBSvr缓存QQ号与Cache层机器的对应关系,如果QQ号在多台Cache机器之间切换,DBSvr缓存整个切换的轨迹,直到发生写操作。发生写操作时,如果当前请求的Cache机器不是缓存记录的唯一机器,则通知Cache同步中心。
lDBSvr在两台机器之间同步QQ缓存信息。
lDBSvr缓存QQ号最后一次写操作的时间戳,并按LRU算法淘汰,对于被淘汰的QQ号或者从来没有记录的QQ号,默认写操作时间记为DBSvr启动时间。