Dubbo——Exchange信息交换层


ReferenceCountExchangeClient:将请求交HeaderExchangeClient处理,不进行任何其他操作。

[html]  view plain  copy
  1. public ResponseFuture request(Object request) throws RemotingException {  
  2.         return client.request(request);  
  3.     }  

HeaderExchangeClient:提供心跳检查功能;将send、request、close等事件转由HeaderExchangeChannel处理,HeaderExchangeChannel对象中的Channel为所选的NIO框架对应的client对象;以request为例,调用流程如下:HeaderExchangeClient.request(Object request)->HeaderExchangeChannel.request(Object request)->(NettyClient)AbstractPeer.send(Object message)->(NettyClient)AbstractClient.send(Object message,boolean sent)。

[html]  view plain  copy
  1. public ResponseFuture request(Object request) throws RemotingException {  
  2.        return channel.request(request);//HeaderExchangeChannel  
  3.    }  

HeaderExchangeChannel:主要是完成同步转异步,在request(Object request,int timeout)方法中,将请求转换成Request对象,将请求消息设置到data属性上,构建DefaultFuture对象,调用NIO框架对应的Client对象(默认NettyClient)的send方法将消息发送出去,返回DefultFuture对象。

[html]  view plain  copy
  1. public ResponseFuture request(Object request, int timeout) throws RemotingException {  
  2.     if (closed) {  
  3.         throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");  
  4.     }  
  5.     // create request.  
  6.     Request req = new Request();  
  7.     req.setVersion("2.0.0");  
  8.     req.setTwoWay(true);  
  9.     req.setData(request);  
  10.     DefaultFuture future = new DefaultFuture(channel, req, timeout);  
  11.     try{  
  12.         channel.send(req);  
  13.     }catch (RemotingException e) {  
  14.         future.cancel();  
  15.         throw e;  
  16.     }  
  17.     return future;  
  18. }  


NettyClient:完成消息的发送。在调用链的最后一个方法AbstractClient.send(Object message, boolean sent)中,首先通过调用NettyClient.getChannel()获取NettyChannel对象,在构建对象时封装了NIOSocketChannel对象(在初始化NettyClient对象时,根据nettyclient和server端建立连接时获取的socket通道)、统一数据模型URL以及channelHandler对象(NettyClient对象自身),然后调用NettyChannel对象的send方法,将Request消息写入NIOSocketChannel通道中,完成消息发送。

[html]  view plain  copy
  1. public void send(Object message, boolean sent) throws RemotingException {//AbstractClient  
  2.         if (send_reconnect && !isConnected()){  
  3.             connect();  
  4.         }  
  5.         Channel channel = getChannel();  
  6.         //TODO getChannel返回的状态是否包含null需要改进  
  7.         if (channel == null || ! channel.isConnected()) {  
  8.           throw new RemotingException(this, "message can not send, because channel is closed . url:" + getUrl());  
  9.         }  
  10.         channel.send(message, sent);  
  11.     }  



[html]  view plain  copy
  1. @Override  
  2.  protected com.sitech.hsf.remoting.Channel getChannel() {//nettyclient  
  3.      Channel c = channel;  
  4.      if (c == null || ! c.isConnected())  
  5.          return null;  
  6.      return NettyChannel.getOrAddChannel(c, getUrl(), this);  
  7.  }  



[html]  view plain  copy
  1. public void send(Object message, boolean sent) throws RemotingException {//NettyChannel  
  2.     super.send(message, sent);  
  3.       
  4.     boolean success = true;  
  5.     int timeout = 0;  
  6.     try {//调用Netty框架  
  7.         ChannelFuture future = channel.write(message);  
  8.         if (sent) {//sent=true等待发送失败将抛出异常,false不等待消息发出,将消息放入IO队列,即刻返回。  
  9.             timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);  
  10.             success = future.await(timeout);  
  11.         }  
  12.         Throwable cause = future.getCause();  
  13.         if (cause != null) {  
  14.             throw cause;  
  15.         }  
  16.     } catch (Throwable e) {  
  17.         throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress() + ", cause: " + e.getMessage(), e);  
  18.     }  
  19.       
  20.     if(! success) {  
  21.         throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress()  
  22.                 + "in timeout(" + timeout + "ms) limit");  
  23.     }  
  24. }  




HeaderExchangeServer:提供心跳检查功能;启动心跳监测线程池,该线程初始化了一个线程,在线程中调用类HeartBeatTask进行心跳检查,HeartBeatTask处理心跳的规则:

(1)若通道的最新的写入时间或者最新的读取时间与当前时间相比,已经超过了心跳间隔时间,则发送心跳请求;

(2)如果通道的最新的读取时间与当前时间相比,已经超过了心跳的超时时间,对于客户端来说则重连,对于服务端来说则关闭通道。

[html]  view plain  copy
  1. public HeaderExchangeServer(Server server) {  
  2.        if (server == null) {  
  3.            throw new IllegalArgumentException("server == null");  
  4.        }  
  5.        this.server = server;  
  6.        this.heartbeat = server.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);//  
  7.        this.heartbeatTimeout = server.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat * 3);  
  8.        if (heartbeatTimeout < heartbeat * 2) {  
  9.            throw new IllegalStateException("heartbeatTimeout < heartbeatInterval * 2");  
  10.        }  
  11.        startHeatbeatTimer();  
  12.    }  
3位心跳时长为心跳超时,如果不设置,默认心跳为0。


[html]  view plain  copy
  1. private void startHeatbeatTimer() {//HeaderExchangeServer  
  2.        stopHeartbeatTimer();  
  3.        if (heartbeat > 0) {  
  4.            heatbeatTimer = scheduled.scheduleWithFixedDelay(  
  5.                    new HeartBeatTask( new HeartBeatTask.ChannelProvider() {  
  6.                        public Collection<Channel> getChannels() {  
  7.                            return Collections.unmodifiableCollection(  
  8.                                    HeaderExchangeServer.this.getChannels() );  
  9.                        }  
  10.                    }, heartbeat, heartbeatTimeout),  
  11.                    heartbeat, heartbeat,TimeUnit.MILLISECONDS);  
  12.        }  
  13.    }  

如果心跳为0,不执行心跳检测功能。

[html]  view plain  copy
  1. public void run() {//HeartBeatTask  
  2.        try {  
  3.            long now = System.currentTimeMillis();//获取当前时间  
  4.            for ( Channel channel : channelProvider.getChannels() ) {  
  5.                if (channel.isClosed()) {//已经关闭了  
  6.                    continue;  
  7.                }  
  8.                try {  
  9.                 //最后一次读的时间戳  
  10.                    Long lastRead = ( Long ) channel.getAttribute(  
  11.                            HeaderExchangeHandler.KEY_READ_TIMESTAMP );  
  12.                    //最后一次写的时间戳  
  13.                    Long lastWrite = ( Long ) channel.getAttribute(  
  14.                            HeaderExchangeHandler.KEY_WRITE_TIMESTAMP );  
  15.                    //如果当前时间距离最后一次写或读超过一个心跳时间  
  16.                    if ( ( lastRead != null && now - lastRead > heartbeat )  
  17.                            || ( lastWrite != null && now - lastWrite > heartbeat ) ) {  
  18.                        Request req = new Request();  
  19.                        req.setVersion( "2.0.0" );  
  20.                        req.setTwoWay( true );  
  21.                        req.setEvent( Request.HEARTBEAT_EVENT );  
  22.                        channel.send( req );  
  23.                        if ( logger.isDebugEnabled() ) {  
  24.                            logger.debug( "Send heartbeat to remote channel " + channel.getRemoteAddress()  
  25.                                                  + ", cause: The channel has no data-transmission exceeds a heartbeat period: " + heartbeat + "ms" );  
  26.                        }  
  27.                    }  
  28.                    //如果上一次读距离现在已经超过心路超时时长,尝试重连  
  29.                    if ( lastRead != null && now - lastRead > heartbeatTimeout ) {  
  30.                        logger.warn( "Close channel " + channel  
  31.                                             + ", because heartbeat read idle time out: " + heartbeatTimeout + "ms" );  
  32.                        if (channel instanceof Client) {  
  33.                         try {  
  34.                             ((Client)channel).reconnect();  
  35.                         }catch (Exception e) {  
  36.                             //do nothing  
  37.                         }  
  38.                        } else {  
  39.                         channel.close();  
  40.                        }  
  41.                    }  
  42.                } catch ( Throwable t ) {  
  43.                    logger.warn( "Exception when heartbeat to remote channel " + channel.getRemoteAddress(), t );  
  44.                }  
  45.            }  
  46.        } catch ( Throwable t ) {  
  47.            logger.warn( "Unhandled exception when heartbeat, cause: " + t.getMessage(), t );  
  48.        }  
  49.    }  

在起动NettyClient和NettyServer连接时,都添加了一个HeartbeatHandler

[html]  view plain  copy
  1. public void received(Channel channel, Object message) throws RemotingException {  
  2.     setReadTimestamp(channel);  
  3.     if (isHeartbeatRequest(message)) {//如果是心跳请求  
  4.         Request req = (Request) message;  
  5.         if (req.isTwoWay()) {  
  6.             Response res = new Response(req.getId(), req.getVersion());  
  7.             res.setEvent(Response.HEARTBEAT_EVENT);  
  8.             channel.send(res);  
  9.             if (logger.isInfoEnabled()) {  
  10.                 int heartbeat = channel.getUrl().getParameter(Constants.HEARTBEAT_KEY, 0);  
  11.                 if(logger.isDebugEnabled()) {  
  12.                     logger.debug("Received heartbeat from remote channel " + channel.getRemoteAddress()  
  13.                                     + ", cause: The channel has no data-transmission exceeds a heartbeat period"  
  14.                                     + (heartbeat > 0 ? ": " + heartbeat + "ms" : ""));  
  15.                 }  
  16.          }  
  17.         }  
  18.         return;  
  19.     }  
  20.     if (isHeartbeatResponse(message)) {//如果是心跳响应  
  21.         if (logger.isDebugEnabled()) {  
  22.             logger.debug(  
  23.                 new StringBuilder(32)  
  24.                     .append("Receive heartbeat response in thread ")  
  25.                     .append(Thread.currentThread().getName())  
  26.                     .toString());  
  27.         }  
  28.         return;  
  29.     }  
  30.     handler.received(channel, message);  
  31. }  


多线程并发请求与单一长连接

如果客户端多线程并发请求的话,服务端通过单一长连接接受并返回响应信息,如果客户端不加控制就会导制通道中的数据变成无序,无法正确的处理请求。为此,Dubbo给每个请求添加一个唯一的标识ID,服务端响应请求也要携带此ID,供客户端多线程领取对应的响应数据,主要采用多线程编程中的Future模式来解决此问题。客户端的实现具体如下:
(1)当客户端发起远程请求时,最终调用HeaderExchangeClient.request方法,在该方法中调用HeaderExchangeChannel.request方法,并返回DefaultFutrue对象。首先创建Request对象,请求消息作为Data值,并创建唯一标识ID;然后在初始化DefaultFuture对象的过程中,将自身this对象以及channel对象存入全局变量DefaultFuture.FUTURES:ConcurrenthashMap和DefaultFuture.CHANNELS:ConcurrentHashMap中,以标识ID为key。
[html]  view plain  copy
  1. public ResponseFuture request(Object request, int timeout) throws RemotingException {//HeaderExchangeChannel.request  
  2.     if (closed) {  
  3.         throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");  
  4.     }  
  5.     // create request.  
  6.     Request req = new Request();  
  7.     req.setVersion("2.0.0");  
  8.     req.setTwoWay(true);  
  9.     req.setData(request);  
  10.     DefaultFuture future = new DefaultFuture(channel, req, timeout);  
  11.     try{  
  12.         channel.send(req);  
  13.     }catch (RemotingException e) {  
  14.         future.cancel();  
  15.         throw e;  
  16.     }  
  17.     return future;  
  18. }  
(2)该方法返回DefaultFuture对象给客户端线程,该线程会在DefaultFuture对象的get方法上面阻塞,有两种情况唤醒该线程:(一)接收到响应消息并调用received方法,根据响应消息中返回的ID从前面的ConcurrentHashMap里面get(ID)里面获取DefaultFuture对象,然后更新该对象的Response变量值。(二)RemotingInvocationTimeoutScan线程,定时扫描响应是否超时,若超时,则从FUTURES:ConcurrentHashMap中删除掉Future对象并且将Response变量设置为超时信息。

同步转异步的逻辑

<dubbo:method>标签中async属性表示是否异步招待;oninvoke表示方法执行前的拦截方法,onreturn表示方法执行返回后的拦截方法,onthrow表示方法执行有异常的拦截方法。
FutureFilter为客户端的过滤器,只有客户端才使用该过过滤器。在FutureFilter的invoke方法中,首先调用oninvoker属性配置的方法,然后调用invoker链的invoke方法,最后调用onreturn和onthrow配置的方法。
(1)当async=true时表示异步执行,获取本地线程类ThreadLocal<RpcContetext>的DefaultFuture对象,设置回调用类callback(该回调用类实现了ResponseCallback接口的done和caught方法),在DefaultFuture对象中若收到响应之后调用回调用类的done方法。该done方法中调用客户端配置的onreturn和onthrow方法。
(2)当async=false时表示由不,在调用完invoker链的invoke方法后继续用客户端配置的onreturn和onthrow方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值