如果要了解微服务流量是如何治理的,那么我们需要先了解微服务的流量是如何调用的,在这之前我们必须先熟悉一些基础的知识。本文将借助于我们常见的微服务开发框架Spring Cloud与Apache Dubbo来介绍微服务的服务注册与发现模型、服务路由模型以及负载均衡。当我们了解并熟悉了这些能力之后,我们可以尝试着从流量治理的场景出发基于以上这些微服务框架的模型与组件,来实现我们想要达到的流量治理能力。
服务注册与发现
通过第一章的介绍,我们了解到,在微服务架构下,将原先复杂的业务系统拆分成一系列简单的服务,且每个服务都是由一个个独立的应用且运行在各自的进程内,单个服务可以由多个节点构成,各个服务之间通过远程通信的方式交互。当微服务架构面对变化的业务流量时,每个服务都可以做到快速地弹性扩容与缩容。那么迎来了微服务架构下的第一个问题,如图 2-1-1 所示,对于其他需要调用 OrderService 的服务来说,OrderService 服务的节点状态如何感知?其他服务(UserService)必须感知到 OrderService 服务的节点信息,才可以通过远程通信的方式与之进行交互,如果节点的信息或者状态感知有误,如果感知到部分地址那么UserService 就会访问不到 OrderService 服务的全部节点,如果感知到的地址中有错误地址,那么UserService 访问 OrderService 服务时,就有概率出现服务调用错误的问题。
可以看出来,准确且及时地让其他需要的服务感知到当前服务的节点信息是非常重要的一件事情,为了更好地解决这个服务节点信息以及状态感知的问题,微服务架构引入了注册中心这个角色。OrderService服务通过向注册中心注册自己的服务并将自己的节点信息与状态维护在注册中心中。UserService 服务如果需要访问 OrderService 服务,那么 UserService 会从注册中心中订阅 OrderService 服务,并拉取注册中心中维护的 OrderService 节点列表。这样 UserService 就可以从 OrderService 节点列表中任意选择一个节点进行调用,实现服务的调用。这个过程也就是服务注册与服务订阅,Spring Cloud 和 Apache Dubbo 都是通过服务注册/服务发现机制实现服务节点信息的维护与感知。
2-1-2
Spring Cloud/Apache Dubbo的服务调用过程如图 2-1-2 所示,Provider 应用(服务提供者)一个或多个节点在启动之后,会在注册中心中注册当前服务的节点信息,Consumer 应用(服务消费者)会向注册中心发现/订阅所需调用的 Provider 的节点信息,得到对应Provider服务的节点列表,之后选择其中的一个节点进行服务调用。
服务路由与负载均衡
站在服务消费者的角度思考,当我们得到了我们所需调用的服务提供者的节点列表之后,会选择其中一个节点进行服务调用,那么我们该如何选择呢?如果我们一直只选择其中第一个节点进行调用,会造成服务提供者的第一个节点压力巨大,但其他节点却闲得发慌,这肯定是不合理的。因此如何选择更合适的节点进行调用,这就是服务路由与负载均衡需要考虑的事情。
1、服务路由
服务路由的指责就是根据一定的路由策略从所有的地址表中筛选出最终目的地址集合。值得一提的是我们可以为目标服务配置多种路由策略,多个路由如同流水线一样,形成一条路由链:target = rn(…r3(r2(r1(src))))
。可以用这样的公式来体现服务路由的过程,其中 r1 的输出作为 r2 的输入。
服务消费者会向注册中心获取到目标服务提供者的地址列表之后,会根据路由策略选出需要调用的服务提供者地址列表。
2、负载均衡
经过服务路由的服务提供者地址列表如果还是存在多个地址,就需要经过客户端负载均衡算法最终选择一个访问的目标节点地址。我们希望服务消费者的请求调用合理地分配至服务提供者的各个节点上,避免服务提供者负载不均。如果出现服务提供者负载过大,轻则导致部分请求超时,重则导致服务提供者崩溃。
微服务中常见客户端负载均衡算法如下:
随机(Random)算法:在节点列表中随机选择一个目标节点。
轮训(RoundRobin)算法:在节点列表中轮流选择一个目标节点。假设有 3 个节点,第一次选择第 1 个节点,第二次选择第 2 个节点。以此类推,第三次选择第 3 个节点,第四次则选择第 1 个节点。
基于权重的随机(Weightd Random)算法:举一个简单的例子说明,假设有 3 个节点,第一个节点的权重是 10,第二个节点的权重是 20,第三个节点的权重是 70 。每次选择一个1到100之间的数字,如果是 1 到 10 之间,就选择第一个节点,如果是10到30之间就选择第二个节点,如果是30到100之间,就选择第三个节点。
基于最少连接数(Least Connections)算法:选择当前具有最小连接数的节点。
基于 hash 一致性(Consistent Hashing)算法:根据一致性 hash 的算法,尽可能将相同参数的请求选择到同一个节点上。
相对于服务路由来说,一个服务的负载均衡算法只能选择一个。负载均衡是一个比较通用的能力,基本上所有 RPC 框架都会有对应的实现。对于Apache Dubbo来说,通过内部 LoadBalance 组件实现了负载均衡能力;对于Spring Cloud 来说,官方提供了Netflix Ribbon跟Spring Cloud LoadBalancer两种实现。
有了服务路由与负载均衡后,开发者无需关心目标服务的节点列表获取与选择的逻辑,只需调用目标服务即可。流量治理能力的建设,离不开对服务路由与负载均衡的实现。我们可以通过实现一个路由策略,做到将特定的流量比如 Header 中带有 X-user-id 为 12345 的流量,路由至服务提供者中带有新版本代码的节点上;我们需要在线上服务提供者的 10.0.0.79 节点上进行内存 Dump ,为了不影响线上的业务流量,还可以通过修改负载均衡策略,让 IP 为 10.0.0.79 的服务提供者节点不接受任何来自消费者的流量,等到我们可以收集完内存 Dump 文件之后,再恢复负载均衡的策略。我们可以通过实现不同的路由与负载均衡策略可以完成对微服务流量调用的控制。