笔者之前实习过程中负责过部门稳定性基建工作开展,其中一项任务就是负责流量染色SDK的实现和验证,具体来说,我负责的只是染色全流程中的一环,但是本文我想借助得物技术团队发表的流量染色实践系列文章,结合我自身实际开发经验,来聊聊我个人的一些拙见。
得物技术团队系列文章:
在微服务架构下,服务数量多导致的链路依赖问题会成为开发和维护过程中一块挥之不去的噩梦,使用流量染色技术便可以很好的处理这类问题。
流量染色简单来说就是对请求的流量打上标签进行染色,然后该请求在整个链路中都会携带整个标签信息,可以通过标签进行流量的调度等功能。
基于流量染色可以实现很多功能,比如灰度逻辑,蓝绿部署,泳道隔离,全链路压测等。
当我们存在多个需求进行并发开发时,如果各个需求都需要独立测试,那么最直接的做法就是多部署几套环境,但是这会导致以下问题:
上述问题的一种解决方案就是流量染色,也可以理解为环境隔离,具体做法分为以下三步:
具体来说,当我们准备开发一个需求时,我们需要在被改动的应用中配置一个版本,这个版本信息会存储在注册中心的元数据里面。
然后就去创建一个属于该需求的泳道(独立环境)进行部署,只需要部署这个需求改动的应用即可。该应用依赖的下游应用无需重新部署,因为在当前环境找不到对应的服务提供者就去路由到稳定环境找,如果稳定环境中也没有就报错。
借助流量染色,我们可以实现如下目标:
研发有时候会在本地启动服务,用于调试某个问题,好处就是能够快速复现测试环境的问题,及时发现问题代码。但是由于本地启动的服务也会注册到注册中心里面,这样测试环境的请求就有可能会路由到研发本地启动的这个服务上,研发本地的这个服务代码有可能不是最新的,导致调用异常。
该问题目前常用的解决方案是通过在本地启动时屏蔽掉服务的注册功能,也就是不注册上去,这样就不会被正常的测试请求路由到。
如果有了流量染色的功能,研发本地启动服务的时候指定一个属于自己的版本号,只要不跟正常测试的版本一致即可。正常测试的请求就不会路由到研发注册的这个实例上。
针对接口级别的灰度,目前都是在应用内进行灰度控制。但是应用级别的,目前没有特别好的方式来控制灰度。
比如有一个技术需求,需要将Redis的Client从Lettuce换成Jedis,这种场景的灰度就是应用级别的,目前的做法就是发布一个节点,然后结束发布流程,具体能被灰度到的量是由服务实例的总数量来决定的,没办法灵活控制。
如果有流量染色,可以新发一个节点,这个节点的版本升级一下,比如之前的版本是V1,那么新发的就是V2版本。首先V1版本肯定是承载生产所有流量的,可以通过网关进行控制让流量按某种方式转发到V2版本,比如用户白名单,地区,用户比例等等。有问题也可以随时将流量切回V1,非常方便。
服务要想无损进行优雅下线,还是需要做很多工作的,比如目前发布时会先将要发布的服务从注册中心注销掉,但是应用内部还是会有服务实例信息的缓存,需要等到一定的时间缓存完成清除后,对应的目标实例才不会被请求到。
如果基于染色去实现的话,将需要下线的实例信息(IP:PORT)通过配置中心推送给网关进行染色处理,染色信息跟随着请求贯穿整个链路,应用内的负载均衡组件,MQ等中间件会对要下线的目标实例信息进行过滤,这样就不会有流量到要下线的实例上去。
目前,主流的发布都是滚动部署,滚动发布的好处是成本低,不用额外增加部署的资源,一个萝卜一个坑,慢慢替换就是。不好的点在于发布时间长,全链路依赖太严重,如果发布之前依赖关系错乱了,那就是一个线上故障。
要解决这个发布速度的问题,可以基于流量染色来实现蓝绿部署。也就是在发布的时候重新部署一个V2的版本,这个V2版本的实例数量跟V1保持一致,由于这个V2版本是没有流量的,所以不存在依赖关系,大家可以同时发布,等到全部发完之后,就可以通过网关进行流量分发了,先分发一点点流量到V2版本进行验证,如果没有问题就可以慢慢放大流量,然后将V1版本的容器释放掉。
发布速度确实提升了,可是问题在于蓝绿部署的成本太高了,资源成本要翻倍,虽然发布后老的资源就回收了,但是你总的资源池还是得容纳下这2个版本并行才行。
那有没有折中的方式,既能提高发布效率又能不增加资源成本呢?
发布完成后,开始放量到V2版本,然后验证。验证之后就可以发布另一半的实例了,这样的方式总的资源是没有变化的,但是有一个比较严重的问题就是直接停掉了一半的实例,剩下的实例能不能支撑当前的流量,因为交易内的应用都是面向C端用户的,流量很有可能在短时间内达到很高的量。
全链路压测对于电商业务来说必不可少,每年有N次大促,都需要提前进行压测来确保大促的稳定。其中全链路压测最核心的一点就是流量的区分,需要区分流量是正常的用户请求还是压测平台的压测流量。
只有区分了流量,才能将压测流量进行对应的路由,比如数据库,Redis等流量需要路由到影子库中。基于流量染色就很容易给流量打标,从而区分流量的类型。
这一块内容也是笔者实习期间负责的流量染色SDK功能,具体实现思路如下图所示:
整个流量染色SDK的核心其实就是一个切面,处于可扩展性设计,我将整个染色SDK分成了四个模块,如下图所示:
这里有三个点很关键:
应用需要具备版本概念
染色信息全链路透传
流量路由控制
(这边需要处理跨线程透传的问题)
这里分两块考虑:
下面先来我们依次来看看上面两个问题在得物内部的具体实现:
服务注册–识别染色节点:
MQ改造–识别和处理MQ消息:
MQ主要解决的是,染色环境的消息生产者producer发送的消息,只被染色环境的消费者消费,染色环境如果没有消费节点,则由基准环境消费者消费。
这里之前讨论了两种做法:
得物技术团队内部选择的是第二种方案,下面基于第二种方案做详细介绍:
解决完染色标透传,以及染色标逻辑处理后,剩下就是如何在流量发起方把染色标给带上了,其实就是把染色标塞到header里面的x-infr-flowtype字段。
其中染色环境列表的获取由发布平台提供接口给到各流量入口方去选择。
至此整个业务改造基本完成,从染色流量如何构造、流量标如何透传、染色节点如何识别以及识别后重点染色逻辑如何处理等一整套流程就清晰了。
笔者实习期间只负责了染色全流程链路中最简单的一环,也就是借助流量染色技术实现压测演练过程中的动态化压力调整,而在阅读了得物技术团队的流量染色落地实践系列文章后,才发觉先前自己对流量染色的整体认知水平偏低,所以特总结此文。
流量染色技术总体而言还是比较好理解的,借助流量染色技术可以解决测试环境冲突和测试环境稳定性的问题,并且相较之前多套独立环境的方案,在成本上也有比较大的节省。并且我们也可以尝试用染色的能力解决生产灰度发布问题,相信也会有不错的效果。