前言
看了很多zookeeper的文章和视频,几乎前篇一律先讲一遍基本使用再讲使用场景,看完还是云里雾里。
所以本文换一个角度讲解zookeeper,从它出现的背景,要解决的问题入手,一步步反推它的功能。用大白话的方式试着从设计者的角度思考zookeeper为何如此设计。
注:本文只是帮助理解zookeeper,具体的搭建和操作可以参考官方文档或百度,从设计者的角度思考也只是一种假设,只是为了方便理解,实际上zookeeper最初的定位可能压根没考虑这么多
协调者
现如今上网用户越来越多,单机服务无法满足各种高并发场景带来的压力,所以采用多个物理节点来共同完成任务,组成一个分布式应用,而zookeeper就是分布式应用的协调者。
什么是协调者?你们公司的领导就是协调者,负责协调每个人的工作。
如果把公司比做动物园,每个员工是一个动物,那么负责协调工作的领导就是动物园管理员(zookeeper的直译)。
分布式应用也是这样,比如有n个订单服务、n个用户服务,两种服务虽然各处理一类事情但总归是需要配合的,需要配合就有公共的数据(比如节点的信息,公共的配置),那这公共的数据存哪里好?单独交给任何一个服务都不合适,因为大家都是平等的,如果所有服务都给,一致性怎么保证,万一不一样到底以哪个为准。
所以就需要一个中间件来协调,就是zookeeper,它的存在就是给分布式应用提供一致性服务。
zookeeper
服务
如果我们是zookeeper的设计者,现在用户的痛点找到了,产品的定位也有了:协调分布式应用。
那么接下来就要想了,要提供怎样的服务,或者说分布式应用中哪些工作需要协调?
分布式注册中心
比如分布式应用有A服务、B服务,A服务需要调用B服务,那A怎么知道B服务在哪些台机器呐?ip是多少?port是多少?
如果想简单解决,那就把B服务的ip和port在A服务存储一份就可以了,但是如果有多个B服务就需要存储多份,某个B服务宕机了还要改。如果又加了个依赖B服务的C服务,又要再C服务存一份,甚至以后有更多服务彼此依赖,那光维护这些信息就能累死。
zookeeper作为分布式协调者,这种事情肯定要管,怎么管呐,所有服务都到我这里注册(ip、port等信息),这样每台机器的每个服务只需注册一次,将来A想调用B,先来注册中心找到一个B的位置,然后去调用就可以了,某个服务宕机了,就把它注册的信息删掉,起到一个注册中心的作用。
文件系统数据结构
有了具体的需求,下面就是设计了,如果自己来设计zookeeper,这个数据结构如何设计合理?
假设场景如下:
- 当前有多个项目
- 每个项目按功能拆分成多个微服务
- 每个微服务有多个节点,节点信息包含ip和port等所在机器信息
如果人工处理,把这些信息交给某个人(注册员)来用电脑存储记录,那么他有两种选择:1.建一个文件夹把所有节点信息存入。2.建多个文件夹把节点信息分类存储。
很明显第一种一但信息多了,找太费劲了,所以一定会用第二种,存储的信息大概如下:
这样存的好处一看便知:方便归纳,方便查找
这时候再回头看zookeeper的数据结构,就能理解为什么要这么设计了:
zookeeper数据结构
上例中分为
文件夹
和txt文件
,zookeeper统称为节点,任何节点都可以包含信息(文件),任何节点也可以包含子节点(文件夹),节点本身可以create
、delete
来创建删除,也可以通过set
、get
存放获取数据(所以也是一个key-value模式)。
接下来展示两个使用zookeeper做注册中心的数据存储
-
spring使用zookeeper做注册中心:
spring-zookeeper -
dubbo使用zookeeper做注册中心:
dubbo-zookeeper
可以看到有些信息直接用节点名来存储,有些信息用set
存储,其实随意,怎么存怎么取呗,但节点名太长如果再有子节点就很不方便
问题?
以上只能算是一个基础版的注册中心,可以注册可以获取仅此而已,但在微服务开发中会有这样的场景,比如1个A服务,2个B服务,A调用B且从zookeeper处获取了2个B服务的位置并缓存起来,可以选一个调用或者负载均衡轮番调用,这时突然有一个B服务宕机了,就会出现问题:
- 问题1,此时zookeeper中存储的两个B的节点信息有一个是错误信息,因为服务已经停了,你不可能依靠宕机的B主动来删除,那么zookeeper如何感知B的掉线?
- 问题2,即使zookeeper中的宕机节点信息删除了,由于A缓存了B的信息,无法知道有一个B服务宕机了,那么如何通知A?
长链接和临时节点
zookeeper的解决问题1是这样的,作为一个注册中心,他要求所有客户端与其保持长链接,一次链接称为一次会话,并通过心跳(ping
,pong
)不断的检验会话是否依然存在,如果某个链接长时间不响应,那就说明服务掉线了,就可以把其注册的节点信息删除。
实际上删除的方式并不是主动去删,而是zookeeper给每个节点添加一个是否是临时节点的属性,并规定一个临时节点在会话结束后自动删除,所以每个服务注册的都是临时节点信息,如果服务长时间不响应代表会话结束,临时节点就会被自动删除(后台删除,有一定延时)
会话结束删除临时节点
通过长链接和临时节点的设计者,解决了zookeeper如何感知某服务的掉线,并自动删除掉线服务注册的节点信息
创建临时节点的方法:
create -e /ephemeral data // 其中-e代表临时节点,非临时节点会一直保存
那么问题1解决了,问题2:如何通知其它服务某服务的掉线事件
监听通知机制
问题2解决也很简单,既然有了长链接保持会话,B服务掉了,zookeeper直接把掉线事件推送给A就行了。
但关键是zookeeper中有那么多节点,比如A只依赖B,那A只关心B服务的所有节点掉没掉,其它节点跟我也没关系啊,推送过多的垃圾信息浪费啊,但zookeeper也不知道谁关心谁啊,所以就推出了监听通知机制:
- 监听: 你告诉zookeeper你关心那些节点
- 通知: 你关心的节点发生变化,zookeeper负责通知你
通过这种方式,A监听B节点,当B节点下有某个节点掉了zookeeper通知A,到此问题解决~
为了实现各种维度的监控,zookeeper提供了多种监听方法:
- 针对节点监听
get -w /path // 注册监听的同时获取数据
stat -w /path // 对节点进行监听,且获取元数据信息
- 针对目录监听,目录的变化,会触发事件,且一旦触发,对应的监听也会被移除,后续对节点的创建没有触发监听事件
ls -w /path
- 针对递归子目录的监听
ls -R -w /path : -R 区分大小写,一定用大写
注:zookeeper的监听是一次性的
总结
通过文件系统数据结构
,长链接
,临时节点
,监听通知机制
,zookeeper就形成了一个完整的注册中心。
实际使用我们可以用zookeeper配合spring来做微服务的注册中心,dubbo也可以使用zookeeper做注册中心,这两个都有相关的支持。
类似的注册中心还有eureka,nacos,目前主流应该是nacos吧,我现在用的也是nacos。
分布式配置中心
这个其实很好理解了,集群里的每个节点都有一样的配置,为了避免每个节点都去维护,还不如直接存在一个配置中心里,zookeeper的文件系统结构可以使用各种配置信息分类存储,还可以通过监听机制来实时感知配置的变化。
发布/订阅
有了监听通知机制
,自然就支持了分布式发布/订阅
分布式锁
分布式应用中还有一个需要协调的场景就是分布式锁,比如给某个商品减库存,分布式锁的作用就是保证隶属于不同服务所有线程同时要操作商品库存时,只有拿到锁的那个线程才可以操作,或者可以理解为只能排着队一个个操作。
实际应用中可能会有多个锁,通过文件系统可以对锁归类整理,如下:
锁
顺序编号
如何实现呐,比如有A,B两个线程都要操作商品1,B看/商品1锁
节点下没有子节点,好,那B建一个临时子节点B排
,此时A线程又来了,看到/商品1锁
下有一个节点了,那说明B在操作商品1了,那就建一个临时节点A排
,并监听B排
节点,B线程操作完之后删除了B排
,此时A线程感知到了,证明A排上号获得锁了,A开始操作商品1,操作完了删除A排
。
但是问题出现了,又来了个线程C,此时下面两个节点A排
,B排
都在,线程C进来监听谁啊,我们都只到肯定是A,但问题是C不知道这些信息,A排
、B排
对它来讲都是一样的,没有办法选择了。
问题的根源是C不知道A和B谁先谁后,所以如果我们是zookeeper设计者,那么就要给节点加顺序了:一个类似mysql自增主键的可以标识先后的顺序值。
这样的话,C进程根据顺序就知道他要跟在A后面了
实际上这种结构就好比排队买票,排队就有了顺序,队伍的第一个人(顺序号最小)就是正在买票的人(获取到了锁),队伍里的每个人都只关注前面的人即可,如果前面的人走了,证明排到自己了(获得锁),而自己买完票自然也要离开队伍(删除节点)。
总结
以上通过注册中心
,配置中心
,分布式锁
等场景介绍了zookeeper主要特性:文件系统数据结构
,CS长链接
,监听通知机制
,临时节点
,有序节点
等主要特性,相信到此就对zookeeper有个基本的概念了。
粗暴总结一下不过就是一个:可监控的文件系统结构的数据库,当你的分布式应用有数据要统一存储,只要它的结构合适,就可以用它存。
其他
除此之外zookeeper还有一些其它使用场景和特性,简单提一下就不细说了:
持久化
zookeeper的持久化是日志+快照,这点和redis很相似
集群
zookeeper作为配置中心肯定不能轻易挂掉,为了高可用,提供了集群功能,并可以实现选举,这也是一个大课题,篇幅有限,以后单说
乐观锁
节点信息包含版本号,做乐观锁再合适不过了(mysql为了实现乐观锁往往还要建一个版本号字段)
TTL
类似redis那种可以设置过期时间的节点,但是好像不准确
权限控制
zookeeper作为数据存储中心,如果暴露在非局域网中那就有点太可怕了,所以加了针对节点的权限控制,有些节点需要登录才访问,有些节点需要秘钥,有些限制固定ip访问,总之就是像要保证数据的安全
网友评论