深入浅出Zookeeper

时间:2023-02-02 23:27:22

能找到的一些zookeeper的资料一上来不是扯一通paxos算法就是一大坨一大坨的代码。很多人对zookeeper更多的是听过,所以这一篇文章就尝试用尽可能用精简的语言科普zookeeper

zookeeper是什么

网上的定义:zookeeper作为一个开源的分布式应用协调系统,作为一个正常人我看完这句话之后是——懵逼。理解一个工具最好的办法是问它解决什么问题的,举个例子:

微服务中每个服务是可以被单独部署的,为了便于Client调用,所有服务都必须在注册中心完成注册。这样Client就可以通过查询注册中心获取有哪些服务可以用(包括服务的调用地址、说明、版本之类的元数据信息)。

无论用什么方式设计注册中心最后我们都要解决一个问题——如何存放服务元数据。

  • 直接JSON序列化后存放在注册中心的硬盘上是最简单的,但是我们的注册中心不应该是单点,它挂了整个系统也就无法正常工作了。所以我们必须集中存储(这样的设计可以让注册中心去中心,变成无状态的服务更容易实现高可用)。

  • 集中存储最好的办法是数据库。比如存放在Mysql或者MongoDB中,如果用Mysql需要使用主从来提高Mysql的可用性;如果是MongoDB我们也需要通过“集群”来提高MongoDB的可用性。这两种技术都增加了系统复杂度——毕竟我只是想存放一些JSON数据而已。

Zookeeper是解决这个问题的一个简单方案,它没有提供像MongoDB的一样的查询、排序功能只提供的简单的数据读写。它的数据结构类似于文件系统,使用API也非常类似读写操作系统的文件系统。比如我们的服务元数据可以设计成这样

app├── app1│ └── metadata└── app2└── metadata

服务注册的时候都会在/app节点下生成以自己服务名称命名的节点(比如 app1)然后把自己的元数据写入到一个名称为metadata的“数据节点”。查询服务的时候只需要获取/app下的所有子节点,需要获取某个服务元数据的时候则直接查询对应目录下的metadata节点。

ZooKeeper zooKeeper = //实例化ZooKeeper//查询服务List<String> appNames = zooKeeper.getChildren("/app", false);for (String appName : appNames) {    String meta = "/app/" + appName + "/meta";     if (zooKeeper.exists(meta, false) != null) {         //查询服务元数据          byte metaBytes[] = zooKeeper.getData(meta, false, null);          //JSON反序列化          AppMetadata metaJava = mapper.readValue(metaBytes, AppMetadata.class);      }}

zookeeper的两把刷子

Zookeeper的一个重要特性是提供了去中心化的数据一致性,在一个Zookeeper集群中我们向任何一台服务器写入数据都会被“同步”到其他服务器上。实现这样的特性必须有两把刷子——选举算法和分布式事务

选举算法

很多人会把zookeeper和paxos算法联系在一起,非常负责任的说——它们没有半毛钱的关系。zookeeper的算法非常简单,比如有3台服务器,(server1的myid=1,server2的myid=2,server3的myid=3)

  • 启动的时候服务器都会向集群中其他服务器发送选举信息(zxid,myid)(相当于选自己做leader)。zxid(ZooKeeper Transaction Id)是最后一次写入的节点数据的事务编号(越大说明数据越新),myid是配置的时候分配的一个编号。比如server1发送的是(0, 1),server2发送(0, 2) 、server3发送(1, 3)

  • 收到选举信息的服务器会做一次比较:1).zxid比较大的一个作为leader(选择最新的数据)2). 如果zxid相同(数据同步)则选择myid比较大的作为leader。3). 把自己选中的leader再次发送出去。比如server1收到server3的信息后选择server3作为leader,server2也选择server3作为leader。二者选择leader之后都会再次发送(1, 3)。

  • 当一台服务器收到(n/2+1)个选举信息的时候就认为leader已经选择成功(n是集群中服务器的数据),停止发送选举信息,进入follower状态。比如3台服务器有2台服务器选择server3作为leader那么选举就成功。

在系统运行过程中如果leader死掉了,所有的follower会重新按照上面的算法选举出新的leader。如果你有过网络相关的经验不难发现这个选举算法其实是OSPF的DB、BDR选举算法。

分布式事务

Zookeeper实现的分布式事务是二两阶段提交算法。Client可以向集群中任何一台服务器发送“写入数据”请求,该请求会被转发给leader,leader会通过两阶段提交协议来保证所有的follower都写入成功。

  • leader发送写入数据命令给所有的follower

  • 所有的follower写入数据,返回leader ack确认

  • leader在收到半数的follower的ack之后向follower广播commit数据包

zookeeper的用途

用作注册中心只能算zookeeper的一个“不误正业”的用途。除此之外它还可以用来实现通知/协调。在使用zookeeper client的时候你可能已经注意到了,无论是getData还是setData你都可以传递一个Watch对象,这个对象用来监视某个节点。当节点发送变化的时候这个Watch对象会被调用。通过这个机制我们可以实现分布式系统中的通知/协调,比如当某个模块已经完成了任务就修改节点,另一个模块就会感知到这个变化,这个动作相当于“任务推送”。(和MQ有异曲同工之妙,区别是zookeeper是一个去中心的分布式系统)除了上面的用途之外zookeeper还可以用来实现心跳(比如hadoop的namenode ha)。无论哪种应用基本上都是利用zookeeper提供的数据一致性Watch这两个特性。