RPC远程调用及Zookeeper注册中心

1. RPC实例

1.1 服务端

接口

public interface ILulfHello {
    String sayHello(String msg);
}

实现类

public class LulfHelloImpl implements ILulfHello {
    @Override
    public String sayHello(String msg) {
        return "RPC,Hello " + msg;
    }
}

采取线程池的方式来处理RPCServer
想想怎么实现一个RPC调用,我们需要采取socket实时监听客户端的请求
所以我们需要采取socket,然后将socket对象和服务名传入

public class RpcServer {
    //创建一个线程池
    private static final ExecutorService executorService = Executors.newCachedThreadPool();

    public void publisher(final Object service,int port){
        ServerSocket serverSocket=null;
        try{
            serverSocket=new ServerSocket(port);  //启动一个服务监听

            while(true){ //循环监听
                Socket socket=serverSocket.accept(); //监听服务
                //通过线程池去处理请求
                executorService.execute(new ProcessorHandler(socket,service));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

线程
采取序列化和反序列化的手段来处理远程调用

public class ProcessorHandler implements Runnable {

    private Socket socket;
    private Object service; //服务端发布的服务

    public ProcessorHandler(Socket socket, Object service) {
        this.socket = socket;
        this.service = service;
    }

    @Override
    public void run() {
        //处理请求
        ObjectInputStream inputStream = null;
        try {
            //获取客户端的输入流
            inputStream = new ObjectInputStream(socket.getInputStream());
            //反序列化远程传输的对象RpcRequest
            RpcRequest request = (RpcRequest) inputStream.readObject();
            Object result = invoke(request); //通过反射去调用本地的方法
            //通过输出流讲结果输出给客户端
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(result);
            outputStream.flush();
            inputStream.close();
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private Object invoke(RpcRequest request) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        //一下均为反射操作,目的是通过反射调用服务
        Object[] args = request.getParameters();
        Class<?>[] types = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            types[i] = args[i].getClass();
        }
        Method method = service.getClass().getMethod(request.getMethodName(), types);
        return method.invoke(service, args);
    }
}

启动

 public static void main(String[] args) {
        ILulfHello lulfHello = new LulfHelloImpl();
        RpcServer rpcServer = new RpcServer();
        rpcServer.publisher(lulfHello, 8080);
    }

1.2 客户端

定义RpcRequest
要知道这个对象是传输的,所有它必须满足可以转化为字节序列方便tcp传输,所以需要继承java的序列化接口

public class RpcRequest implements Serializable {

    private static final long serialVersionUID = -1941512894260946340L;

    private String className;
    private String methodName;
    private Object[] parameters;

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }
}

定义代理类
这是用来创建客户端的远程代理,利用代理来访问

public class RpcClientProxy {
    /**
     * 创建客户端的远程代理。通过远程代理进行访问
     */
    public <T> T clientProxy(final Class<T> interfaceCls, final String host, final int port) {
        // 使用到了动态代理。
        return (T) Proxy.newProxyInstance(interfaceCls.getClassLoader(), new Class[] { interfaceCls },
                new RemoteInvocationHandler(host, port));
    }
}

InvocationHandler
组装请求,然后通过tcp协议传出,获取返回

public class RemoteInvocationHandler implements InvocationHandler {
    private String host;
    private int port;

    public RemoteInvocationHandler(String host, int port) {
        this.host = host;
        this.port = port;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //组装请求
        RpcRequest request=new RpcRequest();
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        request.setParameters(args);
        //通过tcp传输协议进行传输
        TCPTransport tcpTransport=new TCPTransport(this.host,this.port);
        //发送请求
        return tcpTransport.send(request);
    }
}

TCPTransport
建立socket连接,然后输出流将请求发给服务器然后接受服务器的输出流,

public class TCPTransport {
    private String host;

    private int port;

    public TCPTransport(String host, int port) {
        this.host = host;
        this.port = port;
    }

    //创建一个socket连接
    private Socket newSocket(){
        System.out.println("创建一个新的连接");
        Socket socket;
        try{
            socket=new Socket(host,port);
            return socket;
        }catch (Exception e){
            throw new RuntimeException("连接建立失败");
        }
    }

    public Object send(RpcRequest request){
        Socket socket=null;
        try {
            socket = newSocket();
            //获取输出流,将客户端需要调用的远程方法参数request发送给
            ObjectOutputStream outputStream=new ObjectOutputStream
                    (socket.getOutputStream());
            outputStream.writeObject(request);
            outputStream.flush();
            //获取输入流,得到服务端的返回结果
            ObjectInputStream inputStream=new ObjectInputStream
                    (socket.getInputStream());
            Object result=inputStream.readObject();
            inputStream.close();
            outputStream.close();
            return result;

        }catch (Exception e ){
            throw new RuntimeException("发起远程调用异常:",e);
        }finally {
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

测试类

 public static void main(String[] args) {
        RpcClientProxy rpcClientProxy = new RpcClientProxy();
        ILulfHello hello = rpcClientProxy.clientProxy
                (ILulfHello.class, "localhost", 8080);
        System.out.println(hello.sayHello("llf"));
    }

总结下来是如下内容:
在这里插入图片描述

2. 自定义RPC结合ZK注册中心实例

我们之前在服务发布的时候是直接写死一个地址发布的,现在需要结合注册中心 ,那么我们势必要在发布服务的时候在指定的 zookeeper服务上面注册我们的节点,基于 zookeeper 临时节点的特性,一旦服务 down机,那么这个节点也会消失,同时会有事件触发,添加一个注册服务

2.1 服务端

注册中心接口

public interface IRegisterCenter {

    /**
     * 注册服务名称和服务地址
     * @param serviceName
     * @param serviceAddress
     */
    void register(String serviceName,String serviceAddress);
}

实现:注册服务的实现就是往zookeeper 的指点根节点下插入一个临时节点,如果根节点不存在则先创建,由于 curator 做了很好的实现,这里先用他来做实现。

public class RegisterCenterImpl implements IRegisterCenter {

    //zk链接地址
    public final static String CONNNECTION_STR = "192.168.202.128:2181";

    //注册根节点
    public final static String ZK_REGISTER_PATH = "registrys";

    private CuratorFramework curatorFramework;

    public CuratorFramework getFluentInstance() {
        curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(CONNNECTION_STR).sessionTimeoutMs(50000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3)).namespace(ZK_REGISTER_PATH)
                .build();
        curatorFramework.start();
        return curatorFramework;
    }

    @Override
    public void register(String serviceName, String serviceAddress) {
        //注册相应的服务
        String servicePath ="/" + serviceName;
        try {
            curatorFramework = getFluentInstance();
            //判断 /registrys/product-service是否存在,不存在则创建
            if (curatorFramework.checkExists().forPath(servicePath) == null) {
                curatorFramework.create().creatingParentsIfNeeded().
                        withMode(CreateMode.PERSISTENT).forPath(servicePath, "0".getBytes());
            }
            // 组装节点地址
            String addressPath = servicePath + "/" + serviceAddress;
            String rsNode = curatorFramework.create().withMode(CreateMode.EPHEMERAL).
                    forPath(addressPath, "0".getBytes());
            System.out.println("服务注册成功:" + rsNode);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

有了注册服务我们需要进入服务发布的类里面进行一些修改,由于服务的地址及端口都注册到注册中心上了,我们需要增加注册中心属性,便已获取相关信息。

public class ZKRpcServer {
    //创建一个线程池
    private static final ExecutorService executorService = Executors.newCachedThreadPool();

    private IRegisterCenter registerCenter; //注册中心
    private String serviceAddress; //服务发布地址

    // 存放服务名称和服务对象之间的关系
    Map<String, Object> handlerMap = new HashMap<>();

    public ZKRpcServer(IRegisterCenter registerCenter, String serviceAddress) {
        this.registerCenter = registerCenter;
        this.serviceAddress = serviceAddress;
    }

    /**
     * 绑定服务名称和服务对象
     *
     * @param services
     */
    public void bind(Object... services) {
        for (Object service : services) {
            // 这里为了获取对应服务的类名,我们这里定义了一个注解来实现 代码请看下面
            RpcAnnotation annotation = service.getClass().getAnnotation(RpcAnnotation.class);
            String serviceName = annotation.value().getName();
            handlerMap.put(serviceName, service);//绑定服务接口名称对应的服务
        }
    }

    public void publisher() {
        ServerSocket serverSocket = null;
        try {
            String[] addrs = serviceAddress.split(":");//这个时候服务的ip port 都是从这个注册地址上获取
            serverSocket = new ServerSocket(Integer.parseInt(addrs[1]));  //启动一个服务监听
            // handlerMap 可能存放多个发布服务,我这里演示的是一个
            for (String interfaceName : handlerMap.keySet()) {
                registerCenter.register(interfaceName, serviceAddress);
                System.out.println("注册服务成功:" + interfaceName + "->" + serviceAddress);
            }
            while (true) { //循环监听
                Socket socket = serverSocket.accept(); //监听服务
                //通过线程池去处理请求
                executorService.execute(new ZkProcessorHandler(socket, handlerMap));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

我们可以采用注解的方式把这个服务注册上去,注解类:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcAnnotation {
    /**
     * 对外发布的服务的接口地址
     * @return
     */
    Class<?> value();
    // 暂时没用 可以处理版本
    String version() default "";
}

再给原先的实现类上打上注解

@RpcAnnotation(ILulfHello.class)
public class LulfHelloImpl implements ILulfHello {
    @Override
    public String sayHello(String msg) {
        return "RPC,Hello " + msg;
    }
}

上面监听服务请求的处理也发生了改变,之前传入的是单独的service 现在需要把绑定的 handlerMap 带过去。 接下来看 ProcessorHandler :

public class ZkProcessorHandler implements Runnable{
    private Socket socket;

    Map<String, Object> handlerMap;// 现在需要从map里获取需要发布的绑定服务

    public ZkProcessorHandler(Socket socket, Map<String, Object> handlerMap) {
        this.socket = socket;
        this.handlerMap = handlerMap;
    }

    @Override
    public void run() {
        //处理请求
        ObjectInputStream inputStream = null;
        try {
            //获取客户端的输入流
            inputStream = new ObjectInputStream(socket.getInputStream());
            //反序列化远程传输的对象RpcRequest
            RpcRequest request = (RpcRequest) inputStream.readObject();
            Object result = invoke(request); //通过反射去调用本地的方法

            //通过输出流讲结果输出给客户端
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(result);
            outputStream.flush();
            inputStream.close();
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private Object invoke(RpcRequest request) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        //以下均为反射操作,目的是通过反射调用服务
        Object[] args = request.getParameters();
        Class<?>[] types = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            types[i] = args[i].getClass();
        }
        //从handlerMap中,根据客户端请求的地址,去拿到响应的服务,通过反射发起调用
        Object service = handlerMap.get(request.getClassName());
        Method method = service.getClass().getMethod(request.getMethodName(), types);
        return method.invoke(service, args);
    }
}

然后发布服务

public static void main(String[] args) throws IOException {
        HelloService helloService=new HelloServiceImpl();
        IRegisterCenter registerCenter=new RegisterCenterImpl();

        RpcServer rpcServer=new RpcServer(registerCenter,"127.0.0.1:8080");
        rpcServer.bind(helloService);
        rpcServer.publisher();
        System.in.read();
    }

日志输出

服务注册成功:/com.hikvision.rabbitmq.rpc.server.ILulfHello/127.0.0.1:8080
注册服务成功:com.hikvision.rabbitmq.rpc.server.ILulfHello->127.0.0.1:8080

2.2 客户端

对于客户端需要从远程调用服务,现在是需要从注册中心先去获取该服务类锁对应的再注册中心注册的服务地址及端口,再去发起请求,并且对指定到 路径进行监听,那么客户端需要定义一个服务的发现服务:

public interface IServiceDiscovery {
    /**
     * 根据请求的服务地址,获得对应的调用地址
     * @param serviceName
     * @return
     */
    String discover(String serviceName);
}

服务发现的实现类

public class ServiceDiscoveryImpl implements IServiceDiscovery{

    // 从指定节点下获取的子节点列表
    List<String> repos = new ArrayList<>();
    // 服务器连接地址,就是服务端的 ZkConfig.CONNNECTION_STR
    private String address;

    private CuratorFramework curatorFramework;

    //注册根节点
    public final static String ZK_REGISTER_PATH = "/registrys";

    // 为了方便测试,这里直接再构造里面启动连接
    public ServiceDiscoveryImpl(String address) {
        this.address = address;

        curatorFramework = CuratorFrameworkFactory.builder().
                connectString(address).
                sessionTimeoutMs(50000).
                retryPolicy(new ExponentialBackoffRetry(1000,
                        10)).build();
        curatorFramework.start();
    }

    // 本质就是获取指定服务节点下的子节点
    @Override
    public String discover(String serviceName) {
        String path = ZK_REGISTER_PATH + "/" + serviceName;
        try {
            repos = curatorFramework.getChildren().forPath(path);

        } catch (Exception e) {
            throw new RuntimeException("获取子节点异常:" + e);
        }
        //动态发现服务节点的变化
        registerWatcher(path);

        //简单负载均衡机制
        if (repos == null || repos.size() == 0) {
            return null;
        }
        if (repos.size() == 1) {
            return repos.get(0);
        }

        int len = repos.size();
        Random random = new Random();
        return repos.get(random.nextInt(len));//返回调用的服务地址
    }

    // 这里是之前提到的用 curator 客户端进行时间注册的操作
    private void registerWatcher(final String path) {
        PathChildrenCache childrenCache = new PathChildrenCache
                (curatorFramework, path, true);

        PathChildrenCacheListener pathChildrenCacheListener = new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
                repos = curatorFramework.getChildren().forPath(path);
            }
        };
        childrenCache.getListenable().addListener(pathChildrenCacheListener);
        try {
            childrenCache.start();
        } catch (Exception e) {
            throw new RuntimeException("注册PatchChild Watcher 异常" + e);
        }
    }
}

PRC客户端代理
接下去由于客户端是通过动态代理去获取远程对象,由于原来参数为 IP Port ,现在需要通过注册中心去拿

public class ZKRpcClientProxy {
    // 服务发现
    private IServiceDiscovery serviceDiscovery;

    public ZKRpcClientProxy(IServiceDiscovery serviceDiscovery) {
        this.serviceDiscovery = serviceDiscovery;
    }

    /**
     * 创建客户端的远程代理。通过远程代理进行访问
     * @param interfaceCls
     * @param <T>
     * @return
     */
    public <T> T clientProxy(final Class<T> interfaceCls){
        //使用到了动态代理。
        return (T) Proxy.newProxyInstance(interfaceCls.getClassLoader(),
                new Class[]{interfaceCls},new ZKRemoteInvocationHandler(serviceDiscovery));
    }
}

invocationHandler
由于 IP Port 都是来自注册中心的一个服务节点下的子节点的信息,而zookeeper服务器上存储的是该服务的全路径名称,在其之下的直接点才是真的IP/PORT的信息,所以我们通过这个request.getClassName()获取服务的地址,这里的传输方法也要进行修改:仅仅是修改了 IP/PORT的来源。

public class ZKRemoteInvocationHandler implements InvocationHandler {
    private IServiceDiscovery serviceDiscovery;


    public ZKRemoteInvocationHandler(IServiceDiscovery serviceDiscovery) {
        this.serviceDiscovery = serviceDiscovery;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //组装请求
        RpcRequest request = new RpcRequest();
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        request.setParameters(args);

        String serviceAddress = serviceDiscovery.discover(request.getClassName()); //根据接口名称得到对应的服务地址
        //通过tcp传输协议进行传输
        ZKTCPTransport tcpTransport = new ZKTCPTransport(serviceAddress);
        //发送请求
        return tcpTransport.send(request);
    }

建立连接

public class ZKTCPTransport {
    private String serviceAddress;

    public ZKTCPTransport(String serviceAddress) {
        this.serviceAddress=serviceAddress;
    }

    //创建一个socket连接
    private Socket newSocket(){
        System.out.println("创建一个新的连接");
        Socket socket;
        try{
            String[] arrs=serviceAddress.split(":");
            socket=new Socket(arrs[0],Integer.parseInt(arrs[1]));
            return socket;
        }catch (Exception e){
            throw new RuntimeException("连接建立失败");
        }
    }

    public Object send(RpcRequest request){
        Socket socket=null;
        try {
            socket = newSocket();
            //获取输出流,将客户端需要调用的远程方法参数request发送给
            ObjectOutputStream outputStream=new ObjectOutputStream
                    (socket.getOutputStream());
            outputStream.writeObject(request);
            outputStream.flush();
            //获取输入流,得到服务端的返回结果
            ObjectInputStream inputStream=new ObjectInputStream
                    (socket.getInputStream());
            Object result=inputStream.readObject();
            inputStream.close();
            outputStream.close();
            return result;

        }catch (Exception e ){
            throw new RuntimeException("发起远程调用异常:",e);
        }finally {
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

服务调用

public class ZKClientDemo {
    public static void main(String[] args) throws InterruptedException {
        IServiceDiscovery serviceDiscovery = new
                ServiceDiscoveryImpl("192.168.202.128:2181");
        ZKRpcClientProxy rpcClientProxy = new ZKRpcClientProxy(serviceDiscovery);
        ILulfHello hello = rpcClientProxy.clientProxy(ILulfHello.class);
        System.out.println(hello.sayHello("lulf"));
    }
}

这样就完成了注册中心的简单结合,这里还可以通过注解里的 version实现版本的管理,以及如果想要实现负载的效果,由于客户端已经实现了负载的简单实现,只需要启动两个服务端,向zk注册,然后启动客户端去调用即可。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值