pigeon源码分析-处理请求结果

RPC调用处理请求结果可以分为两部分:

  • 获取 response
  • 关联 request 和 response

这么分看起来似乎很奇怪,不是直接等待处理完获取结果就行了吗?

我们说 RPC 调用都是在模拟这个动作: Result result = service.call(args); 但是远程调用毕竟不是本地调用(其实稍后可以看到还是有相似之处的),将请求写到网络之后,就无法命令远端做任何事了,这次请求就已经告一段落了。

pigeon client 只知道:

  • 向网络写数据;
    • 就是写 request
  • 处理网络写入的数据;
    • 处理成 response

于是不难理解为何有此一问:网络另一端写过来的数据,我怎么知道是哪个请求的返回值呢?

获取 response

其实从网络读取数据,转化成 Object。 pigeon 基于 netty,获取 response 就是处理网络写入。

具体实现在:

 // com.dianping.pigeon.remoting.netty.invoker.NettyClientHandler#messageReceived
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
        CodecEvent codecEvent = (CodecEvent) e.getMessage();

        if (codecEvent.isValid() && codecEvent.getInvocation() != null) {
            client.processResponse((InvocationResponse) codecEvent.getInvocation());
        }
    }

从 netty 封装 MessageEvent 转换成 CodecEvent,再剥开一层就是 InvocationResponse,当然这还是一个比较泛化的返回值,com.dianping.pigeon.remoting.invoker.process.ResponseProcessor 将会进一步处理;

继续往下看:

// com.dianping.pigeon.remoting.invoker.process.threadpool.ResponseThreadPoolProcessor#doProcessResponse
public void doProcessResponse(final InvocationResponse response, final Client client) {
        Runnable task = new Runnable() {
            public void run() {
                ServiceInvocationRepository.getInstance().receiveResponse(response);
            }
        };
        try {
            responseProcessThreadPool.execute(task);
        } catch (RejectedExecutionException e) {
            String error = String.format("process response failed:%s, processor stats:%s", response,
                    getProcessorStatistics());
            throw new RejectedException(error, e);
        }
    }

这里封装成了一个 task,交给线程池处理。

再下一层:

// com.dianping.pigeon.remoting.invoker.service.ServiceInvocationRepository#receiveResponse
public class ServiceInvocationRepository {
    // 略
    private static Map invocations = new ConcurrentHashMap();


    public void receiveResponse(InvocationResponse response) {
        RemoteInvocationBean invocationBean = invocations.get(response.getSequence());
        if (invocationBean != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("received response:" + response);
            }
            InvocationRequest request = invocationBean.request;
            try {
                Callback callback = invocationBean.callback;
                if (callback != null) {
                    Client client = callback.getClient();
                    if (client != null) {
                        ServiceStatisticsHolder.flowOut(request, client.getAddress());
                    }
                    callback.callback(response);
                    callback.run();
                }
            } finally {
                invocations.remove(response.getSequence());
            }
        }
    }
// 略
}

这里可以看到, RemoteInvocationBean invocationBean = invocations.get(response.getSequence()); invocations 维护一个 HashMap,key 是一个 long 型的 sequenceId,通过这种方式定位到 invocationBean,而 invocationBean 看实现可知持有 request 引用,以及一个处理返回值的 callback。

Callback 在不同调用模式(sync / future/ oneway/ callback) 下有不同实现类,比如 sync 模式下:

// public class CallbackFuture implements Callback, CallFuture {   
@Override
    public void callback(InvocationResponse response) {
        this.response = response;
    }

sync 和 future 调用,都是将 response 对象实例设置给相应的引用

何时得到真正的 returnValue?

看动态代理的逻辑,com.dianping.pigeon.remoting.invoker.service.ServiceInvocationProxy#invoke

com.dianping.pigeon.remoting.invoker.process.filter.InvocationInvokeFilter 挨个执行完之后,提取返回值

// com.dianping.pigeon.remoting.invoker.service.ServiceInvocationProxy#extractResult
    public Object extractResult(InvocationResponse response, Class returnType) throws Throwable {
        Object responseReturn = response.getReturn();
        // ...
}

拿到的是顶层父类实例 Object,具体的类型匹配需要客户端和服务端自行匹配

关联 request 和 response

从上面的分析可以看出,关键点就在于 sequence,每次调用应该有个唯一的 id 进行匹配

这个 sequence 是唯一的吗?

如果不唯一,就可能导致拿到错误的处理结果。

sequence 的生成位置

//com.dianping.pigeon.remoting.invoker.process.filter.ContextPrepareInvokeFilter#initRequest

private static AtomicLong requestSequenceMaker = new AtomicLong();

request.setSequence(requestSequenceMaker.incrementAndGet() * -1);

可以看到 这个 sequence 是全局唯一的,准确说是同一个 JVM 中是唯一的,而且是 long 类型,足够大;

Q:sequence 发生回绕怎么办?

A:long 类型,即使发生回绕,也需要足够长的时间,一般来说不会堆积有那么多的请求,导致两个相同的 sequenceId 实际对应不同请求;

分布式环境下,sequenceId 在多个机器上可能重复,会出错吗?

A:sequence 存储的 com.dianping.pigeon.remoting.invoker.service.ServiceInvocationRepository#invocations也是同一个 JVM 唯一的,所以只需要担心会不会有这样的场景:

client A 调用 server A,client B 也调用 server A,但是 server A 把 client B的请求返回值处理之后发送到了 client A?

看看服务端的处理,写返回值:

//com.dianping.pigeon.remoting.provider.process.filter.WriteResponseProcessFilter#invoke
    public InvocationResponse invoke(ServiceInvocationHandler handler, ProviderContext invocationContext)
            throws Throwable {
        try {
            ProviderChannel channel = invocationContext.getChannel();
            InvocationRequest request = invocationContext.getRequest();
            InvocationResponse response = handler.handle(invocationContext);
            if (request.getCallType() == Constants.CALLTYPE_REPLY) {
                invocationContext.getTimeline().add(new TimePoint(TimePhase.P));
                channel.write(invocationContext, response);
                invocationContext.getTimeline().add(new TimePoint(TimePhase.P));
            }
        // ...

channel 就是对socket的封装,可以看成是 client / server 对对方的抽象。

那么只需要保证拿到正确的 channel 就对了:

//com.dianping.pigeon.remoting.netty.provider.NettyServerHandler#messageReceived
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent message) {
        CodecEvent codecEvent = (CodecEvent) (message.getMessage());

        if (!codecEvent.isValid() || codecEvent.getInvocation() == null) {
            return;
        }

        InvocationRequest request = (InvocationRequest) codecEvent.getInvocation();

        ProviderContext invocationContext = new DefaultProviderContext(request, new NettyServerChannel(ctx.getChannel()));
        //...
    }

也就是说,再 client 端 writeRequest() 之后,server 端读取网络数据的时候就从 context 中获取到 client 所在的 channel 了,简单来说,”从哪里来,到哪里去“。

总的来说,不会发生以上所述的 sequenceId 错乱的问题。

你可能感兴趣的:(pigeon源码分析-处理请求结果)