[TOC]
zookeeper动物管理员全局把控。提供了配置管理、服务发现等服务。其自身也是能够集群化的。实现上是基于观察者模式。不想eureka/consul等同类产品须要心跳机制。他自身反对察看与被动触发机制;千里之行始于足下,咱们曾经摸索了eureka、consul两个服务注册的中间件了。明天咱们持续学习另外一个作为服务注册的服务。
本文将从zookeeper单机到集群的装置解说;在从集群leader选举机制的解说及数据同步的梳理。到最终的基于zookeeper实现的配置管理及分布式锁的利用。从点到面在到利用带你领会一把过山车
简介
- Zookeeper 大家都晓得是动物管理员的意思。在大数据全家桶中他的作用也是治理。上面咱们别离从装置到应用来看看zk的柔美
中心化
服务 | 特点 | 中心化 | CAP |
---|---|---|---|
eureka | peer to peer 每个eureka服务默认都会向集群中其余server注册及拉去信息 | 去中心化 | AP |
consul | 通过其中节点病毒式蔓延至整个集群 | 多中心化 | CP |
zookeeper | 一个leader多个followes | 中心化 | CP |
事务
- 下面提到zookeeper是一个leader多个followers。
- 除了中心化思维外,zookeeper还有个重要的个性就是事务。zookeeper数据操作是具备原子性的。
- 如何了解zookeeper的事务呢,其实外部是通过版本治理数据实现事务性的。zookeeper每个客户端初始化时都会初始化一个operation。每个client连贯是都有个外部的session治理。同一个session操作都会有对应的版本记录,zxid这样能保证数据的一个一致性。
downlaod
zookeeper3.5.9
单机装置
- 在下面的地址下载后进行解压
tar -zxvf apache-zookeeper-3.5.9-bin.tar.gz
- 官网提供的相当于是个模板,这个时候间接启动会报错的。
- 依据报错信息咱们晓得缺失zoo.cfg默认配置文件。在conf目录下官网给咱们提供了zoo_sample.cfg模板配置文件。咱们只须要复制改文件为zoo.cfg在此基础上进行修该
cp zoo_sample.cfg zoo.cfg
- 咱们批改下data门路就能够启动了。
- 启动之后通过’zkServer.sh status’查看zookeeper运行状态。咱们能够看到此时是单机模式启动的。
- 通过jps咱们也可能看到zookeeper启动胜利了。
集群搭建
- 这里的集群为了不便就演示伪集群版。即在一台服务器上安排三台zk服务。
mkdir {zk1,zk2,zk3}
, 首先创立zk1,zk2,zk3三个文件夹寄存zk- 将之前解压好的zk文件夹别离复制到三个创立zk中
- 在zookeeper进群中,每台服务都须要有一个编号。咱们还须要写入每台机器的编号
- 而后反复咱们单机版的过程。在根目录创立data文件夹,而后批改conf中对应的data配置。只不过这里还须要咱们批改一下端口号。因为在同一台机器上所以须要不同的端口号才行。
- 最初新增集群外部通信端口
server.1=192.168.44.130:28881:38881
server.2=192.168.44.130:28882:38882
server.3=192.168.44.130:28883:38883
- 上述的端口只有保障可用就行了。
- 下面是zk1的配置,其余读者自行配置。
- 在配置总咱们多了server的配置。这个server是集群配置的重点
server.1=192.168.44.139:28881:38881
- server是固定写法
- .1 : 1就是咱们之前每台zk服务写入的myid里的数字
- 192.168.44.130: 示意咱们zk所在服务ip
- 28881: 在zookeeper集群中leader和follewers数据备份通信端口
- 38881: 选举机制的端口
- 下面我是通过zkServer启动不同配置文件。你们也能够在不同的zk包下别离启动。最终成果是一样的。启动实现之后咱们通过jps查看能够看到多了三个zk服务。在单机版的时候jps咱们晓得zk的主启动是QuorumPeerMain。
- 上图是其中一个zk服务的目录构造。咱们能够看到data目录有数据产生了。这是zk集群启动之后生成的文件。
- 集群启动实现了。然而咱们当初对zk集群如同还是没有太大的感知。比如说咱们不晓得谁是leader。 能够通过如下命令查看每台zk的角色
- 咱们的zk2是leader角色。其余是follower
Cli连贯
- 在zookeeper包中还有一个zkCli.sh这个是zk的客户端。通过他咱们能够连贯zookeeper服务并进行操作。
zkCli.sh -server 192.168.44.131:1181
能够连贯zk服务
- 上面咱们测试下对zk的操作会不会是集群化的。
- 咱们zkCli连贯了zk1服务1181,并且创立的一个zk节点名为node。咱们在去zk2服务上同样能够看到这个node节点。
- 至此,咱们zookeeper集群搭建实现,并且测试也曾经通过了。
集群容错
Master选举
- 咱们已上述集群启动是为例,简述下集群选举流程。
①、zk1启动时,这个时候集群中只有一台服务就是zk1。此时zk1给集群投票天然被zk1本人获取。 此时zk1有一票
②、zk2启动时,zk1,zk2都会都一票给集群。因为进群中zk1(myid)小于zk2(myid),所以这两票被zk2获取。这里为什么会是zk2获取到呢。zookeeper节点都一份坐标zk=(myid,zxid);myid是每个zk服务配置的惟一项。zxid是zk服务的一个64位内容。高32没master选举一次递增一次并同时清空低32位。低32位是每产生一次数据事务递增一次。所以zxid最高阐明此zk服务数据越新。
③、zk2取得两票后,此时曾经取得了集群半数以上的票数,多数遵从少数此时zk2曾经是准leader了同时zk1切换为following 。此时zk1曾经是zk2的跟班了
④、zk3启动时,按情理zk3应该会收到三票。然而因为zk1曾经站队到zk2了。zk2作为准leader是不可能给zk3投票的。所以zk3最多只有本人一票,zk3明知zk2取得半数以上,曾经是民心所归了。所以zk3为了本人的前途也就将本人的一票投给了zk2.
- zookeeper的投票选举机制赤裸裸的就是一个官场。充斥的人心
leader宕机从新选举
- 其实在启动阶段zk2获取到两张投票是有一个PK的逻辑在外面的。上述启动阶段的投票是集体的一个抽象化了解。
- 在下面说myid高的不会给myid低的投票实际上是一种全面的了解。实际上是会进行投票的,投票之后会进行两张票PK,将权重高的一张票投出去选举leader。有集群管理者进行统计投票并计数。
- 上面咱们来看看从新选举是的逻辑。也是真正的leader选举的逻辑。
①、zk2服务挂了,这个时候zk1,zk3立马切换为looking状态,并别离对集群内其余服务进行投票
②、zk1收到本人的和其余服务投过来的票(1,0)、(3,0) 。zk1会斟酌这两张票,基于咱们提到的算法num=10*zxid+myid ,所以zk1会将(3,0)这张票投入计数箱中
③、zk3收到两种票(3,0)、(1,0),同样会将(3,0)投入计数箱
④、最终统计zk3取得两票胜出。
- 下面的选举才是真正的选举。启动期间咱们只是退出了咱们本人的了解在外面。选举完之后服务会切换成leader、follower状态进行工作
数据同步
- 同样先上图
- client如果间接将数据变更申请发送到leader端,则间接从图中第三步开始发送proposal申请期待过半机制后再发送commit proposal。follower则会开始更新本地zxid并同步数据。
- 如果client发送的是follower,则须要follower先将申请转发至leader而后在反复下面的步骤。
- 在数据同步期间为了保障数据强一致性。leader发送的proposal都是有序的。follower执行的数据变更也都是有程序的。这样能保证数据最终一致性。
在一段时间内比如说须要对变量a=5和1=1操作。如果a=5和a=1是两个事物。如果leader告诉follower进行同步,zk1先a=1在a=5。则zk1中的a为5.zk3反之来则zk3中a=1;这样就会造成数不统一。然而zookeeper通过znode节点有序排列保障了follower数据生产也是有序的。在莫一时刻zk1执行了a=5,这时候client查了zk1的a是5,尽管外表上是脏数据实际上是zk1未执行完。期待zk1执行完a=1.这就叫数据<red>最终一致性</red>。 - 上面一张图可能更加的形象,来自于网络图片
特色性能
服务治理
- 在咱们eureka、consul章节曾经介绍了springcloud注册的细节了。明天咱们还是同样的操作。已payment和order模块来讲服务注册到zookeeper上。看看成果。
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
- 配置文件配置增加zookeeper
spring:
cloud:
zookeeper:
connect-string: 192.168.44.131:2181
- 和consul一样,这里的注册会将spring.application.name值注册过来。eureka是将spring.application.name的大写名称注册过来。这个影响的就是order订单中调用的地址区别。
点我看源码
- 还是一样的操作。启动order、两个payment之后咱们调用
http://localhost/order/getpayment/123
能够看到后果是负载平衡了。
配置管理
- 相熟springcloud的都晓得,springcloud是有一个配置核心的。外面次要借助git实现配置的实时更新。具体细节咱们前面章节会缓缓开展,本次咱们展现通过zookeeper实现配置核心治理。
- 首先咱们在咱们的payment模块持续开发,引入zookeeper-config模块
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-config</artifactId>
</dependency>
- 而后咱们须要有个储备常识,在spring加载配置文件的程序,会先加载bootstrap文件而后是application文件。这里bootstrap咱们也用yml格式文件。
spring:
application:
name: cloud-payment-service
profiles:
active: dev
cloud:
zookeeper:
connect-string: 192.168.44.131:2181
config:
enabled: true
root: config
profileSeparator: ','
discovery:
enabled: true
enabled: true
- 下面这部分须要解释下
key | 解释 |
---|---|
spring.application.name | 服务名 |
spring.profiles | 环境名 |
spring.cloud.enabled | 用于激活config主动配置 |
spring.cloud.zookeeper.config.root | zookeeper根门路 |
spring.cloud.zookeeper.profilSeparator | key分隔符 |
@Component
@ConfigurationProperties(prefix = "spring.datasources")
@Data
@RefreshScope
public class Db {
private String url;
}
- 零碎中会读取配置文件中的spring.datasources.url这个属性值。联合咱们的bootstrap.yml文件中。此时会去zookeeper零碎中查找/config/cloud-payment-service,dev/spring.datasources.url这个值。
-对于那个zookeeper的key是如何来的。仔细察看下能够发现他的法则 /${spring.cloud.zookeeper.root}/${spring.application.name}${spring.cloud.zookeeper.profileSeparator}${spring.profiles}/${理论的key}
- 惟一留神的是须要在类上增加
@RefreshScope
, 这个注解是cloud提供的。包含到前面的config配置核心都离不开这个注解 - 此时通过前文提到的zkCli连贯zk服务,而后创立对应节点就能够了。zookeeper服务须要逐层创立。比方下面提到的
/config/cloud-payment-services/spring.datasources.url
,咱们须要create /config
而后create /config/cloud-payment-services
在创立最初的内容。 - 咱们能够提前在zk中创立好内容。而后
localhost:8001/payment/getUrl
获取内容。而后通过set /config/cloud-payment-services/spring.datasources.url helloworld
在刷新接口就能够看到最新的helloworld了。这里不做演示。 - zookeeper原生的创立命令因为须要一层一层创立这还是很麻烦。还有咱们有zkui这个插件。这个提供了zookeeper的可视化操作。还反对咱们文件导入。上述的配置内容咱们只须要导入以下内容的文件即可
/config/cloud-payment-services=spring.datasources.url=hello
zkui装置应用
- 下面咱们提到了一个工具
zkui
,顾名思义他是zookeeper可视化工具。咱们间接下载github源码。
- 官网装置步骤也很简略,因为他就是一个jar服务。
- pom同级执行maven clean install 打包jar 而后
nohup java -jar zkui-2.0-SNAPSHOT-jar-with-dependencies.jar &
后盾启动就行了。默认端口9090 - 默认用户名明码 官网都给了。 admin:manager
- 在jar包同级官网提供了一份zookeeper配置模板。在外面咱们能够配置咱们的zookeeper。 如果是集群就配置多个就行了。
- 对于这个zkui的应用这里不多介绍,就是一个可视化。程序员必备技能应该都会应用的。
- 咱们我的项目里应用的都是单机的zookeeper,然而在下面装置的时候咱们也有集群zookeeper。端口别离是1181,1182,1183. 咱们在zkui的配置文件config.cfg中配置集群即可。
分布式锁
分布式锁罕用在共享资源的获取上。在分布式系统的中咱们须要协调每个服务的调度。如果不进行管制的话很大水平会造成资源的节约甚至是资源溢出。常见的就是咱们的库存。
- 之前咱们springcloud中有一个order和两个payment服务。payment次要用来做领取操作。如果订单胜利之后须要调用payment进行扣款。这时候金额相当于资源。这种资源对payment两个服务来说是互斥操作。两个payment过去操作金额时必须先后顺序执行。
mysql隔离管制
- 在以前分布式还不是很遍及的时候咱们失常解决这些操作时都是完结数据库的事务隔离级别。
隔离级别 | 隔离级别 | 景象 |
---|---|---|
read uncommit | 读未提交 | 产生脏读 |
read commited | 读已提交 | 幻读(insert、delete)、不可反复读(update) |
repeatable read | 可反复度 | 幻读 |
serializable | 串行化 | 无问题、效率变慢 |
- 基于mysql隔离级别咱们能够设置数据库为串行模式。然而带来的问题是效率慢,所有的sql执行都会串行。
mysql锁
- 隔离级别尽管能够满足然而带来的问题的确不可承受。上面就会衍生出mysql锁。咱们能够独自建一张表有数据代表上述胜利。否则上锁失败。这样咱们在操作金额扣减时先判断下这张表有没有数据进行上锁。然而如果上锁之后会造成死锁景象。因为程序异样迟迟没有开释锁就会造成程序瘫痪。
- 这个时候咱们能够定时工作革除锁。这样至多保障其余线程可用。
redis锁
- 因为redis自身有生效属性,咱们不用放心死锁问题。且redis是内存操作速度比mysql快很多。对于redis锁的实现能够参考我的其余文章redis分布式锁.
zookeeper锁
- 因为zookeeper基于观察者模式,咱们上锁失败后能够监听对应的值直到他生效时咱们在进行咱们的操作这样可能保障咱们有序解决业务,从而实现锁的性能。
- 上图是两个线程上锁的繁难图示。在zookeeper中实现分布式锁次要依赖
CuratorFramework
、InterProcessMutex
两个类
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.44.131:2181", new ExponentialBackoffRetry(1000, 3));
client.start();
InterProcessMutex mutex = new InterProcessMutex(client, "/config/test");
long s = System.currentTimeMillis();
boolean acquire = mutex.acquire(100, TimeUnit.SECONDS);
if (acquire) {
long e = System.currentTimeMillis();
System.out.println(e - s+"@@@ms");
System.out.println("lock success....");
}
- 最终InterProcessMutex实现加锁。加锁会在指定的key上增加一个新的key且带有编号。此时线程中的编号和获取的/config/test下汇合编号最小值雷同的话则上锁胜利。T2则上锁失败,此时会想前一个序号的key增加监听。即当00001生效时则00002对应的T2就会获取到锁。这样能够保障队列的有序进行。
本文由博客一文多发平台 OpenWrite 公布!
发表回复