你了解zookeeper吗?
你真的了解zookeeper吗?
如果你非常理解,那么请划走,我怕你嘲笑我~
如果你也一知半解,那就往下进行了。
先来看下概念
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
ZooKeeper包含一个简单的原语集,提供Java和C的接口。
ZooKeeper代码版本中,提供了分布式独享锁、选举、队列的接口,代码在$zookeeper_home\src\recipes。其中分布锁和队列有Java和C两个版本,选举只有Java版本。
上边概念来自百度百科
不要嘘!
看看我的理解
假设我们是一个团队(team),这个团队需要一个(领导者,也可以叫领袖)leader,leader是干嘛用的?管理所有团队人员什么的我们先不说,假如,董事会要了解你们这个团队的情况,研究研究1024节日给安排几个鼓励师,那么行政肯定会先去找leader,谁让leader知道的最多呢,并且,leader的回答可信度更高。
leader负责人员 资源调度,比如,客户要加需求,leader就先派一个技术过去讨论技术可行性,发现小胡工作正在空闲期,那好,就安排小胡过去,
过了一会
产品经理giao哥也过来说需要一个java来改需求,leader发现,已经没有人员可以给giao哥了,就跟giao哥说,没人了,都被要走了,你自己去协商吧。
giao哥:一giu窝里giao giao!!!!!!!!(很生气)
如果leader仍然把小胡调配给giao哥,那么客户和giao哥可能就会一起同台rap,场面有点残忍。。。。
所以,leader的作用就是协调 协调 协调
在分布式系统中,同样需要一个leader来协调
同样的,在分布式系统中,也需要这样的协调者,来回答系统下各个节点的提问。
比如我们搭建了一个数据库集群,里面有一个Master,多个Slave,Master负责写,Slave只读,我们需要一个系统,来告诉客户端,哪个是Master。
有人说,很简单,我们把这个信息写到一个Java服务器的内存就好了,用一个map,key:master,value:master机器对应的ip
但是别忘了,这是个单机,一旦这个机器挂了,就完蛋了,客户端将无法知道到底哪个是Master。
于是开始进行拓展,拓展成三台服务器的集群。
这下问题来了,如果我在其中一台机器修改了Master的ip,数据还没同步到其他两台,这时候客户端过来查询,如果查询走的是另外两台还没有同步到的机器,就会拿到旧的数据,往已经不是master的机器写数据。
所以我们需要这个存储master信息的服务器集群,做到当信息还没同步完成时,不对外提供服务,阻塞住查询请求,等待信息同步完成,再给查询请求返回信息。
这样一来,请求就会变慢,变慢的时间取决于什么时候这个集群认为数据同步完成了。
假设这个数据同步时间无限短,比如是1微妙,可以忽略不计,那么其实这个分布式系统,就和我们之前单机的系统一样,既可以保证数据的一致,又让外界感知不到请求阻塞,同时,又不会有SPOF(Single Point of Failure)的风险,即不会因为一台机器的宕机,导致整个系统不可用。
这样的系统,就叫分布式协调系统。谁能把这个数据同步的时间压缩的更短,谁的请求响应就更快,谁就更出色,Zookeeper就是其中的佼佼者。
它用起来像单机一样,能够提供数据强一致性,但是其实背后是多台机器构成的集群,不会有SPOF。
其实就是CAP理论中,满足CP,不满足A的那类分布式系统。
如果把各个节点比作各种小动物,那协调者,就是动物园管理员,这也就是Zookeeper名称的由来了,从名字就可以看出来它的雄心勃勃。
看看zookeeper架构图
- Leader: ZooKeeper 集群工作的核心
事务请求(写操作)的唯一调度和处理者,保证集群事务处理的顺序性;集群内部各个服务的调度者。 对于
create,setData,delete 等有写操作的请求,则需要统一转发给 leader 处理,leader
需要决定编号、执行操作,这个过程称为一个事务。 - Follower: 处理客户端非事务(读操作)请求 转发事务请求给 Leader 参与集群 leader 选举投票2n-1台可以做集群投票
此外,针对访问量比较大的 zookeeper 集群,还可以新增观察者角色 - Observer:
观察者角色,观察ZooKeeper集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给Leader服务器处理
不会参与任何形式的投票只提供服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力 扯淡:说白了就是增加并发的请求
主从与主备
- 主从:主节点少,从节点多,主节点分配任务,从节点具体执行任务
- 主备:主节点与备份节点,主要用于解决我们主机节点挂掉以后,如何选出来一个新的主节点的问题,保证我们的主节点不会宕机
很多时候,主从与主备没有太明显的分界线,很多时候都是一起出现
zookeeper特性
- 全局数据的一致:每个 server 保存一份相同的数据副本,client 无论链接到哪个 server,展示的数据都是一致的
- 可靠性:如果消息被其中一台服务器接受,那么将被所有的服务器接受
- 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息 a 在消息 b 前发布,则在所有 server 上消息 a 在消息 b
前被发布,偏序是指如果以个消息 b 在消息 a 后被同一个发送者发布,a 必须将排在 b 前面 - 数据更新原子性:一次数据更新要么成功,要么失败,不存在中间状态
- 实时性:ZooKeeper 保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息
分布式应用的优点
- 可靠性 - 单个或几个系统的故障不会使整个系统出现故障。
- 可扩展性 - 可以在需要时增加性能,通过添加更多机器,在应用程序配置中进行微小的更改,而不会有停机时间。
- 透明性 - 隐藏系统的复杂性,并将其显示为单个实体/应用程序。
分布式应用的挑战
- 竞争条件 - 两个或多个机器尝试执行特定任务,实际上只需在任意给定时间由单个机器完成。例如,共享资源只能在任意给定时间由单个机器修改。
- 死锁 - 两个或多个操作等待彼此无限期完成。
- 不一致 - 数据的部分失败。
什么是Apache ZooKeeper?
Apache ZooKeeper是由集群(节点组)使用的一种服务,用于在自身之间协调,并通过稳健的同步技术维护共享数据。ZooKeeper本身是一个分布式应用程序,为写入分布式应用程序提供服务。
ZooKeeper提供的常见服务如下 :
- 命名服务 - 按名称标识集群中的节点。它类似于DNS,但仅对于节点。
- 配置管理 - 加入节点的最近的和最新的系统配置信息。
- 集群管理 - 实时地在集群和节点状态中加入/离开节点。
- 选举算法 - 选举一个节点作为协调目的的leader。
- 锁定和同步服务 - 在修改数据的同时锁定数据。此机制可帮助你在连接其他分布式应用程序(如Apache HBase)时进行自动故障恢复。
- 高度可靠的数据注册表 - 即使在一个或几个节点关闭时也可以获得数据。
- 分布式应用程序提供了很多好处,但它们也抛出了一些复杂和难以解决的挑战。ZooKeeper框架提供了一个完整的机制来克服所有的挑战。竞争条件和死锁使用故障安全同步方法进行处理。另一个主要缺点是数据的不一致性,ZooKeeper使用原子性解析。
ZooKeeper的好处
- 简单的分布式协调过程
- 同步 - 服务器进程之间的相互排斥和协作。此过程有助于Apache HBase进行配置管理。
- 有序的消息
- 序列化 - 根据特定规则对数据进行编码。确保应用程序运行一致。这种方法可以在MapReduce中用来协调队列以执行运行的线程。
- 可靠性
- 原子性 - 数据转移完全成功或完全失败,但没有事务是部分的。
使用zookeeper制作分布式锁
我们使用docker构建,如果没有docker环境,建议先搭建一套,可以参考我的这篇博客:
XXX(这个还没写,先准备上)
执行
docker pull zookeeper
输入 docker ps -a查看是否ok
docker ps -a
启动容器(Server端),默认端口2181
docker run --name my_zookeeper -d zookeeper:latest
创建Client端并连接server端
docker run -it --rm --link my_zookeeper:zookeeper zookeeper zkCli.sh -server zookeeper
zookeeper集群配置
创建docker-compose.yml文件
version: '2'
services:
zoo1:
image: zookeeper
restart: always
container_name: zoo1
ports:
- "2181:2181"
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
zoo2:
image: zookeeper
restart: always
container_name: zoo2
ports:
- "2182:2181"
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
zoo3:
image: zookeeper
restart: always
container_name: zoo3
ports:
- "2183:2181"
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
配置文件解读:
- 运行三个 zookeeper 镜像,
- 将本地的 2181, 2182, 2183 端口绑定到对应的容器的2181端口上.
- ZOO_MY_ID 和 ZOO_SERVERS 是搭建 ZK 集群需要设置的两个环境变量, 其中 ZOO_MY_ID 表示 ZK 服务的id, 它是1-255 之间的整数, 必须在集群中唯一. ZOO_SERVERS 是ZK 集群的主机列表.
继续
我们在docker-compose.yml文件同级目录下运行
COMPOSE_PROJECT_NAME=zk_test docker-compose up
nice~~~
已经启动三个zookeeper了
报告报告:我们已经乘坐上火箭上天了~
如果你想查看这几个容器,运行下边这条命令
docker-compose ps
使用zookeeper作为分布式锁
我们先了解一下什么是分布式锁
概念
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。
条件
- 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
- 高可用的获取锁与释放锁;
- 高性能的获取锁与释放锁;
- 具备可重入特性;
- 具备锁失效机制,防止死锁;
- 具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
注意点
在实现分布式锁的过程中需要注意的:
锁的可重入性(递归调用不应该被阻塞、避免死锁)
锁的超时(避免死锁、死循环等意外情况)
锁的阻塞(保证原子性等)
锁的特性支持(阻塞锁、可重入锁、公平锁、联锁、信号量、读写锁)
在使用分布式锁时需要注意:
分布式锁的开销(分布式锁一般能不用就不用,有些场景可以用乐观锁代替)
加锁的粒度(控制加锁的粒度,可以优化系统的性能)
加锁的方式
实现分布式锁的方式
- 缓存(Redis等)实现分布式锁;
- 数据库实现分布式锁;—不推荐使用
- Zookeeper实现分布式锁;
zookeeper的存储结构
zookeeper的存储结构像一棵树Tree,由节点组成,节点我们称之为Znode
节点分类
- 持久节点
- 持久顺序节点 创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号:
- 临时节点 断开连接,节点删除
- 临时顺序节点
zookeeper实现分布式锁思路
每个客户端对某个方法加锁时
在 Zookeeper 上与该方法对应的指定节点的目录下
生成一个唯一的临时有序节点
判断是否获取锁的方式很简单
只需要判断有序节点中序号最小的一个。
当释放锁的时候,只需将这个临时节点删除即可。
同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题
zookeeper实现分布式锁优缺点
优点:
- 有效的解决单点问题,不可重入问题,非阻塞问题以及锁无法释放的问题
- 实现较为简单
缺点:
- 性能上不如使用缓存实现的分布式锁,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁临时节点来实现锁功能
- 需要对Zookeeper的原理有所了解
实现原理
第一步:
我们先创建一个持久节点FatherLock,当客户端A想要获取锁时,需要在FatherLock下创建一个临时顺序节点LockA,客户端A会查找FatherLock下面所有顺序节点并排序,判断自己所创建的是不是第一个,如果是,则成功获取锁。
第二步:
客户端B想获取锁时会在FatherLock下创建LockB锁
客户端B会查找FatherLock下面所有顺序节点并排序,判断自己所创建的是不是第一个。
如果是,则成功获取锁。
如果不是,则在它的前一位节点注册watcher 用于监听前一位节点是否存在,相当于进入等待状态。
第三步:
客户端C来了,~~~创建Lock3 在LockB下注册watcher 监听
第四步:
是不是发现实现了一个队列
A得到了锁
B监听A
C监听B
第五步 释放锁:
A拿到锁->代码执行完毕->调用删除LockA
A拿到锁->异常 断联->自动删除LockA
LockA删除后
LockB会立刻收到通知,客户端B会立刻拿到锁
同理—