分布式协调服务的自我修养
点击上方“Java之间”,选择“置顶或者星标”
你关注的就是我关心的!
作者:林湾村龙猫
微信公众号:林湾村龙猫(rudy_tan_home)
随着互联网技术的发展,大型网站需要的计算能力和存储能力越来越高,网站架构逐渐从集中式转变成分布式系统。
虽然分布式相对于集中式系统有比较多的优势,比如更高更强的计算、存储、处理能力等。但同时也引入了其他一些问题,比如如何在分布式系统中保证数据的一致性和可用性。
在日常中,如果两个员工或用户对某件事产生了分歧,通常我们的做法是找上级,去做数据和信息的同步。
那么对于我们的服务呢,多个节点之间数据不同步如何处理?
在单机发展到集群、分布式服务的过程中,每一件技术或工具都走向了系统化,专一化的道路。举个例子,在单体应用中,如果多个线程想对同一个变量进行修改,我们通常的做法是对要修改的变量或资源加锁。那么对于集群来说,并没有这样的东西,我们应该怎么做?
对于分布式集群来说,这个时候,我们通常需要一个能够在各个服务或节点之间进行协调的服务或中间人
架构设计中,没有一个问题不能通过一层抽象层来解决,如果有,那就是两层。
我们可以一起看看,协调服务中的佼佼者 --ZooKeeper
zookeeper 起源
最初,在 Hadoop 生态中,会存在很多的服务或组件(比如 hive、pig 等),每个服务或组件之间进行协调处理是很麻烦的一件事情,急需一种高可用高性能数据强一致性的协调框架。因此雅虎的工程师们创造了这个中间程序,但中间程序的命名却愁死了开发人员,突然想到 hadoop 中的大多是动物名字,似乎缺乏一个管理员,这个程序的功能有是如此的相似。因此 zookeeper 诞生。
zookeeper 提供了哪些特性,以便于能够很好的完成协调能力的处理呢?
功能与特性
数据存储
zookeeper 提供了类似 Linux 文件系统一样的数据结构。每一个节点对应一个 Znode 节点,每一个 Znode 节点都可以存储 1MB(默认)的数据。
客户端对 zk 的操作就是对 Znode 节点的操作。
Znode: 包含 ACL 权限控制、修改 / 访问时间、最后一次操作的事务 Id(zxid) 等等
说有数据存储在内存中,在内存中维护这么一颗树。
每次对 Znode 节点修改都是保证顺序和原子性的操作。写操作是原子性操作。
举个例子,在注册中心中,可以通过路径 "/fsof / 服务名 1/providers" 找到 "服务 1" 的所有提供者。
每一个 Znode 节点又根据节点的生命周期与类型分为 4 种节点。
生命周期:当客户端会话结束的时候,是否清理掉这个会话创建的节点。持久 - 不清理,临时 - 清理。
类型:每一个会话,创建单独的节点(例子:正常节点:rudytan, 顺序编号节点:rudytan001,rudytan002 等等)
监听机制
zookeeper 除了提供对 Znode 节点的处理能力,还提供了对节点的变更进行监听通知的能力。
监听机制的步骤如下:
任何 session(session1,session2) 都可以对自己感兴趣的 znode 监听。
当 znode 通过 session1 对节点进行了修改。
session1,session2 都会收到 znode 的变更事件通知。
节点常见的事件通知有:
session 建立成功事件
节点添加
节点删除
节点变更
子节点列表变化
需要特别说明的是:
一次监听事件,只会被触发一次,如果想要监听到 znode 的第二次变更,需要重新注册监听。
到这里,我们了解到 zookeeper 提供的能力,那我们在哪些场景可以使用它?如何使用它呢?
应用场景
zookeeper 用得比较多的地方可能是,微服务的集群管理与服务注册与发现。
注册中心
依赖于临时节点
消费者启动的时候,会先去注册中心中全量拉取服务的注册列表。
当某个服务节点有变化的时候,通过监听机制做数据更新。
zookeeper 挂了,不影响消费者的服务调用。
目前还有个比较流行的服务 Eureka 也可以做注册中心,他们有什么优势和劣势呢?留个疑问,哈哈哈。
分布式锁
依赖于临时顺序节点
判断当前 client 的顺序号是否是最小的,如果是获取到锁。
没有获取到锁的节点监听最小节点的删除事件(比如 lockkey001)
锁释放,最小节点删除,剩余节点重新开始获取锁。
重复步骤二到四。
redis 和 db 也能创建分布式锁,哪有什么异同呢?留个疑问,哈哈哈。
集群管理与 master 选举
依赖于临时节点
zookeeper 保证无法重复创建一个已存在的数据节点,创建成功的 client 为 master。
非 master,在已经创建的节点上注册节点删除事件监听。
当 master 挂掉后,其他集群节点收到节点删除事件,进行重新选举
重复步骤二到四
当然还有其他应用场景,不一一列举了。
有人说,zookeeper 可以做分布式配置中心、分布式消息队列,看到这里的小伙伴们,你们觉得合适么?
到这里,可以基本上满足基于 zk 应用开发的理论知识储备。对原理或有更强求知欲的小伙伴可以继续往下看,接下来聊聊 zookeeper 如何做到高性能高可用强一致性的。
高性能高可用强一致性保障
高性能 - 分布式集群
高性能,我们通常想到的是通过集群部署来突破单机的性能瓶颈。对于 zk 来说,就是通过部署多个节点共同对外提供服务,来提供读的高性能。
Master/Slave 模式。
在 zookeeper 中部署多台节点对外提供服务,客户端可以连接到任意一个节点。
每个节点的数据都是一样的。
节点根据角色分为 Leader 节点与 Learner 节点(包括 Follower 节点与 Observer 节点)。
集群中,只有一个 Leader 节点,完成所有的写请求处理。
每次写请求都会生成一个全局的唯一的 64 位整型的事务 ID(可以理解为全局的数据的版本号)。
Learner 节点可以有很多,每个 Leaner 可以独自处理读请求,转写请求到 Leader 节点。
当 Leader 节点挂掉后,会从 Follower 节点中通过选举方式选出一个 Leader 提供对外服务。
Follower 节点与 Observer 节点区别在于不参与选举和提议的事务过半处理。
集群通常是按照奇数个节点进行部署(偶然太对容灾没啥影响,浪费机器)。
数据一致性(zab 协议 - 原子广播协议)
通过集群的部署,根据 CAP 原理,这样,可能导致同一个数据在不同节点上的数据不一致。zookeeper 通过 zab 原子广播协议来保证数据在每一个节点上的一致性。原子广播协议(类似 2PC 提交协议)大概分为 3 个步骤。
Leader 包装写请求,生成唯一 zxid,发起提议,广播给所有 Follower。
Follower 收到提议后,写入本地事务日志,根据自身情况,是否同意该事务的提交。
Leader 收到过半的 Follower 同意,自己先添加事务。然后对所有的 Learner 节点发送提交事务请求。
需要说明的是,zookeeper 对数据一致性的要求是:
顺序一致性:严格按照事务发起的顺序执行写操作。
原子性:所有事务请求的结果在集群中的所有节点上的应用情况是一致的。
单一视图:客户端访问任何一个节点,看到的数据模型都是一致的。
实时性:保证在极小一段时间客户端最终可以从服务读取最新数据状态(如果要实时,需要客户端调用 syn 方法)。
可用性 - leader 选举(zab 协议 - 崩溃恢复协议)
在整个集群中,写请求都集中在一个 Leader 节点上,如果 Leader 节点挂了咋办呢?
当集群初始化或 Follower 无法联系上 Leader 节点的时候,每个 Follower 开始进入选举模式。选举步骤如下:
Follower 节点第一次投票先投自己,然后将自己的选票广播给剩余的 Follower 节点。
Follower 节点接收到其他的选票。
选票比较:比较自己的与接收的选票的投票更有。
如果资金的选票不是最优选票,变更自己的选票,投最优选票的节点。
统计自己收到的选票,如果某个节点获得了过半的节点的投票。确认该节点为新的 Leader 节点。
确认 Leader 节点后,每个节点变更自己的角色。完成投票选举。
选举原则:谁的数据最新,谁就有优先被选为 Leader 的资格。
举个例子,假如现在 zk 集群有 5 个节点,然后挂掉了 2 个节点。剩余节点 S3,S4,S6 开始进行选举,他们的最大事务 ID 分别是 6,2,6。定义投票结构为(投票的节点 ID,被投节点 ID,被投节点最大事务 ID)。
初始状态,S3,S4,S5 分别投自己,并带上自己的最大事务 ID。
S3,S4,S5 分别对自己收到的 2 票与自己的 1 票做比较。
S5 发现自己的是最优投票,不变更投票,S3,S4 发现 S5 的投票是最优解,更改投票。
S3,S4 广播自己变更的投票。
最后大家都确认了 S5 是 Leader,S5 节点状态变更为 Leader 节点,S3,S4 变更为 Follower 节点。
到这里,就是选举的主要过程。
数据的持久化
zookeeper 所有数据都存在内存中。
zookeeper 会定期将内存 dump 到磁盘中,形成数据快照。
zookeeper 每次的事务请求,都会先接入到磁盘中,形成事务日志。
全量数据 = 数据快照 + 事务日志。
比较
前面也提到过,不同的应用场景似乎有不少替代品,我们就大概做个简单的比较。
注册中心的比较(与 Netflix 的 Eureka 方案)
通过上面的架构图,可以发现 Eureka 不同于 zk 中的节点,Eureka 中的节点每一个节点对等。是个 AP 系统,而不是 zk 的 CP 系统。在注册中心的应用场景下,相对于与强数据一致性,更加关心可用性。
分布式锁(与 redis、mysql 比较)
具体差异比较:
Redis:简单,可靠性不高
DB:稳定、性能有所不足
ZK: 比较高的性能和完整的锁的实现,但实现复杂度高,并发不高。
通过这些比较,我们为什么还需要或在什么场景下使用 zookeeper 呢。我觉得就一句话:
zookeeper,一种通用的分布式协调服务解决方案。
感悟
最后,说说在整个学习和使用 zk 过程中的一个感悟吧。
没有银弹,每一种技术或方案都有其优点和缺点。
做一件事情很简单,做好一件事件很难。
最近热文阅读:
关注公众号,你想要的Java都在这里!