• 启动Dubbo项目注册Zookeeper时提示zookeeper not connected异常原理解析


    原创/朱季谦

    遇到一个很诡异的问题,我在启动多个配置相同zookeeper的Dubbo项目时,其他项目都是正常启动,唯独有一个项目在启动过程中,Dubbo注册zookeeper协议时,竟然出现了这样的异常提示——

    1. Caused by: java.lang.IllegalStateException: zookeeper not connected
    2. at org.apache.dubbo.remoting.zookeeper.curator.CuratorZookeeperClient.(CuratorZookeeperClient.java:80)
    3. ... 79 common frames omitted

    我愣了一下,原以为是zookeeper集群挂了,然后检查了一下,都正常啊,奇怪的是,其他系统也是正常连接,为啥会有一台出现了这样的异常呢?

    看了一下异常提示,当我深入研究了一下出错的地方时,才恍然明白出现这个异常究竟是为什么了。

    可谓是,在源码面前,一切都是裸泳。

    先来看异常提示出现的类方法CuratorZookeeperClient,这个方法的作用是建立zookeeper客户端的连接,类似http通信一般,在建立通信前,需要先建立三次握手连接,同理,在zookeeper客户端创建各类节点前,同样需要先建立客户端连接到服务器上——

    1. public CuratorZookeeperClient(URL url) {
    2. super(url);
    3. try {
    4. int timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_CONNECTION_TIMEOUT_MS);
    5. int sessionExpireMs = url.getParameter(ZK_SESSION_EXPIRE_KEY, DEFAULT_SESSION_TIMEOUT_MS);
    6. CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
    7. .connectString(url.getBackupAddress())
    8. .retryPolicy(new RetryNTimes(1, 1000))
    9. .connectionTimeoutMs(timeout)
    10. .sessionTimeoutMs(sessionExpireMs);
    11. String authority = url.getAuthority();
    12. if (authority != null && authority.length() > 0) {
    13. builder = builder.authorization("digest", authority.getBytes());
    14. }
    15. client = builder.build();
    16. client.getConnectionStateListenable().addListener(new CuratorConnectionStateListener(url));
    17. client.start();
    18. boolean connected = client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS);
    19. if (!connected) {
    20. throw new IllegalStateException("zookeeper not connected");
    21. }
    22. } catch (Exception e) {
    23. throw new IllegalStateException(e.getMessage(), e);
    24. }
    25. }

    根据CuratorZookeeperClient方法可知,出现zookeeper not connected异常提示是发生在这一段代码当中——

    1. if (!connected) {
    2. throw new IllegalStateException("zookeeper not connected");
    3. }

    connected表示连接状态,当它的值为false时,便会执行这段代码,那么,究竟是什么情况会导致它的值为false呢?

    接下来,让我们打一个断点,一步一步解析这段代码。

    首先,用作测试的dubbo和zookeeper配置如下——

    1. dubbo:
    2. application:
    3. name: testervice
    4. registry:
    5. address: zookeeper://120.77.217.245
    6. # timeout: 20000
    7. protocol:
    8. name: dubbo
    9. port: 20880

    解析来,开始debug,打断点,CuratorZookeeperClient方法参数url主要包含以下信息——
     

    image


    第一步、从url中获取超时时间timeout参数——

    int timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_CONNECTION_TIMEOUT_MS);
    

    这里的大概逻辑是,如果yaml配置registry注册zookeeper部分参数当中含有 timeout话,那么就返回配置当中定义的超时时间,如果yaml没有进行配置,那么,就用默认的超时时间,默认即常量DEFAULT_CONNECTION_TIMEOUT_MS,值是5 * 1000,也就是5秒,这个参数其实就是本篇文章的核心。

    若自定义形式配置该参数,形式如下timeout: 20000——

    1. dubbo:
    2. application:
    3. name: testervice
    4. registry:
    5. address: zookeeper://120.77.217.245
    6. timeout: 20000

    第二步、获取客户端过期时间——

     int sessionExpireMs = url.getParameter(ZK_SESSION_EXPIRE_KEY, DEFAULT_SESSION_TIMEOUT_MS);
    

    同理,无自定义配置话,则使用默认值DEFAULT_SESSION_TIMEOUT_MS = 60 * 1000,即6分钟;

    第三步、创建一个设置过期时间为6分钟,连接超时为5秒,重试策略为每秒重试一次,连接服务端为url.getBackupAddress()(注:我这里得到的是120.77.217.245:9090,即配置的zookeeper连接url)的CuratorFramework客户端实例——

    1. CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
    2. .connectString(url.getBackupAddress())
    3. .retryPolicy(new RetryNTimes(1, 1000))
    4. .connectionTimeoutMs(timeout)
    5. .sessionTimeoutMs(sessionExpireMs);
    6. client = builder.build();

    第四步、添加连接状态的监控,可以监控操作节点与连接情况——

    client.getConnectionStateListenable().addListener(new CuratorConnectionStateListener(url));
    

    第五步、开启客户端——

    client.start();
    

    最后一步,监控客户端连接情况,若能连接成功,则证明创建客户端成功,反之,失败。可见,若出现zookeeper not connected,问题就在于客户端连接过程是失败的,至于为何失败,原理就在client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS)代码里。

    1. boolean connected = client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS);
    2. if (!connected) {
    3. throw new IllegalStateException("zookeeper not connected");
    4. }

    进入到 client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS)源码里,这里的maxWaitTime即前边的timeout,默认值是5秒,大概分析一下下边代码——

    1. public synchronized boolean blockUntilConnected(int maxWaitTime, TimeUnit units) throws InterruptedException
    2. {
    3. //获取当前时间
    4. long startTime = System.currentTimeMillis();
    5. //这里是true
    6. boolean hasMaxWait = (units != null);
    7. //maxWaitTimeMs等于5000毫秒,即5秒
    8. long maxWaitTimeMs = hasMaxWait ? TimeUnit.MILLISECONDS.convert(maxWaitTime, units) : 0;
    9. while ( !isConnected() )
    10. {
    11. //hasMaxWait为true
    12. if ( hasMaxWait )
    13. {
    14. //倒数5秒
    15. long waitTime = maxWaitTimeMs - (System.currentTimeMillis() - startTime);
    16. //执行到这里,已经过去5秒话,就执行以下方法,返回isConnected()值
    17. if ( waitTime <= 0 )
    18. {
    19. return isConnected();
    20. }
    21. //还没到5秒话,假如执行到这里还有3秒,那么就会执行Object.wait(long timeout)方法,即该线程阻塞3秒后再自动唤醒,接着继续执行
    22. wait(waitTime);
    23. }
    24. else
    25. {
    26. wait();
    27. }
    28. }
    29. return isConnected();
    30. }

    该方法的核心会等待maxWaitTime时间,时间一到,就会返回isConnected()值,这里其实很好理解,就是客户端发起连接后,这里用一个while循环来等待指定的超时时间,默认是5秒,若5秒过了,就返回isConnected()值,而这里的isConnected()就是验证是否连接成功了,

    那么,这里就剩最后一个答案了,isConnected()是什么?

    1. public synchronized boolean isConnected(){
    2. return (currentConnectionState != null) && currentConnectionState.isConnected();
    3. }

    这里应该是判断客户端连接状态,即在client.start()方法里,会有一个状态,若创建连接成功,那么currentConnectionState.isConnected()就能得到true值,这里更像是一个观察模式,观察指定的连接超时时间内,是否连接成功。

    根据debug,发现未连接成功时,值是null,得到的即为false,当我们把默认为5秒的连接超时设置为timeout: 20000,等待连接过程,发现连接成功了,返回currentConnectionState的值为RECONNECTED。

    可见,之前出现zookeeper not connected异常问题,就是连接超时设置太短了!
     

    image


    currentConnectionState.isConnected()得到的是一个枚举值,RECONNECTED返回的是true——

    1. CONNECTED {
    2. public boolean isConnected() {
    3. return true;
    4. }
    5. },
    6. SUSPENDED {
    7. public boolean isConnected() {
    8. return false;
    9. }
    10. },
    11. RECONNECTED {
    12. public boolean isConnected() {
    13. return true;
    14. }
    15. },
    16. LOST {
    17. public boolean isConnected() {
    18. return false;
    19. }
    20. },
    21. READ_ONLY {
    22. public boolean isConnected() {
    23. return true;
    24. }
    25. };

    当返回true话,那么!connected就为false,就不会执行以下异常提示了——

    1. if (!connected) {
    2. throw new IllegalStateException("zookeeper not connected");
    3. }

    根据上边分析,可见启动Dubbo项目注册Zookeeper时提示zookeeper not connected异常,是因为没有在配置里设置连接超时,而是使用了默认的5秒,导致5秒内没有成功连接,就出现连接异常而无法成功连接,当调长时间后,就正常连接成功了,同时也说明了,这次本地连接zookeeper集群的时间超过了五秒。

  • 相关阅读:
    150. 以前编写好能够正常运行的 SAP UI5 代码,几个月后忽然不能运行了该怎么办?
    微软总裁:元宇宙概念应避免炒作,科技行业需配合监管机构
    图扑数字孪生智慧加油站,构建安全防护网
    Mybatis——使用步骤
    MogaFX—乐观的通胀报告意味着美元刚刚经历了15年来最糟糕的一天
    简单学习解析Python的命令行参数
    真的很难理解?RecyclerView 缓存机制到底是几级缓存?
    mysql取出组内按照某时间最新一条数据的其他字段
    什么是VR应急预案演练虚拟化|VR体验馆加盟|元宇宙文旅
    Java synchronized 关键字
  • 原文地址:https://blog.csdn.net/weixin_40706420/article/details/134544816