Netflix开源工具:在SpringBoot实现动态路由

2,406 阅读7分钟

前言

假设你有一个服务A,要调用服务B(有三个实例,B1、B2、B3),如何只调用其中的B1和B2,屏蔽掉B3?实际上解决方法大致分为两类。

一种是外部路由,就是通过网关等组件,在请求链路上进行路由选择,即A -> 网关 -> B。

另一种是内部路由,即A服务借助一些第三方库,直接决定要访问的B服务实例,即A -> B。

本文重点介绍内部路由的一种实现方式~

Ribbon简介

一句话来说,Spring Cloud Ribbon是Netflix Ribbon实现的客户端负载均衡器。一张图能解释它的作用,从一个服务的多个实例中,最终选择一个实例用于消费。

对于没有使用过Spring Cloud的同学来说可能有点陌生,如果你写过Spring Boot应用,要调用的远程服务仅通过IP加端口的形式暴露,远程调用的姿势可能是这样的。

RestTemplate restTemplate = new RestTemplate();
String url = "http://10.10.10.10:8080";
String result = restTemplate.getForObject(url, String.class);

当被消费的服务做高可用,有多个实例的时候,这样的调用方式完全不可行。使用Ribbon的话,就可以将一个服务名映射到多个实例上。大致的使用方式如下,首先在配置文件中加上自己想要的服务名,以及该服务名后面映射的实例地址,在调用的时,url直接使用服务名。

# application.properties
# 服务名 netease-cloud
netease-cloud.ribbon.listOfServers=10.10.10.10:8080,10.10.10.20:8080,10.10.10.30:8080

# code snippet
String url = "http://netease-cloud";
String result = restTemplate.getForObject(url, String.class);

Ribbon的原理也很简单,就是对RestTemplate进行了拦截,将服务名转成了实例列表,然后根据对应的负载均衡规则选择其中一个实例进行真正的调用。所以这个例子里的RestTemplate并非是普通的RestTemplate,是经过Ribbon加强的版本。

事实上Ribbon往往配合注册中心一起使用,由注册中心周期性地提供实例列表供它使用。作为一个负载均衡器,它已经能满足大部分的需求了,基于它的实现原理事实上可以进行加强,从而满足一些更高级的需求。比如,

  1. Ribbon对提供者没有保护措施,当一个提供者接受的请求量过多时,无法将提供者从实例列表中剔除,从而缓解提供者的压力。即使有最小并发等策略的保护,还是无法完全隔绝请求进入。
  2. 做灰度发布时,让一些消费者访问新版本的提供者,另一些消费者访问旧版本的提供者。

上面两点实际上是路由的功能,它可以通过实现Ribbon的一些过滤器(ServerListFilter)来做,但是无法在不重启应用的情况下,动态地改变路由规则。

如果能强化Ribbon,加上路由功能和动态规则配置功能,能实现很多的玩法。

Ribbon加强

在增强Ribbon之前,有必要拆开这个黑盒,了解一下它里面的比较重要的组件。

  • ServerList: 用于获取服务实例列表(从内存、注册中心等地方获取)。
  • ServerListFilter: 从ServerList中得到服务实例列表后,根据特定的条件进行过滤。
  • IRule: 得到ServerListFilter过滤后的服务实例列表,根据具体的规则(如轮询、随机等),最终从列表中选择一个实例。

总体的数据流向就是上图所示。

这里有个细节影响了增强的整体方案——ServerList和ServerListFilter默认每30秒更新一次实例列表到Cached Server List(自己取的名)中。如果自己实现了ServerListFilter或ServerList,即使通过手段进行了实时的替换,也不能立刻提供最新的实例列表供IRule使用。对于实时性要求高的应用,是无法容忍的。

至此,对于增强的方案来说,必然要选择代价小的办法。最好的方式是能够通过Ribbon提供的拓展来实现,而不是直接改动源码。

经过实践,可以通过自定义的IRule,实现了动态路由功能。思路是,使用默认的ServerList和ServerListFilter,将所有的逻辑都在自定义的IRule之中实现。官方对IRule的定义是:A strategy for loadbalancing,一种负载均衡策略。我们赋予它更多的职能,官方实现的IRule(比如:轮询、随机)仅仅做了选择的工作,我们自定义的IRule包含了过滤和选择的功能。

整个自定义IRule的逻辑可以参照上图,在Custom Rule得到实例列表后,先经过一系列的filter,比如黑白名单、区域优先路由、参数分流,过滤掉不满足要求的实例,再将过滤后的实例列表传递给Custom Selector,它做了IRule真正要做的工作,根据规则选定最终的实例。

那么如何动态的配置Custom Filters和Custom Selector?简单来说,因为IRule是固定的(即我们自定义的),只要持有该对象的引用,就能随时更改其中的Filter和Selector。那么该应用如何收到配置变更的指令呢?最简单的方式,让该应用暴露一个接口,使用者调用该接口。不过该方式太不优雅,胜在工作量小,但不适合在生产环境使用。

以上方案如果要自己去实现,还是需要一些工作量,有没有一种无侵入式、代码零改造、操作方便的方案呢?此时需要祭出我们组里的秘密武器——NSF微服务框架。NSF微服务框架主要包含NSF Server(控制面)和NSF Agent(数据面)。NSF Agent的原理是通过javaagent方式增强用户应用的代码,在java类被初始化加载之前对目标类进行增强。用户应用以agent方式启动后,agent会在用户应用侧开放端口用于和NSF Server进行通信(grpc协议),使用者在NSF Server侧进行规则配置后,规则会被封装然后下发至agent,agent会负责将处理规则,使之生效。像前面说的路由规则就可以在NSF Server侧进行配置下发,然后由agent生效。其实整个方案还要复杂的多,但是核心大致是这样,细节的话本文就不赘述了。

NSF微服务框架

如果仅仅为了一个动态路由和动态负载均衡的能力,就做这样一套东西未免有杀鸡用牛刀的感觉。NSF当然不只支持这两个功能,熔断降级、服务发现、服务容错、流量染色、服务监控、动态配置等等功能,都在NSF的能力范围内。

这样一套微服务框架看起来非常美好,但是事实上也确实如此。只要你的应用基于spring boot或者使用dubbo,NSF都可以平滑接入。这里的平滑接入是指不改动应用的任何一行代码,就让应用具备各种强大的服务治理、流量控制、监控等功能。如果你还不满足于此,需要一套能帮助你们快速实现易接入、易运维的微服务解决方案,欢迎试用我们部门的轻舟(已商用)。

不使用java的同学也不要失望,我们组里也有一套基于istio和envoy的service mesh方案,不管你是go、python还是其他任何语言,都可以无侵入地进行微服务化改造。

来源:网易工程师-陈佳翰

任何有疑惑的地方,欢迎大家找我讨论,谢谢。

看到这里的小伙伴,如果你喜欢这篇文章的话,别忘了转发、收藏、留言互动

如果对文章有任何问题,欢迎在留言区和我交流~

最近我新整理了一些Java资料,包含面经分享、模拟试题、和视频干货,如果你需要的话,欢迎留言or私信我

还有,关注我!关注我!关注我!