Pigeon的一次调用客户端发生了什么

Pigeon是美团点评的RPC框架,涉及很多模块,服务注册发现,负载均衡,路由,容灾,故障转移,消息传输等等,要想了解这些模块的实现机制,首先要找到一个入手点,作为一个RPC框架,最重要的当然就是服务之间的请求调用及数据传输了,那么我们就来看一下pigeon客户端的一次调用发生了些什么,又经过了那些链路调用?

首先要介绍一个关于服务调用的工厂类ServiceFactory(pigeon服务添加获取都在这里)。
客户端在依赖于spring启动时,服务代理对象serviceProxy会通过ReferenceBean注入来完成创建。
ReferenceBean的初始化过程如下:
在这里插入图片描述
其中会涉及到服务的获取:ServiceFactory.getService(invokerConfig)其中又调用了serviceProxy来通过代理获取服务对象,来看下这个接口
在这里插入图片描述

这是pigeon自定义的一个服务代理类,客户端就是通过其中的getProxy方法来获取服务代理对象的在这里插入图片描述
这里又通过Serializer这个接口的proxyRequest方法来完成代理对象的创建
在这里插入图片描述
就是这里创建了服务的动态代理对象,并封装了相关的调用链(后面会介绍),最后将服务代理对象返回,供调用方调用,调用逻辑如下:
在这里插入图片描述
从上述源码可以看出,客户端是基于java动态代理来实现服务调用的

我们注意到在服务代理对象生成之前,有一个**InvokerBootStrap.startup();**方法被调用,这个方法是做什么的呢?
这个方法就是客户端的启动方法,接下来我们看一下客户端的启动流程

public static void startup() {
		if (!isStartup) {
			synchronized (InvokerBootStrap.class) {
				if (!isStartup) {
					//服务调用仓库初始化
					ServiceInvocationRepository.getInstance().init();
					//请求调用责任链初始化
					InvokerProcessHandlerFactory.init();
					//序列化工厂,消息的编解码都在这里面
					SerializerFactory.init();
					//负载均衡管理
					LoadBalanceManager.init();
					//路由策略管理
					RoutePolicyManager.INSTANCE.init();
					//监控
					Monitor monitor = MonitorLoader.getMonitor();
					if (monitor != null) {
						monitor.init();
					}
					.....
					isStartup = true;
					logger.warn("pigeon client[version:" + VersionUtils.VERSION + "] has been started");
				}
			}
		}
	}

这里我们只看其中InvokerProcessHandlerFactory.init() 这个方法,看下它的初始化

public static void init() {
		if (!isInitialized) {
			synchronized (InvokerProcessHandlerFactory.class) {
				if (!isInitialized) {
					if (Constants.MONITOR_ENABLE) {
						registerBizProcessFilter(new RemoteCallMonitorInvokeFilter());
					}
					//请求初始的一些定义
					registerBizProcessFilter(new InvokerContextFilter());
					//监控相关
					registerBizProcessFilter(new TracerFilter());
					registerBizProcessFilter(new InnerTracerFilter());
					//通用调用处理
					registerBizProcessFilter(new GenericInvokeFilter());
					//服务降级
					registerBizProcessFilter(new DegradationFilter());
					//集群策略
					registerBizProcessFilter(new ClusterInvokeFilter());
					//故障演练
					registerBizProcessFilter(new FaultInjectionFilter());
					//统计信息
					registerBizProcessFilter(new GatewayInvokeFilter());
					//生成请求
					registerBizProcessFilter(new ContextPrepareInvokeFilter());
					//安全相关
					registerBizProcessFilter(new SecurityFilter());
					//远程请求调用
					registerBizProcessFilter(new RemoteCallInvokeFilter());
					//构建消息发送执行责任链
					sendHandler = createSendProcessHandler(sendProcessFilters);
					//构建消息接收执行责任链
					recvHandler = createRecvProcessHandler(recvProcessFilters);

					isInitialized = true;
				}
			}
		}
	}

这里面会有很多filter过滤器处理逻辑,涉及服务监控,降级,集群策略,故障演练,统计信息,生成请求,安全相关,
客户端调用RPC服务接口的请求会经过这些过滤器层层处理(服务是通过代理的方式调用的),最后交由RemoteCallInvokeFilter,发送到远端。
这里我们只看最后一个RemoteCallInvokeFilter这个涉及传输的处理器,它的核心逻辑在其invoke方法中

@Override
    public InvocationResponse invoke(ServiceInvocationHandler handler, InvokerContext invocationContext) throws Throwable {
        invocationContext.getTimeline().add(new InvocationContext.TimePoint(InvocationContext.TimePhase.Q));
        //服务节点
        Client client = invocationContext.getClient();
        //请求内容
        InvocationRequest request = invocationContext.getRequest();
        //低啊用配置
        InvokerConfig<?> invokerConfig = invocationContext.getInvokerConfig();
        //调用模式
        byte callMethodCode = invokerConfig.getCallMethod(invocationContext.getMethodName());
        
     	......
		......
		
        try {
            switch (callMethod) {
            	//同步模式
                case SYNC:
                    InvokerFuture futureSync = new InvokerFuture(invocationContext, request.getTimeout());
                    response = InvokerUtils.sendRequest(client, invocationContext.getRequest(), futureSync);
                    invocationContext.getTimeline().add(new InvocationContext.TimePoint(InvocationContext.TimePhase.Q));
                    if (response == null) {
                        response = futureSync.getResponse(request.getTimeout());
                    }
                    break;
                //回调模式
                case CALLBACK:
                    InvocationCallback callback = invokerConfig.getCallback();
                    InvocationCallback tlCallback = InvokerHelper.getCallback();
                    if (tlCallback != null) {
                        callback = tlCallback;
                        InvokerHelper.clearCallback();
                    }
                    InvokerUtils.sendRequest(client, invocationContext.getRequest(), new InvokerCallback(
                            invocationContext, callback));
                    response = NO_RETURN_RESPONSE;
                    invocationContext.getTimeline().add(new InvocationContext.TimePoint(InvocationContext.TimePhase.Q));
                    break;
                //future模式
                case FUTURE:
                    InvokerFuture future = new InvokerFuture(invocationContext, request.getTimeout());
                    InvokerUtils.sendRequest(client, invocationContext.getRequest(), future);
                    FutureFactory.setFuture(future);
                    response = InvokerUtils.createFutureResponse(future);
                    invocationContext.getTimeline().add(new InvocationContext.TimePoint(InvocationContext.TimePhase.Q));
                    break;
                //oneway模式
                case ONEWAY:
                    InvokerUtils.sendRequest(client, invocationContext.getRequest(), null);
                    response = NO_RETURN_RESPONSE;
                    invocationContext.getTimeline().add(new InvocationContext.TimePoint(InvocationContext.TimePhase.Q));
                    break;
                default:
                    throw new BadRequestException("Call type[" + callMethod.getName() + "] is not supported!");

            }
            ((DefaultInvokerContext) invocationContext).setResponse(response);
            afterInvoke(invocationContext);
        } catch (Throwable t) {
            afterThrowing(invocationContext, t);
            throw t;
        }
        return response;

    }

这里我们可以看到Pigeon提供了四种调用模式供我们使用,SYNC,CALLBACK,FUTURE,ONEWAY
接下来就每个模式做个简单的介绍

SYNC模式

SYNC模式:顾名思义,是同步调用,客户端会等待服务端结果返回,等待过程是阻塞的,看一下它的实现机制
在这里插入图片描述
创建了一个InvokerFuture对象,参数是当前请求信息和超时时间,这里需要看一下这个对象的属性有哪些呢
在这里插入图片描述
可以看到InvokerFuture对象继承了JDK的future及Callback, CallFuture这两个回调接口,属性中有几个参数要注意看下,一个是InvocationResponse记录了响应的结果,另外是记录了当前调用结果的几种结果done,cancelled,complete。

再回到之前的逻辑,对象创建完成后,执行了InvokerUtils.sendRequest(client, invocationContext.getRequest(), futureSync)这个方法,看一下它的实现
在这里插入图片描述
执行流程:封装了RemoteInvocationBean对象,然后放到invocationRepository仓库里,然后调用了client.write方法发送数据,看它的实现,最终会走到doWrite方法,如下

public InvocationResponse doWrite(InvocationRequest request) throws NetworkException {
        NettyChannel channel = null;

        try {
			//channel池化
            channel = channelPool.selectChannel();
			//netty发送数据
            ChannelFuture future = channel.write0(request);
			//后置处理
            afterWrite(request, channel);
            if (request.getMessageType() == Constants.MESSAGE_TYPE_SERVICE
                    || request.getMessageType() == Constants.MESSAGE_TYPE_HEART) {
                 //future上增加MessageWriteListener监听,用来在发送中断或失败时,继续调用processResponse()做response处理
                future.addListener(new MessageWriteListener(request, channel));
            }

        } catch (Exception e) {
            throw new NetworkException("[doRequest] remote call failed:" + request, e);
        }
        return null;
    }

可以看到Pigeon的TCP数据传输是基于netty(3.x)做的,数据发送(netty的发送是异步非阻塞的)之后,注意这里返回了null,并没有返回future,这里只是对future加了一个监听,在回到sendRequest方法时,response是空的返回,那么结果是怎么被调用方拿到的呢?
答案是:InvokerFuture
上面说到这个InvokerFuture里有InvocationResponse这个属性,它记录了响应的数据,那又是在哪里传进去的呢?

我们先回到最开始的位置继续来看它做了哪些处理
在这里插入图片描述
这里response一定是null,会调用futureSync.getResponse方法,毋庸置疑是用来获取response的,来接着看下它的实现
在这里插入图片描述
这里进来的时候会调用waitResponse方法,我们进去看一下实现

protected InvocationResponse waitResponse(long timeoutMillis) throws InterruptedException {
       	.....
        lock.lock();
        try {
            long start = request.getCreateMillisTime();
            long timeoutLeft = timeoutMillis;
			//判断当前响应是否完成
            while (!isDone()) {
            	//阻塞一定时长
                condition.await(timeoutLeft, TimeUnit.MILLISECONDS);
                long timeoutPassed = System.currentTimeMillis() - start;
				//收到响应消息或超时则跳出循环
                if (isDone() || timeoutPassed >= timeoutMillis) {
                    break;
                } else {
                    timeoutLeft = timeoutMillis - timeoutPassed;
                }
            }
        } finally {
            lock.unlock();
        }
        ....
        return response;
    }

可以看到这里进行了阻塞了调用方以等待响应结果的返回,这里我们先不管它的阻塞是在什么时候打开的,先来看一下接下来发生了什么
调用了**InvokerProcessHandlerFactory.defaultRecvHandler().handle(invocationContext)**方法,该方法就是调用我们最开始构建的责任链中的recvProcessFilters链,对response做处理并返回结果给调用方。
上面我们已经知道对于响应的response是如何处理的,那么这个response是什么时候给到的呢,我们并没有看到response的返回呀,
答案:这里Pigeon利用了netty的channelInboundHandler做的处理,我们需要看一下netty对于返回的数据做了哪些处理
在这里插入图片描述
这是netty客户端的ChannelPipeline的构建过程,来看看它分别添加了哪些handler到ChannelPipeline中,并分别做了些什么处理
netty对于接受消息的整个处理流程:
1.FrameDecoder 帧解码器–拆包提取消息
2.Crc32Handler 校验,差错控制
3.CompressHandler 解压缩
4.InvokerDecoder 字节流解码成消息
5.NettyClientHandler 消息接收处理

我们现在要关心的response在NettyClientHandler这个处理器中进行了处理,来看下
在这里插入图片描述
可以看到这里调用了 client.processResponse,我们进去看调用,最终是doProcessResponse这个方法做了真正的处理
在这里插入图片描述
这里构建了一个任务并将给responseProcessThreadPool(处理响应的线程池)去执行,我们看下receiveResponse的实现

public void receiveResponse(InvocationResponse response) {
		//到调用仓库里拿到sendRequest时构建的调用bean
        RemoteInvocationBean invocationBean = invocations.get(response.getSequence());
        if (invocationBean != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("received response:" + response);
            }
            InvocationRequest request = invocationBean.request;
            try {
            	//拿到我们开始时构建的InvokerFuture(是callback的实现)
                Callback callback = invocationBean.callback;
                if (callback != null) {
                    Client client = callback.getClient();
                    if (client != null) {
                    	//流量统计
                        ServiceStatisticsHolder.flowOut(request, client.getAddress());
                    }
                    //这里调用了InvokerFuture的callback方法实现
                    callback.callback(response);
                    callback.run();
                }
            } finally {
                invocations.remove(response.getSequence());
            }
        }
    }

上述代码可以看到整个过程,我们看到其调用了请求的InvokerFuture回调方法,这里我们应该已经想到response的传递一定是在这个地方做的,我们点进去看下,验证下我们猜想的是否正确

	public void callback(InvocationResponse response) {
        this.response = response;
    }
    public void run() {
        lock.lock();
        try {
            this.done = true;
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

果然,在这里进行了response的传递,并对上面InvokerFuture阻塞的调用方进行了唤醒,调用方这时就可以拿到response,开始对应的recvProcessFilters链的处理了。
我们回到开始,这里response经过调用线程处理后,就可以完成结果的返回,调用完毕。
在这里插入图片描述

CALLBACK模式

接下来我们接着callback模式
在这里插入图片描述
这里和同步不同的是,它构建的是一个InvocationCallback对象,看一下它的接口方法
在这里插入图片描述
通过invokerConfig.getCallback()方法获取,这是一个由调用方实现的接口,并作为参数传给sendRequest方法,和上面的sendRequest是同一个方法,唯一不同的是它实现了自己的回调方法,并做为参数构建了一个InvokerCallback对象,之后的调用链路和同步模式下是一样的,不同在哪里的呢?
上面代码可以看到它是没有阻塞获取response的,并且结果是NO_RETURN_RESPONSE,直接返回了,那么调用方的回调实现在哪里去调用的呢?
这里要从InvokerCallback这个对象入手,我们看下最终的response回调(同Sync模式)
在这里插入图片描述
注意这里和同步模式不同的是:这里的callback接口实现的对象是InvokerCallback,而不是InvokerFuture,那么InvokerCallback的callback方法和run方法又有哪些不同呢?

@Override
    public void callback(InvocationResponse response) {
        this.response = response;
    }
    public void run() {
        ......
        ......
        try {
            ((DefaultInvokerContext) invocationContext).setResponse(response);
            //执行recvProcessFilters链处理
            Object result = ServiceInvocationProxy.extractResult(
                    InvokerProcessHandlerFactory.defaultRecvHandler().handle(invocationContext),
                    invocationContext.getReturnType());
            if (result instanceof Throwable) {
            	//调用方实现的回调
                callback.onFailure((Throwable) result);
            } else {
                //调用方实现的回调
                callback.onSuccess(result);
            }
        } catch (Throwable t) {
            callback.onFailure(t);
        }
    }

可以看到callback()方法实现是一致的,run方法有很大的差别,可以看到,其在拿到response后直接调用了recvProcessFilters链处理,并在处理成功后,执行了由调用方提供的回调callback实现。这时,response就处理完毕了,调用完毕。

总结一下上述两种调用模式的逻辑:
从ServiceInvocationRepository中拿到响应的requestBean,找到其callback对象并执行回调方法,将response传入future,同步模式下会唤醒等待结果的业务线程,回调模式下会去调用recvProcessFilters责任链,完成后执行业务方提供的回调方法,上述逻辑都在ResponseThreadPool中执行

FUTURE模式

接着我们看一下FUTURE模式的调用逻辑
在这里插入图片描述
可以看到创建了一个InvokerFuture对象,和同步模式一样,调用sendRequest发送数据(调用链一致),不同的是,这里将future通过FutureFactory将future交给了threadFuture(ThreadLocal),由当前调用线程保存这个future
在这里插入图片描述
在这里插入图片描述
接着调用了**InvokerUtils.createFutureResponse(future);**去得到FutureResponse
在这里插入图片描述
这里直接将FutureResponse返回,由调用方去做具体的处理,调用完毕。

ONEWAY模式

oneway顾名思义就是单向的传输,不关心返回,这里将callback置为null,并发送消息给远端,调用完毕。
在这里插入图片描述
这种模式适合大量的日志传输且允许数据丢失的情况下使用,消息发送不关心结果,也没有重试容错的机制去保证。


下一篇Pigeon的一次调用服务端发生了什么

### 回答1: 鸽子启发优化(Pigeon-Inspired Optimization,PIO)是一种模拟鸽群行为的启发式优化算法,被广泛应用于求解复杂的连续优化问题。此方法借鉴了鸽子在觅食、繁殖和迁徙等过程中的行为特征。 鸽子的觅食行为是PIO算法的灵感来源之一。鸽子会通过观察和记忆周围环境来寻找食物源。在PIO中,问题的解空间被视为食物源的位置,每个解被视为一个潜在的食物源。鸽子根据周围食物源的质量和数量决定选择哪个方向进行搜索。 鸽子的繁殖行为也对PIO算法有所启发。鸽子的繁殖成功与否与其所在的栖息地质量有关。在PIO中,解被视为潜在的繁殖后代,解的质量被视为栖息地的质量。通过对解进行交叉和变异操作,PIO试图生成质量更高的解,以提升算法的搜索效果。 此外,鸽子的迁徙行为也对PIO算法的设计有所影响。鸽子会根据身边鸽群成员的信息来决定是否迁徙到新的栖息地。在PIO中,个体解之间的信息共享被视为鸽子之间的信息传递。通过利用群体中优秀解的信息,PIO可以在搜索过程中引导个体解向更优的方向移动。 综上所述,鸽子启发优化是一种通过模拟鸽子的觅食、繁殖和迁徙行为来求解优化问题的方法。它的独特之处在于将问题的解空间视为食物源和栖息地,同时利用鸽子之间的信息传递来引导搜索方向。通过这种方式,PIO在解决复杂优化问题时具有较好的性能和效果。 ### 回答2: 鸽子灵感优化算法(PIO)是一种基于鸽子行为的启发式优化算法,旨在模拟鸽子的群体智慧和社会行为。这种算法源于观察和研究鸽子在飞行时的群体行为,尤其是集群寻找食物和迁徙过程中的策略。 鸽子灵感优化算法主要由以下几个步骤组成:初始化鸽子种群、评估适应度、更新位置和速度、调整相关参数以及终止条件。在每一代中,鸽子的位置和速度会根据当前解的适应度进行更新。该算法的目标是通过不断搜索和找到最优解,以找到问题的最佳解决方案。 鸽子灵感优化算法在许多领域和问题中具有广泛的应用,例如神经网络训练、图像处理、路径规划、智能控制等。它能够帮助人们优化复杂系统和模型,提高性能和效率。 与其他优化算法相比,鸽子灵感优化算法具有以下特点:简单易懂,易于实施;能够处理多目标和多约束问题;天然的并行性,可用于分布式计算;对初始值的依赖性较小,具有较好的局部搜索能力。 总之,鸽子灵感优化算法是一种受到鸽子行为启发的优化方法,它通过模拟鸽子的行为和智慧来解决复杂问题。在未来的研究中,我们可以进一步改进和拓展这种算法,使其在更广泛的应用领域发挥更大的作用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值