链路追踪背景
凡事有利必有弊,这种模式在给我们带来更好的可扩展性的同时,也带来了一些新的问题。例如,排查问题的困难:任意节点的异常都可能导致上游链路的异常,难以追根溯源;系统拓扑复杂难以把控,健壮性存在隐患。
下面,我们将简单介绍其在APM领域的应用,以及在服务依赖治理和研发效能提升方面的实践。
服务依赖治理
- 反向依赖。反向依赖指高等级服务依赖了低等级服务。例如,租户服务是我们的核心服务之一,而统计服务重要性相对较低,显然,我们不允许租户服务依赖统计服务。通过服务拓扑图和服务等级的结合,我们可以很容易的将反向依赖分析自动化,实时预警。
- 强弱依赖。强依赖指下游服务发生异常时,将影响当前节点的稳定性。在设计时,我们应该充分考虑强依赖在当前场景中的必要性。强依赖是否可以弱化,如果不能,业务场景是否允许加上熔断降级之类的的保护措施。强弱依赖的梳理,我们可以结合故障注入工具,产出系统化的报告。
- 环状依赖。环状依赖往往是边界不清晰的表现,绞成一团,层次不清。对环状依赖的梳理也是我们对业务边界和系统边界的梳理,对系统整体健康度的提升非常有意义。
研发效能提升
业务发展往往导致并行迭代的增多,而这些并行迭代难免会改动到相同的服务,尤其是一些核心基础服务。如下图,
1. 环境争夺。如图,Story-B需要部署ticket服务,与此同时Hotfix-A也等待验证,同样需要部署ticket服务,这意味着至少有一方会被阻塞等待,这种串行模式,极大地降低了我们的交付效率。并行迭代越多,效率降低越明显。
项目规模不大时,我们往往能通过一些管理手段来协调。例如版本串行化,通过将迭代计划错开,避免在同一个时间段都要去部署某个服务。测试环境只部署特定分支,需要验证时则将各自的代码都合并到此分支;要求部署到测试环境的代码必须达到某种标准以提升测试环境的稳定性。
细想一下,其实问题的根源是大家共用一套测试环境,所以我们的研发活动出现了资源竞争,我们对某个服务的操作可能影响到其他服务。
为了描述方便,我们把上图中的env-x环境,叫测试环境;图中的下半部分,叫回归环境。测试环境只包含本次迭代需要部署的应用,回归环境包含所有应用。
也就是说,对于环境使用者的请求,如果相关的应用在该环境内,则请求只会被该环境内的应用处理,否则路由到回归环境处理。
研发流程方面,我们不再像以前一样部署到大家都在使用的环境中去验证,而是各自创建各自独享的环境,在自己的环境中完成相关工作。
- 识别并传递请求对应的环境信息
- ⼲预中间件的实例选择/消费规则
(一)识别并传递请求对应的环境信息
首先,我们需要能将请求和测试环境关联起来。
例如,在我们的SaaS系统七鱼里,我们使用租户id来作为我们的标识。在我们的平台创建测试环境时,除了指明要部署的应用外,我们还需要输入租户信息,通过这种方式完成请求和测试环境的映射。
(二)⼲预中间件的实例选择/消费规则
RPC框架——
所以改造的手段很明确,provider启动时,我们在元数据中写入环境ID。在实例选择时,我们从请求的链路数据中拿出环境ID与之做匹配。
RPC在调用之前有如上所述的实例筛选过程,但消息中间件没有这个逻辑,不过我们依然可以干预消费规则,即在消费者拿到消息后判断是丢弃消息还是消费该消息。以kafka为例,测试环境的kafka consumer启动时,修改consumer groupid为groupid_${env}。kafka consumer接收到消息时,执行和上述RPC框架筛选实例时类似的逻辑即可。
定时任务其实最为特殊。前文中我们提到,在请求的统一入口,查询请求所对应的环境信息并写入链路。但是定时任务发起的请求并不是用户触发的,它来自系统内部,定时调度组件才是请求的“源头”。所以我们需要在定时任务执行之初,就加入我们的判断标识逻辑,这要求我们:
- 定时任务需要有统一的调度平台,避免各业务模块姿势各异,无法由通用组件统一处理
- 针对调度组件的任务分发/分片机制的改造,统一抽象执行层,加入环境隔离逻辑
结束语
整体来说,我们构建了统一的链路追踪体系,支撑了服务依赖治理及环境隔离技术的实现,但这并不是终点,我们还可以发掘更多的场景,比如SaaS系统的多租户资源隔离,或者异常监控预警。世界很大,一起多探索。