Zookeeper的Docker部署和应用

理论

docker部署

1.下载zookeeper 最新版镜像

     docker search zookeeper 
     docker pull zookeeper
     docker images
  1. 在windows下d盘中创建一个文件夹D:\dockercontainers\zookeeper。
   d:
     mkdir dockercontainers
     cd dockercontainers
     mkdir zookeeper
  1. 启动服务
  docker run -d -e TZ="Asia/Shanghai" -p 2181:2181 -v d:\dockercontainers\zookeeper:/data --name zookeeper --restart always zookeeper
  
-e TZ="Asia/Shanghai" # 指定上海时区 
-d # 表示在一直在后台运行容器
-p 2181:2181 # 对端口进行映射,将本地2181端口映射到容器内部的2181端口
--name # 设置创建的容器名称
-v # 将本地目录(文件)挂载到容器指定目录;
--restart always #始终重新启动zookeeper
  1. 进入容器
  docker exec -it zookeeper /bin/bash
  1. 启动Zookeeper客户端
	zkCLi.sh

基本命令

help 显示所有操作命令
ls   path 使用 ls 命令来查看当前 znode 的子节点 [可监听]
	-w 监听子节点变化
	-s 附加次级信息
create 普通创建(临时节点不能创建字节点)
	-s 含有序列
	-e 临时(重启或者超时消失)
get path 获得节点的值 [可监听]
	-w 监听节点内容变化
	-s 附加次级信息
set 设置节点的具体值
stat 查看节点状态
delete 删除节点
deleteall 递归删除节点

docker集群部署

  1. 在docker中创建zoonet网桥。
docker network ls
docker network create --driver bridge --subnet=172.19.0.0/16 zoonet
--subnet=172.19.0.0/16 子网段,不能与其他网桥冲突
zoonet 网桥名
  1. 创建三个容器,映射到本机不同的端口,挂载到本地不同的目录( data, logs),设置三个容器中运行的zk的不同的服务id, 并配置服务器集群配置。
docker run -d -p 2181:2181 --name zookeeper_node1 --privileged --restart always --network zoonet --ip 172.19.0.2 -v D:\dockercontainers\zkcluster\node1\volumes\data:/data -v D:\dockercontainers\zkcluster\node1\volumes\datalog:/datalog -v D:\dockercontainers\zkcluster\node1\volumes\logs:/logs -e ZOO_MY_ID=1 -e "ZOO_SERVERS=server.1=172.19.0.2:2888:3888;2181 server.2=172.19.0.3:2888:3888;2181 server.3=172.19.0.4:2888:3888;2181" 36c607e7b14d

docker run -d -p 2182:2181 --name zookeeper_node2 --privileged --restart always --network zoonet --ip 172.19.0.3 -v D:\dockercontainers\zkcluster\node2\volumes\data:/data -v D:\dockercontainers\zkcluster\node2\volumes\datalog:/datalog -v D:\dockercontainers\zkcluster\node2\volumes\logs:/logs -e ZOO_MY_ID=2 -e "ZOO_SERVERS=server.1=172.19.0.2:2888:3888;2181 server.2=172.19.0.3:2888:3888;2181 server.3=172.19.0.4:2888:3888;2181" 36c607e7b14d

docker run -d -p 2183:2181 --name zookeeper_node3 --privileged --restart always --network zoonet --ip 172.19.0.4 -v D:\dockercontainers\zkcluster\node3\volumes\data:/data -v D:\dockercontainers\zkcluster\node3\volumes\datalog:/datalog -v D:\dockercontainers\zkcluster\node3\volumes\logs:/logs -e ZOO_MY_ID=3 -e "ZOO_SERVERS=server.1=172.19.0.2:2888:3888;2181 server.2=172.19.0.3:2888:3888;2181 server.3=172.19.0.4:2888:3888;2181" 36c607e7b14d
2181:是zk客户端联接端口
2888:选举leader的端口
3888:集群内服务器通讯端口.
  1. 查看每台主机的状态
    docker exec -it zookeeper_node1 /bin/bash
    zkServer.sh status

Zookeeper理论

Zookeeper是什么

1. 分布式应用程序协调服务,分布式应用提供一致性服务的软件。
2. zookeeper=文件系统+通知机制
2.1文件系统
Zookeeper维护一个类似文件系统的数据结构:每个子目录项都被称作为 znode,和文		件系统一样,我们能够自由的增加、删除znode,在一个znode下(持久节点)增加、删除	子znode,唯一的不同在于znode是可以存储数据的;
它有四种节点类型:顺序持久节点,顺序临时节点,持久节点,临时节点。
2.2 通知机制
客户端注册监听它关心的目录节点,当目录节点发生变化(数据变化、子节点变化)时,zookeeper会通知客户端。

能做什么

Zookeeper 的命名服务(文件系统)
Zookeeper 的配置管理(文件系统、通知机制)
Zookeeper 集群管理(文件系统、通知机制)
Zookeeper 分布式锁(文件系统、通知机制)
Zookeeper 队列管理(文件系统、通知机制)
...

如何实现数据的一致性服务

Paxos 算法

工作原理

基本概念

角色:

领导者(Leader):负责进行投票的发起和决议,更新系统状态;
学习者(Learner):
	跟随者(Follower):用于接受客户端请求并向客户端返回结果,在选举过程中参与投票;
	观察者(ObServer):可以接收客户端请求,将写请求转发给Leader,但不参与投票过程,只同步Leader状态。
			目的:扩展系统,提高读取速度;
客户端(Client):请求发起方;

设计目的:

1)zookeeper是顺序一致性,这是一种强一致性,最终一致性模型是非常弱的一致性模型。可以这么说,满足顺序一致性的系统一定满足最终一致性,但满足最终一致性的系统不一定满足顺序一致性。
2)集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。所以Zookeeper适合安装奇数台服务器。
3)全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的。
4)更新请求顺序执行,来自同一个Client的更新请求按其发送顺序依次执行。
5)数据更新原子性,一次数据更新要么成功,要么失败。
6)实时性,在一定时间范围内,Client能读到最新数据。
7)等待无关,慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。
	当设计 ZooKeeper 的 API 的时候,我们抛弃了阻塞原语,例如 Locks。
	阻塞原语对于一个协调服务会引起其它问题,速度较慢或者故障的客户端会对速度较快的客户端产生负面的影响。
	如果处理请求取决于客户端的响应和其他客户端的失败检测,则服务本身的实现将变得更加复杂。
	因此,我们的系统 ZooKeeper 实现了一个API,该 API 可以按文件系统的层次结构来组织简单的 wait-free 数据对象。 
	实际上,ZooKeeper API类似于任何其他文件系统,并且仅从 API 签名来看,ZooKeeper 很像没有锁定(lock)方法,打开(open)和关闭(close)方法的 Chubby。 
	但是,实现 wait-free 数据对象使 ZooKeeper 与基于锁之类的阻塞原语的系统明显不同。
	尽管 wait-free 对于性能和容错性很重要,但不足以进行协调。我们还必须提供操作的顺序保证。
	特殊的,我们发现,对客户端所有操作提供 FIFO 语义与提供 linearizable writes 可以高效的实现服务,并且足以实现应用程序感兴趣的协调原语。

原理

Zookeeper的核心是事件原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。

Zab协议

Zookeeper应用 代码

工具类

  1. 节点信息输出
  2. 获得Zookeeper连接:zookeeper连接需要一定时间,为保证后面的代码使用的zookeeper对象是已连接上的,所以使用CountDownLatch来进行控制;
 zkClient = new ZooKeeper(connectString, sessionTimeout, (event) -> {
            //收到事件通知后的回调函数(业务逻辑)
            System.out.println("事件类型:" + event.getType() + " 服务器状态:" + event.getState() + " 事件发送的节点路径:" + event.getPath());
            //只有状态是连接上时,才激活判断
            if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
              //  System.out.println("zk客户端与服务器建立连接");
                countDown.countDown();//建立连接了再释放锁,让主程序运行
            }
        });
countDown.await();

基本方法

  1. 创建节点:
//ZooDefs.Ids.OPEN_ACL_UNSAFE通过移位和或运算来实现权限控制
//参数:节点路径、节点数据、节点权限、节点类型:持久,持久有序,临时,临时有序
zk.create(path, bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  1. 删除节点:delete(路径,预期节点版本)
  2. 判断节点是否存在:exists(路径,监听)
  3. 获得节点信息:getData(路径,监听,start)

统一配置管理

知识点

zookeeper
1. 持久节点的创建;
2. 节点数据变化的事件监听;
3. 安全认证;
spring
  1. 生命周期的管理:
    1.1 @DependsOn:强制依赖,此类的对象必须先被 spring 托管。
  2. 在托管类中注入spring容器(ApplicationContextAware接口)。
  3. druid数据源的刷新。
 DruidDataSource ds= (DruidDataSource)ac.getBean(  "getDataSource");
 ds.restart();  //  *********重启数据源

实现步骤

  1. AddZkConfigMessage:向zookeeper中创建节点并存入配置信息;
    1.1 用户输入zookeeper的url、超时时间、配置的起始节点路径、安全认证类型、安全认证密码、配置信息的节点名和内容(Map)节点名和设置数据源的set的属性名相同。
    1.2 先判断是否有这些节点,如果有则删除,然后再创建并写入内容;
  2. MyClient implements Watcher, ApplicationContextAware:
    2.1 设置事件监听,主要是Event.EventType.NodeDataChanged节点信息的更新,触发则刷新数据源。
    2.2 用户输入:需要访问配置的起始节点数据名、设置数据源配置的bean
    2.3 获取数据:递归遍历起始节点的子节点,如果到一个节点没有子节点则取出这个节点的数据,并通过反射将数据通过bean的set方法注入bean中。
  3. DruidConfig:与springBoot整合。
  4. TestMain:springBoot启动类。

统一集群管理

知识点

  1. 临时节点创建:一个master节点,多client节点;
  2. 节点监听:监听master节点的删除事件;
  3. master节点的选举;
  4. master节点的抢占优化;
  5. 节点信息保存使用对象流,对信息的序列化和反序列化;

实现步骤

  1. RunningData:将要保持的数据封装成一个对象,利用Record接口对对象中的属性进行序列化和反序列化处理,Record接口可以可以控制元素的写入顺序,可以处理图片等比implements Serializable 处理更加灵活。
  2. WorkServer:控制服务上下线时,注册进zookeeper和对master节点的争抢。
    2.1 判断服务器是否启动,未启动,则在zookeeper的servers下注册自己(创建临时节点),并利用zk的getData方法绑定对master节点的监听,如果master节点不存在则捕获错误,让当前节点去抢占master节点。
    2.2 如果master掉线则触发事件监听,所有服务去抢占master节点,节点触发监听事件后,比较节点名和保存的master节点名,如果一样则直接抢占master节点,不一样则利用定时任务延时5s后再争抢master(优先上次master节点争抢,防止出现网络抖动等情况导致master掉线)。
  3. LeaderSelectorZkClient:测试类:模拟上线后有多个workServer,和master五秒掉线的情况。

队列实现(生产者消费者模式)

知识点

  1. 持久顺序节点;
  2. 阻塞和非阻塞;
  3. 先进先出(FIFO)原则;

实现步骤

  1. SimpleDistributedQueue非阻塞队列:
    1.1 向队列中存入数据offer(T element) :先拼接节点路径,然后将传入的对象转化为对象流的形式,创建节点并写入数据。
    1.2 从队列中取出数据pull():获得root节点的所有子节点集合,然后对子节点进行从小到大排序(顺序节点),取出排序后的第一个节点(节点顺序最小,最先进入队列的),然后拼接节点路径,读取出这个节点的数据并删除这个节点。
  2. DistributedBlockQueue阻塞队列,继承SimpleDistributedQueue非阻塞队列:
    2.1 因为理论上以zookeeper实现的队列是无界队列(zookeeper的机器无限扩充),所以阻塞和非阻塞队列的添加操作相同,只需重新pull()方法就行了。
    2.2 利用CountDownLatch来阻塞线程;
    2.3 创建一个监听事件,监听root节点的子节点的变化;
    2.4 设置一个while循环,通过zk的getChildren方法来对root节点绑定事件监听,调用非阻塞队列的pull()方法获取数据,如果pull()方法获取到数据则返回,如果没有获得数据则阻塞线程,直到有线程offer(T element) 存入数据触发监听,才解除阻塞重新获取数据。
  3. Order:待存数据;
  4. 测试代码:
    4.1 Test1:以自定义的队列实现生产者消费者
    4.2 Test2:SimpleDistributedQueue测试offer(T element) 和pull()方法;
    4.3 Test3:DistributedBlockQueue测试offer(T element) 和pull()方法;

分布式锁实现(公平锁)

实现方式

  1. 使用数据库,阻塞式的行锁 Select * from xx for updata;
    Updata xxxx set ticks=ticks_1
    效率慢,单点故障(数据库挂了,集群配置困难);
  2. 用redis
    Get lock
    Setnx lock ip… 超时时间,超时时间不好控制)
    用redisson 实现,超时时间控制(看门狗)
  3. 用zookeeper实现:
    独占,非公平:就是所有试图来获取这个锁(znode)的客户端,最终只有一个可以成功获得这把锁。通常的做法是把zk上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。
    公平: 1. 客户端在/locks根节点下面创建临时有序节点(这个可以通过节点的属性控制CreateMode.EPHEMERAL_SEQUENTIAL来指 定),
    2.client调用getChildren(“/root/lock_”,watch)来获取所有已经创建的子节点,并同时在这个节点上注册子节点变更通知的Watcher。

知识点

  1. 创建临时顺序节点;
  2. 每个节点监听前一个节点的删除事件;
  3. redis,存储票数;
  4. nginx,实现多个服务之间的负载均衡;
  5. curator框架;
  6. ab测试;
抢票程序三个版本
  1. 版本一: 单机锁: 采用 ReentrantLock实现。
  2. 版本二: 利用nginx完成服务集群的搭建, 并实现了nginx的负载均衡。解决的问题: 高并发场景下单服务器的处理能力不够的问题。利用ab测试模拟多个用户( 1000个用户 ) 密集并发的场景。ab.exe -n 100 -c 6 http://distributedlock/ticket2
    产生问题: 单机锁无法保证在服务集群下数据操作的安全性。存在一票多卖的情况,但单机上不会出现重复的票。
    解决方案:自定义基于zookeeper的分布式锁。来解决分布式集群环境下单机锁失效的问题。
  3. 版本三:使用curator框架,解决连接重连、反复注册Watcher和NodeExistsException等情况。

自定义分布式锁实现步骤

    1. 使用zk的临时节点和有序节点,每个线程获取锁就是在zk创建一个临时有序的节点,比如在/locks目录下。
    2. 创建节点成功后,获取/locks目录下的所有临时节点,再判断当前线程创建的节点是否是所有的节点的序号最小的节点
    3. 如果当前线程创建的节点是所有节点序号最小的节点,则认为获取锁成功。
    4. 如果当前线程创建的节点不是所有节点序号最小的节点,则对节点序号的前一个节点添加一个事件监听(不是监听最前面的那个节点防止羊群效应)。
    比如当前线程获取到的节点序号为/locks/seq-00000003,然后所有的节点列表为[/locks/seq-00000001,/locks/seq-00000002,/locks/seq-00000003],则对/locks/seq-00000002这个节点添加一个事件监听器。
    5.  如果锁释放了,会唤醒下一个序号的节点,然后重新执行第3步,判断是否自己的节点序号是最小。
  比如/locks/seq-00000001释放了,/locks/seq-00000002监听到事件,此时节点集合为[/locks/seq-00000002,/locks/seq-00000003],则/locks/seq-00000002为最小序号节点,获取到锁。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值