vivo 互联网技术

vivo 互联网业务就近路由技术实战

2020-12-28  本文已影响0人  vivo互联网技术

一、问题背景

在vivo互联网业务高速发展的同时,支撑的服务实例规模也越来越大,然而单个机房能承载的机器容量是有限的,于是同城多机房甚至多地域部署就成为了业务在实际部署过程中不得不面临的场景。

一般情况下,同一个机房内部的网络调用平均时延在0.1ms左右,同城多个机房之间的平均时延在1ms左右,跨地域机房之间的网络时延则更大,例如北京到上海的平均时延达到了30ms以上。

在业务多机房部署场景中,内部服务如果存在大量的跨机房、甚至跨地域的网络调用,则请求时延会显著加大,会直接影响到服务质量,甚至是用户体验。

二、解决方案

要解决以上问题,只需要实现服务消费者和服务提供者之间的网络调用尽量是同机房内部、或者是同地域即可,即服务消费者在调用服务提供者时,优先调用部署在本机房的服务提供者,当本机房没有部署该服务提供者时,再跨机房调用同地域的同城机房、甚至是跨地域机房的服务。

以上策略我们内部称为 就近路由。业务的就近路由示意图如下:

01.png

为了简单易用,vivo内部的服务间调用均使用了RPC框架来实现,在Java技术栈方向,我们选择了阿里巴巴开源的RPC框架 Dubbo,针对该场景的问题,我们扩展了Dubbo框架的源码实现,提供了 Dubbo就近路由能力。

三、技术原理说明

在服务提供者注册服务时,把该实例节点的机房信息【我们内部将机房标签定义为 app_loc 字段】注册到注册中心,在开启了就近路由策略后,消费者在过滤服务列表时,会自动筛选、匹配和消费者自己 app_loc 字段相同的服务提供者列表,从而实现就近路由访问。我们实现的就近路由策略,在本机房存在对应服务提供者的情况下,消费者会优先调用本机房的服务。

四、实现方案

开源版本的Dubbo框架并不提供就近路由能力,我们需要基于Dubbo框架源码扩展实现。Dubbo框架整体设计如下:【左侧为服务消费者使用的接口,右侧为服务提供方使用的接口】。

02.png

我们知道,Dubbo框架中服务消费者选择具体的服务提供者实例调用的匹配、筛选逻辑是在 Consumer 侧完成的,在 Cluster 层中,消费者会先应用用户配置的 Router 规则,然后再符合规则的服务提供者列表中,使用 LoadBalance 策略选择具体的服务提供者节点进行调用。

结合Dubbo框架源码实现,我们选择基于Dubbo 2.7.x版本的router层扩展口 org.apache.dubbo.rpc.cluster.Router 实现一种新的路由方式,即就近路由(我们内部标识为 NearestRouter)

有了具体的解决方案,我们很快就完成了代码开发,内部也发布了一个集成就近路由策略的Dubbo版本,但在实际的线上灰度和业务推广过程中,我们实现的初版就近路由碰到了新的问题:

基于以上问题,我们细化了实现方案:

有了以上细化方案,我们梳理的就近路由大致逻辑流程如下:

image image

扩展Dubbo框架 Router 接口实现的 NearestRouter 核心代码如下:


public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL consumerUrl, Invocation invocation) throws RpcException {
        // validate application name (this.url -> routerUrl)
        String applicationName = getProperty(APP_NAME, consumerUrl.getParameter(CommonConstants.APPLICATION_KEY, ""));
        boolean validAppFlag = application.equals(applicationName) || CommonConstants.ANY_VALUE.equals(application);
        if (!validAppFlag) {
            return invokers;
        }
 
        String local = getProperty(APP_LOC);
        if (invokers == null || invokers.size() == 0) {
            return invokers;
        }
        List<Invoker<T>> result = new ArrayList<Invoker<T>>();
        for (Invoker invoker: invokers) {
            String invokerLoc = getProperty(invoker, invocation, APP_LOC);
            if (local.equals(invokerLoc)) {
                result.add(invoker);
            }
        }
 
        if (result.size() > 0) {
            if (fallback){
                // 开启服务降级,available.ratio = 当前机房可用服务节点数量 / 集群可用服务节点数量
                int curAvailableRatio = (int) Math.floor(result.size() * 100.0d / invokers.size());
                if (curAvailableRatio <= availableRatio) {
                    return invokers;
                }
            }
 
            return result;
        } else if (force) {
            LOGGER.warn("The route result is empty and force execute. consumerIp: " + NetUtils.getLocalHost()
                    + ", service: " + consumerUrl.getServiceKey() + ", appLoc: " + local
                    + ", routerName: " + this.getUrl().getParameterAndDecoded("name"));
            return result;
        } else {
            return invokers;
        }
}

在vivo内部的服务治理平台上,我们提供了可视化的配置能力,页面内容如下:

image

通过扩展Dubbo框架,服务治理平台能力支持,我们以上问题都得到了较好的解决。

五、写在将来

虽然以上解决方案能覆盖我们当前业务场景中的大部分问题,随着业务的高速发展,新的业务问题也接踵而至。

针对以上问题,我们的解决思路如下:

作者:LuoLiang

上一篇 下一篇

猜你喜欢

热点阅读