赞
踩
恁爹说:源码太复杂,有功夫再整理吧,害!
关于分布式系统的相关概念:
1)单个物理节点很容易达到性能,计算或者容量的瓶颈,所以这个时候就需要多个物理节点来共同完成某项任务,
2)一个分布式系统的本质是分布在不同网络或计算机上的程序组件,彼此通过信息传递来协同工作的系统,而Zookeeper正是一个分布式应用协调框架,在分布式系统架构中有广泛的应用场景。
-Dzookeeper.skipACL=yes
zookeeper是一种CS结构(
client-server
)
连接处理支持多线程,具体请求处理则是单线程
client
和server
交互,首先要有三次握手connect request
)给服务端,服务端接受请求后会维护sessionId的
过期时间,并返回一个sessionId
给到客户端超时时间并不完全是客户端说了算,还需要服务端的协商
sessionId
相关联,都会带上sessionId
(和web服务类似)sessionId
的有效时间,确保长连接不断开ping
、pong
session
在timeout
时间内没有收到命令了,那么zookeeper服务端会将该临时节点给删除掉/
打头的全路径,也没有cd
PERSISTENT
:持久化目录节点: 一旦创建好后只要没删就永久存在PERSISTENT_SEQUENTIAL
:持久化顺序编号目录节点:EPHEMERAL
:临时目录节点:create -e /path some-value
session
在timeout
时间内没有收到命令了,那么zookeeper服务端会将该临时节点给删除掉想要保活可以通过发指定
sessionId
的请求给到服务端
Ephemerals cannot have children: 具体的临时节点
]EPHEMERAL_SEQUENTIAL
:临时顺序编号目录节点:Ephemerals cannot have children: 具体的临时节点
]同时,3.5.3版本新增以下类型:
因为失效时间是通过后台轮询进行管理的
sessionId
绑定,这是和临时节点的最大不同-Dzookeeper.extendedTypesEnabled=true
开启bin
目录,vim zkServer.sh
ZOOMAIN
启动参数,在字符串最前方增加-Dzookeeper.extendedTypesEnabled=true
即可配置好后重启zookeeper生效
一般采用递归监听各个目录节点
None
:连接建立事件NodeCreated
:节点创建NodeDeleted
:节点删除NodeDataChanged
:节点数据变化NodeChildrenChanged
:子节点列表变化DataWatchRemoved
:节点监听被移除ChildWatchRemoved
:子节点监听被移除ls -w 节点名称
:例如:ls -w /test
get -w 节点名称
:
ls -R -w 节点名称
removewatches 节点名称
removewatches /test
java ‐version
wget https://mirror.bit.edu.cn/apache/zookeeper/zookeeper‐3.5.8/apache‐zookeepe
r‐3.5.8‐bin.tar.gz
tar ‐zxvf apache‐zookeeper‐3.5.8‐bin.tar.gz
cd apache‐zookeeper‐3.5.8‐bin
zoo_sample.cfg
:cp zoo_sample.cfg zoo.cfg
bin/zkServer.sh start conf/zoo.cfg
可以通过
bin/zkServer.sh
来查看都支持哪些参数
echo stat | nc 192.168.109.200
前提是配置文件中将
stat
四字命令设置了了白名单,如:4lw.commands.whitelist=stat
bin/zkCli.sh ‐server ip:port
/bin
目录下核心文件:zkServer.sh
:启动zookeeper服务的脚本zkCli.sh
:启动zookeeper客户端的脚本/conf
目录:log4j.properties
:日志框架zoo_sample.cfg
:zookeeper配置文件
sample
文件,而是在拷贝后修改tickTime=2000
:最小时间单位为2s,后面的参数会用到该参数initLimit=10
:zookeeper集群中进行数据同步的最长时间为10 * tickTime
,也就是20ssyncLimt=5
:zookeeper集群中Leader
和follower
之间进行心跳检测的超时时间为5 * tickTime
,也就是10sdataDir=/tmp/zookeeper
:存放zookeeper的事务日志对应配置文件以及快照clientPort=2181
:服务端开放给客户端的端口为2181
./bin/zkServer.sh start conf/zoo.cfg
./bin/zkServer.sh stop conf/zoo.cfg
./bin/zkCli.sh
./bin/zkCli.sh -server [服务端ip地址:clientPort]
SyncConnected type:noe
]的事件/bin/logs
目录下,执行tail -f zookeeper-root-server-..out
ls
:和Linux命令功能一致,查看某一个节点下的子节点:ls /
:查看根节点下的子节点,初始只会有[zookeeper]目录节点ls /zookeeper/
:返回[config, quota]两个节点ls -R /
:遍历查看根节点下的所有子节点create [-s] [-e] [-c] [-t ttl] path [data] [acl]
方括号内的参数都是可选的
[-s]
:节点后面会跟一个序号,是依次递增的[-e]
:临时节点[-c]
:容器节点[-t ttl]
:ttl
节点path
:目录节点路径[data]
:目录节点是否包含数据[acl]
:赋权create /test
:未加任何参数,因此创建的是一个持久化节点,客户端断开连接服务端也能ls
查到该/test
节点create /test2 mazai
如何查看目录节点所带的数据?使用
get
命令
create /seq
:前置操作,/seq
节点下存放所有的持久化顺序节点mazai-
)持久化顺序节点:create -s /seq/mazai-
mazai
节点后面带序号(二进制),序号从0
开始,最终基于/seq
下的已存在的节点而来(无论是否是顺序节点!),有多少个就在0上加1create -s /seq/
create -e /seq/ephemeral xxxxx
过了sessionId的超时时间,该节点会被zookeeper后台回收
create -e -s /seq/
create -e -s /seq/ephemeral-
create -c /container XXX
create -c /container/sub0
create -t 5000 /ttl-node ddd
get /test
:结果为null
get /test2
:结果为mazai
get -s /test2
get -s /seq/ephemeral
参数 | 说明 | 备注 |
---|---|---|
cZxid | 创建该节点的事务id | 关于事务请求和事务见下文 |
cTime | 创建该节点的事件 | |
mZxid | 修改该节点的最新事务id | |
mtime | 修改该节点的最新时间 | |
pZxid | 导致当前节点的子节点列表发生变化的最新事务id | |
cversion | 当前节点的子节点结果集版本 | 一个节点的子节点增加、删除都会影响这个版本 |
dataversion | 节点项下数据版本id | 跟乐观锁相关 |
aclVersion | 权限控制版本id | 修改节点的权限控制时该值递增 |
ephemeralOwner | 存储sessionId | |
dataLength | 节点项下数据长度 | |
numChildren | 子节点个数 |
3.1 关于事务请求和事务
id
:
1)所有修改数据的请求叫事务请求(包含session
的建立),zookeeper会通过递增事务id
来维护事务请求的顺序
3.2 跟查看持久化节点的
get -s
指令的返回结果区别:
1)临时节点的ephemeralOwner
有具体的sessionId
,而持久化节点的ephemeralOwner是0x0
(0的意思),表示没有
ephemeralOwner
为0
set [-s] [-v version] path data
set /test xxx
delete [-v version] path
v
跟乐观锁相关
/test
:delete /test
scheme:id:permission
getAcl 节点名称
:返回权限模式:授权对象
口令认证模式下,密码必定是密文
-Dzookeeper.skipACL=yes
进行配置,默认是no
,可以配置为true
bin
目录,vim zkServer.sh
ip:192.168.0.1/24
这样的一段ipzookeeper中是Digest认证
用户名:密码
后传送给服务端SHA-1
和BASE64
算法进行加密,具体有以下两种加密方式:(假定结果是X/NSthOB0fD/OT6iilJ55WJVado=
)
String sId = DigestAuthenticationProvider.generateDigest("gj:test");System.out.println(sId);// gj:X/NSthOB0fD/OT6iilJ55WJVado=
echo ‐n <user>:<password> | openssl dgst ‐binary ‐sha1 | openssl base64
Super
权限模式:是一种特殊的Digest认证,具有Super
权限的客户端可以对ZooKeeper上的任意数据节点进行任意操作密码忘了的话就不慌了
world
权限模式:是zookeeper缺省的权限模式,创建节点时未指明权限默认world
,可通过getAcl
查看到Digest
认证或Super
权限模式的话,ID就是用户名World
模式,ID是系统中的所有用户各种权限如下:
值 | 对应权限 | 说明 |
---|---|---|
c | 创建权限 | 授予权限的对象可以在数据节点下创建子节点 |
w | 更新权限 | 授予权限的对象可以更新该数据节点 |
r | 读取权限 | 授予权限的对象可以读取该节点的内容以及子节点的列表信息 |
d | 删除权限 | 授予权限的对象可以删除该数据节点的子节点 |
a | 管理者权限 | 授予权限的对象可以对该数据节点体进行ACL权限设置 |
存在两种方式,但无论是密文授权还是明文授权,zookeeper底层都是以密文的形式存储授权信息
create /zk‐node datatest digest:gj:X/NSthOB0fD/OT6iilJ55WJVado=:cdrwa
/zk-node
,其用户为gj
/zk‐node
节点:addauth digest gj:test
上面密码
test
别再搞密文了,并且退出后就没法看了,得重新添加权限
参数里dataest这个数据可以省略
setAcl
对已有节点进行授权:setAcl /zk‐node digest:gj:X/NSthOB0fD/OT6iilJ55WJVado=:cdrwa
addauth digest u100:p100
不能再用空数据了,空数据的话会把授权信息当作数据
/zk-node
节点:get /zk-node
create /node‐ip data ip:192.168.109.128:cdwra
setAcl /node‐ip ip:192.168.109.128:cdwra
setAcl /node-ip ip:IP1:rw,ip:IP2:a
ip间使用逗号分隔
sh
文件中进行配置,具体步骤:bin
目录,vim zkServer.sh
‐Dzookeeper.DigestAuthenticationProvider.superDigest=super:<base64encoded(SHA1(password))
即可
super
:具体的用户名<base64encoded(SHA1(password))
:密钥setAcl /znode-2 world:anyone:cdwra
/znode-2
节点任意访问:DataTree
:是内存中的数据组织形式:public class DataTree {
private final ConcurrentHashMap<String, DataNode> nodes = new ConcurrentHashMap<String, DataNode>();// String类型对应path
private final WatchManager dataWatches = new WatchManager();
private final WatchManager childWatches = new WatchManager();
}
DataNode
:是Zookeeper存储节点数据的最小单位:public class DataNode implements Record {
byte data[];// 节点存放的数据
Long acl;// 授权信息
public StatPersisted stat;// 节点状态信息,可用 get -s 节点名查看
private Set<String> children = null;
}
当然,Zookeeper也会将数据变更应用到内存数据库中
/conf
目录下zk的配置文件.cfg
,找到dataDir
参数,其后路径便是:.cfg
中新增dataLogDir
参数log.<当时最大事务ID>
都以log打头,且顺序编号
即在创建文件的时候,就向操作系统申请一块大一点的磁盘块
-Dzookeeper.preAllocSize
系统参数:默认65Mjava ‐classpath .:slf4j‐api‐1.7.25.jar:zookeeper-3.5.8.jar:zookeeper‐jute‐
3.5.8.jar org.apache.zookeeper.server.LogFormatter /usr/local/zookeeper/apache‐zookeeper‐3.5.8‐bin/data/version‐2/log.1
5.1 参数释义:
1)org.apache.zookeeper.server.LogFormatter
:格式化工具包,因为文件是二进制存储的
2)zookeeper‐jute‐3.5.8.jar
:序列化工具,数据最终是通过jute序列化后才存到事务日志中的
末尾的
1
,2
代表数据版本
snapshot.<当时最大事务ID>
都以
snapshot
打头,且顺序编号
snapCount
配置每间隔事务请求个数来生成快照[snapCount/2 + 随机数(随机数范围为1 ~ snapCount/2 )]
个数时开始快照
dataDir
java ‐classpath .:slf4j‐api‐1.7.25.jar:zookeeper-3.5.8.jar:zookeeper‐jute‐3.5.8.jar org.apache.zookeeper.server.SnapshotFormatter /usr/local/zookeeper/apache‐zookeeper‐3.5.8‐bin/data‐dir/version‐2/snapshot.0
此次以订单系统为例:
直接在
main
方法里new
一个客户端!
main
方法里开俩异步守护线程用于Zookeeper客户端实例的创建:
1)sendThread
:负责客户端向服务端发送请求的线程
2)eventThread
:负责接受服务端数据响应的线程,包括事件也在这里处理
main
方法一结束,主线程结束,俩守护线程也就跟着挂了mian
方法最后一行记得加一个睡眠一直等待,避免main
方法自动结束掉:TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
create()
方法是一个同步的阻塞的方法:ObjectMapper
对象序列化工具:byte[] arr = ObjectMapper.writeValueAsBytes(对象实例);
类名 实例 = ObjectMapper.readValue(new String[arr], 类名.class);
/myconfig
节点的数据发生变化,当前main
方法里就会打印该节点的变化信息:getData()
、setData()
方法实现乐观锁:delete(节点路径, version)
version = -1
时:代表匹配所有版本,直接删除version > -1
时:代表删除指定数据版本的节点getData(节点路径, boolean watch, DataCallback cb, Object ctx)
getData()
对应的指定节点数据读取操作watch
:是否采用监听器cb
:方法回调器ctx
:上下文对象,供cb
中process()
方法做入参使用(一般就是你想设置的节点默认数据值了)<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.8</version>
</dependency>
newClient()
静态方法初始话一个zookeeper实例:CuratorFrameworkFactory.newClient(zookeeperConnectionString, retryPolicy)
:
zookeeperConnectionString
:待访问服务端的ip
以及端口(可以是nginx等负载均衡设备的ip
和端口)retryPolicy
:重试策略,可以使用RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3)
初始化一个策略,内部参数释义如下:
1000
:距离上次失败后进行重试的间隔时间(毫秒)3
:重试最大次数更推荐链式编程的原因:
1)因为CuratorFramework
类的实现类CuratorFrameworkImpl
中静态成员很多,通过建造者模式可以避免连接信息在构造完成后随意修改的情况,更加安全
curator
的该方法并不能避免僵尸节点(幽灵节点)的问题
curator
的该方法并不能避免僵尸节点(幽灵节点)的问题
curatorFramework.getChildren()
guaranteed()
方法确保删除节点成功,并自动进行删除失败的重试,尽可能避免通信异常的影响:后台处理任务不再在
eventThread
里执行,进一步提高了效率
curator
客户端创建节点时将UUID
缓存到本地,重试前需判断缓存中是否有UUID
,有的话就看有没有UUID
前缀的临时节点呢,有,就不重试了可以将耗时长的处理放到回调方法里,在监听到指定事件后由线程池统一处理,提高了性能
QuorumPeer.LearnerType
枚举类ServerState
枚举类阶段 | 节点类型 | 扮演角色 | 所处状态 | 职责描述 | 备注 |
---|---|---|---|---|---|
选举出Leader之前 | OBSERVER类型 | observer | Observing状态 | 观察者,负责读数据,减轻服务端写数据的压力 | 不可参与选举 |
PARTICIPANT类型 | 无明确角色 | 初始Looking状态 | 选举参与者,并不是明确的角色 | 此时集群中尚未选举出Leader,待参与选举的节点默认是该状态 | |
选举出Leader之后 | OBSERVER类型 | observer | Observing状态 | 观察者,负责读数据,减轻服务端写数据的压力 | 不可参与选举 |
PARTICIPANT类型 | leader | Leading状态 | 负责写请求、读数据 | 可参与选举 | |
PARTICIPANT类型 | follower | Following状态 | 负责读数据,一旦leader失效,follower可以参与选举,有可能成为新的Leader |
集群中可参与选举的最少3个节点!!
id
:cfg
文件中新增当前服务实例的ip
地址和端口:注意:
1)图片里写错了,不是供外部访问的端口,而是集群内部访问的端口
2)clientPort=2181
才算是供外部访问的端口,也就是供客户端访问的端口
3)server.1
中的这个1
是myid
为1的节点供其他节点访问的sid
(sid
即sendID
的意思)
cfg
文件中,并修改对应端口、角色、数据存储路径一共两种方式:
zkCli.sh -server 服务实例ip:供客户端连接的端口
注意是客户端的连接端口,不是供外部访问的端口或选举端口!!
可参与选举的节点不包含observe节点
缺陷:没有相应的异常处理,需人工加
通过
ls -s
、ls -w
、get -w
等命令实现监听
注意,zk不会去实现非公平锁,最起码是公平锁!此处仅作展示!!
FIFO
)这也是被称为非公平锁的原因
关于羊群效应:羊盲目从众,都在争抢着做一件事情
/lock
节点下创建一个临时顺序节点/lock
节点下所有临时顺序节点,并判断当前临时顺序节点是不是/lock
节点下的编号最小的节点:
/lock
节点下没有子节点时,删除/lock
节点
/lock
节点使用容器节点来实现/lock
节点/lock
下的所有临时顺序节点的编号一定是连续的么?如果不是,是否还能保证锁的公平性?还能保证锁机制?/lock
节点下某个临时顺序子节点被意外删除/失效的情况get
/lock
节点下的最小节点,如果是自己,那就意味着获得了锁,如果不是,就监听此时前面的一个节点即可。UUID
)消灭掉僵尸节点僵尸节点见上文
可参考【Redis缓存设计与性能优化】
.conf
中添加min‐replicas‐to‐write n
的配置来预防脑裂的问题,进而规避锁失效的问题综合上,无论是redis实现还是zk实现,毕竟是独占锁,对并发读取数据而言性能很差,因此还是建议使用读写锁(共享锁)
以下使用负载均衡集群来实现秒杀场景
Number of threads 10
没有任何并发控制,没加任何锁:
CuratorFrameworkFactory.newClient(zookeeperConnectionString, retryPolicy)
zookeeperConnectionString
:待访问服务端的ip
以及端口(可以是nginx等负载均衡设备的ip
和端口)retryPolicy
:重试策略,可以通过如下方式初始化一个实例:RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3)
,具体参数解释:
1000
:距离上次失败后进行重试的间隔时间(毫秒)3
:重试最大次数InterProcessMutex
构造方法:核心是new StandardLockInternalsDriver()
中的StandardLockInternalsDriver
InterProcessMutex.acquire()
方法是加锁的主逻辑:InterProcessMutex
的internalLock()
方法内部:attemptLock()
方法内部:/lock
)是一个容器节点withProtection()
方法,使用安全模式,避免了幽灵节点的产生forPath()
方法,内部会有具体的幽灵节点如何避免的逻辑,具体如下:
protectedPathInForeground()
方法,再进入pathInForeground()
方法:
attemptLock()
方法内部,进入internalLockLoop()
方法内部:getSortedChildren()
方法:问题:为什么已经是顺序节点了,拿的时候还要排序?
1)因为curator访问的zk集群不能保证节点顺序返回给客户端,所以要人工排序下
maxLeases
可以理解为同一时间集群中可以存在的锁的个数,这样更好理解点
InterProcessReadWriteLock(Client client, String basePath, byte[] lockData)
:client
:zk的客户端,此处用Curator实例basePath
:加锁路径,也就是父亲节点(/lock
节点)的路径lockData
:锁节点对应的数据,可有可无driver
中判断加锁成功的逻辑getsTheLock()
方法:具体代码定位:从
acquire()
方法定位到internalLockLoop()
方法内部,直到【判断当前子节点是否是所有节点中编号最小的】getsTheLock()
方法
以下主要围绕
LeaderSelectorListener
实现zk选主来说明
autoRequeue()
可以确保能够在集群中主节点挂掉后重新选举出主节点TimeUinit.SECONDS.sleep(5)
),这样休眠期间Leader权限就会释放掉,便于观察集群中Leader选举效果selector.start()
是在另一个线程中执行的,最好用CountDownLatch
加个锁,main
方法执行到最后进行等待:selector
的start()
方法,进入其中requeue
()方法,再定位到internalRequeue()
:doWorkLoop()
方法,定位doWork()
内部:takeLeaderShip()
方法:
autoRequeue.get()
初始设置为true
,所以会循环执行internalReque()
方法
如果不使用这个,默认会去用Ribborn
搭建环境:
Spring Cloud Zookeeper的服务默认会在zk下创建一个/services
节点:
不想记了,这tm要命啊学的。。。。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。