我只尝试了分布式锁,至于其它功能,如队列等没有尝试。
在创建包含二级路径的节点时,一级路径必须先创建
集群模式下,在创建节点时,会即时同步到其它follower(observer会稍迟)
经过测试,在不同的电脑中,连不同的IP,只要在一个集群中创建相同节点时,只有一个会成功(可以确保唯一),可以以此作为临界锁。
用sh运行程序时,日志文件在zookeeper/logs中
需要JAVA JDK环境,安装方法
下载&简单配置&启动
https://zookeeper.apache.org/releases.html
在里面找到最新的稳定发布版本,下载后解压到一个文件夹里,如:D:/Soft/zookeeper3.8.4
新建一个data目录,将conf目录下的zoo_sample.cfg复制一个并重命名为zoo.cfg
extendedTypesEnabled=true #启用TTL,允许创建带过期时间的节点
dataDir=D:/Soft/zookeeper3.8.4/data #data前面的路径改为实际解压程序的路径,注意WINDOWS中路径不要用\,用/
保存zoo.cfg
(windows中)进入bin目录双击zkServer.cmd运行即可
windows中用sh启动(默认是直接运行cmd文件)
安装git for windows
进入zookeeper/bin,右键,点open git bash here
./zkServer.sh start #启动
./zkServer.sh status #查看当前状态
./zkServer.sh stop #停止
mac中启动
像上面一样修改zoo.cfg,然后在终端中进入bin目录
chmod +x zkServer.sh #给权限(如果需要)
./zkServer.sh start #启动
./zkServer.sh status #查看当前状态
./zkServer.sh stop #停止
(配置)修改Java系统属性
有些配置项只有修改启动参数,就像检查过期节点的周期
如果不修改,默认是60秒,如果设置一个节点过期时间是5秒,那么理论上最久需要65秒才被删除......
以下用checkIntervalMs作为例子
znode.container.checkIntervalMs : (Java system property only) New in 3.5.1: The time interval in milliseconds for each check of candidate container and ttl nodes. Default is "60000".
翻译过来就是:每次检查候选容器和ttl节点的时间间隔(毫秒)。默认值为“60000”。
它只属于java属性,所以无法在zoo.cfg中修改
修改zkServer.cmd(windows中的运行脚本)
#用记事本打开,找到类似:
#call %JAVA% "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.log.file=%ZOO_LOG_FILE%"
#改成,2000=2秒
call %JAVA% "-Dznode.container.checkIntervalMs=2000" "-Dzookeeper.log.dir=%ZOO_LOG_DIR%"..................................
修改zkServer.sh(Linux/bash)
#找到类似:
# nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \
# "-Dzookeeper.log.file=${ZOO_LOG_FILE}" \
改成:
nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \
"-Dznode.container.checkIntervalMs=2000" \
"-Dzookeeper.log.file=${ZOO_LOG_FILE}" \
修改后运行,日志中看到:Using checkIntervalMs=2000 maxPerMinute=10000 maxNeverUsedIntervalMs=0说明就成功了
其它参数修改方法相同。
如果看到类似:
admin.enableServer : (Java system property: zookeeper.admin.enableServer)
这样的属性,说明即可以在zoo.cfg中配置,也可以在java参数中设置
golang中简单使用
以下代码只会有一个线程创建成功(如果无法获取锁Create方法会创建失败并立即返回错误,不会等待)
conn, _, err := zk.Connect([]string{"127.0.0.1:2181"}, 5*time.Second)
if err != nil {
log.Fatalf("Failed to connect to Zookeeper: %v", err)
return
}
defer conn.Close()
path := "/JY01-ruku"
var data = make([]byte, 0)
for i := 0; i < 20; i++ {
go func() {
_, err := conn.Create(path, data, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
if err != nil {
fmt.Println("创建失败", i, time.Now().UnixMilli())
return
}
fmt.Println("节点创建成功(持有锁)", i, time.Now().UnixMilli())
}()
}
其它常用方法:
conn.Children("/") //获取所有一级节点,如有:/test/a,/zhangsan/b,将获取到["test","zhangsan"]
conn.Children("/test") //获取test下的所有子节点,如有:/test/a,/zhangsan/b,将获取到["a"]
conn.Delete("/test/zhangsan", -1) //移除test下的zhangsan,test仍然会保留
conn.Exists("/ruku/test") //节点是否存在
//flag并用
conn.Create("/ruku8", data, zk.FlagEphemeral | zk.FlagSequence, zk.WorldACL(zk.PermAll))
flags
const (
FlagPersistent = 0 //即使连接和zk客户端关闭,创建的节点依然存在,只能通过delete移除
FlagEphemeral = 1 //临时节点,在连接断开后即移除,连接不断它会一直存在,也可以通过delete移除,
FlagSequence = 2 //顺序节点,会在传入的path后面加上一串唯一的数字,比如"/ceshi-",实际创建出来是/ceshi-00000000010
FlagEphemeralSequential = 3 //FlagEphemeral+FlagSequence
FlagContainer = 4 //类似FlagPersistent,只是由它创建的一级节点下没有子节点时,会自动移除
FlagTTL = 5 //带过期时间的持久节点
FlagPersistentSequentialWithTTL = 6 //带过期时间、持久、顺序的节点
)
到达过期时间自动删除和container没有子节点自动删除不是瞬时完成的,zk内有检测线程,周期轮询去取消、删除符合条件的节点。
轮询的周期根据配置文件的checkIntervalMs来控制,默认60000毫秒一次。(但是我还没尝试出如何正确配置这一项,可能配置项有前缀之类的)
container首次添加是没有子节点的,在首次添加子节点前,它不会被自动删除,直到它至少曾经有一个子节点并且当前没有子节点,才会被移除
conn.CreateTTL("/ruku7", data, zk.FlagTTL, zk.WorldACL(zk.PermAll), time.Second*3)
conn.CreateTTL("/ruku7", data, zk.FlagPersistentSequentialWithTTL, zk.WorldACL(zk.PermAll), time.Second*3)
conn.CreateContainer("/ruku7", data, zk.FlagContainer, zk.WorldACL(zk.PermAll))
Node.js中简单使用
node-zookeeper-client模块不支持TTL,而且flags也不完整,不太好用
npm install zookeeper
代码:
const zookeeper = require('zookeeper');
var client = new zookeeper({
connect: '127.0.0.1:2181', //集群连接方式:'127.0.0.1:2181,192.168.101.101:2181,192.168.101.102:2181'
timeout: 5000,
debug_level: zookeeper.constants.ZOO_LOG_LEVEL_INFO,
host_order_deterministic: false,
})
client.connect(null, ()=>{}) //因为在new时已经传入了config这里不用传,回调函数必须传,否则报错
//使用await/async
async function test(){
for(let i = 0; i<1; i++) {
//创建临时节点
// var a = await client.create("/ruku-200", "test", zookeeper.constants.ZOO_EPHEMERAL)
//创建TTL
var a = await client.create("/ruku-200", "test", zookeeper.constants.ZOO_PERSISTENT_WITH_TTL, 3000)
//create函数签名,返回值为string类型,如果创建失败,报抛出错误,所以要在try{}中运行
// create(path: string, data: (string | Buffer), flags: number, ttl?: number | undefined): Promise<string>;
await new Promise(s => setTimeout(() => { s() }, 5000))
//删除节点
// var b = await client.delete_("/ruku-200", -1)
// console.log(b);
}
}
test()
flags在require('zookeeper').constants对象里面,与GOLANG名称相似,
在linux系统中的一波三折
安装zookeeper的npm包时就出现了错误,要求python3.6.0+(又好像是python3.10+,事后整理的可能记错)、gcc等环境
首先是安装GCC,安装GCC后npm i zookeeper时才会成功
运行时又会报错
Error: /lib64/libstdc++.so.6: version CXXABI_1.3.9' not found (required by /data/nodejs_playground/node_modules/zookeeper/build/Release/zookeeper.node)
原因是依然在使用旧的libstdc++这个动态库,无法识别到新装的GCC的lib64目录
解决方法:添加动态库路径
让系统识别到新装的GCC lib64目录后,就可以成功运行了
集群中的多种模式(角色)
leader:领导者,无法通过配置文件指定哪一个IP是leader,只能通过自动选举。主要管理事务日志,协调集群一致性
Standalone:单节点时,就是这个模式,自己是leader,也是follower
Observer:观察者,只同步数据,不参与投票和写数据
follower:同步数据,且参与投票。不需要特殊配置,所有角色默认都是follower,自动选举之后其中一台会成为leader.
集群配置
需要配置奇数台服务,比如1台、3台,偶数台无法运行,会报错:No server failure will be tolerated. You need at least 3 servers
每台服务务的dataDir目录中,新建一个文件名为myid的文件(没有后缀),内容为自己服务器的数字编号(第一台是1,第二台是2....)
每台服务器的zoo.cfg中都要添加如下信息,将IP换成自己的服务器或局域名电脑IP,2888和3888是固定的
server.1=192.168.101.105:2888:3888
server.2=192.168.101.101:2888:3888
server.3=192.168.101.102:2888:3888
#server.3=192.168.101.102:2888:3888:observer 配置当前服务器为observer
然后每台服务器启动zkServer
在golang中连接集群
conn, _, err := zk.Connect([]string{"192.168.101.101:2181", "192.168.101.105:2181", "192.168.101.102:2181"}, 5*time.Second)
if err != nil {
log.Fatalf("Failed to connect to Zookeeper: %v", err)
return
}
defer conn.Close()
会随机连接一个,如果正连接的节点无效(比如说未开启),会自动尝试其它节点