深度剖析线上应用节点流量隔离技术

本文涉及的产品
可观测监控 Prometheus 版,每月50GB免费额度
函数计算FC,每月15万CU 3个月
可观测可视化 Grafana 版,10个用户账号 1个月
简介: 深度剖析线上应用节点流量隔离技术

作者:谢文欣(风敬)


为什么要做流量隔离


源于一个 EDAS 客户遇到的棘手情况:他们线上的一个 Pod CPU 指标异常,为了进一步诊断问题,客户希望在不重建此 Pod 的情况下保留现场,但诊断期间流量还会经过这个异常 Pod,导致影响服务质量,于是询问我们有没有办法可以把流入异常节点的流量摘除掉,形成一个隔离的诊断环境。经诊断后,如果异常可以修复,待修复完成后,再解除流量隔离,节点恢复正常工作。


除了在诊断场景需要对所有输入流量进行隔离外,在一些线上演练中还需对特定流量进行隔离以实现模拟演练效果。面对这类流量隔离问题时,我们首先考虑的是全链路流量控制。目前,EDAS 上的全链路流控能够在不重启应用节点的情况下控制流量走向。然而,全链路流控仅能控制微服务框架流量,无法满足隔离所有或特定流量的需求。


为此,我们进行了深入研究,实现了一套开箱即用的流量隔离工具,能够动态隔离特定流量,并在隔离后可随时恢复,以满足各种场景下的流量隔离需求。


隔离哪些流量


流量隔离的目的是阻断应用节点的流入流量,首先明确下微服务应用节点流入的流量有哪些。


流入微服务应用节点的流量大致可以分为两大类:服务流量、事件流量。以常见的微服务应用为例,其流量组成如下图所示。


image.png


服务流量指一个微服务应用的所有节点作为一个网络实体,对外提供一组服务,被其他系统、服务或用户发起请求产生的调用。对于服务流量,节点本身不直接决定流量的流入与否,而是由一套服务注册与发现机制维护流量路径的逻辑关系。节点经过注册,成为服务的一个端点。调用方对服务发起请求时,被调用方是服务的逻辑地址,经过转发和地址转换,请求被路由到服务端点的实体节点。隔离服务流量的一个可选方案是破坏服务调用的通信连接,但这种方法势必会影响服务质量。在保持服务整体功能正常运行的同时,一个更优雅的方案是破坏服务与实体节点之间的映射关系。这样,在路由过程中,流量将按照预期避开特定节点,而被引导至其他节点。服务流量主要涵盖了 K8s Service 以及使用 Nacos 等注册中心发布的由 Spring Cloud、Dubbo 等微服务框架构建的服务。


事件流量指应用内部的事件驱动架构产生的流量,包括由中间件传递至应用节点的事件或消息,这类通信通常是异步的,例如来自消息队列 RocketMQ 的消息流量,来自调度框架 SchedulerX 触发调度的事件流量。中间件和应用节点之间通常遵循 client-server 通信,因此可以考虑通过破坏通信连接来隔离中间件发来的消息或事件流量。


服务流量隔离


K8s Service

对于使用 K8s Service 暴露服务的应用,Service 声明的服务与应用 Pod 之间的映射关系由 Endpoints 对象维护。Endpoints 对象的 subsets 字段表示 Serivce 的一组端点,每个端点代表一个应用 Pod 的网络地址,即一个实际提供服务的 Pod 实例。subsets 字段包含了这些端点的详细信息,如 IP 地址和端口。Endpoints 控制器通过 API Server 监听 Pod 的变更情况,并随后同步更新 Endpoints 的端点列表。因此,要隔离 K8s Service 的流量,需要破坏 Endpoints 对 Pod 的指向,将待隔离的 Pod 网络地址从 Endpoints 的端点列表中移除。同时,需要通过 Informer 机制监听 Endpoints 对象的变化,以保证 Endpoints 在后续变更或控制器 Reconcile 过程中也能维持预期状态。




Dubbo

对于使用注册中心暴露服务的应用,注册中心负责管理服务节点。只要注册关系存在且应用节点存活,注册中心会将流量调度到该应用节点。而破坏服务注册关系的操作被称为服务注销,应用节点进行服务注销之后,注册中心便不会将流量导入到注销节点,也就形成了流量隔离。


要实现 Dubbo 微服务的动态注销,首先需要从源码级别了解 Dubbo 服务注册原理。以 Dubbo 2.7.0 为例,其服务注册模块的大致结构如下:


  1. Dubbo 应用中存在一个 AbstractRegistryFactory 单例,负责注册中心 Registry 的容器初始化。类属性 REGISTRIES 维护了微服务列表与注册中心实例的映射关系。
  2. AbstractRegistry 实现了 Registry 接口,作为一个模板,实现了特定的公共方法,如服务注册(register)、服务注销(unregister)等。它还维护了已注册服务 URL 列表。
  3. FailbackRegistry 基于 AbstractRegistry,提供了失败重试机制。同时,它提供了注册中心的 doRegister 和 doUnregister 抽象方法。当执行 register/unregister 时,会调用 doRegister/doUnregister 方法。
  4. 注册中心(如 NacosRegistry、RedisRegistry)实现了具体的服务注册(doRegister)和服务注销(doUnregister)逻辑。


image.png


由源码可见,Dubbo 的服务注册模块已经内置了可动态注销/重注册服务的方法。因此, Dubbo 微服务隔离可通过主动触发其注册中心对象的服务注销方法来实现。同理,如果需要恢复服务节点,主动触发服务注册方法,更新注册中心的服务映射关系。


在确定「触发注册中心对象的服务注销方法」这一技术方向之后,需要解决如何获取对象和触发方法这两个问题。在 Java 环境中,我们很容易想到使用 Agent 技术对进程行为进行干预。然而,常规的基于字节码埋点的 Agent 无法满足随时启用的需求,因为它依赖于应用代码的具体执行路径。只有当执行路径触及埋点时,Agent 代码才会被触发,从而从上下文中获取对象并通过反射调用相关方法。然而,与注册中心相关的埋点通常设置在程序启动初期,此时会执行注册中心初始化、服务注册等操作,比较容易找到合适的埋点。在程序对外提供服务期间,程序主动发起的注册中心操作较少,因此很难找到合适的埋点来获取预期的上下文。在需要隔离应用流量时,此时动态挂入 Agent,由于执行路径中没有能获取注册中心上下文的埋点,Agent 代码将无法生效。


因此,我们需要一个能够主动获取对象并触发对象方法的即开即用的 Agent 工具。在这里,我们引入了 JVMTI 技术。JVMTI(JVM Tool Interface)是一种虚拟机提供的原生编程接口,允许开发人员创建 Agent 以探查 JVM 内部的运行状态,甚至控制 JVM 应用程序的执行。JVMTI 能够从 Java 堆中获取特定类和对象信息,然后通过反射触发方法,完美地满足了我们的需求。


由于 JVMTI 是一套 JVM 原生编程接口,需要使用 C/C++ 进行编写。编译后的产物是动态链接库(.so 或 .dll 文件)。Java 运行环境通过 JNI(Java Native Interface)与 JVMTI 进行交互。整体作为一个 Java Agent,通过 Attach API 动态地挂载到目标 JVM 中。

image.png


得益于 JVMTI Agent 的强大功能,我们能够在 Java 应用内相对简便地实施某些控制逻辑。为实现 Dubbo 服务流量隔离,首先需要获取 AbstractRegistryFactory 类的静态属性 REGISTRIES,它包含应用当前已注册服务的服务列表以及相应的注册中心 Registry 实例。对于特定的微服务,仅需调用其注册中心 Registry 的 register/unregister 方法,便可实现服务的动态摘除和恢复。这一方案直接在较高抽象层级上操作,而无需依赖具体的注册中心 Registry 实现类,使其兼容所有注册中心。


image.png


Spring CLoud

Spring Cloud 服务流量隔离方法类似于 Dubbo,在了解 Spring Cloud 服务注册原理后,获得服务注册/注销方法路径,然后通过 JVMTI 干预应用的服务注册/注销行为。


Spring Cloud 的服务注册原理较为简单。在 Spring 容器启动时,AbstractAutoServiceRegistration 监听启动事件,并调用 ServiceRegistry 的 register 方法将 Registration(服务实例数据)注册到注册中心。例如,Nacos服务注册类 NacosServiceRegistry 实现了 ServiceRegistry 接口,通过重载 register/deregister 方法完成服务在注册中心的注册和注销。


// 服务注册类
public abstract class AbstractAutoServiceRegistration<R extends Registration>...{      
    // 注册中心实例
    private final ServiceRegistry<R> serviceRegistry;
    // 服务注册
  protected void register() {
    this.serviceRegistry.register(getRegistration());
  }
    // 服务注销
  protected void deregister() {
    this.serviceRegistry.deregister(getRegistration());
  }
}


在处理 Spring Cloud 服务流量隔离时,首先获取 AbstractAutoServiceRegistration 的服务注册实例,然后调用 register/deregister 方法以在注册中心上完成服务的注销和重注册。这种方法同样不依赖于某个特定注册中心的具体实现类,兼容所有注册中心。




事件流量隔离


应用节点和中间件通常采用 client-server 模式通信,像 RocketMQ 和 SchedulerX 使用了 Netty 作为底层网络框架完成客户端和服务端通信 。在此,我们以 RocketMQ 为例,来说明如何实现类似的事件驱动中间件的流量隔离。


RocketMQ client 端的主要实现类是 NettyRemotingClient。如下图所示,NettyRemotingClient 类中的属性 channelTables 存储了用于传输数据的 Channel,而 lockChannelTables 是用于控制 channelTables 更新的锁。与此同时,有几个 invoke 方法负责处理通信过程。


image.png


通信处理流程如下图。首先,尝试从 channelTables 中获取用于通信的 Channel。如果没有可用的 Channel,则重新连接 server 端以创建 Channel。为了保证线程间同步,新 Channel 更新到 channelTables 时需要获得 lockChannelTables 锁。如果在指定时间窗口内 lockChannelTables 一直被占用,将会抛出连接异常。





根据以上的原理分析,我们可以通过占用 lockChannelTables 锁来阻止 Channel 的建立,再把现存的 Channel 关闭,则 client 端在 lockChannelTables 被释放之前都无法与 server 端建立通信连接。若要恢复流量,仅需释放 lockChannelTables 锁,client 端将自动重建 Channel 并恢复通信。由于这种管控是在网络客户端层进行的,因此它不受应用消息模型的影响,既适用于同步消息也适用于异步消息;同时也与 client 角色无关,既适用于消费者也适用于生产者。



结语

目前流量隔离工具在 EDAS-云原生工具箱 可试用体验。如果对流量隔离以及更多云原生工具感兴趣,欢迎留言或加入钉群:21958624 与我们进行沟通与交流。

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
4月前
|
负载均衡 监控 算法
揭秘负载均衡的五大算法秘籍:让你的服务器轻松应对亿万流量,不再崩溃!
【8月更文挑战第31天】在互联网快速发展的今天,高可用性和可扩展性成为企业关注的重点。负载均衡作为关键技术,通过高效分配网络流量提升系统处理能力。本文介绍了轮询、加权轮询、最少连接及IP哈希等常见负载均衡算法及其应用场景,并提供Nginx配置示例。此外,还探讨了如何根据业务需求选择合适算法、配置服务器权重、实现高可用方案、监控性能及定期维护等最佳实践,助力系统优化与用户体验提升。
88 2
|
4月前
|
存储 监控 开发者
分布式链路监控系统问题之实现应用级透明的问题如何解决
分布式链路监控系统问题之实现应用级透明的问题如何解决
|
5月前
|
运维 分布式计算 监控
交易链路设计原则&模式问题之女娲系统打通监控链路,如何解决
交易链路设计原则&模式问题之女娲系统打通监控链路,如何解决
|
Ubuntu 虚拟化
搭建单机四节点联盟链
build_chain.sh 可以搭建联盟链,本文介绍了如何使用build_chain.sh 搭建单四节点联盟链。
276 0
搭建单机四节点联盟链
|
消息中间件 Dubbo Java
深度剖析线上应用节点流量隔离技术
EDAS-云原生工具箱提供一套开箱即用的流量隔离工具,能够动态隔离特定流量,并在隔离后可随时恢复,满足各种场景下的流量隔离需求。
|
7月前
|
监控 应用服务中间件 测试技术
4种典型限流实践保障应用高可用
大家好,我叫黄博文,花名延枚,目前负责云效旗下产品Flow流水线的设计和开发。在微服务架构下,服务越来越多,服务之间的调用也会越来越复杂。如何保障服务的高可用性就成为了一个挑战。之前我参与过的某个产品就曾出过故障,原因是某个API调用突然间增加了数十倍,导致服务负载过高,影响了用户使用。如果当时能够...
214 0
4种典型限流实践保障应用高可用
|
Cloud Native 应用服务中间件 测试技术
《云原生网络数据面可观测性最佳实践》——五、 典型问题华山论剑——5.某客户SVC后端负载不均
《云原生网络数据面可观测性最佳实践》——五、 典型问题华山论剑——5.某客户SVC后端负载不均
|
缓存 数据挖掘 BI
面试官问你:日亿万级请求日志收集如何不影响主业务?你怎么回复
数据收集 上篇详细讨论了写缓存的架构解决方案,它虽然可以减少数据库写操作的压力,但也存在一些不足。比如需要长期高频插入数据时,这个方案就无法满足,接下来将围绕这个问题逐步提出解决方案。
|
缓存 监控 Cloud Native
阿里大规模业务混部下的全链路资源隔离技术演进
本文作为混部实践系列开篇,本篇文章将介绍资源隔离技术在混部中的重要性、其落地挑战及我们的应对思路。
阿里大规模业务混部下的全链路资源隔离技术演进
|
数据采集 存储 Dubbo
亿级流量架构怎么做资源隔离?写得太好了!
常见的资源,例如磁盘、网络、CPU等等,都会存在竞争的问题,在构建分布式架构时,可以将原本连接在一起的组件、模块、资源拆分开来,以便达到最大的利用效率或性能。资源隔离之后,当某一部分组件出现故障时,可以隔离故障,方便定位的同时,阻止传播,避免出现滚雪球以及雪崩效应。
亿级流量架构怎么做资源隔离?写得太好了!
下一篇
DataWorks