微服务架构解决方案 Spring Boot + Dubbo + Zookeeper
Zookeeper
Zookeeper
分布式协调框架,实现分布式锁
ZooKeeper 将数据保存在内存中,性能是非常棒
Zookeeper数据模型
- Zookeeper 提供了一个类似于 Linux 文件系统的树形结构,
- 最上层是根节点以“/”来代表
- 每个节点还可以拥有 N 个子节点
- 提供了对于每个节点的监控与通知机制
Zookeeper 的这种节点叫做 Znode, 每个节点的数据最大不能超过 1MB, 每一个 Znode 节点拥有唯一的路径
Znode节点
- stat:包含 Znode 的各种元数据,比如事务 ID、版本号、时间戳、大小等等。
- data:Znode 存储的数据信息。可以是数字、字符串或者是二级制序列。
- ACL:记录 Znode 的访问权限,即哪些人或哪些 IP 可以访问本节点。
- child:当前节点的子节点引用
节点类型
顺序节点 保证znode路径将是唯一的, ZooKeeper集合将向znode路径填充10位序列号
stat
Stat 中记录了这个 znode 的三个相关的版本:
- dataVersion :当前 znode 节点的版本号
- cversion : 当前 znode 子节点的版本
- aclVersion : 当前 znode 的 ACL 的版本。
ACL权限设置
对于 znode 操作的权限,ZooKeeper 提供了以下 5 种:
- READ :能获取节点数据和列出其子节点
- WRITE : 能设置/更新节点数据
- ADMIN : 能设置节点 ACL 的权限
- CREATE : 能创建子节点
- DELETE : 能删除子节点
对于身份认证,提供了以下几种方式:
- world : 默认方式,所有用户都可无条件访问。
- auth :不使用任何 id,代表任何已认证的用户。
- digest :用户名:密码认证方式: username:password 。
- ip : 对指定 ip 进行限制。
事件通知Watch
可以把Watch 理解成是注册在特定 Znode 上的触发器。在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去
znode相关的数据的修改或znode的子项中的更改。只触发一次watches。 如果客户端想要再次通知,则必须通过另一个读取操作来完成。当连接会话过期时,客户端将与服务器断开连接,相关的watches也将被删除。
使用场景:
- 服务端在 ZooKeeper 创建一个Znode, 包含服务端的ip、访问方式。
把服务端注册在zookeeper
- Zookeeper 客户端在请求读操作的时候,选择设置 Watch 。
比如调用 getData 方法,watch 参数是 true。
- Zookeeper 接到请求,返回节点数据,并且在对应的哈希表里插入被 Watch 的 Znode 路径,以及 Watcher 列表。
此时这个 Znode 就被客户端 watch
- 当这个 Znode 发生写操作,也就是调用了 create,delete,setData 方法的时候,将会触发 Znode 上注册的对应事件,请求 Watch 的客户端会接收到异步通知。
Zookeeper 集群
为了防止单机挂掉的情况,Zookeeper 维护了一个集群Zookeeper Service
Zookeeper Service 集群是一主多从结构。集群为大于等于3的奇数
- 在更新数据时,首先更新到主节点(这里的节点是指服务器,不是 Znode),再同步到从节点。
- 在读取数据时,直接读取任意从节点。
为了保证主从节点的数据一致性,Zookeeper 采用了 ZAB 协议,这种协议非常类似于一致性算法 Paxos 和 Raft。
ZAB 协议
Zookeeper Atomic Broadcast,解决 Zookeeper 集群崩溃恢复,主从同步数据的问题
集群崩溃恢复
-
ZAB 协议定义的三种 Server节点状态
- Election/Looking :选举状态,寻找 Leader。
- Following :Follower 节点(从节点)所处的状态。
- Leading :Leader 节点(主节点)所处状态。
- 代码实现中,多了一种:Observing 状态
这是 Zookeeper 引入 Observer 之后加入的,Observer 不参与选举,是只读节点,跟 Zab 协议没有关系。
-
Zab 选举的四个阶段
- 选举阶段 Leader election
正常情况 Server主节点是Leading,从节点的是Following
没有Leading的时候,所以节点都变为Looking,然后会把数据最新节点的选举准 Leader状态变为 Leading,其他节点的状态变为 Following - 发现阶段 Discovery
followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。
接收所有 Follower 发来各自的最新 epoch 值。准Leader 从中选出最大的 epoch,基于此值加 1,生成新的 epoch 分发给各个 Follower。 - 同步阶段 Synchronization
防止产生多个 Leader ,当半数 Follower 同步成功,这个准 Leader 才能成为正式的 Leader。 - 广播阶段 Broadcast
到了这个阶段,ZooKeeper 集群才能正式对外提供事务服务
- 选举阶段 Leader election
主从同步数据
Zookeeper 常规情况下更新数据:
- 客户端发出写入数据请求给任意 Follower。
- Follower 把写入数据请求转发给 Leader。
- Leader 采用二阶段提交方式,先发送 Propose 广播给 Follower
- Follower 接到 Propose 消息,写入日志成功后,返回 ACK 消息给 Leader。
- Leader 接到半数以上ACK消息,返回成功给客户端,并且广播 Commit 请求给 Follower
部署
-
模式
- 单机模式:存在单点故障
- 集群模式:在多台机器上部署 Zookeeper 集群,适合线上环境使用。
- 伪集群模式:在一台机器同时运行多个 Zookeeper 实例,仍然有单点故障问题,当然,其中配置的端口号要错开的,适合实验环境模拟集群使用
-
端口
- 2181:客户端连接 Zookeeper 集群使用的监听端口号
- 3888:选举 leader 使用
- 2888:集群内机器通讯使用(Leader 和 Follower 之间数据同步使用的端口号,Leader 监听此端口)
-
配置文件路径:/conf/zoo.cfg
clientPort=2181 #客户端连接 Zookeeper 服务器的端口
dataDir=/data #数据目录
dataLogDir=/datalog #日志目录
tickTime=2000 #客心跳检测时间间隔
initLimit=5 #Leader 与 Follower 服务器心跳时间间隔数 tickTime*initLimit
syncLimit=2 #Leader 与 Follower 请求和应答时间长度 tickTime*syncLimit
autopurge.snapRetainCount=3 #定时清理需要保留的文件数目
autopurge.purgeInterval=0 #定时清理频率
maxClientCnxns=60 #限制连接到 Zookeeper 的客户端的数量
server.1=192.168.0.1:2888:3888 #集群节点1的 ip:集群通讯端口:选举端口
server.2=192.168.0.2:2888:3888 #集群节点2
server.3=192.168.0.3:2888:3888 #集群节点3
CLI命令行操作
- ZooKeeper服务器(“bin/zkServer.sh start”)
- ZooKeeper客户端(“bin/zkCli.sh”)
创建节点:create
语法:
create /path /data
create /parent/path/subnode/path /data
案例:
[zk: localhost:2181(CONNECTED) 0] create /FirstZnode"node1"
创建顺序节点 -s
create -s /path /data
[zk: localhost:2181(CONNECTED) 2] create -s /FirstZnode "node2"
Created /FirstZnode0000000001
创建临时节点 -e
create -e /path /data
检查状态
znode的元数据。它包含时间戳,版本号,ACL,数据长度和子znode等细项
stat /path
列出子项 ls
ls /path
获取节点的数据:getData
返回znode的关联数据和指定znode的元数据
get /path
get /FirstZnode
get /FirstZnode0000000001
获取并监听节点
get /path [watch] 1
get /FirstZnode 1
删除节点:delete
rmr /path
设置节点数据:setData
set /path /data
- 读操作 exists,getData,getChildren
- 写操作 create,delete,setData
java API
Java和C有的官方API
其他语言(.NET,python等)也有提供非官方API
- connect - 连接到ZooKeeper集合
ZooKeeper(String connectionString, int sessionTimeout, Watcher watcher)
- create- 创建znode
create(String path, byte[] data, List<ACL> acl, CreateMode createMode)
- exists- 检查znode是否存在及其信息
exists(String path, boolean watcher)
- getData - 从特定的znode获取数据
getData(String path, Watcher watcher, Stat stat)
- setData - 在特定的znode中设置数据
setData(String path, byte[] data, int version)
- getChildren - 获取特定znode中的所有子节点
getChildren(String path, Watcher watcher)
- delete - 删除特定的znode及其所有子项
delete(String path, int version)
- close - 关闭连接
应用场景
分布式锁
这是雅虎研究员设计 Zookeeper 的初衷。利用 Zookeeper 的临时顺序节点,可以轻松实现分布式锁。
-
方法1
- A 发现没有临时节点, 则创建临时节点 获取锁
- 后续B, C,D… 发现已有临时节点 , 则watch它排队等待
- A释放锁之后 , 通知后一个排队的对象, 后续同上
-
方法2
- A创建临时顺序节点 0000001, 发现自己是最小的节点, 则获取锁
- B创建临时顺序节点 0000002, 发现前面有更小的节点则watch 0000001
- C创建临时顺序节点 0000003, 发现前面有更小的节点则watch 0000002
- …
-
对比Redis
Zookeeper 使用简单,添加和删除节点性能比 Redis 性能低
Redis 使用复杂(要考虑超时,误删,原子性),性能高,没有等待锁队列只能客户端不断自旋锁
服务注册和发现
利用 Znode 和 Watcher,可以实现分布式服务的注册和发现。最著名的应用就是阿里的分布式 RPC 框架 Dubbo。
共享配置和状态信息
Redis 的分布式解决方案 Codis,就利用了 Zookeeper 来存放数据路由表和 codis-proxy 节点的元信息。同时 codis-config 发起的命令都会通过 ZooKeeper 同步到各个存活的 codis-proxy。
此外,Kafka、HBase、Hadoop,也都依靠Zookeeper同步节点信息,实现高可用。