tokyo tyrant源码分析-主从复制实现
"tyrant分析-总体设计"中已经提到,slave起一个线程(do_slave)做主从复制,它和master建立tcp连接,发送请求命令和起始时间rts +1(上次的更新时间加1秒)给master,然后循环的从master那里接收一条条的记录,更新自己db、ulog和rts file。do_slave是以1秒为频率执行的。(实际是等待一次do_slave执行完毕后,再等待1秒,然后进入下一次的do_slave,依次循环。所以"以1秒为频率执行"的表达似乎并不准确。从下面可以看到一次do_slave有可能执行较长时间)
主从复制是一个主、从交互的过程。本节依次描述协议细节、slave细节、master细节。
------------
协议细节:
do_slave(slave) do_repl(master)
-------------------
| TTMAGICNUM|
| TTCMDREPL |
| ts (+1) |
| sid | send and recv (with timeout)
------------------- ------------------------>
-----------------
send and cnd wait | NOP |
<--------------------- -----------------
-----------------------
| TCULMAGICNUM |
| rts |
| rsid |
| rsiz |
content send | rsiz-content |
<--------------------- ------------------------
next content send
<---------------------
......
rsiz-content格式:
MAGIC + cmd + ksize + vsize + key + value
其中:
cmd: TTCMDPUT | TTCMDOUT | ...
ksize,vsize分别是本条记录的key,value的长度;
slave就根据cmd和key-value对对db进行相应操作。
master的ulog由一条条独立记录组成,每条记录有相同格式:
MAGIC + ts + sid + size + content
其中:
ts : 本条记录对应的时间戳。slave请求时会带上上次更新时间戳,master根据它们来判断需要传送哪些记录给slave;
sid : server id. 标识server。
size : 后面"content"长度
content格式即上面"rsiz-content"的格式,描述了一条key-value对以及对它做的操作命令。
--------------
do_slave流程:
打开rts文件(默认为ttserver.rts),读取上次的rts(replication timestamp);
和master建立socket连接(参数:-mhost,-mport),并设置socket选项:
SO_RCVTIMEO、SO_SNDTIMEO - 发送、接收超时设置为0.25秒
TCP_NODELAY - 禁止nagle算法
发送REPL请求(详见协议细节);
循环:
用recv接收数据;
解析接收数据,根据数据中指定的命令(TTCMDPUT、TTCMDOUT等)更新db和slave自己的ulog;
用接收数据里的新rts更新slave的rts文件;
后关闭连接
解释:
1、slave不能因偶然的网络故障之类永远阻塞在send或recv中,这样的话更新就会永远停滞了。所以它要设置发送和接收的超时。如果超时,则这次do_slave失败,等待1秒后进行下一次。send | recv失败时,它并不会用新的rts(可能压根就没请求到它)去更新自己的rts文件,所以下次还是会用旧的rts去请求,所以不会因do_slave失败而导致slave数据不全。
2、禁止nagle算法是因为有小数据的命令包的交互,不能拖延。
3、请求只发送一次,但数据是一直循环接收的。循环失败的条件是:recv失败(或超时),收到SIGINT或SIGTERM,或是更新库失败或写文件失败等;
---------------
do_repl流程:
根据slave的请求ts找到合适的ulog文件(文件名使用数字编号,依次递增),逻辑是:
从编号大的文件依次往编号小的文件:(编号越大,ulog内容越新,ts越大)
打开文件查看它的条记录的ts,如果请求ts大于它,则该文件即为要找的ulog文件。
循环。当对端连接未关闭且没收到SIGINT、SIGTERM信号时:
发送NOP(测试对端连接是否关闭);
pthread_cond_timedwait等待ulog更新信号,超时值为1秒;
循环:
一次读取一条日志记录;
加上头部(MAGIC,rts,rsid,rsiz。见"协议细节");
发送给slave。
当上面读取日志失败或发送失败时,退出循环。
解释:
1、ulog由一条条的记录组成,每条记录有相同格式: MAGIC + ts + sid + size + content
2、因为ulog文件有大小上限,所以写满一个后会写下一个。按上面所说那样,文件名用数字编号,依次递增;
3、找合适ulog文件的逻辑。因为是按内容从新到旧的顺序(也即ts从大到小的顺序)查看文件,所以先找到的其中条记录ts小于slave所请求ts的那个文件就是合适的文件;(该文件里ts会随着一条条记录慢慢增加,直到大于等于请求ts,这时就到了slave需要的数据处);
4、关于这两层循环的逻辑。内层循环一次发送一条记录,它是希望尽可能多地发送记录给slave,直到发送完所有记录(意外发送故障不考虑下)。退出到外层逻辑时希望这时又有ulog更新,能继续进行发送。这两层循环的目的都是希望能尽可能长地维持与slave的一次连接,从而让数据的同步更及时。
————————————————
版权声明:本文为CSDN博主「pingnning」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/pingnning/article/details/4724377
相关文章