转:

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

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

前言

前一段时间看了下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();
}
}
}

技术博客

NIO单一长连接——dubbo通信模型实现的更多相关文章

  1. dubbo协议下的单一长连接与多线程并发如何协同工作

    上班的路上突然就冒出了这么个问题:既然在dubbo中描述消费者和提供者之间采用的是单一长连接,那么如果消费者端是高并发多线程模型的web应用,单一长连接如何解决多线程并发请求问题呢? 其实如果不太了解 ...

  2. 71.Android之长连接实现

    转载:http://blog.csdn.net/qq_23547831/article/details/51690047 本文中我们将讲解一下App的长连接实现.一般而言长连接已经是App的标配了,推 ...

  3. 聊聊iOS中网络编程长连接的那些事

    1.长连接在iOS开发中的应用 常见的短连接应用场景:一般的App的网络请求都是基于Http1.0进行的,使用的是NSURLConnection.NSURLSession或者是AFNetworking ...

  4. 170122、Netty 长连接服务

    推送服务 还记得一年半前,做的一个项目需要用到 Android 推送服务.和 iOS 不同,Android 生态中没有统一的推送服务.Google 虽然有 Google Cloud Messaging ...

  5. LinkedIn的即时消息:在一台机器上支持几十万条长连接

    最近我们介绍了LinkedIn的即时通信,最后提到了分型指标和读回复.为了实现这些功能,我们需要有办法通过长连接来把数据从服务器端推送到手机或网页客户端,而不是许多当代应用所采取的标准的请求-响应模式 ...

  6. nginx长连接设置

    http { keepalive_timeout 20; --长连接timeout keepalive_requests 8192; --每个连接最大请求数} events { worker_conn ...

  7. Comet:基于 HTTP 长连接的“服务器推”技术

    “服务器推”技术的应用 请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档.教程.论坛.blog.wiki 和新闻.任何 Ajax 的新信息都能在这里找到. c ...

  8. Comet技术详解:基于HTTP长连接的Web端实时通信技术

    前言 一般来说,Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Ser ...

  9. comet基于HTTP长连接技术(java即时通信,推送技术详解)

    服务器推送技术的基础思想是将浏览器主动查询信息改为服务器主动发送信息,服务器发送一批数据,浏览器显示消息,同时保证与服务器的连接,当服务器需要再一次的发送数据,浏览器显示数据并保持连接. comet基 ...

随机推荐

  1. Image Processing and Analysis_8_Edge Detection:Learning to Detect Natural Image Boundaries Using Local Brightness, Color, and Texture Cues ——2004

    此主要讨论图像处理与分析.虽然计算机视觉部分的有些内容比如特 征提取等也可以归结到图像分析中来,但鉴于它们与计算机视觉的紧密联系,以 及它们的出处,没有把它们纳入到图像处理与分析中来.同样,这里面也有 ...

  2. 网络分类及OSI七层模型

    一.网络分类: 局域网(LAN)是指在某一区域内由多台计算机互联成的计算机组.一般是方圆几千米以内.局域网可以实现文件管理.应用软件共享.打印机共享.工作组内的日程安排.电子邮件和传真通信服务等功能. ...

  3. Linux中找回误删除的文件

    Linux中找回误删除的文件 作为一个多用户.多任务的Linux操作系统,会出现在没有备份的情况下将一些用户文件误删的情况,Linux下的文件一旦被删除,是难以恢复的.尽管删除命令只是在文件节点中作删 ...

  4. 三星Q470c Logo界面无限掉电重启,变砖后的挽救过程

    背景 三星笔记本的部分型号如:NP530 Q470等 安装win8后再次重装系统(我弄了个Ubuntu18)会导致无法进入BIOS菜单页面的问题.启动显示logo页面后,能够听到明显啪的一声(硬盘掉电 ...

  5. webpack中环境变量的使用方法

    这节课讲解一下,在webpack打包过程中,怎么去使用一些环境变量. 首先我有一个打包配置的三个文件 "scripts": { "dev-build": &qu ...

  6. vue 后台获取文件流导出excel文件

    let params = { compStartTm: Date.parse(this.searchForm.compStartTm) / 1000, compEndTm: Date.parse(th ...

  7. mysql基础篇--表的管理

    表的创建 常见的数据类型 数值型: 整型 tinyint.smallint.mediumint.int/integer.bigint 特点: 1.如果不设置无符号还是有符号,默认是有符号,如果想设置无 ...

  8. 使用 uni-app 开发遇到的问题

    想法: uni-app 给我的感觉就像是把微信小程序的API,组件和vue的语法揉捏在一起所组成框架,没有原生小程序和vue那种流畅感,官方的 uni-ui 组件库不够成熟,坑比较多.   问题一:自 ...

  9. Win7下如何安装切换jdk7和jdk8

    一.安装好JDK1.7和1.8. 二.配置环境变量: 1. 创建三个JAVA_HOME.JAVA7_HOME,存放JDK7的安装路径.JAVA8_HOME,存放JDK8的安装路径.JAVA_HOME, ...

  10. poj 2762

    Tarjan + TopsortTarjan 缩点Topsort 判断 Topsort 判断:在DAG中若初始状态下存在多于1个入度为0的点则说明这些 入度为0的点之间不会有路径可达若不存在入度为0的 ...