16 | 业务分组:如何隔离流量?

你好,我是何小锋。上一讲我们介绍了RPC中常用的保护手段“熔断限流”,熔断是调用方为了避免在调用过程中,服务提供方出现问题的时候,自身资源被耗尽的一种保护行为;而限流则是服务提供方为防止自己被突发流量打垮的一种保护行为。虽然这两种手段作用的对象不同,但出发点都是为了实现自我保护,所以一旦发生这种行为,业务都是有损的。

那说起突发流量,限流固然是一种手段,但其实面对复杂的业务以及高并发场景时,我们还有别的手段,可以最大限度地保障业务无损,那就是隔离流量。这也是我今天重点要和你分享的内容,接下来我们就一起看看分组在RPC中的应用。

为什么需要分组?

在我们的日常开发中,我们不都提倡让用户使用起来越简单越好吗?如果在接口上再加一个分组维度去管理,不就让事情变复杂了吗?

实则不然,举个例子。在没有汽车的年代,我们的道路很简单,就一条,行人、洋车都在上边走。那随着汽车的普及以及猛增,我们的道路越来越宽,慢慢地有了高速、辅路、人行道等等。很显然,交通网的建设与完善不仅提高了我们的出行效率,而且还更好地保障了我们行人的安全。

同样的道理,我们用在RPC治理上也是一样的。假设你是一个服务提供方应用的负责人,在早期业务量不大的情况下,应用之间的调用关系并不会复杂,请求量也不会很大,我们的应用有足够的能力扛住日常的所有流量。我们并不需要花太多的时间去治理调用请求过来的流量,我们通常会选择最简单的方法,就是把服务实例统一管理,把所有的请求都用一个共享的“大池子”来处理。这就类似于“简单道路时期”,服务调用方跟服务提供方之间的调用拓扑如下图所示:

后期因为业务发展丰富了,调用你接口的调用方就会越来越多,流量也会渐渐多起来。可能某一天,一个“爆炸式惊喜”就来了。其中一个调用方的流量突然激增,让你整个集群瞬间处于高负载运行,进而影响到其它调用方,导致它们的整体可用率下降。而这时候作为应用负责人的你,那就得变身“救火队长”了,要想尽各种办法来保证应用的稳定。

在经过一系列的救火操作后,我们肯定要去想更好的应对办法。那回到问题的根本去看,关键就在于,早期为了管理方便,我们把接口都放到了同一个分组下面,所有的服务实例是以一个整体对外提供能力的。

但后期因为业务发展,这种粗暴的管理模式已经不适用了,这就好比“汽车来了,我们的交通网也得抓紧建设”一样,让人车分流。此时,道路上的人和车就好比我们应用的调用方,我们可以尝试把应用提供方这个大池子划分出不同规格的小池子,再分配给不同的调用方,而不同小池子之间的隔离带,就是我们在RPC里面所说的分组,它可以实现流量隔离。

怎么实现分组?

现在分组是怎么回事我们搞清楚了,那放到RPC里我们该怎么实现呢?

既然是要求不同的调用方应用能拿到的池子内容不同,那我们就要回想下服务发现了,因为在RPC流程里,能影响到调用方获取服务节点的逻辑就是它了。

[第 08 讲] 我们说过,服务调用方是通过接口名去注册中心找到所有的服务节点来完成服务发现的,那换到这里的话,这样做其实并不合适,因为这样调用方会拿到所有的服务节点。因此为了实现分组隔离逻辑,我们需要重新改造下服务发现的逻辑,调用方去获取服务节点的时候除了要带着接口名,还需要另外加一个分组参数,相应的服务提供方在注册的时候也要带上分组参数。

通过改造后的分组逻辑,我们可以把服务提供方所有的实例分成若干组,每一个分组可以提供给单个或者多个不同的调用方来调用。那怎么分组好呢,有没有统一的标准?

坦白讲,这个分组并没有一个可衡量的标准,但我自己总结了一个规则可以供你参考,就是按照应用重要级别划分。

非核心应用不要跟核心应用分在同一个组,核心应用之间应该做好隔离,一个重要的原则就是保障核心应用不受影响。比如提供给电商下单过程中用的商品信息接口,我们肯定是需要独立出一个单独分组,避免受其它调用方污染的。有了分组之后,我们的服务调用方跟服务提供方之间的调用拓扑就如下图所示:

通过分组的方式隔离调用方的流量,从而避免因为一个调用方出现流量激增而影响其它调用方的可用率。对服务提供方来说,这种方式是我们日常治理服务过程中一个高频使用的手段,那通过这种分组进行流量隔离,对调用方应用会不会有影响呢?

如何实现高可用?

分组隔离后,单个调用方在发RPC请求的时候可选择的服务节点数相比没有分组前减少了,那对于单个调用方来说,出错的概率就升高了。比如一个集中交换机设备突然坏了,而这个调用方的所有服务节点都在这个交换机下面,在这种情况下对于服务调用方来说,它的请求无论如何也到达不了服务提供方,从而导致这个调用方业务受损。

那有没有更高可用一点的方案呢?回到我们前面说的那个马路例子上,正常情况下我们是必须让车在车道行驶,人在人行道上行走。但当人行道或者车道出现抢修的时候,在条件允许的情况下,我们一般都是允许对方借道行驶一段时间,直到道路完全恢复。

我们同样可以把这个特性用到我们的RPC中,要怎么实现呢?

在前面我们也说了,调用方应用服务发现的时候,除了带上对应的接口名,还需要带上一个特定分组名,所以对于调用方来说,它是拿不到其它分组的服务节点的,那这样的话调用方就没法建立起连接发请求了。

因此问题的核心就变成了调用方要拿到其它分组的服务节点,但是又不能拿到所有的服务节点,否则分组就没有意义了。一个最简单的办法就是,允许调用方可以配置多个分组。但这样的话,这些节点对于调用方来说就都是一样的了,调用方可以随意选择获取到的所有节点发送请求,这样就又失去了分组隔离的意义,并且还没有实现我们想要的“借道”的效果。

所以我们还需要把配置的分组区分下主次分组,只有在主分组上的节点都不可用的情况下才去选择次分组节点;只要主分组里面的节点恢复正常,我们就必须把流量都切换到主节点上,整个切换过程对于应用层完全透明,从而在一定程度上保障调用方应用的高可用。

总结

今天我们通过一个道路划分的案例,引出了在RPC里面我们可以通过分组的方式人为地给不同的调用方划分出不同的小集群,从而实现调用方流量隔离的效果,保障我们的核心业务不受非核心业务的干扰。但我们在考虑问题的时候,不能顾此失彼,不能因为新加一个的功能而影响到原有系统的稳定性。

其实我们不仅可以通过分组把服务提供方划分成不同规模的小集群,我们还可以利用分组完成一个接口多种实现的功能。正常情况下,为了方便我们自己管理服务,我一般都会建议每个接口完成的功能尽量保证唯一。但在有些特殊场景下,两个接口也会完全一样,只是具体实现上有那么一点不同,那么我们就可以在服务提供方应用里面同时暴露两个相同接口,但只是接口分组不一样罢了。

课后思考

在我们的实际工作中,测试人员和开发人员的工作一般都是并行的,这就导致一个问题经常出现:开发人员在开发过程中可能需要启动自身的应用,而测试人员为了能验证功能,会在测试环境中部署同样的应用。如果开发人员和测试人员用的接口分组名刚好一样,在这种情况下,就可能会干扰其它正在联调的调用方进行功能验证,进而影响整体的工作效率。不知道面对这种情况,你有什么好办法吗?

欢迎留言和我分享你的思考,也欢迎你把文章分享给你的朋友,邀请他加入学习。我们下节课再见!

精选留言

  • Darren

    2020-03-25 00:21:25

    我们目前使用的不同的注册中心,就是注册中心是部署了3份,prod、qa、test等,其实test和dev用的是同一个注册中心,因为我们的注册中心内部也有环境的区分,服务在往注册中心注册时,需要说明自己的环境。
    所以我们目前服务间的调用是:
    prod走生产网关prod.gateway.com(网关同步注册中心信息) 环境参数默认prod
    qa走生产网关qa.gateway.com(网关同步注册中心信息) 环境参数默认qa
    test和dev走 test.gateway.com(网关同步注册中心信息),test需要环境的参数,test就是tets,dev就是dev
    作者回复

    是的,环境硬隔离最省心

    2020-03-30 20:40:52

  • 刘楠

    2020-03-25 07:34:51

    环境不同,注册中心不同
    作者回复

    是个好办法。

    2020-03-25 08:57:20

  • 张申傲

    2021-02-03 15:24:57

    解决高可用的思路,还可以增加通用的紧急车道,对应 RPC 里面就是默认分组,当一个分组下所有节点都不可用时,允许将流量切到默认分组。
  • cwfighter

    2020-09-02 23:32:39

    K8S的namespace是个环境隔离好解决方案,放在rpc觉得有点重;实际的流量隔离可能更多是做业务做SET,同时也是容灾考虑,因为只是服务提供方做了流量隔离,但服务提供方依赖的服务没有做流量隔离其实意义不大,同样会因为流量突增引发异常。这样如果都做了流量隔离,其实也就是SET化架构了。所以不太明白rpc的分组功能究竟使用场景是什么,调用链一长,分组就很难维护了,还是说分组就是为了SET化功能考虑的?
  • 李志博

    2020-05-18 20:29:59

    其实我想问1个问题,服务分组的方式,代码仓库还是1个,我们想用服务分组做核心和非核心链路的隔离,但是谁能保证,非核心结构的改动,不会间接影响核心的分组呢,毕竟代码还是同一套,这样的话,隔离还是不彻底的,不知道老师有没有好的解决方案
  • 百威

    2020-03-27 19:53:07

    直连
    作者回复

    直连的话,服务发现就失去意义了

    2020-03-30 20:14:59

  • 盘胧

    2020-03-25 21:14:24

    不是很明白了。开发这边开发环境容器化,我们云主机克隆过来或者直接拉镜像都行,最后修改好配置。就相当于和开发隔离开来了。开发这边每天的更新打包好,我们也拿过来打包升级就行了。
    作者回复

    关键在于配置是否存在开发测试冲突

    2020-03-30 20:38:57

  • Dovelol

    2020-03-25 09:00:52

    老师好,如果没有用rpc而是走http调用的微服务怎么做分组呢,现在有在网关可以做分组的吗?
    作者回复

    可以通过不同vip来隔离

    2020-03-30 20:37:23

  • Geek_9815f1

    2021-05-02 21:12:17

    采用染色流量的方式区分就好了。
  • 陈国林

    2020-03-26 11:54:38

    开发推荐使用开发机测试,测试则使用Pre环境,互不干扰
    作者回复

    如果能隔离环境肯定更好,很多企业是不隔离测试和开发环境的

    2020-03-30 20:32:29

  • hello

    2020-03-25 16:05:26

    故障隔离还能这么玩,今天又见识到了一种新思路(”借道“),感谢老师!
    作者回复

    加油

    2020-03-30 20:33:35

  • 小哇

    2020-03-25 11:22:44

    请问老师,您讲的是同个服务集群的分组还是说不同服务的分组,如果是同个服务分组,既然同个服务里有核心和非核心接口,那是不是继续拆分服务才对呢?如果是不同服务,分组只是为了标识不同的应用而已吧?
    作者回复

    同一个接口分不同组,分组的目的就是为了隔离不同调用方

    2020-03-30 20:35:44

  • Reason

    2020-03-25 10:03:34

    服务本身增加环境属性,注册信息中增加环境信息,服务发现时默认发现同环境的服务,类似于再增加个分组级别?
    作者回复

    是的,可以利用自动部署来自动加

    2020-03-30 20:36:49

  • 神经蛙

    2023-03-14 09:24:55

    分组不就是路由吗
  • 丰丰丰锅ÄÖÜ🇩🇪

    2022-11-30 09:22:35

    多分组的话,主分组的节点是平时正常调用的节点,次分组则没调用过。那么因异常情况切到次分组后,会不会遇到过前面“优雅启动”章节里提到的冷启情况呢
  • hillwater

    2022-10-12 23:13:40

    给服务提供方分组,分组怎么实现呢,特别是有扩缩容的情况,怎么管理维护分组呢
  • gsz

    2021-06-07 08:18:45

    分组是指将服务提供方进行分组,不同的服务调用放访问不同组的服务提供方吗?
  • Geek_9oth6g

    2021-05-19 00:35:33

    这里提到对服务提供方进行分组,一个应用部署到集群上,部分机器提供核心业务A类接口,部分机器提供非核心业务B类接口,这样会不会额外因素分布式事务的问题又提升了复杂度呢?
  • êwěn

    2021-04-02 18:55:41

    给请求染色经过网关做路由。
  • 程志东

    2021-03-24 08:41:28

    不明白:服务在注册时,是不同的ip,提供不同的分组参数吗?怎么做到的?