1.概述
转载并且补充:Zookeeper 全景介绍
2.案例
public class Constant {
public static final String ZOOKEEPER_PATH = "/rmiservers";
public static final String ZOOKEEPER_PATH_SERVER = "server";
/**
* 服务器地址
*/
public static final String HOSTS = "hnode1:2181,hnode2:2181,hnode3:2181";
/**
* 超时时间,单位毫秒 这里超时时间为6秒 超时时间后才会把临时节点删除
*/
public static final int SESSION_TIMEOUT = 6000;
}
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* @Author haonan.bian
* @Description //TODO
* @Date 2020-02-02 22:34
**/
public interface HelloService extends Remote {
String sayHello(String name) throws RemoteException;
}
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* @Author haonan.bian
* @Description //TODO
* @Date 2020-02-02 22:35
**/
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
protected HelloServiceImpl() throws RemoteException {
}
@Override
public String sayHello(String name) {
System.out.println("server print> hello: "+name);
return "hello: "+name;
}
}
import java.rmi.Remote;
public class RmiClient {
/***
* todo: 九师兄 2023/3/30 09:38
*
* 【zookeeper】基于Zookeeper 实现 RMI Server的HA
* https://blog.csdn.net/qq_21383435/article/details/129850501
*
* 准备链接:localhost:2181State:CONNECTING sessionid:0x0
* zk等待状态
* WatchedEvent => nullCONNECTED
* 获取节点信息
* 获取节点信息
* do ...
* urlList.size():2
* rmi://localhost:9996/com.zk.rmi_ha.HelloServiceImpl
* hello: world
* do ...
* urlList.size():2
*/
public void rmiClient(){
ServiceConsumer serviceConsumer = new ServiceConsumer();
// 测试zookeeper
while(true){
try {
System.out.println("do ... ");
Remote remote = serviceConsumer.lookup();
if(null != remote){
if(remote instanceof HelloService){
HelloService helloService = (HelloService) remote;
String result = helloService.sayHello("world");
System.out.println(result);
}else {
System.out.println("返回信息不是HelloService");
}
}else{
System.out.println("helloService is null");
}
Thread.sleep(10000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
public class RmiServer {
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
public class RmiServer {
/***
* todo: 九师兄 2023/3/30 09:20
*
* 【zookeeper】基于Zookeeper 实现 RMI Server的HA
* https://blog.csdn.net/qq_21383435/article/details/129850501
*
* 经过测试发现需要先在zk上创建路径
*
* [zk: localhost:2181(CONNECTED) 6] create /rmiservers ""
* Created /rmiservers
* [zk: localhost:2181(CONNECTED) 7] create /rmiservers/server ""
* Created /rmiservers/server
* [zk: localhost:2181(CONNECTED) 8] ls / todo: 2023/3/30 09:37 九师兄
*
* 然后服务端运行如下
*
* 准备发布
* 发布地址rmi://localhost:9996/com.zk.rmi_ha.HelloServiceImpl
* 准备获取zk链接localhost:2181
* 现场等待中
* 观察到事件:WatchedEvent state:SyncConnected type:None path:null
* 服务端准备创建路径:/rmiservers/server
* created path:/rmiservers/server0000000002
* server print> hello: world
*
*/
public void startServer(Integer port) throws RemoteException {
// int port = 9999;
// String url = "rmi://localhost:9999/com.hnbian.ha.HelloServiceImpl";
// LocateRegistry.createRegistry(port);
// Naming.rebind(url,new HelloServiceImpl());
//
String host = "localhost";
HelloService helloService = new HelloServiceImpl();
ServiceProvider serviceProvider = new ServiceProvider();
System.out.println("准备发布");
serviceProvider.publish(helloService,host,port);
}
}
ServiceConsumer
import com.hnbian.zk.ZkWatcher;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.rmi.Naming;
import java.rmi.Remote;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
/**
* @Author haonan.bian
* @Description //TODO
* @Date 2020-02-05 18:44
**/
public class ServiceConsumer {
private volatile List<String> urlList = new ArrayList<>();
/**
* 用于等待SyncConnected 事件出发后继续执行当前线程
*/
private CountDownLatch latch = new CountDownLatch(1);
//构造方法
public ServiceConsumer(){
ZooKeeper zk = getConnection();
if(null != zk){
watchNode(zk);
}
}
public Remote lookup() throws Exception{
Remote service = null;
int size = urlList.size();
System.out.println("urlList.size():"+urlList.size());
if(size > 0 ){
String url;
if(size == 1){
//若urlList中只有一个元素,则直接获取该元素
url = urlList.get(0);
System.out.println(url);
}else{
//若urlList中有多个元素,则随机获取一个元素
url = urlList.get(ThreadLocalRandom.current().nextInt(size));
System.out.println(url);
}
// 从JNDI中查找RMI服务
service = lookupService(url);
}
return service;
}
private Remote lookupService(String url) throws Exception{
/*HelloService helloService = (HelloService) Naming.lookup(url);
return helloService;*/
return Naming.lookup(url);
}
// 观察 /rmiservice 节点下的子节点是否有变化
private void watchNode(final ZooKeeper zk){
try{
List<String> nodeList = zk.getChildren(Constant.ZOOKEEPER_PATH, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if(watchedEvent.getType() == Event.EventType.NodeChildrenChanged){
System.out.println("发生变化,重新生成服务地址列表");
watchNode(zk);
}
}
});
//用于存放 /rmiservers 所有子节点中的数据
List<String> dataList = new ArrayList<>();
for(String node: nodeList){
byte[] data = zk.getData(Constant.ZOOKEEPER_PATH+"/"+node,false,null);
dataList.add(new String(data));
}
urlList = dataList;
}catch (Exception e){
e.printStackTrace();
}
}
//连接zookeeper服务器
private ZooKeeper getConnection() {
ZooKeeper zk = null;
try {
Watcher watcher = new ZkWatcher();
//获取zookeeper连接 创建zookeeper客户端
zk = new ZooKeeper(Constant.HOSTS, Constant.SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
latch.countDown(); //唤醒当前正在执行的线程
}
}
});
latch.await(); // 使当前线程处于等待状态
System.out.println(zk.getState());
// CONNECTING: 正在连接,CONNECTED: 已经连接上
} catch (Exception e) {
e.printStackTrace();
}
return zk;
}
}
ServiceProvider
import com.hnbian.zk.ZkWatcher;
import org.apache.zookeeper.*;
import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.util.concurrent.CountDownLatch;
/**
* @Author haonan.bian
* @Description //TODO
* @Date 2020-02-05 16:56
**/
public class ServiceProvider {
/**
* 用于等待SyncConnected 事件出发后继续执行当前线程
*/
private CountDownLatch latch = new CountDownLatch(1);
//发布RMI服务并注册RMI 服务到zookeeper中
public void publish(Remote remote, String host, int port) {
String url = publishService(remote, host, port);
if (null != url) {
ZooKeeper zk = getConnection();
if (null != zk) {
//创建ZNode 并将RMI地址放入ZNode上。
createNode(zk, url);
}
}
}
//创建ZNode
private void createNode(ZooKeeper zk, String url) {
try {
byte[] data = url.getBytes();
//
String path = zk.create(Constant.ZOOKEEPER_PATH+"/"+Constant.ZOOKEEPER_PATH_SERVER,data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println("created path:"+path);
} catch (Exception e) {
e.printStackTrace();
}
}
//发布RMI服务
public String publishService(Remote remote, String host, int port) {
String url = null;
try{
url = String.format("rmi://%s:%d/%s",host,port,remote.getClass().getName());
System.out.println(url);
LocateRegistry.createRegistry(port);
Naming.rebind(url,new HelloServiceImpl());
}catch (Exception e){
e.printStackTrace();
}
return url;
}
//连接zookeeper服务器
private ZooKeeper getConnection() {
ZooKeeper zk = null;
try {
Watcher watcher = new ZkWatcher();
//获取zookeeper连接 创建zookeeper客户端
zk = new ZooKeeper(Constant.HOSTS, Constant.SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
latch.countDown(); //唤醒当前正在执行的线程
}
}
});
latch.await(); // 使当前线程处于等待状态
// CONNECTING: 正在连接,CONNECTED: 已经连接上
} catch (Exception e) {
e.printStackTrace();
}
return zk;
}
}
java
ZkWatcher
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
/**
* @Author haonan.bian
* @Description //TODO
* @Date 2020-02-02 15:02
**/
public class ZkWatcher implements Watcher {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("ZkWatcher.process"+watchedEvent);
}
}
2.1 测试
经过测试发现需要先在zk上创建路径
[zk: localhost:2181(CONNECTED) 6] create /rmiservers ""
Created /rmiservers
[zk: localhost:2181(CONNECTED) 7] create /rmiservers/server ""
Created /rmiservers/server
[zk: localhost:2181(CONNECTED) 8] ls /
然后启动服务端
准备发布
发布地址rmi://localhost:9996/com.zk.rmi_ha.HelloServiceImpl
准备获取zk链接localhost:2181
现场等待中
观察到事件:WatchedEvent state:SyncConnected type:None path:null
服务端准备创建路径:/rmiservers/server
created path:/rmiservers/server0000000002
server print> hello: world
然后运行客户端如下
准备链接:localhost:2181State:CONNECTING sessionid:0x0
zk等待状态
WatchedEvent => nullCONNECTED
获取节点信息
获取节点信息
do ...
urlList.size():2
rmi://localhost:9996/com.zk.rmi_ha.HelloServiceImpl
hello: world
do ...
urlList.size():2
2.2 集群测试
上面注释中都是单个测试,后来集群测试一下
先启动两个server,然后启动一个客户端
public class RmiServerTest {
private RmiServer rmiServer = new RmiServer();
@Test
public void startServer1() throws RemoteException {
rmiServer.startServer(9996);
LockSupport.park();
}
@Test
public void startServer2() throws RemoteException {
rmiServer.startServer(9997);
LockSupport.park();
}
}
启动客户端
public class RmiClientTest {
private RmiClient rmiClient = new RmiClient();
@Test
public void rmiClient() {
rmiClient.rmiClient();
}
}
启动第一个两个server后,过一段时间,关掉正在使用的server,可以看到客户端正常使用,而且发生了切换
server1
准备发布
发布地址rmi://localhost:9996/com.zk.rmi_ha.HelloServiceImpl
准备获取zk链接localhost:2181
现场等待中
观察到事件:WatchedEvent state:SyncConnected type:None path:null
服务端准备创建路径:/rmiservers/server
created path:/rmiservers/server0000000016
server print> hello: world
server2
准备发布
发布地址rmi://localhost:9997/com.zk.rmi_ha.HelloServiceImpl
准备获取zk链接localhost:2181
现场等待中
观察到事件:WatchedEvent state:SyncConnected type:None path:null
服务端准备创建路径:/rmiservers/server
created path:/rmiservers/server0000000017
server print> hello: world
server print> hello: world
客户端
准备链接:localhost:2181State:CONNECTING sessionid:0x0 local:null remoteserver:null lastZxid:0 xid:1 sent:0 recv:0 queuedpkts:0 pendingresp:0 queuedevents:0
zk等待状态
WatchedEvent => nullCONNECTED
获取节点信息
获取节点信息
获取节点信息
do ...
urlList.size():3
rmi://localhost:9997/com.zk.rmi_ha.HelloServiceImpl
hello: world
do ...
urlList.size():3
rmi://localhost:9997/com.zk.rmi_ha.HelloServiceImpl
hello: world
do ...
urlList.size():3
// 这里可以看到 从9997 切换到9996
rmi://localhost:9996/com.zk.rmi_ha.HelloServiceImpl
hello: world
发生变化,重新生成服务地址列表
获取节点信息
获取节点信息