Curator连接状态源码浅析

本文深入剖析Curator源码,详细阐述如何通过Curator控制Zookeeper连接状态,以及非后台执行的数据获取逻辑。重点讨论了在连接断开后更新状态的机制,包括如何响应ZooKeeper状态变化、处理不同事件类型以及在连接恢复时的流程。同时,介绍了如何在非后台执行下获取数据的实现细节,包括调用逻辑、异常处理以及重试机制。

Curator作为操作Zookeeper的优秀工具,网上有很多的文章介绍和实例,我这里就不再介绍了。

我这篇主要想写写最近看Curator源码一点总结,关于ZooKeeper连接不上时,如何控制连接状态的源码。

Curator大体类结构(主要是和重试相关的类):

(家里电脑没有UML工具,不想装,欠着)

CuratorFrameworkImpl->CuratorZookeeperClient->ConnectionState->HandleHolder->Helper->ZooKeeper(zk客户端原生)


先看如何在连接断开后,更新连接状态。

ZooKeeper连接建立时使用ConnectionState作为watcher监听zk状态,当连接断开后,zk通知watcher(ConnectionState),执行process,更新状态。

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
class ConnectionState implements Watcher, Closeable{
    @Override
    public void process(WatchedEvent event){
        if ( LOG_EVENTS )
        {
            log.debug("ConnectState watcher: " + event);
        }

        for ( Watcher parentWatcher : parentWatchers )
        {
            TimeTrace timeTrace = new TimeTrace("connection-state-parent-process", tracer.get());
            parentWatcher.process(event);
            timeTrace.commit();
        }

        boolean wasConnected = isConnected.get();
        boolean newIsConnected = wasConnected;
        if ( event.getType() == Watcher.Event.EventType.None )
        {
            newIsConnected = checkState(event.getState(), wasConnected);
        }

        if ( newIsConnected != wasConnected )
        {
            isConnected.set(newIsConnected);
            connectionStartMs = System.currentTimeMillis();
        }
    }

   private boolean checkState(Event.KeeperState state, boolean wasConnected)   {
        boolean isConnected = wasConnected;
        boolean checkNewConnectionString = true;
        switch ( state )
        {
        default:
        case Disconnected:
        {
            isConnected = false;
            break;
        }

        case SyncConnected:
        case ConnectedReadOnly:
        {
            isConnected = true;
            break;
        }

        case AuthFailed:
        {
            isConnected = false;
            log.error("Authentication failed");
            break;
        }

        case Expired:
        {
            isConnected = false;
            checkNewConnectionString = false;
            handleExpiredSession();
            break;
        }

        case SaslAuthenticated:
        {
            // NOP
            break;
        }
        }

        if ( checkNewConnectionString && zooKeeper.hasNewConnectionString() )
        {
            handleNewConnectionString();
        }

        return isConnected;
    }
    ......
}



下面来看一个getData操作

org.apache.curator.framework.imps.CuratorFrameworkImpl.getData()

    @Override
    public GetDataBuilder getData()
    {
        Preconditions.checkState(isStarted(), "instance must be started before calling this method");

        return new GetDataBuilderImpl(this);
    }

无论GetData还是SetData都有对应Builder实现类,实现相应的操作。

我们来看看GetDataBuilderImpl的代码。

org.apache.curator.framework.imps.GetDataBuilderImpl

    @Override
    public byte[] forPath(String path) throws Exception
    {
        path = client.fixForNamespace(path);

        byte[]      responseData = null;
        if ( backgrounding.inBackground() )
        {
            client.processBackgroundOperation(new OperationAndData<String>(this, path, backgrounding.getCallback(), null, backgrounding.getContext()), null);
        }
        else
        {
            responseData = pathInForeground(path);
        }
        return responseData;
    }

我这里只是看看非后台执行的逻辑,后台执行代码,请小伙伴自己查看。

private byte[] pathInForeground(final String path) throws Exception
    {
        TimeTrace   trace = client.getZookeeperClient().startTracer("GetDataBuilderImpl-Foreground");
        byte[]      responseData = RetryLoop.callWithRetry
        (
            client.getZookeeperClient(),
            new Callable<byte[]>()
            {
                @Override
                public byte[] call() throws Exception
                {
                    byte[]      responseData;
                    if ( watching.isWatched() )
                    {
                        responseData = client.getZooKeeper().getData(path, true, responseStat);
                    }
                    else
                    {
                        responseData = client.getZooKeeper().getData(path, watching.getWatcher(), responseStat);
                    }
                    return responseData;
                }
            }
        );
        trace.commit();

        return decompress ? client.getCompressionProvider().decompress(path, responseData) : responseData;
    }

RetryLoop.callWithRetry代码如下:

    public static<T> T      callWithRetry(CuratorZookeeperClient client, Callable<T> proc) throws Exception
    {
        T               result = null;
        RetryLoop       retryLoop = client.newRetryLoop();
        while ( retryLoop.shouldContinue() )
        {
            try
            {
                client.internalBlockUntilConnectedOrTimedOut();
                
                result = proc.call();
                retryLoop.markComplete();
            }
            catch ( Exception e )
            {
                retryLoop.takeException(e);
            }
        }
        return result;
    }

上面函数中client.internalBlockUntilConnectedOrTimedOut()这句代码,就是测试当前状态是否处于连接态,如果不是就一直等待,一直到Timeout时间用完.

超过了Timeout时间,状态还是非连接态。这时仍然调用client.getZooKeeper().getData()仿佛,之所以这么设计,我分析原因,如果非连接态直接调用getZooKeeper()会触发线程加锁及重试连接的过程。加锁是一个相对比较重型操作,线程的事前重试比阻塞相对效果更好。

重点是下面这个方法:

org.apache.curator.ConnectionState.getZooKeeper()

    ZooKeeper getZooKeeper() throws Exception
    {
        if ( SessionFailRetryLoop.sessionForThreadHasFailed() )
        {
            throw new SessionFailRetryLoop.SessionFailedException();
        }

        Exception exception = backgroundExceptions.poll();
        if ( exception != null )
        {
            log.error("Background exception caught", exception);
            tracer.get().addCount("background-exceptions", 1);
            throw exception;
        }

        boolean localIsConnected = isConnected.get();
        if ( !localIsConnected )
        {
            checkTimeouts();
        }

        return zooKeeper.getZooKeeper();
    }

判断如果是非连接状态,就会调用checkTimeouts方法进行检查,重试。

checkTimeouts逻辑:

1.判断是否到了最小的连接过期时间。没有到,先不处理,证明刚才的连接检查可能不准确。

2.判断是否有新的zk地址。有则重新设定环境后进行重连。

3.如果过了最大Session过期时间(证明这个连接,时间太长了,已经过期了),需要重连。如没有大于Session过期时间,连接还是断开的,抛出异常。

 private synchronized void checkTimeouts() throws Exception
    {
        int minTimeout = Math.min(sessionTimeoutMs, connectionTimeoutMs);
        long elapsed = System.currentTimeMillis() - connectionStartMs;
        if ( elapsed >= minTimeout )
        {
            if ( zooKeeper.hasNewConnectionString() )
            {
                handleNewConnectionString();
            }
            else
            {
                int maxTimeout = Math.max(sessionTimeoutMs, connectionTimeoutMs);
                if ( elapsed > maxTimeout )
                {
                    if ( !Boolean.getBoolean(DebugUtils.PROPERTY_DONT_LOG_CONNECTION_ISSUES) )
                    {
                        log.warn(String.format("Connection attempt unsuccessful after %d (greater than max timeout of %d). Resetting connection and trying again with a new connection.", elapsed, maxTimeout));
                    }
                    reset();
                }
                else
                {
                    KeeperException.ConnectionLossException connectionLossException = new CuratorConnectionLossException();
                    if ( !Boolean.getBoolean(DebugUtils.PROPERTY_DONT_LOG_CONNECTION_ISSUES) )
                    {
                        log.error(String.format("Connection timed out for connection string (%s) and timeout (%d) / elapsed (%d)", zooKeeper.getConnectionString(), connectionTimeoutMs, elapsed), connectionLossException);
                    }
                    tracer.get().addCount("connections-timed-out", 1);
                    throw connectionLossException;
                }
            }
        }
    }


    private synchronized void reset() throws Exception
    {
        log.debug("reset");

        instanceIndex.incrementAndGet();

        isConnected.set(false);
        connectionStartMs = System.currentTimeMillis();
        zooKeeper.closeAndReset();
        zooKeeper.getZooKeeper();   // initiate connection
    }



















<think>嗯,我现在需要了解在Spring Boot中如何使用CuratorFramework连接ZooKeeper服务。首先,我得回忆一下ZooKeeperCurator分别是什么。ZooKeeper是一个分布式的协调服务,用于维护配置信息、命名、分布式同步等。而Curator是Netflix开源的一个ZooKeeper客户端库,简化了与ZooKeeper的交互,提供了更高层次的API,还有重试机制等。 那么,在Spring Boot项目中集成CuratorFramework的话,应该怎么做呢?可能首先需要添加相关的依赖。Maven的话,可能需要curator-recipes和curator-framework。另外,Spring Boot可能有自己的starter,不过Curator好像不是Spring官方维护的,所以可能需要手动配置。 接下来,配置ZooKeeper的连接信息,比如地址、端口、会话超时时间等。通常这些配置会放在application.properties或application.yml文件中,然后通过@Value注解注入到Java配置类中。 然后,需要创建一个CuratorFramework的Bean,在配置类中使用@Bean注解,由Spring来管理其生命周期。CuratorFrameworkFactory类应该提供了工厂方法,比如newClient(),传入连接字符串、重试策略等参数。常用的重试策略可能有ExponentialBackoffRetry,允许设置重试次数和间隔。 创建Bean的时候,可能还需要调用start()方法启动客户端,并且确保在应用关闭时调用close()方法。Spring的@Bean可以指定initMethod和destroyMethod,或者在@Bean方法里直接调用start(),返回的实例会被Spring管理关闭。 另外,可能需要处理连接状态的变化,比如监听连接事件,但如果是基本的连接,可能不需要处理这些。不过如果有需要,可以通过CuratorFramework的getConnectionStateListenable().addListener()来添加监听器。 测试连接是否成功的话,可以在应用启动后,通过CuratorFramework实例检查是否连接,比如使用getZookeeperClient().isConnected(),或者尝试执行一些操作,比如创建节点、读取数据等。 可能出现的问题包括依赖版本冲突,比如CuratorZooKeeper的版本需要兼容。需要查看Curator的官方文档,确认使用的Curator版本对应的ZooKeeper版本。例如,Curator 5.x对应ZooKeeper 3.5.x以上,而如果项目中用的是ZooKeeper 3.4.x,可能需要使用Curator 4.x版本。 另外,配置参数是否正确,比如连接字符串是否正确,超时时间是否足够,防火墙是否开放了ZooKeeper的端口。还有重试策略是否合理,避免因为短暂的网络问题导致连接失败。 总结一下步骤应该是:添加依赖、配置参数、创建CuratorFramework的Bean、在需要的地方注入使用,处理可能的异常和重试,确保连接正常。 可能还需要在Spring Boot的配置类中启用配置属性,比如使用@ConfigurationProperties注解来绑定配置项,这样更规范一些。 然后,编写一个Service类,注入CuratorFramework,在需要的地方调用它的方法,比如create().forPath(),getData().forPath()等,进行节点的操作。同时需要注意异常处理,比如InterruptedException和Exception,Curator的API可能会抛出这些异常,需要适当捕获或抛出。 还有一个考虑点是,CuratorFramework是否需要在Spring Boot启动完成后才进行某些初始化操作,比如使用@EventListener监听ApplicationReadyEvent,确保在应用完全启动后再进行ZooKeeper的操作。 可能还需要考虑连接断开后的恢复机制,虽然Curator本身有重试策略,但需要确认在Spring Boot中如何处理这些情况,是否需要额外的健康检查或重新连接逻辑。 最后,可能需要编写测试用例,使用@SpringBootTest来测试CuratorFramework的连接和操作是否正常,确保集成没有问题。</think>在Spring Boot中使用CuratorFramework连接ZooKeeper的步骤如下: --- ### 1. **添加依赖** 在`pom.xml`中引入CuratorZooKeeper客户端依赖(注意版本兼容性): ```xml <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>5.5.0</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.8.0</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency> ``` --- ### 2. **配置参数** 在`application.yml`或`application.properties`中添加配置: ```yaml zookeeper: connect-string: localhost:2181 session-timeout: 5000 connection-timeout: 5000 retry: base-sleep-time: 1000 max-retries: 3 ``` --- ### 3. **创建配置类** 编写Java配置类,初始化`CuratorFramework` Bean: ```java @Configuration @ConfigurationProperties(prefix = "zookeeper") public class ZookeeperConfig { private String connectString; private int sessionTimeout; private int connectionTimeout; private RetryPolicy retryPolicy; @Bean public CuratorFramework curatorFramework() { // 创建重试策略(指数退避) RetryPolicy retryPolicy = new ExponentialBackoffRetry( retry.getBaseSleepTime(), retry.getMaxRetries() ); // 构建Curator客户端 CuratorFramework client = CuratorFrameworkFactory.builder() .connectString(connectString) .sessionTimeoutMs(sessionTimeout) .connectionTimeoutMs(connectionTimeout) .retryPolicy(retryPolicy) .build(); client.start(); // 启动连接 return client; } // Getter/Setter省略(需补充) } ``` --- ### 4. **使用CuratorFramework** 在Service中注入`CuratorFramework`,执行操作: ```java @Service public class ZkService { @Autowired private CuratorFramework curator; // 创建节点 public void createNode(String path, byte[] data) throws Exception { curator.create() .creatingParentsIfNeeded() .forPath(path, data); } // 读取数据 public byte[] getData(String path) throws Exception { return curator.getData().forPath(path); } } ``` --- ### 5. **连接状态监听(可选)** 监听ZooKeeper连接状态变化: ```java @PostConstruct public void init() { curator.getConnectionStateListenable().addListener( (client, state) -> { if (state == ConnectionState.RECONNECTED) { System.out.println("Reconnected to ZooKeeper"); } } ); } ``` --- ### 6. **注意事项** - **版本兼容性**:确保CuratorZooKeeper版本匹配(如Curator 5.x需ZooKeeper 3.5+)。 - **异常处理**:所有Curator操作需处理`Exception`(如网络中断、节点不存在)。 - **生命周期管理**:Spring会自动关闭`CuratorFramework`,无需手动调用`close()`。 --- ### 示例:健康检查 通过检查连接状态验证ZooKeeper是否正常: ```java @Component public class ZkHealthChecker implements HealthIndicator { @Autowired private CuratorFramework curator; @Override public Health health() { return curator.getZookeeperClient().isConnected() ? Health.up().build() : Health.down().build(); } } ``` --- 通过以上步骤,即可在Spring Boot中高效集成CuratorFramework,实现与ZooKeeper服务的可靠交互。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值