一致性辨析
- 顺序一致性:
各进程之间的操作合并成一个操作序列, 每个进程自己的操作序列顺序和大操作序列中的相对顺序相同(不一定相邻), 这样不一定满足全局时钟顺序 - 线性一致性(强一致性):
- 任意时刻, 节点中的数据都是一样的
- 集群内服务器数据都同步完成之后才能对外提供服务
- 弱一致性:
- 任意时刻, 节点中的数据不一定一样
- 最终一致性:
- 弱一致性的特殊形式
- 在没有新的写入时, 不同节点上的同一份数据趋向于相同
ZooKeeper
实现的一致性是 写入操作的线性一致性和 读操作的顺序一致性
服务
- 客户端: 使用
ZooKeeper
服务的用户 - 服务器: 提供服务的进程
znode
:Zookeeper
数据的内存数据节点- 数据树: 以层次命名空间组织起来的
znode
- 会话: 客户端连接到
ZooKeeper
时建立一个会话, 通过会话句柄提交请求
服务概述
客户端可以创建两种 znode
- 普通: 通过创建和删除来操作普通节点
- 临时: 客户端创建这类节点, 显式删除或者会话结束时自动删除
创建新的 znode
节点时, 可以指定 sequential
标识, 这样该节点名字后面会附加序号
生成的序号不会比父节点之前创建的所有子节点小
Zookeeper
实现了 watch
, 客户端无需轮询, 而是由服务器在状态变化时通知客户端
数据模型
每个 znode
默认 1M 大小, 通常用来存储某种元数据
会话
有类似心跳的机制让客户端和服务器之间确认对方是否正常工作
会话持续期间, 客户端可以持续观察到状态变化
客户端 API
所有的方法都有同步和异步两个版本, 当没有并发情况的时候使用同步
Zookeeper
使用绝对路径访问 znode
, 这样可以简化 API
和服务器需要维护的状态
每次更新都是对特定版本的更新, 如果版本号与期望的版本号不一致, 那么更新就会失败
原语例子
配置管理
配置信息保存在节点 Zc
中 (watch
), 配置被更新的时候, 客户端会读取到更新信息
信息汇合
客户端创建一个 Zr
节点, 并把绝对路径传给主进程和工作进程
主进程把 IP 地址和端口号写入 Zr
工作进程监视 Zr
节点, 更新的时候才能和主进程通信
如果 Zr
节点是临时节点, 那么主进程和工作进程可以监视 Zr
的存活情况(客户端是否退出)从而决定是否退出进程
群组关系
设置一个 Zg
节点表示组, 这个组的成员进程启动时, 在 Zg
下面创建一个临时节点, 进程启动
进程故障或者终止的时候, 相关的 znode
自动删除
简单锁
客户端尝试创建一个 znode
创建成功就表示持有锁, 删除表示释放锁
创建失败就 watch
这个节点, 被删除了再尝试创建
这样会有群体效应问题, 很多客户端竞争一个锁
其次这样只实现了互斥锁
无群体效应的简单锁
定义一个 znode
名称为 l
1 | // lock |
1 | // unlock |
优点:
- 移除一个锁只会释放一个客户端
- 没有轮询或超时
- 方便观察和调试
读写锁
1 | // write lock |
1 | // read lock |
ZooKeeper
实现
对于读请求, 服务器读取本地数据库并返回
对于写请求, 使用 zab
一致性协议实现同步
写请求被发送给 Leader
ZooKeeper
在将修改应用到数据库的时候会写入磁盘, 方便故障之后通过本地磁盘类似快照的方式恢复
请求处理器
请求处理器在收到写入请求的时候, 根据请求内容计算出新的状态, 版本号和时间戳, 等待应用到数据库中
原子广播
使用 Zab
一致性协议, 使用多数认同来达成一致性
领导节点广播之前要收到前一个领导的广播
复制数据库
模糊快照: 不需要锁定状态而生成快照
因为写入的状态变更消息是幂等的
如果服务器从快照恢复, Zab
会重新发送状态变更, 最终保证与宕机前 的状态一致
C/S
交互
- 串行写: 所有更新操作都是串行的, 数据更新时,
server
会通知 观测它的所有client
, 注意这些事件会随会话的销毁而消亡 - 本地读: 可以选择在本地读, 但是可能会读取旧数据, 可以选择使用
sync
得到最新数据, 但是效率降低 - 一致性视图: 维护一个
zxid
,client
在故障重启时连接到一个新的server
, 如果该server
的zxid
小于client
的zxid
, 那么就换一个server
连接 - 会话过期: 心跳未响应的时候过期