不废话,直接开搞
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的时候,配置当前类可以多实例启动,这样可以配置不同的端口启动,配置如下图所示:
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. 重新启动被停止的服务端,再查看客户端日志