副本有什么作用
在计算机软件行业,经常会听到大家讨论服务的可用性问题。可能会经常听到有人谈到4个9,单点故障等名词,其本质上都是在讨论服务的可用性能力。
目前业内解决高可用问题的主要手段是冗余备份,说白了就是多搞几个替补,当当前服务出现了故障,替补赶紧补上去,使系统继续对外提供服务,来实现业务系统对外持续可用的能力。
其实,不仅仅在服务计算层,在服务的存储层也是通过冗余副本来实现存储的高可用,比如你应该经常听别人说过“我们数据存储用的是mysql集群”,“我们的服务采用的是集群部署模式”。在实际的应用场景中,我们会把数据复制到多个服务器中,在多个服务器中,通常会存在两个角色:leader和follower,leader中保存有最新数据,而follower从leader同步数据,来保证和leaer中的数据一致。
副本机制有以下好处:
1.提供数据冗余
当leader服务器宕机后,可以从follower中选择一个作为leader继续对外提供服务。
2.提高扩展性
在follower可以对外提供服务的场景中,那么通过增加follower的数据,来提升服务的性能,使服务具备了横向扩展的能力
3.基于地理位置提供更高效服务
其实我们常用的CDN本质上也是副本机制的应用。根据使用者的位置,来访问就近的那个副本。
个人认为,学习一项技能,知识或者理论时,最好了解一下这个理论在业内主流软件中是怎么使用的,可以把这种使用方式作为一种最佳实践。其实,在之前的文章"如何让mysql存储海量数据" 中,我们有讨论过,副本机制在存储服务中的应用。下面我们再来看一下,在kafka中是如何使用副本机制的。
kafka中的副本
在了解kafka的副本机制前,我们先了解一下kafka中的副本是怎么定义的。
在kafka中有topic的概念,每个topic会包含多个partition,而副本所描述的对象就是partition,通常情况下(默认情况下kafka集群最少3个broker节点),每个partition会有1个leader副本,2个follower副本。3个partition副本会保存到不同的broker上,这样即使当某个broker宕机了,由于副本的存在,kafka依旧可以对外提供服务,partition副本分布如下图:
上图kafka集群中有3个broker,topic1有三个partition,每个partition有3个副本。
在partition 副本机制中,只有leader副本可以对外提供读写服务,follower不对外提供服务,只是异步的从leader中拉群最新的数据,来保证follower和leader数据的一致性。
当leader所在broker宕机挂掉后,kafka依托Zookeeper提供的监控功能 ,能够实时的感知到,并立即在存活的follower中选择一个副本作为leader副本,对外提供服务,当老的leader副本重新回来后,只能作为follower副本加入到集群中。
上文中我们说到使用副本机制有3大好处,但是在kafka中只使用到了第一个,原因就是在kafka的多个副本中,只有leader副本对外提供服务,扩展再多的follower,也不会增加kafka的性能,自然也不会根据客户端的位置,选择某一个副本来提供服务。
kafka如何使用副本机制的
虽然kafka中的副本机制只提供数据冗余的能力,但是这样设计可以为客户端提供更高的数据一致性。方便实现"Read-your-write"
"Read-your-write"的意思是说:你可以立即读到你写的数据,因为读写请求是同一个partition副本提供的,所以,只要数据向这个partition写成功,那么就可以从这个partition中读到。
试想,如果对读写请求的处理是由不同的partition副本来处理:写清求由leader副本来处理,读请求由follower副本来处理,由于follower和leader之间的数据同步的方式是follower副本异步拉取的,那么写到leader副本中数据不一定能够实时同步到follower中,那么此时从follower中读取数据,就不一定能够读取到对应的数据。
在消息读写场景下,kafka保证了良好的数据一致性,但是浪费了一定的集群资源,多个follower副本处于空闲状态。好在kafka集群中通常会有多个topic,每个topic中有会有多个partition,这样每个broker基本上都会分配到多个leader副本,kafka集群中的多个broker的硬件资源也不会被浪费。
主从切换的一致性问题
上文说了kafka中的副本机制,为了实现数据的一致性,数据的读写请求只有leader副本才可以处理,其他的follower副本只做从leader副本同步数据的操作,也就是说follower副本中的数据和leader中的副本可能会存在一定的差异,当leader副本不可用时,选举一个follower副本作为leader副本时,就可能会导致数据的不一致,甚至数据丢失。当然要想从根本上解决这个问题也可以,具体方式可以参考如何保证kafka消息不丢失?
上述方式虽然可以解决主从切换数据不一致问题,但是会导致消息写入性能下降,牺牲了kafka的高吞吐的特性,如果业务场景不要求数据强一致性的话,尽量不要采用上述方法。
不过kafka自身也考虑到了,主从切换对数据一致性的影响,采取了ISR来降低数据的不一致。
ISR本质上是一个partition副本的集合,只不过副本要想进入这个集合中是有条件的。这个条件是:和leader副本中的数据是同步的follower副本才可以进入到ISR中。leader副本本身就符合条件,天然就在ISR中。那么“和leader副本数据是同步的”这个概念是怎么定义的呢?
用来衡量follower是否符合“和leader副本是同步的”,不是依据两者数据的差异量,而是两者数据不一致持续的时间,如果持续的时间过长,那么就认为两者不同步。其实这种设定也是有道理的,试想:发送者一次批量发送了大量的消息,这些消息先写入到leader中,此时follower中没有这些数据,那么所有follower都变成和leader是不同步的了,但是不足之处也很明显:ISR中和leader数据差异比较大的follower,是有可能被选为新的leader的。
上述持续时间的长短有参数 replica.lag.max.ms 来定义,默认10s,如果ISR中的follower副本和leader副本数据存在差异的时间超过10s,那么这个follower副本就会被从ISR中踢出出去,当然如果该follower后续慢慢追上了leader副本,那么这个follower就会被重新添加到ISR中,也就是说iSR是一个动态的集合。
当leader副本不可用时,不是从所有follower副本选举leader,而是从ISR中选举,这样可以尽可能的降低数据的不一致性。
既然 ISR 是可以动态调整的,那么自然就可以出现这样的情形:ISR 为空。因为 leader 副本天然就在 ISR 中,如果 ISR 为空了,就说明 leader 副本也“挂掉”了,Kafka 需要重新选举一个新的 leader。可是 ISR 是空,此时该怎么选举新 leader 呢?
kafka 把所有不在 ISR 中的存活副本都称为非同步副本,通常来说,非同步副本落后 leader 太多,因此,如果选择这些副本作为新 leader,就可能出现数据的丢失。毕竟,这些副本中保存的消息远远落后于老 leader 中的消息。在 Kafka 中,选举这种副本的过程称为 Unclean 领导者选举。Broker 端参数 unclean.leader.election.enable 控制是否允许 Unclean 领导者选举。
开启 Unclean 领导者选举可能会造成数据丢失,但好处是,它使得分区 Leader 副本一直存在,不至于停止对外提供服务,因此提升了高可用性。
反之,禁止 Unclean 领导者选举的好处在于维护了数据的一致性,避免了消息丢失,但牺牲了高可用性。
由此也可以看出,参数 unclean.leader.election.enable 是kafka提供给使用者一个选择"一致性"和"可用性"的权利,也由此印证了分布式系统中的CAP理论。