背景:使用zk的生产消费模型完成配置数据的同步,A生产消息,B消费消息。 使用包org.apache.curator.framework下的CuratorFramework类。
问题:由于网络波动或其它不可控因素,导致B与zk的tcp连接断开,而A的网络正常在继续生产消息。而后B的网络恢复正常进行zk的重连,这时候不管session是未过期的还是expired过期的,curator jar包都保证了NodeCache的Node Listener能够继续监听节点数据的变化(CuratorFrameworkFactory.builder()默认session过期时间是60秒,若session过期重连时会新建一个session)。虽然node Listener可正常监听到以后节点数据的改变事件,但是断线期间的数据没有处理,所以需要处理断线期间未同步的数据。
解决:在重连时connection Listener会监听到重连事件,在监听到的重连事件中进行数据同步!
curatorFramework = CuratorFrameworkFactory.builder().connectString(zkUrl).retryPolicy(new RetryForever(10000)).build();
curatorFramework.getConnectionStateListenable().addListener(new ConnectionStateListener() {
@Override
public void stateChanged(CuratorFramework client, ConnectionState newState) {
if (ConnectionState.RECONNECTED.equals(newState)) {
//重连成功,拉取最新数据同步。同步新节点数据时会被node listener监听到
log.warn("zookeeper connection reconnected!");
client.sync();
}
}
});
这样就完成了由于网络波动而导致消费者与生产者数据不一致的问题!
其它补充
node listener代码补充:
//创建节点的cache
final NodeCache nodeCache = new NodeCache(client, path, false);
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
String path = nodeCache.getCurrentData().getPath();
byte[] data = nodeCache.getCurrentData().getData();
//业务处理
...
}
});
nodeCache.start(true);
断线期间B的服务端日志:
2021-03-26 16:40:55.968 WARN 4143 --- [ain-EventThread] org.apache.curator.ConnectionState : Session expired event received
2021-03-26 16:40:55.968 WARN 4143 --- [130.84.15:5107)] org.apache.zookeeper.ClientCnxn : Unable to reconnect to ZooKeeper service, session 0x1e00ae08d2780609 has expired
2021-03-26 16:40:55.969 INFO 4143 --- [ain-EventThread] org.apache.zookeeper.ZooKeeper : Initiating client connection, connectString= sessionTimeout=60000 watcher=org.apache.curator.ConnectionState@459df48d
2021-03-26 16:40:55.969 INFO 4143 --- [ain-EventThread] org.apache.zookeeper.ClientCnxnSocket : jute.maxbuffer value is 1048575 Bytes
2021-03-26 16:40:55.969 WARN 4143 --- [130.84.15:5107)] org.apache.zookeeper.ClientCnxn : Session 0x1e00ae08d2780609 for sever , Closing socket connection. Attempting reconnect except it is a SessionExpiredException.
org.apache.zookeeper.ClientCnxn$SessionExpiredException: Unable to reconnect to ZooKeeper service, session 0x1e00ae08d2780609 has expired
at org.apache.zookeeper.ClientCnxn$SendThread.onConnected(ClientCnxn.java:1419) ~[zookeeper-3.6.2.jar:3.6.2]
at org.apache.zookeeper.ClientCnxnSocket.readConnectResult(ClientCnxnSocket.java:154) ~[zookeeper-3.6.2.jar:3.6.2]
at org.apache.zookeeper.ClientCnxnSocketNIO.doIO(ClientCnxnSocketNIO.java:86) ~[zookeeper-3.6.2.jar:3.6.2]
at org.apache.zookeeper.ClientCnxnSocketNIO.doTransport(ClientCnxnSocketNIO.java:350) ~[zookeeper-3.6.2.jar:3.6.2]
at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1275) ~[zookeeper-3.6.2.jar:3.6.2]
2021-03-26 16:40:55.969 INFO 4143 --- [ain-EventThread] org.apache.zookeeper.ClientCnxn : zookeeper.request.timeout value is 0. feature enabled=false
2021-03-26 16:40:55.970 INFO 4143 --- [ain-EventThread] o.a.c.f.state.ConnectionStateManager : State change: LOST
zookeeper的消息监听为watcher机制,watcher特性:
特性 | 说明 |
一次性 | Watcher是一次性的,一旦被触发就会移除,再次使用时需要重新注册 |
客户端顺序回调 | Watcher回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态。一个Watcher回调逻辑不应该太多,以免影响别的watcher执行 |
轻量级 | WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容; |
时效性 | Watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接收到通知; |
apache curator jar包优点:
原生的zookeeper client jar虽提供了watcher的对应接口,但由于watcher是一次性监听,每次监听完成后都要重新注册,而且session失效后watcher的重建也需要关心。
原生api | zkclient | apache curator | |
优点 | 1、session会话超时重连; 2、解决watcher反复注册; 3、简化api开发; | 1、解决watch注册一次就会失效的问题; 2、api更加简单易用; 3、提供了更多解决方案并且实现简单,比如分布式锁; 4、提供了常用的zk工具类; | |
缺点 | 1、watch注册一次后会失效; 2、session超时之后没有实现重连机制; 3、异常处理繁琐; 4、只提供了简单的byte[]数组的接口,没有提供针对对象级别的序列化; 5、创建节点时如果节点存在抛出异常,需要自行检查节点是否存在; 6、删除节点无法实现级联删除; | 1、异常处理简化,抛出RuntimeException; 2、重试机制比较难用; 3、没有提供各种使用场景的实现; |
curator是通过观察者设计模式解决watcher一次性监听特性的,通过Node Listener来完成节点数据改变的多次监听。