• rpc简易实现-zookeeper


      一、RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

      RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
      二、调用过程
      1.调用客户端句柄;执行传送参数
      2.调用本地系统内核发送网络消息
      3.消息传送到远程主机
      4.服务器句柄得到消息并取得参数
      5.执行远程过程
      6.执行的过程将结果返回服务器句柄
      7.服务器句柄返回结果,调用远程系统内核
      8.消息传回本地主机
      9.客户句柄由内核接收消息
      10.客户接收句柄返回的数据
      三、调用过程,大概就是这样子,这样子来张图简单一点
      

      1、这张图是dubbo官网的模型图

      2、过程:服务——>注册地址到zk

            客户端——>通过zk获取对应服务地址

            代理——>通过接口代理,实现服务调用

            调用方式——>这个不一定了,一般tcp方式比较直接

      四、简易版rpc实现,注册中心用的zk,可以使用其他注册中心

      1)目录结构

      

      2)依赖的jar

          <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>Zookeeper</artifactId>
                <version>3.4.0</version>
            </dependency>

      3)zookeeper的连接工具

    import org.apache.zookeeper.*;
    
    import java.io.IOException;
    import java.util.concurrent.CountDownLatch;
    
    public class ZookeeperUtils {
    
        private static ZooKeeper zooKeeper = null;
    
        public static ZooKeeper connect() throws IOException, InterruptedException {
            CountDownLatch latch = new CountDownLatch(1);
            //连接zk
            zooKeeper = new ZooKeeper("localhost:2182", 60000, watchedEvent -> {
                if (watchedEvent.getState() == Watcher.Event.KeeperState.SyncConnected) {
                    latch.countDown();
                }
            });
            //无连接阻塞
            latch.await();
            return zooKeeper;
        }
    
    }

      4)数据传输的实体

    import java.io.Serializable;
    
    /**
     * 请求需要的数据
     */
    public class RpcRequest implements Serializable{
        //接口名称,用于反射
        private String interfaceName;
        //调用方法
        private String method;
        //参数类型
        private Class<?>[] parameterTypes;
        //参数
        private Object[] params;
    
        public String getInterfaceName() {
            return interfaceName;
        }
    
        public void setInterfaceName(String interfaceName) {
            this.interfaceName = interfaceName;
        }
    
        public String getMethod() {
            return method;
        }
    
        public void setMethod(String method) {
            this.method = method;
        }
    
        public Class<?>[] getParameterTypes() {
            return parameterTypes;
        }
    
        public void setParameterTypes(Class<?>[] parameterTypes) {
            this.parameterTypes = parameterTypes;
        }
    
        public Object[] getParams() {
            return params;
        }
    
        public void setParams(Object[] params) {
            this.params = params;
        }
    }
    import java.io.Serializable;
    
    /**
     * 相应对象
     */
    public class RpcResponse implements Serializable {
    
        //返回状态,当然这里可以,加入对应的异常等(这里简化)
        private String status;
        //返回的数据
        private Object data;
    
        public String getStatus() {
            return status;
        }
    
        public void setStatus(String status) {
            this.status = status;
        }
    
        public Object getData() {
            return data;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
    }

      5)测试的服务以及实现

    import com.pinnet.zookeeper.service.ITestService;
    
    public class TestServiceImpl implements ITestService{
    
        public String test(String name) {
            System.out.println(name);
            return "return" + name;
        }
    }

      6)提供方

    import com.pinnet.zookeeper.bean.RpcBeanFactory;
    import com.pinnet.zookeeper.data.RpcRequest;
    import com.pinnet.zookeeper.data.RpcResponse;
    import com.pinnet.zookeeper.service.ITestService;
    import com.pinnet.zookeeper.service.impl.TestServiceImpl;
    import com.pinnet.zookeeper.zookeeper.ZookeeperUtils;
    import org.apache.zookeeper.*;
    
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * rpc服务端
     */
    public class Server {
    
        //服务器设定的目录
        private String registryPath = "/registry";
        //接口,这里方便测试用
        private String serviceName = ITestService.class.getName();
        //地址目录
        private static String addressName = "address";
        //本地地址
        private static String ip = "localhost";
        //监听接口
        public static Integer port = 8000;
    
        //链接zk
        public ZooKeeper connect() throws Exception {
            ZooKeeper zooKeeper = ZookeeperUtils.connect();
            return zooKeeper;
        }
    
        //创建节点,也就是访问的,目录
        public void createNode(ZooKeeper zooKeeper) throws Exception {
            if (zooKeeper.exists(registryPath, false) == null) {
                //创建永久目录,接口服务,可以创建永久目录
                zooKeeper.create(registryPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
    
            String servicePath = registryPath + "/" +serviceName;
            if (zooKeeper.exists(servicePath, false) == null) {
                //接口目录
                zooKeeper.create(servicePath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
    
            String addressPath = servicePath + "/" +addressName;
            //地址目录,这里ip就是本地的地址,用于tcp链接使用
            //这里创建的是临时目录,当zk服务断连过后,自动删除临时节点
            zooKeeper.create(addressPath, (ip + ":"+ port).getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        }
    
        //监听过程
        private void accept() throws Exception {
            //当然这里也可以使用netty来进行监听和其他过程
            //这里简化
            ServerSocket serverSocket = new ServerSocket(port);
            while (true) {
                System.out.println("监听中。。。。。。。");
                Socket socket = serverSocket.accept();
                resultData(socket);
            }
        }
    
        //执行并返回数据
        private void resultData(Socket socket) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            //读取请求的参数
            RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
            //这里从容器中获取bean,当然这里的bean可以自己缓存,独立spring容器之外
            Object bean = RpcBeanFactory.getBean(rpcRequest.getInterfaceName());
            //方法调用
            Method method = bean.getClass().getMethod(rpcRequest.getMethod(), rpcRequest.getParameterTypes());
            Object data = method.invoke(bean, rpcRequest.getParams());
            //返回数据
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            RpcResponse rpcResponse = new RpcResponse();
            rpcResponse.setStatus("success");
            rpcResponse.setData(data);
            objectOutputStream.writeObject(rpcResponse);
        }
    
        public static void main(String[] args) throws Exception {
            //模拟spring容器的加载过程
            RpcBeanFactory.putBean(ITestService.class.getName(), new TestServiceImpl());
            Server server = new Server();
            ZooKeeper zooKeeper = server.connect();
            //创建节点,用于地址访问
            server.createNode(zooKeeper);
            //监听,当然多线程更加理想,这里只显示效果
            server.accept();
        }
    
    }

      7)调用方

    import com.pinnet.zookeeper.bean.RpcBeanFactory;
    import com.pinnet.zookeeper.data.RpcRequest;
    import com.pinnet.zookeeper.data.RpcResponse;
    import com.pinnet.zookeeper.service.ITestService;
    import com.pinnet.zookeeper.zookeeper.ZookeeperUtils;
    import org.apache.zookeeper.ZooKeeper;
    
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * rpc客户端
     */
    public class Client {
    
        //缓存地址
        private Map<String, List<String>> adressMap = new ConcurrentHashMap<>();
    
        //链接zk
        public ZooKeeper connect() throws Exception {
            return ZookeeperUtils.connect();
        }
    
        /**
         * 这里主要是创建代理,利用代理接口实现来达到调用的目的
         * @param interfaceName
         * @return
         * @throws ClassNotFoundException
         */
        public Object createProxy(final String interfaceName) throws ClassNotFoundException {
            //使用线程实例化,主要考虑处理性
            final Class clazz = Thread.currentThread().getContextClassLoader().loadClass(interfaceName);
            //创建代理
            return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
                //用重连计数
                private int num = 5;
    
                @Override
                public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
                    //发送请求需要的请求参数
                    RpcRequest rpcRequest = new RpcRequest();
                    //接口名称
                    rpcRequest.setInterfaceName(interfaceName);
                    //调用方法名
                    rpcRequest.setMethod(method.getName());
                    //对应参数类型
                    rpcRequest.setParameterTypes(method.getParameterTypes());
                    //对应参数
                    rpcRequest.setParams(params);
                    //返回响应结果
                    return sendData(rpcRequest);
                }
    
                //tcp方式调用
                private Object sendData(RpcRequest rpcRequest) throws Exception {
                    //当访问地址存在时
                    if (adressMap.containsKey(interfaceName)) {
                        List<String> adresses = adressMap.get(interfaceName);
                        if (adresses != null && !adresses.isEmpty()) {
                            //如果存在多个地址,使用可以调通的一个
                            for (String adress:adresses) {
                                //这个是注册zk的时候设定的数据
                                String[] strs = adress.split(":");
                                //这里简易版的实现,所以直接使用的socket.
                                //实际上可以采用netty框架编写,保存channel访问就可以了,也可以对数据进行加解密
                                Socket socket = new Socket();
                                try {
                                    //连接可以访问zk,如果连接失败直接抛出异常,循环下一个地址
                                    socket.connect(new InetSocketAddress(strs[0], Integer.valueOf(strs[1])));
                                    ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
                                    objectOutputStream.writeObject(rpcRequest);
                                    ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
                                    //这里是响应数据,也就是执行过后的数据。远程执行结果
                                    RpcResponse rpcResponse = (RpcResponse) objectInputStream.readObject();
                                    return rpcResponse.getData();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                            //设置重连机制,跳出循环
                            if (num == 0) {
                                throw new RuntimeException("server connect fail");
                            }
                            num--;
                            //如果多个地址还是不能访问,则从zk上面更新地址
                            getAddress(interfaceName);
                            //如果多个地址还是不能访问,则重新访问
                            sendData(rpcRequest);
                        }
                        throw new RuntimeException("not found service");
                    }else {
                        //如果没有地址存储时的访问,主要是请求链接问题
                        if (num == 0) {
                            throw new RuntimeException("not found server");
                        }
                        num--;
                        getAddress(interfaceName);
                        return sendData(rpcRequest);
                    }
                }
            });
        }
    
        //从zk获取最新的地址
        private void getAddress(String interfaceName) throws Exception {
            //链接zk
            ZooKeeper zooKeeper = connect();
            //设定的地址目录
            String interfacePath = "/registry/" + interfaceName;
            //获取地址目录
            List<String> addresses = zooKeeper.getChildren(interfacePath, false);
            if (addresses != null && !addresses.isEmpty()) {
                List<String> datas = new ArrayList<>();
                for (String address:addresses) {
                    //获取数据,也就是配置对应的访问地址
                    byte[] bytes = zooKeeper.getData(interfacePath + "/" + address, false, null);
                    if (bytes.length > 0) {
                        //放入数组
                        datas.add(new String(bytes));
                    }
                }
                //加入缓存
                adressMap.put(interfaceName, datas);
            }
        }
    
        public static void main(String[] args) throws ClassNotFoundException, InterruptedException {
            Client client = new Client();
            //这一步是模拟spring容器放入bean的过程,实际用spring容器可自定义标签实现
            RpcBeanFactory.putBean("testService", client.createProxy(ITestService.class.getName()));
            //获取bean的过程,实际上实现是代理接口的实现
            ITestService testService = (ITestService) RpcBeanFactory.getBean("testService");
            //调用方法,也就是调用代理的过程
            String test = testService.test("test");
            System.out.println(test);
        }
    }

      五、代码部分就这么多,看一下测试结果

      服务端:

      

      客户端:

      

      zookeeper的展示部分

      

      六、源码分享

      rpc-zk代码:https://github.com/lilin409546297/rpc-zk

      zkui代码:https://github.com/lilin409546297/zkui(这个是别人做的,我放在github的)

      zookeeper下载地址:http://apache.org/dist/zookeeper/

  • 相关阅读:
    004: 基本数据类型-List
    003: 基本类型-字符串类型
    002: 基本类型-数值型(int, float)
    001: Hello World
    Python中的单例模式的几种实现方式的及优化
    django之admin组件
    权限管理---设计分析以及具体细节
    基于Form组件实现的增删改和基于ModelForm实现的增删改
    Python常见问题系列
    django的render的说明
  • 原文地址:https://www.cnblogs.com/ll409546297/p/9724485.html
Copyright © 2020-2023  润新知