赞
踩
首先介绍下Zookeeper的背景、数据类型、使用场景以及ZAB协议,让大家对Zookeeper有一个清晰的认识。
ZooKeeper是一个分布式的、开放源码的分布式协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。由于Hadoop生态系统中很多项目都依赖于zookeeper,如Pig,Hive等, 似乎很像一个动物园管理员,于是取名为Zookeeper。 Zookeeper官网地址为http://zookeeper.apache.org/。
Zookeeper的每个ZNode上都会存储数据,对应到每个ZNode,Zookeeper都会为其维护一个叫做Stat的数据结构,Stat中记录的内容如下:
Watcher(事件监听器)是 Zookeeper提供的一种 发布/订阅的机制。Zookeeper允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,Zookeeper服务端会将事件通知给订阅的客户端。该机制是 Zookeeper实现分布式协调的重要特性。
zookeeper会为每个客户端分配一个session,类似于web服务器一样,用来标识客户端的身份。
在Zookeeper中,node的ACL是没有继承关系的。ACL表现形式:scheme:permissions。
ZAB 是 ZooKeeper Atomic Broadcast (ZooKeeper 原子广播协议)的缩写,它是特别为 ZooKeeper 设计的崩溃可恢复的原子消息广播算法。ZooKeeper 使用 Leader来接收并处理所有事务请求,并采用 ZAB 协议,将服务器数据的状态变更以事务 Proposal 的形式广播到所有的 Follower 服务器上去。这种主备模型架构保证了同一时刻集群中只有一个服务器广播服务器的状态变更,因此能够很好的保证事物的完整性和顺序性。 Zab协议有两种模式,它们分别是恢复模式(recovery)和广播模式(broadcast)。当服务启动或者在leader崩溃后,Zab就进入了恢复模式,当leader被选举出来,且大多数follower完成了和leader的状态同步以后, 恢复模式就结束了,ZAB开始进入广播模式。
当Leader崩溃或者Leader失去大多数的Follower时,Zookeeper处于恢复模式,在恢复模式下需要重新选举出一个新的Leader,让所有的 Server都恢复到一个正确的状态。Zookeeper的选举算法有两种:一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。
当集群重新选举出Leader后,所有的Follower需要和Leader同步数据,确保集群数据的一致性。
当数据同步完成后,集群开始从恢复模式进入广播模式,开始接受客户端的事物请求。 当只有Leader或少数机器批准执行某个任务时,则极端情况下Leader和这些少量机器挂掉,则无法保证新Leader知道之前已经批准该任务,这样就违反了数据可靠性。所以Leader在批准一个任务之前应该保证集群里大部分的机器知道这个提案,这样即使Leader挂掉,选举出来的新Leader也会从其他Follower处获取这个提案。而如果Leader要求所有Follower都同意才执行提案也不行,此时若有一个机器挂掉,Leader就无法继续工作,这样的话整个集群相当于单节点,无法保证可靠性。
介绍完Zookeeper的基本知识后,接下来从运维的角度来了解下zookeeper。
nc -z 127.1 2181
cat /proc/$PID/status|egrep '(FDSize|^Vm|^Rss|Threads|ctxt_switches)'
jstat -gcutil $PID
- echo mntr|nc 127.1 2181
- echo srvr|nc 127.1 2181
grep xxx zookeeper.log
- #!/usr/bin/env python
- import json
- import socket
- import time
- import re
- import os
- import sys
-
-
- def get_zxid(port):
- s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
- try:
- s.connect(('127.0.0.1', port))
- except Exception:
- return -2
- s.send('srvr')
- data = s.recv(10240)
- s.close()
- for line in data.split('n'):
- if line.startswith('Zxid'):
- return eval(line.split(':')[1].strip() + " & 0xffffffff")
- return -1
-
- if __name__ == '__main__':
- port=2181
- res_maps = []
- data1 = get_zxid(port)
- time.sleep(1)
- data2 = get_zxid(port)
- if data1 < 0 or data2 < 0:
- sys.exit(1)
- map1 = {}
- map1['name'] = 'cur_zxid'
- map1['value'] = data2
- map1['timestamp'] = int("%d" % time.time())
- map1["tags"] = {"ZKPort": str(port)}
- res_maps.append(map1)
- map2 = {}
- zxid_rate = (data2 - data1)
- map2['name'] = 'zxid_rate'
- map2['value'] = (data2 - data1)
- map2['timestamp'] = int("%d" % time.time())
- map2["tags"] = {"ZKPort": str(port)}
- res_maps.append(map2)
- map3 = {}
- map3['name'] = 'zxid_left_hour'
- ### (0xffffffff - cur_zxid)/zxid_rate/60/60
- if zxid_rate == 0:
- map3['value'] = 4294967295-data2
- else:
- map3['value'] = (4294967295-data2)/zxid_rate/60/60
- map3['timestamp'] = int("%d" % time.time())
- map3["tags"] = {"ZKPort": str(port)}
- res_maps.append(map3)
- print json.dumps(res_maps)

- # 容量计算方法:
- # 1.每个指标设置不同的权重,综合计算容量水位
- # 2.最差的指标作为容量水位
- min(cpu_used/cpu_max,cons_num/cons_max,data_num/data_max,watch_num/watch_max,outstanding_num/outstanding_max)
- #get from https://github.com/pyinx/zk-sniffer
- zk-sniffer -device=eth0 -port=2181
- #!/bin/sh
-
- function help(){
- echo "-----------------"
- echo "HELP: $0 LogFile"
- echo "-----------------"
- exit 1
- }
-
- if [ $# -ne 1 ]
- then
- help
- fi
-
- LogFile=$1
- if [ ! -f $LogFile ]
- then
- echo "ERROR: $LogFile not found"
- exit 1
- fi
- zkDir=/usr/local/zookeeper
- JAVA_OPTS="$JAVA_OPTS -Djava.ext.dirs=$zkDir:$zkDir/lib"
- java $JAVA_OPTS org.apache.zookeeper.server.LogFormatter "$LogFile"

- #!/bin/sh
-
- function help(){
- echo "-----------------"
- echo "HELP: $0 SnapshotFile"
- echo "-----------------"
- exit 1
- }
-
- if [ $# -ne 1 ]
- then
- help
- fi
-
- file=$1
- if [ ! -f $file ]
- then
- echo "ERROR: $file not found"
- exit 1
- fi
- zkDir=/usr/local/zookeeper
- JAVA_OPTS="$JAVA_OPTS -Djava.ext.dirs=$zkDir:$zkDir/lib"
- java $JAVA_OPTS org.apache.zookeeper.server.SnapshotFormatter "$file"

- zkCli.sh -server localhost:2181 <<EOF
- ls /
- get /
- quit
- EOF
- #!/bin/bash
- rm -f con_ip.txt path_count.txt session_count.txt session_ip.txt watch_path.txt watch_sess.txt
-
- #记录session和watch的path
- echo wchc|nc 127.1 2181 > watch_sess.txt
-
- #记录所有的ip连接
- echo cons|nc 127.1 2181 > con_ip.txt
-
- #记录session和watch的count数
- > session_count.txt
- last=1
- sesion=$(sed -n '1p' watch_sess.txt)
- for i in `grep -n '^0x' watch_sess.txt |awk -F: '{print $1}'`
- do
- if [ $i -eq $last ]
- then
- continue
- fi
- x=$(let last++)
- y=$(let i--)
- let x=last+1
- let y=i-1
- count=$(sed -n ''$x','$y'p' watch_sess.txt|wc -l)
- echo "$sesion $count" >> session_count.txt
- last=$i
- sesion=$(sed -n ''$i'p' watch_sess.txt)
- done
-
- #把ip和session关联起来
- > session_ip.txt
- while read sess count
- do
- n=$(grep $sess con_ip.txt -c)
- if [ $n -eq 1 ]
- then
- ip=$(grep $sess con_ip.txt|awk -F: '{print $1}'|sed -n 's# /##p')
- else
- ip="NULL"
- fi
- echo "$count $ip $sess" >> session_ip.txt
- done < session_count.txt
-
- #记录每个path watch的session
- echo wchp |nc 127.1 2181 > watch_path.txt
-
- #记录每个path的watch数量
- > path_count.txt
- last=""
- next=""
- while read line
- do
- if [ $(echo $line|grep '^/' -c) -eq 1 ]
- then
- last=$next
- next=$line
- if [ ${last}x != "x" ]
- then
- echo "$count $last" >> path_count.txt
- fi
- count=0
- else
- let count++
- fi
- done < watch_path.txt
- echo "$count $last" >> path_count.txt
- #打印watch数最高的Top10 IP列表
- awk '{a[$2]+=$1}END{for (i in a)print a[i],i}' session_ip.txt |sort -nr -k1|head
-
- #打印watch数最高的Top10 Path列表
- awk '{a[$2]+=$1}END{for (i in a)print a[i],i}' path_count.txt |sort -nr -k1|head

Zookeeper是不是已经足够稳定了,一经部署就不再需要关注了呢?答案当然是否定的,目前我们在运维过程中还存在如下几个痛点:
面临这些问题,我们是如何解决的呢?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。