在使用Apache Curator2.11操作Zookeeper时,发现这么一个场景:
创建CuratorFramework时,指定的失败重试策略尝试的总时间tryTime超过了会话超时和连接超时中最大值maxTimeout时,如果发生网络中断,且网络恢复时发生在大于会话超时和连接超时中最大值maxTimeout,小于重试策略尝试的总时间tryTime时,Curator中的ConnectionStateListner监听器将接收不到ConnectionState.LOST事件,即使使用Watcher也获取不到Expired事件。场景说明如下图:
在最新Apache Curator官方文档中有关于ConnectionState.LOST的描述,内容如下:
注意最后一句话,大概意思是说:3.x以前版本中ConnectionState.LOST仅表示重试策略失败,不代表会话超时。
查看curator源码发现,触发ConnectionState.LOST事件时有两种情况:
1.重试策略失败时,代码如下:
2.客户端接收到会话超时事件时:
那在curator2.11中会话超时事件丢失的原因是什么呢,经过跟踪代码发现,在CuratorFrameworkImpl客户端start()时,内部开启了一个线程不断的循环用于处理异步提交的操作:
在处理异步请求的逻辑中,每次都会从client中获取Zookeeper对象,并在获取Zookeeper时,判断如果当前处于未连接成功状态时,则重试尝试连接,并检查当前等待恢复连接的时间是否超过会话超时和连接超时之间的最大值,如果未超过,则抛出CuratorConnectionLossException,如果超出会话超时和连接超时之间的最大值,则客户端自动重置Zookeeper对象,关闭并释放原来的Zookeeper,并尝试重新创建一个新的Zookeeper对象,代码如下:
当客户端reset了Zookeeper后,客户端放弃了原来的会话,而尝试与服务器建立一个新的会话。此时,已经超过了会话超时时间,服务器端已经把原来的会话从会话桶中移走,并把该会话创建的临时节点等丢失掉。这时,如果网络恢复了,客户端就会按新的会话请求与服务器建立连接,服务器正常接收这个请求,创建会话,返回给客户端新的会话的信息。至此,原来的会话的过期事件已经丢失。
综合来看,在重试策略有效期内,Curator客户端对当前会话的有效性进行检查,当超过会话超时与连接超时最大值仍未恢复连接时,Curator的重连机制处理对原来的会话进行关闭,释放并尝试创建一个新的会话,网络恢复后,客户端与服务器建立新的会话成功,之前的会话超时事件就丢失了,这样无法获取ConnectionState.LOST状态了。
所以如果在ConnectionStateListner中监听ConnectionState.LOST状态,并在该状态下去恢复临时节点时,就会出现无法成功恢复临时节点的情况。
该怎么解决呢?丢失的原因主要是因为,客户端在会话超时时reset了Zookeeper对象。所以我们只要保证重试策略的总时间设置的比会话超时和连接超时最大值小即可,这样,Curator会在重置Zookeeper对象之前重试策略失败,这样就会变为ConnectionState.LOST状态,这时我们可以恢复临时节点,但如果在重试策略失败后会话超时前恢复网络,就有可能会重复创建临时节点。所以在使用时需要谨慎,我们可以创建CreateMode.EPHEMERAL类型而非CreateMode.EPHEMERAL_SEQUENTIAL类的临时节点,或恢复节点前判断节点是否存在,不存在时再创建该节点,这样不会创建第二个临时节点而造成问题。
当然我们在使用Curator框架时,想使用它的重试策略,不能为了这个会话超时丢失的问题而不使用它的会话超时自动恢复会话功能。可我们又想获得会话超时事件,想在ConnectionState.LOST状态时做一些事情。我们可以使用两种重试策略的客户端,一个客户端用于感知会话超时事件,保证它的重试策略的总时间设置的比会话超时和连接超时最大值小;另一个客户端用于我们业务上的其他操作,它的重试策略可以根据我们的实际情况来设置即可。