文章目录
本篇为动物园之旅~
dubbo看作动物园的动物,那么zookeeper就是动物园
如果有人想去动物园看小老虎,那么动物园中有小老虎才能看到,否则看不到~
关系:dubbo与 zookeeper的关系 Dubbo建议使用Zookeeper作为服务的注册中心
一.Dubbo框架(远程过程调用)
1.分布式系统中的相关概念
- 大型互联网项目的架构目标
传统项目和互联网项目
大型互联网项目的架构目标
- 集群和分布式
两者区别
没有集群和分布式的服务器
进行集群的服务器-可以进行
负载均衡
,实现了高性能、高可用的目标
同时进行集群和分布式的服务器-除了集群实现的功能和目标,还可以实现可伸缩、高可扩展的目标
集群和分布式的理解
- 架构演进
2.Dubbo概述
- Dubbo概念
Dubbo是一款高性能、轻量级的开源
Java RPC框架
,它提供了三大核心能力:
面向接口的远程方法调用
,
智能容错
和
负载均衡
,以及服务自动注册和发现。
Dubbo是微服务开发框架,提供
RPC通信
、
微服务治理的能力
。
- Dubbo架构
Dubbo 基于消费端的自动服务发现能力
服务发现,c能力,是微服务框架需要具备的关键能力,借助于自动化的服务发现,微服务之间可以在无需感知对端部署位置与 IP 地址的情况下实现通信。
实现服务发现的方式有很多种,Dubbo 提供的是一种 Client-Based 的服务发现机制,通常还需要部署额外的第三方注册中心组件来协调服务发现过程,如常用的 Nacos、Consul、Zookeeper 等,Dubbo 自身也提供了对多种注册中心组件的对接,用户可以灵活选择。
服务发现的一个核心组件是注册中心,Provider 注册地址到注册中心,Consumer 从注册中心读取和订阅 Provider 地址列表。
下图是 Dubbo2 的服务发现模型:Provider 注册服务地址,Consumer 经过注册中心协调并发现服务地址,进而对地址发起通信,这是被绝大多数微服务框架的经典服务发现流程。而 Dubbo2 的特殊之处在于,它把 “RPC 接口”的信息也融合在了地址发现过程中,而这部分信息往往是和具体的业务定义密切相关的。
3.Dubbo快速入门
使用zookeeper作为注册中心
4.Dubbo高级特性
- dubbo-admin管理平台
- dubbo高级特性
二.ZooKeeper框架(分布式协调服务)
1.ZooKeeper主要功能
1.1 配置管理
分布式项目
的配置总管理处
1.2 分布式锁
对于分布式项目修改共享数据时加入锁管理(同一时间只能有一个服务对数据进更改)
1.3 集群管理
最常见的功能,作为注册中心使用.
2.ZooKeeper命令操作
2.1 ZooKeeper数据模型
- Zookeeper是一个树形目录服务,其数据模型和Unix的文件系统目录树很类似,拥有一个层次化结构
- 这里面的每一个结点都被称为ZNode,每个节点都会保存自己的数据和节点信息
- 节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下
- 节点可以分为四大类- PERSISTENT 持久化节点- EPHEMRAL 临时节点 : -e- PERSISTENT_SENQUENTIAL 持久化顺序节点 : -s- EPHEMRAL_SEQUENTIAL 临时顺序节点 : -es
2.2 ZooKeeper 服务端常用命令
在安装目录的bin目录下:
- 启动Zookeeper服务 : ./zkServer.sh start
- 查看Zookeeper服务状态 : ./zkServer.sh statu
- 停止Zookeeper服务 : ./zkServer.sh stop
- 重启Zookeeper服务 : ./zkServer.sh restart
2.3 ZooKeeper 客户端常用命令
- 连接ZooKeeper服务端
./zkCli.sh -server ip:port
- 断开客户端连接
quit
- 设置节点的值
set /节点path value
- 查看帮助命令
help
- 删除单个节点
delete/节点path
- 显示指定目录下的节点
ls 目录
- 删除带有子节点的节点
deleteall /节点path
- 创建节点
create /节点path value
- 获取节点的值
get /节点path
- 创建临时节点
create -e /节点path value
- 创建顺序节点
create -s /节点path value
- 查询节点详细信息
ls -s /节点path
- 节点详细信息
czxid: 节点被创建的事务IDdataversion: 数据版本号
ctime: 创建时间
aclversion: 权限版本号
mzxid: 最后一次被更新的事务IDephemeralOwner: 用于临时节点 ,代表临时节点的事务ID,如果为持久节点则为0pzxid: 子节点列表最后一次被更新的事务IDdataLength: 节点存储的数据长度
cversion: 子节点的版本号
numChildren: 当前节点的子节点数
3.Java API-Curator
Curator是Apache ZooKeeper的Java客户端库
官网:https://curator.apache.org/
依赖:
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId></dependency>
4.Java API 常用操作
- 建立连接
- 添加节点
- 删除节点
- 修改节点
- 查询节点
- Watch时间监听
- 分布式锁实现
4.1 建立连接
4.1.1 方式一
/**
* Create a new client
* Params:
* connectString – "192.168.36.100:2181,192.168.36.101:2181"
* sessionTimeoutMs – 会话超时时间 单位ms
* connectionTimeoutMs – 连接超时时间 单位ms
* retryPolicy – 重试策略
* Returns:
* client
*///重试策略 每间隔x一共重试x次 每隔3秒连接一次一共连接10次RetryPolicy retryPolicy =newExponentialBackoffRetry(3000,10);//1.第一种方式CuratorFramework client =CuratorFrameworkFactory.newClient("192.168.36.100:2181",60*1000,15*1000, retryPolicy);//开启连接
client.start();
4.1.2 方式二 : 链式编程(有提示)
//2.第二种方式
client =CuratorFrameworkFactory.builder().connectString("192.168.36.100:2181").sessionTimeoutMs(60*1000).connectionTimeoutMs(15*1000).retryPolicy(retryPolicy).namespace("jmpower").build();//开启连接
client.start();
- namespace : 创建根节点jmpower(为了方便,不用之后每次进行客户端操作都写/根节点path)
4.2 添加节点
创建结点:create 持久化 临时 顺序 数据
1.基本创建 : create().forPath(“”)
2.创建结点,带有数据 : create().forPath(“”,data)
3.设计节点类型 : create().withMode().forPath(“”)
4.创建多级节点 /app1/p1 : create().creatingParentsIfNeeded().forPath(“”)
4.2.1 基本创建
@TestpublicvoidtestCreate()throwsException{//1.基本创建String path = client.create().forPath("/app1");System.out.println(path);}
4.2.2 创建带有数据的结点
@TestpublicvoidtestCreate2()throwsException{//2.创建结点,带有数据//如果创建的节点,没有指定数据,则默认将当前客户端的ip作为数据存储String path = client.create().forPath("/app2","hehe".getBytes());System.out.println(path);}
4.2.3 设计节点类型
@TestpublicvoidtestCreate3()throwsException{//3.设计节点类型//默认类型:持久化String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3");System.out.println(path);}
4.2.4 创建多级节点
@TestpublicvoidtestCreate4()throwsException{//4.创建多级节点 /app1/p1//creatingParentsIfNeeded():如果父节点不存在,则创建父节点String path = client.create().creatingParentsIfNeeded().forPath("/app4/p1");System.out.println(path);}
4.3 删除节点
删除节点:delete deleteall
1.删除单个节点 : delete().forPath()
2.删除带有子节点的节点 : delete().deletingChildrenIfNeeded().forPath()
3.必须成功的删除(为了防止网路抖动,本质就是重试) : delete().guaranteed().forPath()
4.回调 : inBackground
4.3.1 删除单个节点
@TestpublicvoidtestDelete()throwsException{//1.删除单个节点
client.delete().forPath("/app1");}
4.3.2 删除带有子节点的节点
@TestpublicvoidtestDelete2()throwsException{//2.删除带有子节点的节点
client.delete().deletingChildrenIfNeeded().forPath("/app4");}
4.3.3 必须成功的删除
@TestpublicvoidtestDelete3()throwsException{//3.必须成功的删除
client.delete().guaranteed().forPath("/app2");}
4.3.4 回调
@TestpublicvoidtestDelete4()throwsException{//4.回调
client.delete().guaranteed().inBackground(newBackgroundCallback(){@OverridepublicvoidprocessResult(CuratorFramework client,CuratorEvent event)throwsException{System.out.println("我被删除了~");System.out.println(event);}}).forPath("/app1");}
4.4 修改节点
修改数据
1.修改数据 : setData().forPath()
2.根据版本修改 : setData().withVersion(version).forPath()
version是通过查询出来的。目的就是为了让其他客户端或线程不干扰我。
4.4.1 修改数据
@TestpublicvoidtestSet()throwsException{
client.setData().forPath("/app1","jm666".getBytes());}
4.4.2 根据版本修改
@TestpublicvoidtestSetForVersion()throwsException{Stat stat =newStat();//3.查询节点的状态信息:ls -s
client.getData().storingStatIn(stat).forPath("/app1");int version=stat.getVersion();//查询出来的版本号System.out.println(version);
client.setData().withVersion(version).forPath("/app1","hahaha".getBytes());}
4.5 查询节点
查询节点:
1.查询数据:get : getData().forPath(“”)
2.查询子节点:ls : getChildren().forPath(“”)
3.查询节点的状态信息:ls -s : getData().storingStatIn(stat).forPath(“”)
4.5.1 查询数据
@TestpublicvoidtestGet()throwsException{//1.查询数据:getbyte[] data = client.getData().forPath("/app1");System.out.println(newString(data));}
4.5.2 查询子节点
@TestpublicvoidtestGet2()throwsException{//2.查询子节点:lsList<String> path = client.getChildren().forPath("/");System.out.println(path);}
4.5.3 查询节点的状态信息
@TestpublicvoidtestGet3()throwsException{Stat stat =newStat();//3.查询节点的状态信息:ls -s
client.getData().storingStatIn(stat).forPath("/app1");System.out.println(stat);}
5.Watch事件监听
5.1 基本概念
- ZooKeeper允许用户在指定节点上注册一些Watchr,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制时ZooKeeper实现分布式协调服务的重要特性.
- ZooKeeper中引入了Watcher机制来实现了发布/订阅功能.能够让多个订阅者同时监听某一个对象,当一个对象自身状态变化的时候,会通知所有订阅者.
- ZooKeeper原生支持通过注册Watcher来进行事件监听,但是其使用并不是特别方便需要开发人员自己反复注册Watcher,比较繁琐.
- Curator引入了Cache来实现对ZooKeeper服务端事件的监听.
- ZooKeeper提供了三种Watcher:- NodeCache : 只是监听了某一特定节点- PathChildrenCache : 监控一个ZNode的子节点- TreeCache : 可以监控整个树上的所有节点,类似于PathChildrenCache和NodeCache组合.
5.1 NodeCache
/**
* nodeCache:监听某个节点
*/@TestpublicvoidtestNodeCatch()throwsException{//1.创建NodeCatch对象NodeCache nodeCache =newNodeCache(client,"/app1");//2.注册监听
nodeCache.getListenable().addListener(newNodeCacheListener(){@OverridepublicvoidnodeChanged()throwsException{System.out.println("节点变化了~");//获取节点更改后的数据byte[] data = nodeCache.getCurrentData().getData();System.out.println(newString(data));}});//3.开启监听,如果设置为true,则开启监听,加载缓冲数据
nodeCache.start(true);while(true){}}
5.2 PathChildrenCache
/**
* PathChildrenCache:监听某个节点的所有子节点们
*/@TestpublicvoidtestPathChildrenCache()throwsException{//1.创建监听对象PathChildrenCache pathChildrenCache =newPathChildrenCache(client,"/app2",true);//2.绑定监听器
pathChildrenCache.getListenable().addListener(newPathChildrenCacheListener(){@OverridepublicvoidchildEvent(CuratorFramework curatorFramework,PathChildrenCacheEvent pathChildrenCacheEvent)throwsException{System.out.println("子节点变化了~");System.out.println(pathChildrenCacheEvent);//监听子节点的数据变更,并且拿到变更后的数据//1.获取类型PathChildrenCacheEvent.Type type = pathChildrenCacheEvent.getType();//2.判断类型是否为updateif(type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){System.out.println("数据变了!!!");byte[] data = pathChildrenCacheEvent.getData().getData();System.out.println(newString(data));}}});//3.开启
pathChildrenCache.start();while(true){}}
5.3 TreeCache
/**
* TreeCache:监听某个节点的所有子节点们
*/@TestpublicvoidtestTreeCache()throwsException{//1.创建监听器TreeCache treeCache =newTreeCache(client,"/app2");//2.注册监听器
treeCache.getListenable().addListener(newTreeCacheListener(){@OverridepublicvoidchildEvent(CuratorFramework curatorFramework,TreeCacheEvent treeCacheEvent)throwsException{System.out.println("节点变化了~");System.out.println(treeCacheEvent);if(treeCacheEvent.getType()==TreeCacheEvent.Type.NODE_UPDATED){System.out.println("更改操作!!");}}});//3.开启
treeCache.start();while(true){}}
6.分布式锁
6.1 分布式锁
- 在我们进行单机应用开发,涉及并发同步的时候,我们往往采用synchronized或者lock的方式来解决多线程见的代码同步问题,这时多线程的运行都是运行在同一 JVM下,没有任何问题
- 但当我们的应用时分布式集群工作的情况下,属于JVM下的工作环境,跨JVM之间已经无法通过多线程的锁解决同步问题
- 那么久需要一种更加高级的锁机制,来处理
跨机器的进程之间的数据同步问题
—这就是分布式锁.
6.2 分布式锁原理
- 核心思想 : 当客户端要获取锁,则创建节点,使用完锁则删除该节点- 客户端获取锁时,在lock节点下创建临时顺序节点- 然后分别获取lock下面的所有子节点,客户端获取到所有子节点之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁.- 如果发现自己创建的节点并非lock所有子节点中最小的,说明还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件.- 如果发现比自己小得那个节点被删除,则客户端的Watcher会受到相应通知,此时再次判断自己创建的节点是否是lock子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取比自己小的一个节点并注册监听.
6.3 模拟12306售票案例
- 在Curator中有五种锁方案- InterProcessSemaphoreMutex : 分布式排它锁 (非可重入锁)- InterProcessMutex : 分布式可重入排它锁- InterProcessReadWriteLock : 分布式读写锁- InterProcessMultiLock : 将多个锁作为单个实体管理的容器- InterProcessSemaphoreV2 : 共享信号量
Runnable
packagecom.jm.curator;importorg.apache.curator.RetryPolicy;importorg.apache.curator.framework.CuratorFramework;importorg.apache.curator.framework.CuratorFrameworkFactory;importorg.apache.curator.framework.recipes.locks.InterProcessMutex;importorg.apache.curator.retry.ExponentialBackoffRetry;importjava.util.concurrent.TimeUnit;publicclassTicket12306implementsRunnable{privateint tickets=10;//数据库的票数privateInterProcessMutex lock;publicTicket12306(){//重试策略RetryPolicy retryPolicy =newExponentialBackoffRetry(3000,10);CuratorFramework client =CuratorFrameworkFactory.builder().connectString("192.168.36.100:2181").sessionTimeoutMs(60*1000).connectionTimeoutMs(15*1000).retryPolicy(retryPolicy).build();//开启连接
client.start();
lock=newInterProcessMutex(client,"/lock");}@Overridepublicvoidrun(){while(true){//获取锁try{
lock.acquire(3,TimeUnit.SECONDS);if(tickets>0){System.out.println(Thread.currentThread()+":"+tickets);Thread.sleep(100);
tickets--;}}catch(Exception e){thrownewRuntimeException(e);}finally{//释放锁try{
lock.release();}catch(Exception e){thrownewRuntimeException(e);}}}}}
Main
packagecom.jm.curator;publicclassLockTest{publicstaticvoidmain(String[] args){Ticket12306 ticket12306=newTicket12306();//创建客户端Thread t1=newThread(ticket12306,"携程");Thread t2=newThread(ticket12306,"飞猪");
t1.start();
t2.start();}}
7.ZooKeeper集群
7.1 集群简介
最典型集群模式:Master/Slave 模式(主备模式)。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。
7.2 ZooKeeper集群搭建
与创建单机环境类似
修改conf目录下的zoo_sample.cfg为zoo.cfg
修改data目录为指定目录,每个集群成员分别配置
在data目录下创建myid文件,内容分别为1,2,3
分别在conf目录下的zoo.cfg文件加入如下内容
server.1=192.168.149.135:2881:3881
server.2=192.168.149.135:2882:3882
server.3=192.168.149.135:2883:3883
- 解释:server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口,搭建伪集群,端口可以写成127.0.0.1
7.3 ZooKeeper集群角色
- Leader领导者- 处理事务请求(增删改)- 集群内部各个服务器的调度者
- Follower跟随者- 处理客户端非事务请求(查),转发事务请求给Leader服务器.- 参数Leader选举投票
- Observer观察者- 处理客户端非事务请求,转发事务请求给Leader服务器.
7.4 ZooKeeper 集群中的服务器状态
- LOOKING :寻找 Leader。
- LEADING :Leader 状态,对应的节点为 Leader。
- FOLLOWING :Follower 状态,对应的节点为 Follower。
- OBSERVING :Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。
7.5 ZooKeeper选举流程
- Leader选举 :- Serverid ; 服务器ID- 比如有三台机器,编号为1,2,3.编号越大在选择算法中权重越大.- Zxid : 数据ID- 服务器中存放的最大数据ID,值越大说明数据越新,在选举算法中数据越新则权重越大- 在Leader选举的过程中,如果某台ZooKeeper获得了超过半数的选票,则此ZooKeeper就可以成为Leader了
每个节点都有自己携带的唯一id
- 1.启动第一台机器1,发起选举,自己投票给自己,票数不过半,选举失败,服务器1保持looking状态。
- 2.启动第二台机器2,发起选举,1和2都先给自己投一票,1发现2 比自己的id大,所以把自己的票也投给服务器2,此时服务器2有2票,服务器1有0票,票数不过半,选举失败。服务器1,2均保持looking 状态
- 3.启动第三台机器3,发起选举,1,2,3都先给自己投上一票,但是1和2发现服务器3的id比自己大,所以把自己的票都投给服务器3,次数服务器3票数过半,选举成功,服务器3当选leader服务器1,2变为follower,服务器3变为leader.此时已经选举成功了,后面两台机器的选举不会改变结果
- 4.启动第四台机器 发起一次选举,此时服务器1,2,3是folllower 状态,不会更改选票信息。此时:服务器3为3票,服务器4为1票。服务器4少数服从多数,更改选票信息为服务器3。服务器4更改状态为follower
- 5.启动第五台机器 与服务器4一样投票给3,此时服务器3一共5票,服务器5为0票。服务器5更改状态为follower
版权归原作者 Jm呀 所有, 如有侵权,请联系我们删除。