Zookeeper服务注册和监听实战

222 阅读2分钟

不废话,直接开搞

1. 环境

JDK1.8 + zookeeper-3.8.3 + 依赖如下:

<!-- curator-framework -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>5.1.0</version>
</dependency>
<!-- curator-recipes -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.1.0</version>
</dependency>

2. zookeeper连接

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class ZkClient {

    public static final String PATH = "/servers";

    private CuratorFramework client;

    public ZkClient() {
        client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181")
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .connectionTimeoutMs(5 * 1000)
                .sessionTimeoutMs(60 * 1000)
                .build();
        client.start();
    }

    public CuratorFramework getClient() {
        return client;
    }
}

3. 服务启动

这里以ServerSocket服务为例,你也可以在此基础上使用netty测试,使用idea的时候,配置当前类可以多实例启动,这样可以配置不同的端口启动,配置如下图所示:

image.png


import com.hefei.garden.test.common.ZkClient;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MainServer {

    public static void main(String[] args) throws Exception {
        // 启动参数自己定义(比如先8888,9999,6666)
        if (args.length == 0 || !paramCheck(args[0])) {
            System.out.println("参数错误或者端口有误[6000-20000]");
            return;
        }
        // 启动zookeeper客户端
        ZkClient zkClient = new ZkClient();
        CuratorFramework client = zkClient.getClient();
        // 判断需要使用的节点是否存在
        Stat stat = client.checkExists().forPath(ZkClient.PATH);
        if (stat == null) {
            client.create().forPath(ZkClient.PATH);
        }
        // ServerSocket连接
        ServerSocket serverSocket = new ServerSocket(Integer.parseInt(args[0]));
        // 服务连接无误在进行服务注册,节点类型:临时顺序节点(这里面只注册了端口)
        client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(ZkClient.PATH + "/server_", args[0].getBytes());
        while (true) {
            try (Socket socket = serverSocket.accept()) {
                InputStream inputStream = socket.getInputStream();
                byte[] bytes = new byte[1024];
                int len = inputStream.read(bytes);
                System.out.println("接收客户端消息:" + new String(bytes, 0, len));
                OutputStream outputStream = socket.getOutputStream();
                outputStream.write((args[0] + ":响应客户端").getBytes());
                outputStream.flush();
            } catch (Exception e) {
                serverSocket.close();
                break;
            }
        }
    }

    /**
     * 端口范围: [6000,20000]
     *
     * @param port
     * @return
     */
    public static boolean paramCheck(String port) {
        try {
            int num = Integer.parseInt(port);
            return num >= 6000 && num <= 20000;
        } catch (NumberFormatException e) {
            return false;
        }
    }

}

4. 客户端访问


import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hefei.garden.test.common.ZkClient;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.CuratorCache;
import org.apache.curator.framework.recipes.cache.CuratorCacheListener;
import org.springframework.util.CollectionUtils;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class MainClient {

    private static final Map<String, Integer> MAP = Maps.newConcurrentMap();

    public static void main(String[] args) throws Exception {
        // 连接zookeeper
        ZkClient zkClient = new ZkClient();
        CuratorFramework client = zkClient.getClient();
        // 注册 /servers 子节点监听
        CuratorCache curatorCache = CuratorCache.build(client, ZkClient.PATH);
        curatorCache.listenable().addListener((type, oldData, data) -> {
            // 添加子节点(服务启动)
            if (type.name().equals(CuratorCacheListener.Type.NODE_CREATED.name())) {
                // 符合条件说明是子节点,子节点才能加入缓存
                if (data.getPath().startsWith(ZkClient.PATH + "/")) {
                    try {
                        String port = new String(client.getData().forPath(data.getPath()));
                        MAP.put(data.getPath(), Integer.parseInt(port));
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
            // 删除子节点(服务停止)
            else if (type.name().equals(CuratorCacheListener.Type.NODE_DELETED.name())) {
                MAP.remove(oldData.getPath());
            }
            // 数据被修改,异常操作
            else {
                throw new RuntimeException("数据被修改, 异常操作...");
            }
        });
        curatorCache.start();
        while (true) {
            if (CollectionUtils.isEmpty(MAP)) {
                System.out.println("服务器列表为空...");
                TimeUnit.SECONDS.sleep(3);
                continue;
            }
            List<Integer> values = Lists.newArrayList(MAP.values());
            // 负载均衡
            int index = new Random().nextInt(values.size());
            // 服务器访问
            try (Socket socket = new Socket("127.0.0.1", values.get(index))) {
                OutputStream outputStream = socket.getOutputStream();
                outputStream.write((values.get(index) + ", 你好服务器").getBytes());
                outputStream.flush();
                InputStream inputStream = socket.getInputStream();
                byte[] bytes = new byte[1024];
                int len = inputStream.read(bytes);
                System.out.println(new String(bytes, 0, len));
            } catch (Exception e) {
                // 崩溃一小段时间内会自动剔除,可以自己写剔除逻辑(就是删除MAP里面的缓存),比如三次访问之后失败,直接删除对应的端口
                System.out.println(values.get(index) + ": 服务器暂时崩溃");
            }
            TimeUnit.SECONDS.sleep(3);
        }
    }
}

5. 测试

观看多种场景下的日志打印即可,比如:

1. 客户端先启动,服务端后启动,
2. 多个服务端先启动,客户端再启动
3. 停止一个服务端,查看客户端日志
4. 重新启动被停止的服务端,再查看客户端日志