[分布式]Raft和ZAB的异同

前言 为了学习etcd,先学习了解一下Raft协议,想总结一下Raft和zookeeper的ZAB协议协议的异同。 ZAB是对PAXOS算法的改进(没看过PASXOS,好像没有leader概念,我直接看的ZAB),增加了leader、follower、learner的角色。 Raft自称是比PAXOS更容易理解的一致性算法,和ZAB一样有leader、follower,而且一个强leader的算法。 时间 Raft:使用任期term表示一个选举轮次。 ZAB:使用electionEpoch表示一个选举轮次。 选举 投票 Raft:忽略上一轮投票。选举过程只能进行一次投票,如果投过票了,收到投票请求就会无视。这样越早发起投票的人越有可能当leader;同时,也可能出现每个节点都没有收到majority的投票,出现投票被瓜分的情况。Raft采用设置随机的选举超时时间来解决投票被瓜分。 ZAB:忽略上一轮投票。每次收到投票请求都会进行判定,然后若自己的投票有变,会重新通知所有节点。这样不会出现投票被瓜分,但是时间会比Raft多很多,导致服务可用性降低。 投票pk Raft:term大的胜出,相同时index大的胜出 ZAB:electionEpoch大的胜出,相同时zxid大的胜出 投票结果 Raft:每个节点都只有自己的投票结果,如果发现自己投票过半,要通知所有节点,并发送心跳,心跳间隔 < 选举超时时间. ZAB:每个节点保存所有节点的票根信息,每个节点收到投票请求后都会检查是否有过半的票根,如果有,会和leader建立起一个连接,leader会发送心跳。 选举结束 Raft:选举完可以立刻提供服务,对于节点不一致的问题,Raft靠接下来附加条目RPC来逐渐修复。按论文说的5台节点的集群,重新选举完成的时间平均是35ms,最长是150ms(选举超时时间配置为12-24ms)。 ZAB:选举完得完成日志同步才能对外提供服务,而且ZAB的选举可能长达秒级的时间,导致服务可用性降低。 分区容错性 当 可用节点 > N/2,Raft和ZAB的集群都是可用的。 客户端请求 读(针对读请求落到follower的情况) Raft:Raft的读其实有几个方案 强一致读:转发给leader;leader插入一个空日志获得readIndex;心跳广播(确认自己是leader,才能拥有最新日志);等待状态机applyIndex经过readIndex(同步最新日志条目);返回给follower;返回给客户端; 在follower读:从leader获得readIndex;等待applyIndex经过readIndex;查询自身状态机;(从leader获得readIndex时,leader也要进行心跳广播) 折中方案:leader在接受到majority心跳响应后一段时间内不广播,这是论文作者不推荐的,因为“响应后一段时间内”这个时间可能是不准确的。 ZAB:follower直接返回,如果一个follower和leader未同步完成,follower返回的是脏数据,如果要保证数据最新,需要客户端调用sync()方法同步数据,正常情况下ZAB只保证最终一致性。 写 主要流程 Raft: 转发给leader; leader将请求封装为entries,写入日志,得到在日志中的index,连同entries发送给followers,注意这可以是批量的 follower执行接收逻辑,如果成功写入文件,返回true leader收到过半成功回复就更新committIndex,把entries应用到状态机中,回复客户端 leader下次心跳会带上committIndex的值用leaderCommit表示,followers发现leaderCommit大于自己维护的committIndex,就令 commitIndex 等于 leaderCommit 和 新日志条目索引值中较小的一个 ZAB:典型的两阶段提交 转发给leader leader封装为一个proposal,写入日志,发送给followers follower执行接收逻辑,如果成功写入文件,返回true leader收到过半成功回复就提交proposal,同时广播一个commit消息,通知followers提交提议 接收逻辑 Raft:如果prevLogIndex和prevLogTerm不匹配,返回false,由leader调整,从而达到在写请求再同步数据的目的 ZAB:没有什么特别的,接收到proposal写入文件,接收到commit提交日志 旧leader数据 这个是指旧leader崩溃后,新leader对旧数据的处理 Raft:保守,过半或未过半日志都是未提交。只能提交当前term的日志,如果提交了当前日志,那么旧term的日志也会被一波提交(旧term的日志只能被间接提交)。允许出现未提交的数据被覆盖。 ZAB:激进,过半或未过半日志都被提交,由zookeeper选举完成后的数据同步完成。 leader假死 Raft:leader和follower是没有连接的。旧leader假死后,新leader诞生,旧leader复活后发送带有旧term的RPCs,follower收到之后返回新term给旧leader,旧leader更新term,加入follower大军。 ZAB:leader和follower存在连接。旧leader假死后,连接断开,旧leader进入LOOKING状态,从集群中学习投票结果/重新选举,最终找到leader建立起连接。 请求异常 Raft:重试,Raft要保证RPCs是幂等的。 ZAB:follower和leader断开连接,重新加入集群 挂了的机器加入一个选举完成的集群(不是新加机器) Raft:leader会对follower进行RPCs重试,所以恢复的follower会收到leader的心跳请求。 ZAB:恢复的follower会学习集群中的投票结果,可以识别到leader 日志复制的顺序 Raft:由leader维护log顺序。如果follower重启,不会阻塞leader写入请求,会按照leader顺序追赶日志;如果leader挂了,新leader也可以将旧term、新term日志按顺序提交。...

August 18, 2019

[网络]TCP拥塞控制那些事

前言 看完了《TCP/IP详解 卷一》,对TCP/IP协议簇的认知多了一些。总结一下TCP窗口有关的慢启动、拥塞避免、快速重传、快速恢复的概念。 TCP滑动窗口 窗口有两种,通告窗口(Receiver Window,rwnd)和拥塞窗口(Congestion Window,cwnd)。 通告窗口:通告窗口表明了接收端当前的接受能力。TCP在发送端和接收端都是有缓冲区的,通告窗口声明了当前接收端的缓冲区还能接收的字节大小。这个数值会在TCP报文里携带。 拥塞窗口:拥塞窗口不被TCP报文传输,是发送端基于拥塞避免算法算出来的一个窗口。这个窗口限制了发送方的发送速率避免网络拥塞。 两个窗口共同组成了一个滑动窗口。简单来说,通告窗口是强制限制,拥塞窗口是自发限制。 有一点要注意的是,窗口的单位用字节表示,但是拥塞窗口的调整总是以一个MSS的倍数来调整。 这里用书上的图描述滑动窗口, 当一个TCP发送方发送数据的时候就会查看可用窗口能否发送(如果启用了Nagle算法,可用窗口必须大于等于一个MSS,发送方才发送数据) 上面是抓包得到的一个报文,Win=2027是一个通告窗口,表示服务器的缓冲区还能接受2027字节的数据。 拥塞控制 上图是一个tcp刚开始传输数据时的速率变化走向。 拥塞避免、慢启动、快速重传、快速恢复这四个词其实并不能单独分开讲。当一个连接的网络情况不好的时候,就会丢包或超时,这时就要降低发送方的发送速率防止恶化,这种就是拥塞控制。 这种机制涉及到cwnd和ssthresh两个指标,ssthresh是一个区分慢启动和拥塞避免的阈值,当拥塞发生时,分两种情况 超时:ssthresh = cwnd / 2(最小为2MSS),cwnd = 1MSS,进入慢启动 丢包:ssthresh = cwnd / 2(最小为2MSS),cwnd = ssthresh + 3MSS,进入快速重传 慢启动 慢启动其实是发送速率重新计算,cwnd 初始值为一个数据报大小,ssthresh初始值为65535,是一个然后在到达阈值之前,每接收到一个新的ACK,cwnd就会增加一个报文段的大小,这样子慢启动其实是以指数增加速率. 拥塞控制 上图是一个tcp刚开始传输数据时的速率变化走向。 拥塞避免、慢启动、快速重传、快速恢复这四个词其实并不能单独分开讲。当一个连接的网络情况不好的时候,就会丢包或超时,这时就要降低发送方的发送速率防止恶化,这种就是拥塞控制。 这种机制涉及到cwnd和ssthresh两个指标,ssthresh是一个区分慢启动和拥塞避免的阈值,当拥塞发生时,分两种情况 超时:ssthresh = cwnd / 2(最小为2MSS),cwnd = 1MSS,进入慢启动 丢包:进入快速重传 慢启动 慢启动其实是发送速率重新计算,cwnd 初始值为一个数据报大小,ssthresh初始值为65535,是一个然后在到达阈值之前,每接收到一个新的ACK,cwnd就会增加一个报文段的大小,这样子慢启动其实是以指数增加网速到一个比较平衡的水平。 拥塞避免 当cwnd大于等于ssthresh时进入拥塞避免状态,在一个RTT内无论收到多少ACK都只将cwnd增加一个报文大小,从时间上来看网速线性增加。 快速重传和快速恢复 快速重传指,当收到重复的3个ACK报文时(duplicate ack),设置ssthresh = cwnd / 2(最小为2MSS),cwnd = ssthresh + 3MSS,然后进入快速恢复阶段。 暂停发送新的报文,重传丢失报文。 接下来每收到重复的ACK时,将cwnd增加一个报文大小。如果cwnd大于未确认报文大小(报文丢失后我们还在发新的报文,未确认报文指丢失报文到最后一个报文之间报文总大小),可以发送新报文。 接下来如果收到新的ACK报文,将cwnd设置为ssthresh,也就是网速降为一半,并进入拥塞避免阶段。 总的来说,网速一直处于一个动态调整的过程,一个连接上cwnd随时间的变化如图所示 还有一点,上面关于cwnd的比较其实还要考虑rwnd的值,如果rwnd>cwnd,应取rwnd去比较,毕竟两者决定了可用窗口大小。...

August 15, 2019

[FCM]用FCM做一个跨设备消息同步工具

Fcm真是个好东西,希望你也有。 ...

May 18, 2019

[NIO]linux的I/O多路复用

前言 上篇提到i/o多路复用,是通过单进程监听多个文件描述的状态,达到减少线程阻塞的目的。 内核(kernel)利用文件描述符(file descriptor)来访问文件。 文件描述符是非负整数。 打开现存文件或新建文件时(包括socket被打开),内核会返回一个文件描述符。 读写文件也需要使用文件描述符来指定待读写的文件。在linux环境下,进入/proc目录可以看到许多代表文件描述符的文件夹。 linux i/o多路复用的系统调用接口有三种,分别是 select,poll,epoll。 接口 作为一个学java的,了解一下java底层调用的函数,还是挺有助于理解的。 i/o多路复用原理 linux(2.6+)内核的事件wakeup callback机制,是linux i/o多路复用的原理。内核管理一个process的睡眠队列,当socket事件发生的时候,唤醒队列的process,调用callback函数完成通知。总体上会涉及两大逻辑:(1)睡眠等待逻辑;(2)唤醒逻辑。 1.睡眠等待逻辑:涉及select、poll、epoll_wait的阻塞等待逻辑 select、poll、epoll_wait陷入内核,判断监控的socket是否有关心的事件发生了,如果没,则为当前process构建一个wait_entry节点,然后插入到监控socket的sleep_list 进入循环的schedule直到关心的事件发生了 关心的事件发生后,将当前process的wait_entry节点从socket的sleep_list中删除。 2.唤醒逻辑。 socket的事件发生了,然后socket顺序遍历其睡眠队列,依次调用每个wait_entry节点的callback函数 直到完成队列的遍历或遇到某个wait_entry节点是排他的才停止。 一般情况下callback包含两个逻辑:1.wait_entry自定义的私有逻辑;2.唤醒的公共逻辑,主要用于将该wait_entry的process放入CPU的就绪队列,让CPU随后可以调度其执行。 select #include <sys/select.h> #include <sys/time.h> int select(int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout) FD_ZERO(int fd, fd_set* fds) //清空集合 FD_SET(int fd, fd_set* fds) //将给定的描述符加入集合 FD_ISSET(int fd, fd_set* fds) //将给定的描述符从文件中删除 FD_CLR(int fd, fd_set* fds) //判断指定描述符是否在集合中 select 方法的第一个参数max_fd指待测试的fd(fd即文件描述符,一个socket会有一个文件描述符)个数,它的值是待测试的最大文件描述符加1,文件描述符从0开始到max_fd-1都将被测试。中间三个参数readset、writeset和exceptset指定要让内核测试读、写和异常条件的fd集合,如果不需要测试的可以设置为NULL。 select被调用的时候,被监控的readset(假设对socket的读事件感兴趣)会从用户空间复制到内核空间,然后遍历监听的socket,如果在超时或者有一个或多个socket产生了读事件,那么select唤醒线程,注意这里只是唤醒,并没有返回就绪的fd,接下来线程要再次遍历readset,收集可读事件。 select的问题是: 监听的socket数量有限,为了减少fd拷贝的性能损耗,限定了1024个文件描述符 线程被唤醒的时候,需要再次遍历fd列表。 poll #include <poll.h> int poll(struct pollfd fds[], nfds_t nfds, int timeout); typedef struct pollfd { int fd; // 需要被检测或选择的文件描述符 short events; // 对文件描述符fd上感兴趣的事件 short revents; // 文件描述符fd上当前实际发生的事件*/ } pollfd_t; poll换了个数据结构,解决了select其中一个问题:监听的数量有限。但实际上并有解决拷贝的性能损耗和需要再次遍历fd列表获取就绪事件。...

January 31, 2019 · 土川

[NIO]五个I/O模型

前言 不先了解一下Linux的IO模型,看java的nio真是一脸懵逼。。 linux的io模型 Blocking (I/O阻塞IO模型) 刚学java的时候想必学的都是阻塞IO传输,底层调用就是上面的图锁展示的过程,进程调用recvfrom,进入阻塞状态,然后系统将数据从网卡/硬盘读取到内核,由从内核复制到用户态,最终返回给进程,进程继续运行。 这是比较耗时和浪费CPU的做法,需要阻塞数据到达,数据复制 Nonblocking I/O(非阻塞IO模型) 底层轮询调用recvfrom,系统会立刻返回读取结果,如果读取不到数据,则开启下一次调用,直到数据返回。 这种模式不用阻塞数据到达,需要阻塞数据复制。但是处于轮询状态的进程又是另一种意义上的阻塞,所以其实效率没有提高多少。 I/O Multiplexing(多路复用) Unix/Linux 环境下的 I/O 复用模型包含三组系统调用,分别是 select、poll 和 epoll,在历史上依次出现。 select 有三个文件描述符集(readfds),分别是可读文件描述符集(writefds)、可写文件描述符集和异常文件描述符集(exceptfds)。进程将文件描述符(socket也有文件描述符表示)注册到感兴趣的文件描述符集中, 在这种模式下,select先被调用,进程处于阻塞状态,直至一个或多个事件返回。然后使用recvfrom读取数据。 这种模式需要阻塞数据到达,数据复制。但是BIO由于一次只等待一个数据到达,所以性能上多路复用更优。 Signal-Driven I/O(信号驱动I/O) 进程告诉内核,某个socket 的某个事件发生时,向进程发送信号。接收到信号后,对应的函数回去处理事件。 这种模式不用阻塞数据到达,需要阻塞数据复制 想想,如果数据复制完再通知进程,不就不用阻塞了。于是有下面的异步IO的模型出现。 Asynchronous I/O (异步I/O) 这就是信号驱动I/O的升级版,完全异步,进程无阻塞。对于大部分平台来说,底层利用的还是非异步模型结合回调函数来实现。 遗憾的是,linux的网络IO中是不存在异步IO的,linux的网络IO处理的第二阶段总是阻塞等待数据copy完成的。真正意义上的网络异步IO是Windows下的IOCP(IO完成端口)模型。 对比 总结 Unix网络编程」中说道,按照POSIX标准中的术语,同步指的是I/O动作会导致用户进程阻塞,异步则刚好相反。按照这种分类,上边5种I/O模型中,只有AIO一种是异步的,其他都是同步的。 但是这些只是相对的,程序往往是多线程运行,拿Java来说,主线程调用select操作是阻塞的,但是数据复制这个阻塞过程放到子线程中,对主线程来说没有影响。这也是为什么java的NIO称为同步非阻塞IO。 参考 IO复用

January 27, 2019 · 土川

[Dubbo]探索dubbo2.6.3版本之前的一个问题

由于dubbo版本较低遇到了一个诡异的问题。 ...

October 20, 2018 · 土川

[分布式]接口限流

尝试一下用lua脚本执行redis命令 ...

September 29, 2018 · 土川

[Json]secure json

安全的Json.. ...

June 8, 2018 · 土川

[Java基础]找到Java进程中哪个线程占用了大量CPU处理时间

在Linux环境下找到最大线程占用 ...

June 5, 2018 · 土川

[Cron]linux定时任务的坑

写了个脚本定时执行,有天发现命令没有正确找到我配在PATH的环境变量。 ...

April 18, 2018 · 土川