一般情况下每个service的调用的过程都是同步的,例如在一个service中通过RestTemplate
调用一个接口,这样也可以认为是一个远程调用,这种是同步进行的,整个调用的思路如下图。调用线程只需要等待调用结果,并且返回即可。这是一种简单的调用方式。
但是,在使用netty时,整个调用过程是一个异步的过程,上图那种思路就无法达到预期效果。整个调用过程如下图。结果响应之后,selector不知道将结果交给哪个线程处理。
解决这种异步转同步的过程比较复杂。因此为了使得之前的内容更好理解,并不曾说明此问题。只是在代码中傻傻的写了两行,休眠,然后等待其结果返回。
//等待其返回
Thread.sleep (1000L);
Object result = client.getResult ();
这种方式,是一中愚蠢的方式。在一个高性能的框架中,显然不能通过这种方式实现,难道每个接口的调用都要等1秒才返回吗?
异步转同步解决思路
之前的文章中写到,在每个请求的请求信息中,有一个使用AtomicLong类生成的id。这样思路就很明显了,在请求之后,每个线程只需要等待其对应请求编号id响应的结果就好。
将所有的等待响应的请求存放在一个Map中,为了这个Map更加高效、线程安全,采用ConcurrentHashMap
。
每一个正在等待响应的请求都要有一个唯一的id,因此采用AtomicLong
获取id。这个id,只需要在所有等待响应的请求中保证唯一即可。
核心类DefaultFuture
。
public class DefaultFuture
{
private static final Map<Long, DefaultFuture> FUTURES = new ConcurrentHashMap<Long, DefaultFuture> ();
private final DubboRequest request;
private final Long reqId;
//加volatile关键字
private volatile DubboResponse response;
public DefaultFuture (DubboRequest request)
{
this.request = request;
this.reqId = request.getMId ();
FUTURES.put (reqId,this);
}
public synchronized Object get() {
while (response == null) {
try
{
wait (500l);
System.out.println ("————请求未响应————"+reqId+request.getMData ());
}
catch (InterruptedException e)
{
e.printStackTrace ();
}
}
return response.getMData ();
}
public static void received(DubboResponse response){
DefaultFuture future = FUTURES.remove(response.getMId ());
if (future != null)
{
future.doReceived (response);
}
}
private synchronized void doReceived(DubboResponse rsp){
this.response = rsp;
notify ();
}
}
利用wait/notify
实现响应之后立即唤醒对应线程。
当channel发送消息之后,创建DefaultFuture对象,并且调用get方法进行等待。
DubboRequest req = new DubboRequest ();
RpcInvocation rpc = this.getRpc(serivceClass,method,args);
req.setMData (rpc);
channel.writeAndFlush (req);
DefaultFuture defaultFuture = new DefaultFuture (req);
//等待其返回
Object result = defaultFuture.get ();
return result;
此思路,与dubbo中com.alibaba.dubbo.remoting.exchange.support.DefaultFuture
实现思路一致,另外为了更方便理解,采用synchronized+wait/notify
实现等待通知,对java锁机制更加熟悉的小伙伴可以使用ReentrantLock
实现等待通知,锁范围更小,性能更佳。
源码地址:gitee