• NIO单一长连接——dubbo通信模型实现


    转:

    NIO单一长连接——dubbo通信模型实现

    1.2 2018.07.15 19:04* 字数 2552 阅读 6001评论 30

    前言

    前一段时间看了下dubbo,原想将dubbo详细总结下来,从使用简介、SPI扩展机制、Spring的schema扩展、启动过程、动态注册与发现、分层设计、通信设计、线程模型等方面来总结,但是越看越发现架子太大,涉及的点太广,反而RPC的思想其实已经印象深刻了,再来总结这么多的点似乎不太值得,因为不懂的东西才是最有价值的,所以有了本文,将个人认为dubbo中比较有特色的通信模型总结于此,本文是一个demo,当然不乏一些脑补的东西在里面,如您偶然阅读此文发现问题,还请不吝指出问题所在。

    BIO通信缺陷

    为了相对更好的理解dubbo这个通信模型的优势,首先需要回顾一下BIO通信的缺陷。
    为此引用一下我之前借鉴dubbo作者梁飞的博客(RPC框架几行代码就够了)而实现的一个用BIO实现的RPC示例:RPC原理简析——三分钟看完,这里使用的是BIO的socket和ServerSocket实现通讯,用流来写入写出数据。BIO的完整连接示意图如下:

    完整BIO连接示意图

    缺陷一、IO阻塞

    可以看出,在BIO中,除了建立连接比较耗时之外,在客户端将数据传输到服务端之前,服务端的IO(输入流)阻塞,然后在服务端将返回值传输回来之前,客户端的IO(输入流)阻塞。也就是说在一次RPC调用开始到完成之前,这个连接一直被此次调用所占用,但是实际上这次调用中,真正需要网络连接的只有中间的数据传输过程,在客户端写出和服务端读取并执行远端方法这两个时间点,其实网络连接是空闲的。这就是BIO连接中浪费了网络资源的地方。

    缺陷二、大量连接

    由于BIO的IO阻塞,导致每次RPC调用会占用一个连接。而正因为如此,为了减少频繁创建连接消耗的时间,引入了连接池(此处的连接池指普通的HTTP连接池,非异步连接池)的概念,连接池解决了频繁创建连接的资源消耗,但是没有解决根本性的阻塞问题。而且在服务消费者(客户端)数量远大于服务提供者(服务端)数量的时候,会导致服务提供者建立了大量的连接,而本身由于硬件资源的限制,单机最大连接数是有限的(这个限制以前是1w,也就是以前的C10K问题,据说近几年已经提升至50万个连接),所以在服务消费者过多,而服务提供者数量过少的情况下,服务提供者有因为过多的连接而被拖垮的风险(这需要极大的并发数,每秒上百万次的调用)。当然,要解决这个问题,增加机器,从而增加服务提供者数量是可以解决的,但是没有充分利用单机性能。

    建立大量连接的另一个弊端,是操作系统频繁的线程上下文切换,因为连接数过多,线程切换频繁,会消耗大量的资源,而且这些切换可能不是必要的。比如当前建立了大量的连接,可能大部分处于阻塞状态,根本没有挨个挨个切换的必要。但是因为操作系统任务调度时并不会忽略阻塞状态的线程,所以造成浪费。

    NIO单一长连接实现分析

    长连接宏观简介

    长连接其实不算NIO这里的特点,因为BIO也可以实现长连接(每次写完数据之后手动写入结束符而不关闭流就可以了),而且连接池一般也是使用的长连接方式。

    NIO真正解决的是阻塞问题,因为阻塞问题解决了,所以也就不需要大量连接了。由于篇幅问题,此处不讨论NIO的细节(API),从相对宏观的角度介绍大体角色和调用方式,NIO三种角色示意如下图:

    NIO角色示意图

    NIO由三种角色组成,Selector、SocketChannel、Buffer。

    SocketChannel相当于BIO中的Socket,分为SocketChannel和ServerSocketChannel两种,是真正建立连接并传输数据的管道。这个管道不同于BIO的Socket的点就是,这个管道可以被多个线程共用,线程A使用这个管道写出数据了之后,线程B还可以使用这个管道写出数据,不再被某一次调用所独占。所以就可以不需要像BIO一样建立那么多的连接,一个客户端的一个连接就够了(当然,实际应用中因为机器都是多核,实际上建立核数个连接个人感觉是比较好的)。

    Buffer是用来与SocketChannel互通数据的对象,本质上是一块内存区域。SocketChannel是不支持直接读写数据的,所有的读写操作必须通过Buffer来实现。值得一提的是,我们经常说在JVM中有的时候会使用虚拟机之外的内存,说的就是NIO中的Buffer,在分配内存的时候可以选择使用虚拟机外内存,减少数据的复制。

    Selector是用来监控SocketChannel的事件的,其实是实现非阻塞的关键。NIO是基于事件的,将BIO中的流式传输改为了事件机制。BIO中,一个连接拥有一个输入/输出流,只要数据传输完成,流就可以读取数据。在NIO中,Selector定义了四种事件,OP_READ、OP_WRITE、OP_CONNECT、OP_ACCEPT。当服务端或者客户端收到写入完成的一次数据时,会触发OP_READ事件,此时可以从连接中读取数据。同理,当可以往连接中写入数据的时候,触发OP_WRITE事件(但是一般情况下这个事件没有必要,因为连接一般都是可写的)。客户端与服务端建立连接的时候,客户端会收到OP_CONNECT事件,而服务端会触发OP_ACCEPT事件。通过这一系列事件将数据的发送与读写解耦,实现异步调用。将一个SocketChannel+一个事件绑定在一个Selector上,Selector本质上是轮询每一个SocketChannel,如果没有事件触发,那么线程阻塞,如果有事件触发,返回对应的SocketChannel,以便进行后续的处理。

    使用NIO设计RPC调用分析

    前面提到,由于NIO的SocketChannel是非阻塞的,所以不再需要连接池,使用一个连接就够了。

    NIO单一长连接RPC线程模型

    但是如果真的使用NIO来进行RPC调用的话,会有数据和调用方对应不上的问题,如下图:

    NIO异步顺序问题

    如上图所示,如果多个线程共用一个连接,那么每个线程调用之后返回的顺序是不可控的,所以有可能先发出数据的反而后得到返回值,这就使得数据对应不上了。个人觉得因为这一点,NIO及其适合聊天室类型的设计,因为每个聊天方都是一个单独的SocketChannel连接,而此时并没有顺序问题。

    但是对RPC调用来说,每次调用的返回值必须与调用方对应上,为此,Dubbo的设计是给每个请求设计一个请求id,在发送请求与发送返回值时都带上这个id。详细思路如下图:

    NIO单一长连接的RPC设计

    业务线程在发出请求之前,需要存储一个请求对象,同时挂起相应的业务线程(挂起不会被任务调度,所以不存在线程切换消耗),这个请求对象包含了此次请求的id,然后在获取服务端返回的数据的时候,解析出这个id,通过这个id取出请求对象,并唤醒对应的线程。

    NIO单一长连接实现demo

    废话了这么多,终于可以上代码了,我这里服务端使用了线程池去执行远端方法,使得真正的服务端线程只需要读取数据就可以了。可能作为demo来讲,写的有些复杂,但是理解的时候以RpcNioConsumer和RpcNioProvider作为入口就比较好梳理了。NIO的主要实现在RpcNioMultClient和RpcNioMultServer中。

    工具类

    序列化/反序列化工具类

    package com.liepin.common;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    /**
     * @author: lifs
     * @create: 2018-06-27 22:03
     **/
    public class SerializeUtil {
    
        public static byte[] serialize(Object obj) {
            try {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(bos);
                oos.writeObject(obj);
                oos.flush();
                byte[] bytes = bos.toByteArray();
                return bytes;
            } catch (IOException e) {
                System.out.println("序列化对象出错!");
                e.printStackTrace();
                return null;
            }
        }
    
        public static Object unSerialize(byte[] bytes) {
            try {
                ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
                ObjectInputStream ois = new ObjectInputStream(bis);
                return ois.readObject();
            } catch (IOException e) {
                System.out.println("反序列化出错!");
                e.printStackTrace();
                return null;
            } catch (ClassNotFoundException e) {
                System.out.println("反序列化出错!");
                e.printStackTrace();
                return null;
            }
        }
    }
    

    服务端代码

    RPC调用的接口:

    package com.liepin.service;
    
    /**
     * @author: lifs
     * @create: 2018-04-01 20:35
     **/
    public interface HelloService {
        public String sayHello(String name);
    }
    

    服务端的接口实现:

    package com.liepin.service;
    /**
     * @author: lifs
     * @create: 2018-04-01 20:40
     **/
    public class HelloServiceImpl implements HelloService {
        @Override
        public String sayHello(String name) {
            return "hello " + name;
        }
    }
    

    服务端,用于与客户端建立连接、读取数据:

    package com.liepin.niomultipart;
    
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    import com.liepin.common.BeanContainer;
    import com.liepin.common.RequstMultObject;
    import com.liepin.common.SerializeUtil;
    import com.liepin.common.ThreadPoolUtil;
    
    /**
     * @author: lifs
     * @create: 2018-07-01 11:38
     **/
    public class RpcNioMultServer {
    
        // 通道管理器
        private Selector selector;
    
        public static void start() throws IOException {
            RpcNioMultServer server = new RpcNioMultServer();
            server.initServer(8080);
            server.listen();
        }
    
        /**
         * 获得一个ServerSocket通道,并对该通道做一些初始化的工作
         *
         * @param port
         *            绑定的端口号
         * @throws IOException
         */
        public void initServer(int port) throws IOException {
            // 获得一个ServerSocket通道
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            // 设置通道为非阻塞
            serverChannel.configureBlocking(false);
            // 将该通道对应的ServerSocket绑定到port端口
            serverChannel.socket().bind(new InetSocketAddress(port));
            // 获得一个通道管理器
            this.selector = Selector.open();
            // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
            // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        }
    
        public void listen() {
            System.out.println("服务端启动成功!");
            // 轮询访问selector
            while (true) {
                try {
                    // 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
                    selector.select();
                    // 获得selector中选中的项的迭代器,选中的项为注册的事件
                    Iterator ite = selector.selectedKeys().iterator();
                    while (ite.hasNext()) {
                        SelectionKey key = (SelectionKey) ite.next();
                        // 删除已选的key,以防重复处理
                        ite.remove();
                        // 客户端请求连接事件
                        if (key.isAcceptable()) {
                            ServerSocketChannel server = (ServerSocketChannel) key.channel();
                            // 获得和客户端连接的通道
                            SocketChannel channel = server.accept();
                            // 设置成非阻塞
                            channel.configureBlocking(false);
    
                            // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
                            channel.register(this.selector, SelectionKey.OP_READ);
    
                            // 获得了可读的事件
                        } else if (key.isReadable()) {
                            SocketChannel channel = (SocketChannel) key.channel();
                            byte[] bytes = readMsgFromClient(channel);
                            if (bytes != null && bytes.length > 0) {
                                // 读取之后将任务放入线程池异步返回
                                RpcNioMultServerTask task = new RpcNioMultServerTask(bytes, channel);
                                ThreadPoolUtil.addTask(task);
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
        }
    
        public byte[] readMsgFromClient(SocketChannel channel) {
            ByteBuffer byteBuffer = ByteBuffer.allocate(4);
            try {
                // 首先读取消息头(自己设计的协议头,此处是消息体的长度)
                int headCount = channel.read(byteBuffer);
                if (headCount < 0) {
                    return null;
                }
                byteBuffer.flip();
                int length = byteBuffer.getInt();
                // 读取消息体
                byteBuffer = ByteBuffer.allocate(length);
                int bodyCount = channel.read(byteBuffer);
                if (bodyCount < 0) {
                    return null;
                }
                return byteBuffer.array();
            } catch (IOException e) {
                System.out.println("读取数据异常");
                e.printStackTrace();
                return null;
            }
        }
    }
    
    

    线程池工具类

    package com.liepin.common;
    
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    import com.liepin.niomultipart.RpcNioMultServerTask;
    
    /**
     * @author: lifs
     * @create: 2018-07-01 21:39
     **/
    public class ThreadPoolUtil {
    
        private static volatile ThreadPoolExecutor executor;
    
        public static void init() {
            if (executor == null) {
                synchronized (ThreadPoolUtil.class) {
                    if (executor == null) {
                        executor = new ThreadPoolExecutor(10, 20, 200, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
                    }
                }
            }
        }
    
        public static void addTask(RpcNioMultServerTask task) {
            if (executor == null) {
                init();
            }
            executor.execute(task);
        }
    }
    
    

    用于获取数据执行远端方法的线程池任务类:

    package com.liepin.niomultipart;
    
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.nio.ByteBuffer;
    import java.nio.channels.SocketChannel;
    
    import com.liepin.common.BeanContainer;
    import com.liepin.common.RequstMultObject;
    import com.liepin.common.SerializeUtil;
    
    /**
     * 服务端线程池任务
     * 
     * @author: lifs
     * @create: 2018-07-01 21:18
     **/
    public class RpcNioMultServerTask implements Runnable {
    
        private byte[] bytes;
    
        private SocketChannel channel;
    
        public RpcNioMultServerTask(byte[] bytes, SocketChannel channel) {
            this.bytes = bytes;
            this.channel = channel;
        }
    
        @Override
        public void run() {
            if (bytes != null && bytes.length > 0 && channel != null) {
                // 反序列化
                RequstMultObject requstMultObject = (RequstMultObject) SerializeUtil.unSerialize(bytes);
                // 调用服务并序列化结果然后返回
                requestHandle(requstMultObject, channel);
            }
        }
    
        public void requestHandle(RequstMultObject requstObject, SocketChannel channel) {
            Long requestId = requstObject.getRequestId();
            Object obj = BeanContainer.getBean(requstObject.getCalzz());
            String methodName = requstObject.getMethodName();
            Class<?>[] parameterTypes = requstObject.getParamTypes();
            Object[] arguments = requstObject.getArgs();
            try {
                Method method = obj.getClass().getMethod(methodName, parameterTypes);
                String result = (String) method.invoke(obj, arguments);
                byte[] bytes = SerializeUtil.serialize(result);
                ByteBuffer buffer = ByteBuffer.allocate(bytes.length + 12);
                // 为了便于客户端获得请求ID,直接将id写在头部(这样客户端直接解析即可获得,不需要将所有消息反序列化才能得到)
                // 然后写入消息题的长度,最后写入返回内容
                buffer.putLong(requestId);
                buffer.putInt(bytes.length);
                buffer.put(bytes);
                buffer.flip();
                channel.write(buffer);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | IOException e) {
                e.printStackTrace();
            }
        }
    
        public SocketChannel getChannel() {
            return channel;
        }
    
        public void setChannel(SocketChannel channel) {
            this.channel = channel;
        }
    }
    
    

    服务端服务发布:

    package com.liepin.rpc;
    
    import java.io.IOException;
    
    import com.liepin.common.BeanContainer;
    import com.liepin.niomultipart.RpcNioMultServer;
    import com.liepin.service.HelloService;
    import com.liepin.service.HelloServiceImpl;
    
    /**
     * @author: lifs
     * @create: 2018-06-28 23:24
     **/
    public class RpcNioProvider {
        public static void main(String[] args) throws IOException {
            // 将服务放进bean容器
            HelloService helloService = new HelloServiceImpl();
            BeanContainer.addBean(HelloService.class, helloService);
            // 启动NIO服务端
            startMultRpcNioServer();
        }
    
        public static void startMultRpcNioServer() {
            Runnable r = () -> {
                try {
                    RpcNioMultServer.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            };
            Thread t = new Thread(r);
            t.start();
        }
    }
    

    服务端服务发布时使用的bean容器:

    package com.liepin.common;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * @author: lifs
     * @create: 2018-06-28 07:52
     **/
    public class BeanContainer {
    
        private static ConcurrentHashMap<Class<?>, Object> container = new ConcurrentHashMap<>();
    
        public static boolean addBean(Class<?> clazz, Object object) {
            container.put(clazz, object);
            return true;
        }
    
        public static Object getBean(Class<?> clazz) {
            return container.get(clazz);
        }
    }
    

    客户端代码

    客户端:

    package com.liepin.niomultipart;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    
    import com.liepin.common.RpcContainer;
    
    /**
     * @author: lifs
     * @create: 2018-07-01 01:12
     **/
    public class RpcNioMultClient {
    
        private static RpcNioMultClient rpcNioClient;
    
        // 通道管理器
        private Selector selector;
    
        // 通道
        private SocketChannel channel;
    
        private String serverIp = "localhost";
    
        private int port = 8080;
    
        private RpcNioMultClient() {
            // 初始化client
            initClient();
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    listen();
                }
            };
            Thread t = new Thread(runnable);
            t.start();
        }
    
        public static RpcNioMultClient getInstance() {
            if (rpcNioClient == null) {
                synchronized (RpcNioMultClient.class) {
                    if (rpcNioClient == null) {
                        rpcNioClient = new RpcNioMultClient();
                    }
                }
            }
            return rpcNioClient;
        }
    
        public void initClient() {
            try {
                // 打开一个通道
                channel = SocketChannel.open();
                // 设置为非阻塞通道(异步)
                channel.configureBlocking(false);
                // 获得通道管理器,用于监听通道事件
                selector = Selector.open();
                // 建立连接
                channel.connect(new InetSocketAddress(serverIp, port));
                // 由于是非阻塞的,所以有可能连接并未建立完成,调用finishConnect完成连接
                if (channel.isConnectionPending()) {
                    channel.finishConnect();
                }
                System.out.println("客户端初始化完成,建立连接完成");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public void listen() {
            try {
                while (true) {
                    // 绑定到通道管理器,监听可读事件,因为客户端只需要从服务端获得数据然后读取,所以只需要监听READ事件
                    channel.register(selector, SelectionKey.OP_READ);
                    // 开始轮询READ事件
                    selector.select();
                    Iterator ite = selector.selectedKeys().iterator();
                    while (ite.hasNext()) {
                        SelectionKey key = (SelectionKey) ite.next();
                        // 删除已选的key,以防重复处理
                        ite.remove();
                        if (key.isReadable()) {
                            // 读取信息
                            readMsgFromServer();
                        }
                    }
                }
            } catch (IOException e) {
                System.out.println("客户端建立连接失败");
            }
        }
    
        public boolean sendMsg2Server(byte[] bytes) {
            try {
                ByteBuffer buffer = ByteBuffer.allocate(bytes.length + 4);
                // 放入消息长度,然后放入消息体
                buffer.putInt(bytes.length);
                buffer.put(bytes);
                // 写完之后buffer设置为可读状态
                buffer.flip();
                // 写出消息
                channel.write(buffer);
            } catch (IOException e) {
                System.out.println("客户端写出消息失败!");
                e.printStackTrace();
            }
            return true;
        }
    
        public void readMsgFromServer() {
            ByteBuffer byteBuffer;
            try {
                // 首先读取请求id
                byteBuffer = ByteBuffer.allocate(8);
                int readIdCount = channel.read(byteBuffer);
                if (readIdCount < 0) {
                    return;
                }
                byteBuffer.flip();
                Long requsetId = byteBuffer.getLong();
    
                // 读取返回值长度
                byteBuffer = ByteBuffer.allocate(4);
                int readHeadCount = channel.read(byteBuffer);
                if (readHeadCount < 0) {
                    return;
                }
                // 将buffer切换为待读取状态
                byteBuffer.flip();
                int length = byteBuffer.getInt();
    
                // 读取消息体
                byteBuffer = ByteBuffer.allocate(length);
                int readBodyCount = channel.read(byteBuffer);
                if (readBodyCount < 0) {
                    return;
                }
                byte[] bytes = byteBuffer.array();
    
                // 将返回值放入指定容器
                RpcContainer.addResponse(requsetId, bytes);
            } catch (IOException e) {
                System.out.println("读取数据异常");
                e.printStackTrace();
            }
        }
    }
    

    请求时的数据结构

    package com.liepin.common;
    
    import java.io.Serializable;
    
    /**
     * @author: lifs
     * @create: 2018-07-01 11:10
     **/
    public class RequstMultObject implements Serializable {
    
        private static final long serialVersionUID = 3132836600205356306L;
    
        // 请求id
        private Long requestId;
    
        // 服务提供者接口
        private Class<?> calzz;
    
        // 服务的方法名称
        private String methodName;
    
        // 参数类型
        private Class<?>[] paramTypes;
    
        // 参数
        private Object[] args;
    
        public RequstMultObject(Class<?> calzz, String methodName, Class<?>[] paramTypes, Object[] args) {
            this.calzz = calzz;
            this.methodName = methodName;
            this.paramTypes = paramTypes;
            this.args = args;
        }
    
        public Long getRequestId() {
            return requestId;
        }
    
        public void setRequestId(Long requstId) {
            this.requestId = requstId;
        }
    
        public Class<?> getCalzz() {
            return calzz;
        }
    
        public void setCalzz(Class<?> calzz) {
            this.calzz = calzz;
        }
    
        public String getMethodName() {
            return methodName;
        }
    
        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }
    
        public Class<?>[] getParamTypes() {
            return paramTypes;
        }
    
        public void setParamTypes(Class<?>[] paramTypes) {
            this.paramTypes = paramTypes;
        }
    
        public Object[] getArgs() {
            return args;
        }
    
        public void setArgs(Object[] args) {
            this.args = args;
        }
    }
    
    

    请求数据获取类,用来挂起和唤醒挂起的线程:

    package com.liepin.common;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author: lifs
     * @create: 2018-07-01 16:53
     **/
    public class RpcResponseFuture {
    
        private final Lock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();
        private Long requstId;
    
        public RpcResponseFuture(Long requstId) {
            this.requstId = requstId;
        }
    
        public byte[] get() {
            byte[] bytes = RpcContainer.getResponse(requstId);
            if (bytes == null || bytes.length < 0) {
                lock.lock();
                try {
                    System.out.println("请求id:" + requstId + ",请求结果尚未返回,线程挂起");
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
            System.out.println("请求id:" + requstId + ",请求结果返回,线程挂起结束");
            return RpcContainer.getResponse(requstId);
        }
    
        public void rpcIsDone() {
            lock.lock();
            try {
                condition.signal();
            } finally {
                lock.unlock();
            }
        }
    
        public Long getRequstId() {
            return requstId;
        }
    
        public void setRequstId(Long requstId) {
            this.requstId = requstId;
        }
    }
    
    

    RPC容器 用来存储发送RPC请求时的请求对象,以及存储返回值

    package com.liepin.common;
    
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.atomic.AtomicLong;
    
    /**
     * @author: lifs
     * @create: 2018-07-01 10:58
     **/
    public class RpcContainer {
        //返回值容器
        private static ConcurrentHashMap<Long, byte[]> responseContainer = new ConcurrentHashMap<>();
        //请求对象容器
        private static ConcurrentHashMap<Long, RpcResponseFuture> requestFuture = new ConcurrentHashMap<>();
        //请求id
        private volatile static AtomicLong requstId = new AtomicLong(0);
    
        public static Long getRequestId() {
            return requstId.getAndIncrement();
        }
    
        public static void addResponse(Long requestId, byte[] responseBytes) {
            responseContainer.put(requestId, responseBytes);
            RpcResponseFuture responseFuture = requestFuture.get(requestId);
            responseFuture.rpcIsDone();
        }
    
        public static byte[] getResponse(Long requestId) {
            return responseContainer.get(requestId);
        }
    
        public static void addRequstFuture(RpcResponseFuture rpcResponseFuture) {
            requestFuture.put(rpcResponseFuture.getRequstId(), rpcResponseFuture);
        }
    
        public static RpcResponseFuture getRpcRequstFutue(Long requestId) {
            return requestFuture.get(requestId);
        }
    
        public static void removeResponseAndFuture(Long requestId) {
            responseContainer.remove(requestId);
            requestFuture.remove(requestId);
        }
    }
    
    

    RPC代理工厂类:

    package com.liepin.proxy;
    
    import java.lang.reflect.Proxy;
    
    import com.liepin.niomultipart.RpcNIoMultHandler;
    /**
     * @author: lifs
     * @create: 2018-06-28 23:35
     **/
    public class RpcProxyFactory {
        /**
         * 多线程环境代理对象
         * 
         * @param interfaceClass
         * @return T
         * @throws BizException
         * @createTime:2018/7/1
         * @author: shakeli
         */
        public static <T> T getMultService(Class<T> interfaceClass) {
            return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[] { interfaceClass },
                    new RpcNIoMultHandler());
        }
    }
    

    实际代理类

    package com.liepin.niomultipart;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    import com.liepin.common.RequstMultObject;
    import com.liepin.common.RpcContainer;
    import com.liepin.common.RpcResponseFuture;
    import com.liepin.common.SerializeUtil;
    
    /**
     * @author: lifs
     * @create: 2018-07-01 11:41
     **/
    public class RpcNIoMultHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 获得请求id
            Long responseId = RpcContainer.getRequestId();
            // 封装请求对象
            RequstMultObject requstMultObject = new RequstMultObject(method.getDeclaringClass(), method.getName(),
                    method.getParameterTypes(), args);
            requstMultObject.setRequestId(responseId);
    
            // 封装设置rpcResponseFuture,主要用于获取返回值
            RpcResponseFuture rpcResponseFuture = new RpcResponseFuture(responseId);
            RpcContainer.addRequstFuture(rpcResponseFuture);
    
            // 序列化
            byte[] requstBytes = SerializeUtil.serialize(requstMultObject);
            // 发送请求信息
            RpcNioMultClient rpcNioMultClient = RpcNioMultClient.getInstance();
            rpcNioMultClient.sendMsg2Server(requstBytes);
    
            // 从ResponseContainer获取返回值
            byte[] responseBytes = rpcResponseFuture.get();
            if (requstBytes != null) {
                RpcContainer.removeResponseAndFuture(responseId);
            }
    
            // 反序列化获得结果
            Object result = SerializeUtil.unSerialize(responseBytes);
            System.out.println("请求id:" + responseId + " 结果:" + result);
            return result;
        }
    }
    

    客户端RPC调用:

    package com.liepin.rpc;
    
    import com.liepin.proxy.RpcProxyFactory;
    import com.liepin.service.HelloService;
    
    /**
     * @author: lifs
     * @create: 2018-06-28 23:34
     **/
    public class RpcNioConsumer {
        public static void main(String[] args) {
            multipartRpcNio();
        }
    
        /**
         * 多线程IO调用示例
         * 
         * @param
         * @return void
         * @throws BizException
         * @createTime:2018/7/1
         * @author: shakeli
         */
        public static void multipartRpcNio() {
            HelloService proxy = RpcProxyFactory.getMultService(HelloService.class);
            for (int i = 0; i < 100; i++) {
                final int j = i;
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        String result = proxy.sayHello("world!");
                    }
                };
                Thread t = new Thread(runnable);
                t.start();
            }
        }
    }
    

    技术博客

  • 相关阅读:
    Redis分布式锁
    面试题2
    JVM面试题
    ThreadLocal相关代码和面试题
    Redis淘汰策略及LRU算法
    线上一些问题排查
    面试题1
    webmagic坑日志slf4j和springboot日志冲突
    URL using bad/illegal format or missing URL
    Git修复已发布版本的 Bug
  • 原文地址:https://www.cnblogs.com/libin6505/p/11265474.html
Copyright © 2020-2023  润新知