假设你有一个服务A,要调用服务B(有三个实例,B一、B二、B3),如何只调用其中的B1和B2,屏蔽掉B3?实际上解决方法大体分为两类。java
一种是外部路由,就是经过网关等组件,在请求链路上进行路由选择,即A -> 网关 -> B。python
另外一种是内部路由,即A服务借助一些第三方库,直接决定要访问的B服务实例,即A -> B。spring
本文重点介绍内部路由的一种实现方式~markdown
一句话来讲,Spring Cloud Ribbon是Netflix Ribbon实现的客户端负载均衡器。一张图能解释它的做用,从一个服务的多个实例中,最终选择一个实例用于消费。并发
对于没有使用过Spring Cloud的同窗来讲可能有点陌生,若是你写过Spring Boot应用,要调用的远程服务仅经过IP加端口的形式暴露,远程调用的姿式多是这样的。app
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每每配合注册中心一块儿使用,由注册中心周期性地提供实例列表供它使用。做为一个负载均衡器,它已经能知足大部分的需求了,基于它的实现原理事实上能够进行增强,从而知足一些更高级的需求。好比,运维
上面两点其实是路由的功能,它能够经过实现Ribbon的一些过滤器(ServerListFilter)来作,可是没法在不重启应用的状况下,动态地改变路由规则。微服务
若是能强化Ribbon,加上路由功能和动态规则配置功能,能实现不少的玩法。
在加强Ribbon以前,有必要拆开这个黑盒,了解一下它里面的比较重要的组件。
整体的数据流向就是上图所示。
这里有个细节影响了加强的总体方案——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的能力范围内。
这样一套微服务框架看起来很是美好,可是事实上也确实如此。只要你的应用基于spring boot或者使用dubbo,NSF均可以平滑接入。这里的平滑接入是指不改动应用的任何一行代码,就让应用具有各类强大的服务治理、流量控制、监控等功能。若是你还不知足于此,须要一套能帮助大家快速实现易接入、易运维的微服务解决方案,欢迎试用咱们部门的轻舟(已商用)。
不使用java的同窗也不要失望,咱们组里也有一套基于istio和envoy的service mesh方案,无论你是go、python仍是其余任何语言,均可以无侵入地进行微服务化改造。
来源:网易工程师-陈佳翰
任何有疑惑的地方,欢迎你们找我讨论,谢谢。
看到这里的小伙伴,若是你喜欢这篇文章的话,别忘了转发、收藏、留言互动!
若是对文章有任何问题,欢迎在留言区和我交流~
最近我新整理了一些Java资料,包含面经分享、模拟试题、和视频干货,若是你须要的话,欢迎留言or私信我!