深度剖析线上应用节点流量隔离技术
为什么要做流量隔离 Aliware 源于一个 EDAS 客户遇到的棘手情况:他们线上的一个 Pod CPU 指标异常,为了进一步诊断问题,客户希望在不重建此 Pod 的情况下保留现场,但诊断期间流量还会经过这个异常 Pod,导致影响服务质量,于是询问我们有没有办法可以把流入异常节点的流量摘除掉,形成一个隔离的诊断环境。经诊断后,如果异常可以修复,待修复完成后,再解除流量隔离,节点恢复正常工作。 除了在诊断场景需要对所有输入流量进行隔离外,在一些线上演练中还需对特定流量进行隔离以实现模拟演练效果。面对这类流量隔离问题时,我们首先考虑的是全链路流量控制。目前,EDAS 上的全链路流控能够在不重启应用节点的情况下控制流量走向。然而,全链路流控仅能控制微服务框架流量,无法满足隔离所有或特定流量的需求。 为此,我们进行了深入研究,实现了一套开箱即用的流量隔离工具,能够动态隔离特定流量,并在隔离后可随时恢复,以满足各种场景下的流量隔离需求。
隔离哪些流量 Aliware 流量隔离的目的是阻断应用节点的流入流量,首先明确下微服务应用节点流入的流量有哪些。 流入微服务应用节点的流量大致可以分为两大类:服务流量、事件流量。以常见的微服务应用为例,其流量组成如下图所示。
服务流量隔离 Aliware 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 应用中存在一个 AbstractRegistryFactory 单例,负责注册中心 Registry 的容器初始化。类属性 REGISTRIES 维护了微服务列表与注册中心实例的映射关系。
- AbstractRegistry 实现了 Registry 接口,作为一个模板,实现了特定的公共方法,如服务注册(register)、服务注销(unregister)等。它还维护了已注册服务 URL 列表。
- FailbackRegistry 基于 AbstractRegistry,提供了失败重试机制。同时,它提供了注册中心的 doRegister 和 doUnregister 抽象方法。当执行 register/unregister 时,会调用 doRegister/doUnregister 方法。
- 注册中心(如 NacosRegistry、RedisRegistry)实现了具体的服务注册(doRegister)和服务注销(doUnregister)逻辑。
由源码可见,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 中。
在处理 Spring Cloud 服务流量隔离时,首先获取 AbstractAutoServiceRegistration 的服务注册实例,然后调用 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());}}
结语 Aliware
目前流量隔离工具在 EDAS-云原生工具箱 可试用体验。如果对流量隔离以及更多云原生工具感兴趣,欢迎留言或加入钉群:21958624 与我们进行沟通与交流。