Zookeeper 是什么
Zookeeper 是一个基于 Google Chubby 论文实现的一款解决分布式数据一致性问题的开源实现,方便了依赖 Zookeeper 的应用实现 数据发布 / 订阅
、负载均衡
、服务注册与发现
、分布式协调
、事件通知
、集群管理
、Leader 选举
、 分布式锁和队列
等功能
基本概念
集群角色
一般的,在分布式系统中,构成集群的每一台机器都有自己的角色,最为典型的集群模式就是 Master / Slave
主备模式。在该模式中,我们把能够处理所有写操作
的机器称为 Master
节点,并把所有通过异步复制
方式获取最新数据、提供读服务
的机器称为 Slave
节点
而 Zookeeper 中,则是引入了 领导者(Leader)
、跟随者(Follower)
、观察者(Observer)
三种角色 和 领导(Leading)
、跟随(Following)
、观察(Observing)
、寻找(Looking)
等相应的状态。在 Zookeeper 集群中的通过一种 Leader 选举
的过程,来选定某个节点作为 Leader
节点,该节点为客户端提供读
和写
服务。而 Follower
和 Observer
节点,则都能提供读
服务,唯一的区别在于,Observer
机器不参与 Leader 选举
过程 和 写操作
的"过半写成功"
策略,Observer
只会被告知已经 commit 的 proposal。因此 Observer
可以在不影响写性能
的情况下提升集群的读性能
(详见下文 “性能优化 - 优化策略 - Observer 模式” 部分)
会话
Session 指客户端会话。在 Zookeeper 中,一个客户端会话
是指 客户端
和服务器
之间的一个 TCP 长连接
。客户端启动的时候,会与服务端建立一个 TCP 连接
,客户端会话的生命周期,则是从第一次连接建立开始算起。通过这个连接,客户端能够通过心跳检测
与服务器保持有效的会话,并向 Zookeeper 服务器发送请求并接收响应,以及接收来自服务端的 Watch 事件通知
Session 的 sessionTimeout 参数,用来控制一个客户端会话的超时时间。当服务器压力
太大 或者是网络故障
等各种原因导致客户端连接断开时,Client 会自动从 Zookeeper 地址列表中逐一尝试重连(重试策略可使用 Curator 来实现)。只要在 sessionTimeout 规定的时间内能够重新连接
上集群中任意一台服务器
,那么之前创建的会话仍然有效。如果,在 sessionTimeout 时间外重连了,就会因为 Session 已经被清除了,而被告知 SESSION_EXPIRED
,此时需要程序去恢复临时数据;还有一种 Session 重建后的在新节点上的数据,被之前节点上因网络延迟
晚来的写请求
所覆盖的情况,在 ZOOKEEPER-417 中被提出,并在该 JIRA 中新加入的 SessionMovedException
,使得 用同一个 sessionld/sessionPasswd
重建 Session 的客户端能感知到,但是这个问题到 ZOOKEEPER-2219 仍然没有得到很好的解决
数据模型
在 Zookeeper 中,节点
分为两类,第一类是指 构成集群的机器
,称之为机器节点
;第二类则是指 数据模型中的数据单元
,称之为数据节点 ZNode
。Zookeeper 将所有数据存储在内存中,数据模型的结构类似于树(ZNode Tree),由斜杠(/)
进行分割的路径,就是一个 ZNode
,例如 /foo/path1
。每个 ZNode 上都会保存自己的数据内容 和 一系列属性信息
ZNode 可以分为持久节点(PERSISTENT)
和临时节点(EPHEMERAL)
两类。所谓持久节点
是指一旦这个 ZNode 被创建了,除非主动进行移除操作,否则这个节点将一直保存在 Zookeeper 上。而临时节点
的生命周期,是与客户端会话绑定的,一旦客户端会话失效
,那么这个客户端创建的所有临时节点
都会被移除
。在 HBase 中,集群则是通过 /hbase/rs/* 和 /hbase/master 两个临时节点,来监控 HRegionServer 进程的加入和宕机 和 HMaster 进程的 Active 状态
另外,Zookeeper 还有一种 顺序节点(SEQUENTIAL)
。该节点被创建的时候,Zookeeper 会自动在其子节点名上,加一个由父节点维护的、自增整数的后缀(上限:Integer.MAX_VALUE
)。该节点的特性,还可以应用到 持久 / 临时节点 上,组合成 持久顺序节点(PERSISTENT_SEQUENTIAL)
和 临时顺序节点(EPHEMERAL_SEQUENTIAL)
版本
Zookeeper 的每个 ZNode
上都会存储数据,对应于每个 ZNode
,Zookeeper 都会为其维护一个叫做 Stat
的数据结构,Stat
中记录了这个 ZNode
的三个数据版本,分别是 version
(当前 ZNode 数据内容的版本),cversion
(当前 ZNode 子节点的版本)和 aversion
(当前 ZNode 的 ACL 变更版本)。这里的版本
起到了控制 Zookeeper 操作原子性
的作用(详见下文 “源码分析 - 落脚点 - Zookeeper 乐观锁” 部分)
Watcher
Watcher(事件监听器)是 Zookeeper 提供的一种 发布/订阅
的机制。Zookeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发
的时候,Zookeeper 服务端会将事件通知给订阅
的客户端。该机制是 Zookeeper 实现分布式协调
的重要特性
ACL
类似于 Unix 文件系统,Zookeeper 采用 ACL(Access Control Lists)
策略来进行权限控制(使用方式,详见下文 “常用命令 - 执行脚本 - zkCli - 节点操作” 部分;代码实现,详见 PrepRequestProcessor#checkACL)
常用的权限控制
Command | Comment |
---|---|
CREATE (c) | 创建子节点的权限 |
READ (r) | 获取节点数据和子节点列表的权限 |
WRITE (w) | 更新节点数据的权限 |
DELETE (d) | 删除当前节点的权限 |
ADMIN (a) | 管理权限,可以设置当前节点的 permission |
Scheme | ID | Comment |
---|---|---|
world | anyone | Zookeeper 中对所有人有权限的结点就是属于 world:anyone |
auth | 不需要 id | 通过 authentication 的 user 都有权限 |
(Zookeeper 支持通过 kerberos 来进行 authencation ,也支持 username /password 形式的 authentication) |
||
digest | username:BASE64 (SHA1(password)) | 需要先通过 username:password 形式的 authentication |
ip | id 为客户机的 IP 地址(或者 IP 地址段) | ip:192.168.1.0/14,表示匹配前 14 个 bit 的 IP 段 |
super | 对应的 id 拥有超级权限(CRWDA) |
IP
编码
@Before public void init() throws Exception { zoo = new ZooKeeper(HOST.concat(":" + CLIENT_PORT), TIME_OUT_MILLISECOND, null); acls = new ArrayList<>(); acls.add(new ACL(ZooDefs.Perms.ALL, new Id(IP, "10.24.40.178"))); acls.add(new ACL(ZooDefs.Perms.ALL, new Id(IP, "127.0.0.1"))); aclsNoAuth = new ArrayList<>(); aclsNoAuth.add(new ACL(ZooDefs.Perms.ALL, new Id(IP, "127.0.0.1"))); } @Test public void ipAcl() throws Exception { if (zoo.exists(IP_PATH, null) != null) zoo.delete(IP_PATH, -1); if (zoo.exists(IP_PATH_NO_AUTH, null) != null) zoo.delete(IP_PATH_NO_AUTH, -1); zoo.create(IP_PATH, IP.getBytes(), acls, CreateMode.PERSISTENT); assertEquals(IP, new String(zoo.getData(IP_PATH, false, null))); zoo.create(IP_PATH_NO_AUTH, IP.getBytes(), aclsNoAuth, CreateMode.PERSISTENT); try { zoo.getData(IP_PATH_NO_AUTH, false, null); } catch (KeeperException.NoAuthException e) { assertEquals("KeeperErrorCode = NoAuth for ".concat(IP_PATH_NO_AUTH), e.getMessage()); } }
Tips: Full code is here.
命令行
$ zkCli.sh -server localhost:2181 [zk: localhost:2181(CONNECTED) 16] ls / [leader, election, zookeeper, origin, ip, auth_test, benchmark] [zk: localhost:2181(CONNECTED) 17] ls /ip Authentication is not valid : /ip [zk: localhost:2181(CONNECTED) 18] getAcl /ip 'ip,'10.24.40.178 : cdrwa 'ip,'127.0.0.1 : cdrwa $ zkCli.sh -server 127.0.0.1:2181 [zk: 127.0.0.1:2181(CONNECTED) 1] ls /ip [] [zk: 127.0.0.1:2181(CONNECTED) 2] get /ip ip cZxid = 0x10000c43b ctime = Tue Aug 22 16:50:37 CST 2017 mZxid = 0x10000c43b mtime = Tue Aug 22 16:50:37 CST 2017 pZxid = 0x10000c43b cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 2 numChildren = 0
优缺点
简单易用,直接在物理层面,对用户进行权限隔离;但是,如果不将 127.0.0.1
放入到 IP Acl 列表里,会给服务端的运维带来麻烦
Digest
编码
@Before public void init() throws Exception { zoo = new ZooKeeper(HOST.concat(":" + CLIENT_PORT), TIME_OUT_MILLISECOND, null); zoo.addAuthInfo("digest", "yuzhouwan:com".getBytes()); zooNoAuth = new ZooKeeper(HOST.concat(":" + CLIENT_PORT), TIME_OUT_MILLISECOND, null); } @Test public void digestAcl() throws Exception { if (zoo.exists(AUTH_PATH_CHILD, null) != null) zoo.delete(AUTH_PATH_CHILD, -1); if (zoo.exists(AUTH_PATH, null) != null) zoo.delete(AUTH_PATH, -1); zoo.create(AUTH_PATH, bytes, ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT); try { zooNoAuth.create(AUTH_PATH_CHILD, bytes, ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT); } catch (KeeperException.InvalidACLException e) { assertEquals("KeeperErrorCode = InvalidACL for /auth_test/child", e.getMessage()); } zoo.create(AUTH_PATH_CHILD, bytes, ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT); try { zooNoAuth.delete(AUTH_PATH_CHILD, -1); } catch (KeeperException.NoAuthException e) { assertEquals("KeeperErrorCode = NoAuth for /auth_test/child", e.getMessage()); } assertEquals(AUTH_PATH, new String(zoo.getData(AUTH_PATH, false, null))); }
Tips: Full code is here.
命令行
$ zkCli.sh -server localhost:2181 [zk: localhost:2181(CONNECTED) 5] ls / [leader, auth_test, election, zookeeper, benchmark, origin] [zk: localhost:2181(CONNECTED) 6] ls /auth_test Authentication is not valid : /auth_test [zk: localhost:2181(CONNECTED) 7] get /auth_test Authentication is not valid : /auth_test [zk: localhost:2181(CONNECTED) 8] getAcl /auth_test 'digest,'yuzhouwan:h/j+/wDlblTtA48jnbq8snP1glA= : cdrwa [zk: localhost:2181(CONNECTED) 9] addauth digest yuzhouwan:true [zk: localhost:2181(CONNECTED) 10] get /auth_test /auth_test cZxid = 0x10000c31e ctime = Tue Aug 22 15:26:27 CST 2017 mZxid = 0x10000c31e mtime = Tue Aug 22 15:26:27 CST 2017 pZxid = 0x10000c31e cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 10 numChildren = 0
优缺点
可以建立角色,按照用户名、密码进行权限控制;但是,想要修改某个用户的密码,需要对所有的 ACLs 做更改
SASL & Kerberos
环境搭建
单机版
安装
$ cd ~/install/ $ wget http://archive.apache.org/dist/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz $ wget http://archive.apache.org/dist/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz.md5 # 校验 MD5 $ head -n 1 zookeeper-3.4.10.tar.gz.md5 e4cf1b1593ca870bf1c7a75188f09678 zookeeper-3.4.10.tar.gz $ md5sum zookeeper-3.4.10.tar.gz e4cf1b1593ca870bf1c7a75188f09678 *zookeeper-3.4.10.tar.gz # 对比 MD5 码一致后进行解压安装 $ tar zxvf zookeeper-3.4.10.tar.gz -C ~/software/ $ cd ~/software $ ln -s zookeeper-3.4.10 zookeeper
配置
$ cd zookeeper $ mkdir tmp $ cp conf/zoo_sample.cfg conf/zoo.cfg $ mkdir -p /home/zookeeper/data/zookeeper $ mkdir -p /home/zookeeper/logs/zookeeper # 更多配置,详见下文 “常用配置” 部分 $ vim conf/zoo.cfg tickTime=2000 initLimit=10 syncLimit=5 dataDir=/home/zookeeper/data/zookeeper dataLogDir=/home/zookeeper/logs/zookeeper clientPort=2181
启动
$ bin/zkServer.sh start $ bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /home/zookeeper/software/zookeeper/bin/../conf/zoo.cfg Mode: standalone $ bin/zkCli.sh
分布式
# 更多配置,详见下文 “常用配置” 部分 $ vim conf/zoo.cfg tickTime=2000 initLimit=10 syncLimit=5 dataDir=/home/zookeeper/data/zookeeper dataLogDir=/home/zookeeper/logs/zookeeper clientPort=2181 server.1=yuzhouwan01:2281:2282 server.2=yuzhouwan02:2281:2282 server.3=yuzhouwan03:2281:2282 # 在各个节点的 dataDir下创建 myid 文件,并对应 zoo.cfg中配置的 id [zookeeper@yuzhouwan01 ~] echo "1" > /home/zookeeper/data/zookeeper/myid [zookeeper@yuzhouwan02 ~] echo "2" > /home/zookeeper/data/zookeeper/myid [zookeeper@yuzhouwan03 ~] echo "3" > /home/zookeeper/data/zookeeper/myid
常用命令
四字命令
Command | Comment |
---|---|
conf | 输出相关服务配置的详细信息 |
cons | 列出所有连接到服务器的客户端的完全的连接 / 会话 的详细信息 (包括“接受 / 发送” 的包数量、会话 id 、操作延迟、最后的操作执行等等信息) |
envi | 输出关于服务环境的详细信息 (区别于 conf 命令) |
dump | 列出未经处理的会话和临时节点 |
stat | 查看哪个节点被选择作为 Follower 或者 Leader |
ruok | 测试是否启动了该 Server,若回复 imok 表示已经启动 |
mntr | 输出一些运行时信息(latency / packets / alive_connections / outstanding_requests / server_state / znode + watch + ephemerals count …) |
reqs | 列出未经处理的请求 |
wchs | 列出服务器 watch 的简要信息 |
wchc | 通过 session 列出服务器 watch 的详细信息(输出是一个与 watch 相关的会话的列表) |
wchp | 通过路径列出服务器 watch 的详细信息(输出一个与 session 相关的路径) |
srvr | 输出服务的所有信息(可以用来检查当前节点同步完毕集群数据,处于 Follower 状态) |
srst | 重置服务器统计信息 |
kill | 关掉 Server |
Tips: 部分加粗命令对资源消耗比较大,生产环境慎用!
使用方式
安装 Netcat
# online $ yum install nc # offline $ uname -a Linux yuzhouwan 2.6.32-279.el6_sn.7.x86_64 #1 SMP Fri May 27 18:04:25 CST 2016 x86_64 x86_64 x86_64 GNU/Linux # 搜索rpm包 https://rpmfind.net/linux/rpm2html/search.php?query=nc&submit=Search+...&system=&arch=x86_64 $ wget ftp://rpmfind.net/linux/centos/6.9/os/x86_64/Packages/nc-1.84-24.el6.x86_64.rpm $ rpm -ivh nc-1.84-24.el6.x86_64.rpm
Netcat 执行
$ echo <four-letter command> | nc 127.0.0.1 2181
DOS 攻击
# 避免 wchp / wchc 四字命令被 DOS 攻击利用 $ vim zoo.cfg # 4lw.commands.whitelist=* 4lw.commands.whitelist=stat, ruok, conf, isro
产生的日志
# 可以看到 zk.out 文件中出现 0:0:0:0:0:0:0:1(IPv6 的回送地址,相当于 IPv4 的 127.0.0.1)和 Processing xxxx command 相应的日志 2017-06-13 23:05:01,998 [myid:5] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@197] - Accepted socket connection from /0:0:0:0:0:0:0:1:40986 2017-06-13 23:05:01,998 [myid:5] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing srvr command from /0:0:0:0:0:0:0:1:40986 2017-06-13 23:05:02,000 [myid:5] - INFO [Thread-64778:NIOServerCnxn@1007] - Closed socket connection for client /0:0:0:0:0:0:0:1:40986 (no session established for client)
执行脚本
zkServer
启动
# 启动 $ bin/zkServer.sh start # 查看状态 $ bin/zkServer.sh status # 停止服务 $ bin/zkServer.sh stop # 重启 $ bin/zkServer.sh restart
排查问题
# 可以通过增加 `start-foreground` 参数来排查失败原因 $ bin/zkServer.sh start-foreground ZooKeeper JMX enabled by default Using config: /home/eagle/software/zookeeper/bin/../conf/zoo.cfg Error: Could not find or load main class org.apache.zookeeper.server.quorum.QuorumPeerMain # /home/eagle/software/zookeeper/zookeeper-3.4.10.jar 的问题,重新下载,校验 md5 正确后,再次安装即可
zkCli
启动
$ cd $ZOOKEEPER_HOME $ bin/zkCli -server <zk host>:2181,<zk host>:2181,<zk host>:2181
节点操作
Command | Example | Comment |
---|---|---|
create | 创建一个节点 | |
ls | 查看当前节点数据 | |
ls2 | 查看当前节点数据,并能看到更新次数 等信息 |
|
set | 修改节点 | |
get | 得到一个节点,包含数据 和更新次数 等信息 |
|
delete | 删除一个节点 | |
rmr | 递归删除 | |
history | 列出最近的历史命令 | |
redo <command number: n> |
redo 1 | 重做第 n 步命令 |
stat | 打印节点状态 | |
close | 关闭当前连接 | |
connect <host>:<port> |
connect localhost:2181 | 当 close 当前连接或者意外退出后,可在 zkCli 命令模式中重连 |
quit | 退出当前连接 | |
setAcl <path> <acl: scheme + id + permissions> |
setAcl /zk world:anyone:cdrw | 设置节点权限策略(详见上文 “基本概念 - ACL” 部分) |
getAcl <path> |
获取节点权限策略 | |
addauth <scheme> <auth> |
addauth digest username:password | 节点权限认证 |
setquota -n | -b val <path> |
|
listquota <path> |
listquota /zookeeper | 查看节点的配额 |
count=5, bytes=-1 | /zookeeper 节点个数限额为 5,长度无限额 | |
delquota [-n or -b] <path> |
||
sync <path> |
强制同步 | |
(由于 “过半原则”,导致某些 Zookeeper Server 上的数据是旧的,用 sync 命令可强制同步所有的更新操作) |
||
printwatches on | off |
常见组合(for Kafka)
Command | Comment |
---|---|
get /consumers/<topic> /owners |
查看 Topic 实时消费的 Group ID |
get /consumers/<topic> /offsets/<group id> /<partitionor> |
查看 Offset 情况(ctime:创建时间;mtime:修改时间) |
常用配置
dataDir
Zookeeper 保存服务器存储快照文件的目录,默认情况,Zookeeper 将 写数据的日志文件
也保存在这个目录里(default:/tmp/zookeeper)
dataLogDir
用来存储服务器事务日志
clientPort
客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求(default:2181)
tickTime(SS / CS)
用来指示 服务器
之间或客户端
与服务器
之间维护心跳
机制的 最小时间单元,Session
最小过期时间默认为两倍的 tickTime(default:2000
ms)
initLimit(LF)
集群中的 Leader
节点和 Follower
节点之间初始连接
时能容忍的最多心跳数(default:5 tickTime)
syncLimit(LF)
集群中的 Leader
节点和 Follower
节点之间请求和应答
时能容忍的最多心跳数(default:2 tickTime)
minSessionTimeout & maxSessionTimeout
默认分别是 2 * tickTime ~ 20 * tickTime,来用控制 客户端设置的 Session 超时时间。如果超出或者小于,将自动被服务端强制设置为 最大或者最小
集群节点
配置 Zookeeper 集群中的服务器节点
格式:server.<myid>
=<服务器地址>
:<LF通讯端口>
:<选举端口>
样例:server.1=yuzhouwan:2888:3888
动态配置
$ vim zoo_replicated1.cfg tickTime=2000 dataDir=/zookeeper/data/zookeeper1 initLimit=5 syncLimit=2 dynamicConfigFile=/zookeeper/conf/zoo_replicated1.cfg.dynamic $ vim zoo_replicated1.cfg.dynamic server.1=125.23.63.23:2780:2783:participant;2791 server.2=125.23.63.24:2781:2784:participant;2792 server.3=125.23.63.25:2782:2785:participant;2793
监控
采集方式
JMX
远程连接
Zookeeper 默认支持 JMX 连接,但是只支持本地连接
# 开启远程 JMX $ vim bin/zkServer.sh # ZOOMAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=$JMXLOCALONLY org.apache.zookeeper.server.quorum.QuorumPeerMain" ZOOMAIN="-Dcom.sun.management.jmxremote.port=8888 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false org.apache.zookeeper.server.quorum.QuorumPeerMain"
Four-letter command
TCP Dump
# 使用 tcpdump 命令,需要在 root 权限下执行 $ sudo tcpdump tcp port 2181 and host ! 127.0.0.1 # 下面抓包到的信息,是 Curator 远程连接,并在 /children 命名空间下,创建临时节点 /yuzhouwan,最终断开 TCP 连接的过程 14:50:16. IP 10.10.10.10.56342 > yuzhouwan01.eforward: Flags [S], seq , win 8192, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0 14:50:16. IP yuzhouwan01.eforward > 10.10.10.10.56342: Flags [S.], seq , ack , win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0 14:50:16. IP 10.10.10.10.56342 > yuzhouwan01.eforward: Flags [.], ack 1, win 256, length 0 14:50:16. IP 10.10.10.10.56342 > yuzhouwan01.eforward: Flags [P.], seq 1:50, ack 1, win 256, length 49 14:50:16. IP yuzhouwan01.eforward > 10.10.10.10.56342: Flags [.], ack 50, win 115, length 0 14:50:16. IP yuzhouwan01.eforward > 10.10.10.10.56342: Flags [P.], seq 1:42, ack 50, win 115, length 41 14:50:16. IP 10.10.10.10.56342 > yuzhouwan01.eforward: Flags [P.], seq 50:76, ack 42, win 256, length 26 14:50:16. IP yuzhouwan01.eforward > 10.10.10.10.56342: Flags [P.], seq 42:62, ack 76, win 115, length 20
Tips: 比较关心的一个问题是,tcpdump
是否会对性能造成影响?答案是,会的。当过滤上千的 IP 时,已经会影响到服务器性能。主要瓶颈在 BPF Filter
,这是一个 O(n) O ( n ) 线性时间复杂度的算法,可以考虑 HiPAC 多维树匹配
替代
指标
Zookeeper 运行状态(mntr)
Metrics | Comment | Threshold |
---|---|---|
zk_version | 版本 | |
zk_avg_latency | 平均 响应延迟 |
50ms,比上次统计增长超过 20ms,且上一次延迟不为 0 |
zk_max_latency | 最大 响应延迟 | |
zk_min_latency | 最小 响应延迟 | |
zk_packets_received | 收包数 | |
zk_packets_sent | 发包数 | |
zk_num_alive_connections | 活跃连接数 |
3000 |
zk_outstanding_requests | 堆积请求数 | 连续两次大于 5(粒度:1min) |
zk_server_state | 主从状态 | 由 Leader 变为 Follower 或 由 Follower 变为 Leader |
zk_znode_count | znode 数 |
40000 |
zk_watch_count | watch 数 |
50000 |
zk_ephemerals_count | 临时节点数 | |
zk_approximate_data_size | 近似数据总和大小 | |
zk_open_file_descriptor_count | 打开 文件描述符 数 |
|
zk_max_file_descriptor_count | 最大 文件描述符 数 |
|
zk_followers | Follower 数 | |
zk_synced_followers | 已同步的 Follower 数 | 连续两次检测到未同步的 Follower 节点 (粒度:1min) |
zk_pending_syncs | 阻塞中的 sync 操作 |
Tips: 加粗指标,只有 Leader 节点才会有
实时预警
numenta / nupic
NuPIC(Numenta Platform for Intelligent Computing,Numenta智能计算平台)
是一个与众不同的开源人工智能平台,它基于一种脑皮质学习算法
,即 “层级实时记忆”(Hierarchical Temporal Memory,HTM)。该算法旨在模拟新大脑皮层的工作原理,将复杂的问题转化为模式匹配与预测,而传统的 AI 算法大多是针对特定的任务目标而设计的
NuPIC 聚焦于分析实时数据流,可以通过学习数据之间基于时间的状态变化(而非阀值设置),对未知数据进行预测,并揭示其中的非常规特性。详见我的另一篇博客:人工智能
性能调优
Benchmark(Test First)
brownsys / zookeeper-benchmark (很难找到合适的开源项目,需自己编写 Benchmark 工具)
优化策略
部署
日志目录
- 快照目录 dataDir 和 事务日志目录 dataLogDir 分离
- 写事务日志的目录,需要保证目录空间足够大,并挂载到单独的磁盘上(为了保证
数据的一致性
, ZooKeeper 在返回客户端事务请求响应之前, 必须要将此次请求对应的事务日志
刷入到磁盘中 [forceSync 参数控制,default:yes],所以事务日志的写入速度,直接决定了 Zookeeper 的吞吐率)
自动日志清理
autopurge.purgeInterval
指定清理频率,单位为小时(default:0 表示不开启自动清理)
autopurge.snapRetainCount
和上面 purgeInterval
参数配合使用,指定需要保留的文件数目(default:3)
$ vim conf/zoo.cfg autopurge.snapRetainCount=3 autopurge.purgeInterval=1 # 注意:Zookeeper 重启会自动清除 zookeeper.out 日志,如果有排错需要,则应先备份好日志文件 # 如果发现单事务日志量过大,导致定时清理无法及时处理,可以使用 zkCleanup.sh 进行手动清除 $ cd ~/software/zookeeper1 $ zookeeper1/bin/zkCleanup.sh /home/zookeeper/logs/zookeeper1/version-2/ 3 Removing file: Aug 9, 2017 12:08:49 PM /home/zookeeper/logs/zookeeper1/version-2/log.1c00000001 Removing file: Aug 9, 2017 02:03:33 PM /home/zookeeper/data/zookeeper1/version-2/snapshot.1c0000ab90
Log4j 滚动日志
$ cd $ZOOKEEPER_HOME $ vim conf/log4j.properties zookeeper.root.logger=INFO, CONSOLE zookeeper.console.threshold=INFO zookeeper.log.dir=. zookeeper.log.file=zookeeper.log zookeeper.log.threshold=DEBUG zookeeper.tracelog.dir=. zookeeper.tracelog.file=zookeeper_trace.log # 可以调整为 DaliyRollingFileAppender,每天滚动创建新的日志文件 log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender log4j.appender.ROLLINGFILE.Threshold=${zookeeper.log.threshold} log4j.appender.ROLLINGFILE.File=${zookeeper.log.dir}/${zookeeper.log.file} $ vim bin/zkServer.sh # 增加 ZOO_LOG_DIR 配置 ZOO_LOG_DIR=$ZOOBINDIR/../log4j $ vim bin/zkEnv.sh # if [ "x${ZOO_LOG4J_PROP}" = "x" ] # then # ZOO_LOG4J_PROP="INFO,CONSOLE" # fi if [ "x${ZOO_LOG4J_PROP}" = "x" ] then ZOO_LOG4J_PROP="INFO,ROLLINGFILE" fi
Observer 模式
作用
对读请求进行扩展
通过增加更多的 Observer
,可以接收更多的读请求
流量,却不会牺牲写操作
的吞吐量(写操作的吞吐量取决于 quorum
法定人数的个数)
如果增加更多的 Server 进行投票,Quorum 会变大,这会降低写操作
的吞吐量
然而增加 Observer
并不会完全没有损耗,新的 Observer
在提交一个事务后收到一条额外的 INFORM
消息。这个损耗比加入 Follower
进行投票来说会小很多
跨数据中心部署
把 participant
分散到多个数据中心,可能会因为数据中心之间的网络延迟,导致系统被拖慢
使用 Observer
的话,更新操作都在单独的数据中心来处理,再发送到其他数据中心,让 Client
消费数据(分布式数据库 [中美异地机房] 同步系统 Otter 就使用该模式)
设置
$ vim conf/zoo.cfg peerType=observer server.1:localhost:2181:3181:observer # 其他需要扩展成 Observer 的 Server 都需要加上 `:observer` 后缀
INFORM 消息
因为 Observer 不参与到 ZAB 选举中,所以 Leader 节点不会发送 proposal 给 Observer,只会发送一条包含已经通过选举的 zxid
的 INFORM 消息。这里,参与 ZAB 选举的 Leader、Follower 节点称之为 PARTICIPANT
Server,而 Observer 则属于 OBSERVER
Server
配置
JVM 相关
swappiness
$ cd $ZOOKEEEPER_HOME # 常驻进程,需要避免 swapping 损害性能 # 临时生效 $ echo 0 > /proc/sys/vm/swappiness # 永久生效 $ vim /etc/sysctl.conf vm.swappiness=0 # memory first # 设置 `-XX:+AlwaysPreTouch` 参数,在进程启动的时候,让 jvm 通过 demand-zeroed 方式将内存一次分配到位(ES #16937 / ZK #301) # 使用 CMS 垃圾回收器(“jdk7 + 内存使用不多” 的缘故,可以暂不考虑 G1GC) $ vim conf/java.env export JVMFLAGS="-Xms3G -Xmx3G -Xmn1G -XX:+AlwaysPreTouch -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC" # 如果需要打印 GC 日志,则多增加一些 flag export JVMFLAGS="-Xms3G -Xmx3G -Xmn1G -XX:+AlwaysPreTouch -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:-PrintGCTimeStamps -Xloggc:/home/zookeeper/logs/zookeeper_`date '+%Y%m%d%H%M%S'`.gc -XX:-UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=64M" # 需要注意的是,如果不希望 zkCli 等命令创建 gc 日志文件,需要把 JVMFLAGS 改成 SERVER_JVMFLAGS # 更进一步,如果有四字命令在做监控,则建议,直接修改 zkServer.sh,否则因为 zookeeper_`date '+%Y%m%d%H%M%S'`.gc 的存在,导致每次四字命令执行,会有很多小日志被创建(ZK#302 已解决,待分析) $ vim bin/zkServer.sh start) # ... START_SERVER_JVMFLAGS="-Xms3G -Xmx3G -Xmn1G -XX:+AlwaysPreTouch -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:-PrintGCTimeStamps -Xloggc:/home/zookeeper/logs/zookeeper_`date '+%Y%m%d%H%M%S'`.gc -XX:-UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=64M" nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \ "-Dzookeeper.log.file=${ZOO_LOG_FILE}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \ -XX:+HeapDumpOnOutOfMemoryError -XX:OnOutOfMemoryError='kill -9 %p' \ -cp "$CLASSPATH" $JVMFLAGS $START_SERVER_JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null & # 堆大小的最终确定,需要在 benchmark 结果的基础之上,再做调整 # 另外,一旦创建完该文件,Zookeeper 进程会自动加载,因此,需要确保无误之后,再建立 java.env 文件
升级 JDK8
为何建议升级 JDK8 呢?因为 Zookeeper 里面很多关键的功能点,都用到了 Atomic 类,而该类在 JDK8 中做了一次升级,性能提升了 6x 倍(JDK8 中加入了 Unsafe.getUnsafe().getAnd[Add|Set][Int|Long|Object]
一系列方法对 Atomic 类做了增强,由于无法看到 Oracle JDK 里 Unsafe 的相关实现,有兴趣可以参考 OpenJDK 源码。目前,存在一种比较靠谱的猜测是,compare-and-swap 被替换成系统底层的 fetch-and-add,后者用 lock xadd 替代了 lock cmpxchg 来实现原子操作。其中 指令前缀 lock 用来锁定指令涉及的存储区域,
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/hd-xnyh/10514.html