【zookeeper】基于Zookeeper 实现 RMI Server的HA

在这里插入图片描述

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
发生变化,重新生成服务地址列表
获取节点信息
获取节点信息

M.扩展

【java】RMI教程:入门与编译方法 远程

【zookeeper】ZooKeeper 权限管理与Curator增加权限验证

【zookeeper】zookeeper的ACL权限控制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值