4、SOFA RPC 源码解析 —— 负载均衡篇

我们在前面讲的服务调用里面会发现有这样一段代码:

   // 创建代理类
   proxyIns = (T) ProxyFactory.buildProxy(consumerConfig.getProxy(), consumerConfig.getProxyClass(),proxyInvoker);

这段代码就是我们说的根据SPI加载代理类,这个代理类可以在服务调用时候指定,jdk或者javassist两种,这两种区别我这里简单讲一下:jdk代理是基于反射生成代理类的,所以必须要求代理类是接口或者抽象类,而javassist是基于ASM字节码技术的,不需要限定类的类型,它是为代理类使用asm技术生成子类,这个是大致的区别,那我们来看下我们调用的时候到底是怎么调用的,是怎么使用的负载均衡呢?我们就以JDK代理为例吧!

    // 创建代理类
    proxyIns = (T) ProxyFactory.buildProxy(consumerConfig.getProxy(), consumerConfig.getProxyClass(),proxyInvoker);

    /**
     * 构建代理类实例
     *
     * @param proxyType    代理类型
     * @param clazz        原始类
     * @param proxyInvoker 代码执行的Invoker
     * @param <T>          类型
     * @return 代理类实例
     * @throws Exception
     */
    public static <T> T buildProxy(String proxyType, Class<T> clazz, Invoker proxyInvoker) throws Exception {
        try {
            // 使用spi加载代理类,没设置则使用的javassist
            ExtensionClass<Proxy> ext = ExtensionLoaderFactory.getExtensionLoader(Proxy.class)
                .getExtensionClass(proxyType);
            if (ext == null) {
                throw ExceptionUtils.buildRuntime("consumer.proxy", proxyType,
                    "Unsupported proxy of client!");
            }
            Proxy proxy = ext.getExtInstance();
            return proxy.getProxy(clazz, proxyInvoker);
        } catch (SofaRpcRuntimeException e) {
            throw e;
        } catch (Throwable e) {
            throw new SofaRpcRuntimeException(e.getMessage(), e);
        }
    }
/**
 * Proxy implement base on jdk
 *
 * @author <a href=mailto:[email protected]>GengZhang</a>
 */
@Extension("jdk")
public class JDKProxy implements Proxy {

    @Override
    public <T> T getProxy(Class<T> interfaceClass, Invoker proxyInvoker) {
        InvocationHandler handler = new JDKInvocationHandler(interfaceClass, proxyInvoker);
        ClassLoader classLoader = ClassLoaderUtils.getCurrentClassLoader();
        // 通过反射获取数据
        T result = (T) java.lang.reflect.Proxy.newProxyInstance(classLoader,
            new Class[] { interfaceClass }, handler);
        return result;
    }

    @Override
    public Invoker getInvoker(Object proxyObject) {
        return parseInvoker(proxyObject);
    }

    /**
     * Parse proxy invoker from proxy object
     *
     * @param proxyObject Proxy object
     * @return proxy invoker
     */
    public static Invoker parseInvoker(Object proxyObject) {
        InvocationHandler handler = java.lang.reflect.Proxy.getInvocationHandler(proxyObject);
        if (handler instanceof JDKInvocationHandler) {
            return ((JDKInvocationHandler) handler).getProxyInvoker();
        }
        return null;
    }
}

既然获得了一个代理对象,那我们就看下他是怎么处理请求的吧!JDKInvocationHandler#invoke方法

    @Override
    public Object invoke(Object proxy, Method method, Object[] paramValues)
        throws Throwable {
        // 获取方法名称
        String methodName = method.getName();
        // 获取方法参数
        Class[] paramTypes = method.getParameterTypes();
        // 如果是调用toString、hashCode、equals 则直接调用本类的方法不进行远程调用
        if ("toString".equals(methodName) && paramTypes.length == 0) {
            return proxyInvoker.toString();
        } else if ("hashCode".equals(methodName) && paramTypes.length == 0) {
            return proxyInvoker.hashCode();
        } else if ("equals".equals(methodName) && paramTypes.length == 1) {
            Object another = paramValues[0];
            return proxy == another ||
                (proxy.getClass().isInstance(another) && proxyInvoker.equals(JDKProxy.parseInvoker(another)));
        }
        // 构建请求
        SofaRequest sofaRequest = MessageBuilder.buildSofaRequest(method.getDeclaringClass(),
            method, paramTypes, paramValues);
        // invoke调用开始
        SofaResponse response = proxyInvoker.invoke(sofaRequest);
        if (response.isError()) {
            throw new SofaRpcException(RpcErrorType.SERVER_UNDECLARED_ERROR, response.getErrorMsg());
        }
        Object ret = response.getAppResponse();
        if (ret instanceof Throwable) {
            throw (Throwable) ret;
        } else {
            if (ret == null) {
                return ClassUtils.getDefaultPrimitiveValue(method.getReturnType());
            }
            return ret;
        }
    }

proxyInvoker.invoke(sofaRequest);这句话就涉及到我们初始化代理invoker的时候我们返回的是ClientProxyInvoker,所以我们点进ClientProxyInvoker#invoke

   /**
     * proxy拦截的调用
     *
     * @param request 请求消息
     * @return 调用结果
     */
    @Override
    public SofaResponse invoke(SofaRequest request) throws SofaRpcException {
        SofaResponse response = null;
        Throwable throwable = null;
        try {
            // 将请求的上下文放入Deque
            RpcInternalContext.pushContext();   
            RpcInternalContext context = RpcInternalContext.getContext();
            context.setProviderSide(false);
            // 包装请求
            decorateRequest(request);
            try {
                // 产生开始调用事件
                if (EventBus.isEnable(ClientStartInvokeEvent.class)) {
                    EventBus.post(new ClientStartInvokeEvent(request));
                }
                // 得到结果
                response = cluster.invoke(request);
            } catch (SofaRpcException e) {
                throwable = e;
                throw e;
            } finally {
                // 产生调用结束事件
                if (!request.isAsync()) {
                    if (EventBus.isEnable(ClientEndInvokeEvent.class)) {
                        EventBus.post(new ClientEndInvokeEvent(request, response, throwable));
                    }
                }
            }
            // 包装响应
            decorateResponse(response);
            return response;
        } finally {
            RpcInternalContext.removeContext();
            RpcInternalContext.popContext();
        }
    }

我们先来看下包装请求decorateRequest(request),因为前面设置的invoker是DefaultClientProxyInvoker所以调用的是这个方法
DefaultClientProxyInvoker#decorateRequest,而不是ClientProxyInvoker#decorateRequest,这个方法是空的

   @Override
    protected void decorateRequest(SofaRequest request) {
        // 公共的设置,空的
        super.decorateRequest(request);
        // 缓存是为了加快速度
        request.setTargetServiceUniqueName(serviceName);
        // 设置序列化类型
        request.setSerializeType(serializeType == null ? 0 : serializeType);
        // 是否是泛化调用,限于篇幅泛化调用的概念可以先自行百度一下
        if (!consumerConfig.isGeneric()) {
            // 找到调用类型, generic的时候类型在filter里进行判断
            request.setInvokeType(consumerConfig.getMethodInvokeType(request.getMethodName()));
        }
        // 取出第一个context,如果我们需要使用回调和透传参数的话这个就会拿到值,否则如果不提前设置的话都是null的
        // 比如 RpcInvokeContext.getContext.setsetResponseCallback或putRequestBaggage这种等
        RpcInvokeContext invokeCtx = RpcInvokeContext.peekContext();
        // 取出我们上面存储的上下文context
        RpcInternalContext internalContext = RpcInternalContext.getContext();
        if (invokeCtx != null) {
            // 如果用户设置了调用级别回调函数
            SofaResponseCallback responseCallback = invokeCtx.getResponseCallback();
            if (responseCallback != null) {
                request.setSofaResponseCallback(responseCallback);
                invokeCtx.setResponseCallback(null); // 一次性用完
                invokeCtx.put(RemotingConstants.INVOKE_CTX_IS_ASYNC_CHAIN,
                    isSendableResponseCallback(responseCallback));
            }
            // 如果用户设置了调用级别超时时间
            Integer timeout = invokeCtx.getTimeout();
            if (timeout != null) {
                request.setTimeout(timeout);
                invokeCtx.setTimeout(null);// 一次性用完
            }
            // 如果用户指定了调用的URL
            String targetURL = invokeCtx.getTargetURL();
            if (targetURL != null) {
                internalContext.setAttachment(HIDDEN_KEY_PINPOINT, targetURL);
                invokeCtx.setTargetURL(null);// 一次性用完
            }
            // 如果用户指定了透传数据
            if (RpcInvokeContext.isBaggageEnable()) {
                // 需要透传
                BaggageResolver.carryWithRequest(invokeCtx, request);
                internalContext.setAttachment(HIDDEN_KEY_INVOKE_CONTEXT, invokeCtx);
            }
        }
        // 是否开启附件传递功能
        if (RpcInternalContext.isAttachmentEnable()) {
            internalContext.setAttachment(INTERNAL_KEY_APP_NAME, consumerConfig.getAppName());
            internalContext.setAttachment(INTERNAL_KEY_PROTOCOL_NAME, consumerConfig.getProtocol());
        }

        // 额外属性通过HEAD传递给服务端
        request.addRequestProp(RemotingConstants.HEAD_APP_NAME, consumerConfig.getAppName());
        request.addRequestProp(RemotingConstants.HEAD_PROTOCOL, consumerConfig.getProtocol());
    }

我们再来看下他们的invoke方法,首先调用的是AbstractCluster#invoke方法:

    @Override
    public SofaResponse invoke(SofaRequest request) throws SofaRpcException {
        SofaResponse response = null;
        try {
            // 做一些初始化检查,例如未连接可以连接
            checkClusterState();
            // 开始调用
            countOfInvoke.incrementAndGet(); // 计数+1   
            // 调用实现类的doInvoke
            response = doInvoke(request);
            return response;
        } catch (SofaRpcException e) {
            // 客户端收到异常(客户端自己的异常)
            throw e;
        } finally {
            countOfInvoke.decrementAndGet(); // 计数-1
        }
    }

我们可以从前面的DefaultConsumerBootstrap中看到他这里加载的cluster默认是取得配置文件的FailoverCluster#doInvoke

    public SofaResponse doInvoke(SofaRequest request) throws SofaRpcException {
        String methodName = request.getMethodName();
        // 获取配置的失败重试次数
        int retries = consumerConfig.getMethodRetries(methodName);
        int time = 0;
        SofaRpcException throwable = null;// 异常日志
        List<ProviderInfo> invokedProviderInfos = new ArrayList<ProviderInfo>(retries + 1);
        do {
            // 选择调用的provider,这里也就是我们讲的负载均衡的选择啦
            ProviderInfo providerInfo = select(request, invokedProviderInfos);
            try {
                // 执行过滤器链,得到返回结果
                SofaResponse response = filterChain(providerInfo, request);
                if (response != null) {
                    if (throwable != null) {
                        if (LOGGER.isWarnEnabled(consumerConfig.getAppName())) {
                            LOGGER.warnWithApp(consumerConfig.getAppName(),
                                LogCodes.getLog(LogCodes.WARN_SUCCESS_BY_RETRY,
                                    throwable.getClass() + ":" + throwable.getMessage(),
                                    invokedProviderInfos));
                        }
                    }
                    return response;
                } else {
                    throwable = new SofaRpcException(RpcErrorType.CLIENT_UNDECLARED_ERROR,
                        "Failed to call " + request.getInterfaceName() + "." + methodName
                            + " on remote server " + providerInfo + ", return null");
                    time++;
                }
            } catch (SofaRpcException e) { // 服务端异常+ 超时异常 才发起rpc异常重试
                if (e.getErrorType() == RpcErrorType.SERVER_BUSY
                    || e.getErrorType() == RpcErrorType.CLIENT_TIMEOUT) {
                    throwable = e;
                    time++;
                } else {
                    throw e;
                }
            } catch (Exception e) { // 其它异常不重试
                throw new SofaRpcException(RpcErrorType.CLIENT_UNDECLARED_ERROR,
                    "Failed to call " + request.getInterfaceName() + "." + request.getMethodName()
                        + " on remote server: " + providerInfo + ", cause by unknown exception: "
                        + e.getClass().getName() + ", message is: " + e.getMessage(), e);
            } finally {
                if (RpcInternalContext.isAttachmentEnable()) {
                    RpcInternalContext.getContext().setAttachment(RpcConstants.INTERNAL_KEY_INVOKE_TIMES,
                        time + 1); // 重试次数
                }
            }
            invokedProviderInfos.add(providerInfo);
        } while (time <= retries);

        throw throwable;
    }

我们一步步分析,先看下我们这边最重要的部分选择provider,也就是我们要讲的负载均衡!

    /**
     * 根据规则进行负载均衡
     *
     * @param message              调用对象
     * @param invokedProviderInfos 已调用列表
     * @return 一个可用的provider
     * @throws SofaRpcException rpc异常
     */
    protected ProviderInfo select(SofaRequest message, List<ProviderInfo> invokedProviderInfos)
        throws SofaRpcException {
        // 粘滞连接,当前连接可用
        if (consumerConfig.isSticky()) {
            if (lastProviderInfo != null) {
                ProviderInfo providerInfo = lastProviderInfo;
                ClientTransport lastTransport = connectionHolder.getAvailableClientTransport(providerInfo);
                if (lastTransport != null && lastTransport.isAvailable()) {
                    checkAlias(providerInfo, message);
                    return providerInfo;
                }
            }
        }
        // 原始服务列表数据 --> 路由结果
        List<ProviderInfo> providerInfos = routerChain.route(message, null);

        //保存一下原始地址,为了打印
        List<ProviderInfo> orginalProviderInfos = new ArrayList<ProviderInfo>(providerInfos);

        if (CommonUtils.isEmpty(providerInfos)) {
            throw noAvailableProviderException(message.getTargetServiceUniqueName());
        }
        // 这个主要是重试的时候将异常的调用剔除掉,后面那个总数大于已调用数的条件是为了防止剔除后没有provider了
        if (CommonUtils.isNotEmpty(invokedProviderInfos) && providerInfos.size() > invokedProviderInfos.size()) { 
            providerInfos.removeAll(invokedProviderInfos);// 已经调用异常的本次不再重试
        }

        String targetIP = null;
        ProviderInfo providerInfo;
        RpcInternalContext context = RpcInternalContext.peekContext();
        if (context != null) {
            targetIP = (String) RpcInternalContext.getContext().getAttachment(RpcConstants.HIDDEN_KEY_PINPOINT);
        }
        if (StringUtils.isNotBlank(targetIP)) {
            // 如果指定了调用地址
            providerInfo = selectPinpointProvider(targetIP, providerInfos);
            if (providerInfo == null) {
                // 指定的不存在
                throw unavailableProviderException(message.getTargetServiceUniqueName(), targetIP);
            }
            ClientTransport clientTransport = selectByProvider(message, providerInfo);
            if (clientTransport == null) {
                // 指定的不存在或已死,抛出异常
                throw unavailableProviderException(message.getTargetServiceUniqueName(), targetIP);
            }
            return providerInfo;
        } else {
            do {
                // 再进行负载均衡筛选,前面在发布consumerConfig的时候有初始化loadBalancer,默认使用的random
                providerInfo = loadBalancer.select(message, providerInfos);
                ClientTransport transport = selectByProvider(message, providerInfo);
                if (transport != null) {
                    return providerInfo;
                }
                providerInfos.remove(providerInfo);
            } while (!providerInfos.isEmpty());
        }
        throw unavailableProviderException(message.getTargetServiceUniqueName(),
            convertProviders2Urls(orginalProviderInfos));
    }

对于loadBalancer.select(message, providerInfos);我们点进去是AbstractLoadBalancer#select

    @Override
    public ProviderInfo select(SofaRequest request, List<ProviderInfo> providerInfos) throws SofaRpcException {
        if (providerInfos.size() == 0) {
            throw noAvailableProviderException(request.getTargetServiceUniqueName());
        }
        // 如果只有一个provider就会直接使用这个
        if (providerInfos.size() == 1) {
            return providerInfos.get(0);
        } else {
            // 否则调用子类的筛选方法,我们以random为例,也就是RandomLoadBalancer#doSelect
            return doSelect(request, providerInfos);
        }
    }
    
    @Override
    public ProviderInfo doSelect(SofaRequest invocation, List<ProviderInfo> providerInfos) {
        ProviderInfo providerInfo = null;
        int size = providerInfos.size(); // 总个数
        int totalWeight = 0; // 总权重
        boolean isWeightSame = true; // 权重是否都一样
        for (int i = 0; i < size; i++) {
            // 这里的权重数据来自于provider发布的时候的设置,有一个weight属性可以设置
            int weight = getWeight(providerInfos.get(i));
            totalWeight += weight; // 累计总权重
            if (isWeightSame && i > 0 && weight != getWeight(providerInfos.get(i - 1))) {
                isWeightSame = false; // 计算所有权重是否一样
            }
        }
        if (totalWeight > 0 && !isWeightSame) {
            // 如果权重不相同且权重大于0则按总权重数随机
            int offset = random.nextInt(totalWeight);
            // 并确定随机值落在哪个片断上
            for (int i = 0; i < size; i++) {
                // 使用总权重随机取值,然后落区间,那个减了之后小于0则落在哪个区间就直接取它既可
                offset -= getWeight(providerInfos.get(i));
                if (offset < 0) {
                    providerInfo = providerInfos.get(i);
                    break;
                }
            }
        } else {
            // 如果权重相同或权重为0则均等随机
            providerInfo = providerInfos.get(random.nextInt(size));
        }
        return providerInfo;
    }

然后就讲到下一行了 ClientTransport transport = selectByProvider(message, providerInfo);

    /**
     * 得到provider得到连接
     *
     * @param message      调用对象
     * @param providerInfo 指定Provider
     * @return 一个可用的transport或者null
     */
    protected ClientTransport selectByProvider(SofaRequest message, ProviderInfo providerInfo) {
        // 这里面就是前面维护的存活列表,亚健康列表和不可用列表或者第一次初始化下列表
        ClientTransport transport = connectionHolder.getAvailableClientTransport(providerInfo);
        if (transport != null) {
            // 获得服务提供者的客户端传输者
            if (transport.isAvailable()) {
                // 将这个provider设置为最后一个调用的provider
                lastProviderInfo = providerInfo;
                checkAlias(providerInfo, message); //检查分组
                return transport;
            } else {
                connectionHolder.setUnavailable(providerInfo, transport);
            }
        }
        return null;
    }

这里讲完了之后我们的负载均衡就讲完了,贴下前面的源码:

    public SofaResponse doInvoke(SofaRequest request) throws SofaRpcException {
        String methodName = request.getMethodName();
        int retries = consumerConfig.getMethodRetries(methodName);
        int time = 0;
        SofaRpcException throwable = null;// 异常日志
        List<ProviderInfo> invokedProviderInfos = new ArrayList<ProviderInfo>(retries + 1);
        do {
            ProviderInfo providerInfo = select(request, invokedProviderInfos);
            try {
                SofaResponse response = filterChain(providerInfo, request);
                if (response != null) {
                    if (throwable != null) {
                        if (LOGGER.isWarnEnabled(consumerConfig.getAppName())) {
                            LOGGER.warnWithApp(consumerConfig.getAppName(),
                                LogCodes.getLog(LogCodes.WARN_SUCCESS_BY_RETRY,
                                    throwable.getClass() + ":" + throwable.getMessage(),
                                    invokedProviderInfos));
                        }
                    }
                    return response;
                } else {
                    throwable = new SofaRpcException(RpcErrorType.CLIENT_UNDECLARED_ERROR,
                        "Failed to call " + request.getInterfaceName() + "." + methodName
                            + " on remote server " + providerInfo + ", return null");
                    time++;
                }
            } catch (SofaRpcException e) { // 服务端异常+ 超时异常 才发起rpc异常重试
                if (e.getErrorType() == RpcErrorType.SERVER_BUSY
                    || e.getErrorType() == RpcErrorType.CLIENT_TIMEOUT) {
                    throwable = e;
                    time++;
                } else {
                    throw e;
                }
            } catch (Exception e) { // 其它异常不重试
                throw new SofaRpcException(RpcErrorType.CLIENT_UNDECLARED_ERROR,
                    "Failed to call " + request.getInterfaceName() + "." + request.getMethodName()
                        + " on remote server: " + providerInfo + ", cause by unknown exception: "
                        + e.getClass().getName() + ", message is: " + e.getMessage(), e);
            } finally {
                if (RpcInternalContext.isAttachmentEnable()) {
                    RpcInternalContext.getContext().setAttachment(RpcConstants.INTERNAL_KEY_INVOKE_TIMES,
                        time + 1); // 重试次数
                }
            }
            invokedProviderInfos.add(providerInfo);
        } while (time <= retries);

        throw throwable;
    }

从上面贴的源码可以看出,我们ProviderInfo providerInfo = select(request, invokedProviderInfos);已经分析完毕,只剩最后一步
SofaResponse response = filterChain(providerInfo, request);这里就是一个过滤器链的调用,我们挑一个发送请求的讲一下,ConsumerInvoker#invoke,实际上也没啥讲的

    public SofaResponse invoke(SofaRequest sofaRequest) throws SofaRpcException {
        // 设置下服务器应用
        ProviderInfo providerInfo = RpcInternalContext.getContext().getProviderInfo();
        String appName = providerInfo.getStaticAttr(ProviderInfoAttrs.ATTR_APP_NAME);
        if (StringUtils.isNotEmpty(appName)) {
            sofaRequest.setTargetAppName(appName);
        }
        // 目前只是通过client发送给服务端
        return consumerBootstrap.getCluster().sendMsg(providerInfo, sofaRequest);
    }

以上就是负载均衡使用的上下文全部解析,我们来总结下:
1、根据路由寻址找到所有的providers
2、筛选provider
3、获取clientTransport(发送使用客户端)
4、执行filterInovker调用链

猜你喜欢

转载自www.cnblogs.com/paul-lb/p/11498793.html
今日推荐