JvmTI Agent 在腾讯云微服务领域的应用
本文摘自腾讯云官方电子书《腾讯云技术实践精选集 2021》中间件与基础设施部分。
下载链接:https://www.infoq.cn/minibook/RuveM045ptfTNKwfeq6c
导读
中间件是介于应用和数据或者基础设施之间的一层能力组件,现在企业技术栈纷繁复杂,中间件涉及范围十分广泛。互联网的本质是数据的传输,而中间件扮演着数据治理和软件调度的角色,高 SLA、安全性、性能都至关重要。在众多语言中,Java 在中间件领域的生态发展一骑绝尘,JvmTI Agent 基于 Java 原生技术,在中间件领域的应用越越来越广泛,从 CPU 性能的 Profile 监控,到 APM 的探针,到 Service Mesh ProxyLess 的 Agent 代理,已然成为中间件从业者的必备技能°ArchSummit2022 杭州站,腾讯云中间件专家工程师童子龙带来分享《JvmTI Agent 在中间件领域的应用》,围绕 Agent 的原理和最佳应用实践,描述 Agent 在中间件领域不可忽视的技术价值。
从字节码到 JvmTI
JAVA 字节码的结构是十六进制的,看起来很晦涩。但其实做 Agent 不需要太关注它,很多上层字节码架构基本屏蔽了那些细节。
JvmTI 有很多能力:加载 Class 文件之前修改字节码;运行时修改已经加载的类字节码;添加 JAR 到
BootstrapClassloader 中加载;添加 JAR 到 SystemClassLoader 中加载;获取已经被加载过的类,等等。
基于 Agent 的能力矩阵,我们可以应用到很多具体场景:加密 Class 文件、Debug Java 程序、应用性能分析、Profile 诊断、APM 全链路性能监控、业务应用无感知 / 无侵入代理、热加载 Class 类等等。
Agent 原理简介
JvmTI Premain
顾名思义就是作用在 Main 方法之前,在启动时把我们自定义的 Transformer 加入到 Instrumentation 里。这个 Instrumentation 每个虚拟机都有自己的实现,有一些 OnLoad 函数帮我们做这件事情。我们在 ClassFile 加载到 Jvm 内存时,会触发一个 ClassFileLoadHook 事件,这个 Hook 事件会调用我们之前添加到 Instrumentation 里的 Transform 方法,去改写字节码,然后把我们改写过的字节码重新载入到 Jvm 里。
JvmTI agentmain
Agentmain 机制有两个进程,彼此通过 Socket 互相通信,目标 JVM 启动是创建 dispatcher 线程,线程
接收到 attach 进程信号时,目标 JVM 启动 Attach Listener 线程创建 Socket 进行进程间通信,可以把用
户自定义 Transformer 字节码添加到 Target 虚拟机中。我们在 Target 虚拟机加载这些 Class 文件的时
候触发 ClassFileLoadHook 事件,改写目标字节码,实现 JVM 运行时的一些线程 dump 和 cpu\method
profile 之类的功能。
Agent 的核心优势包括:低成本接入,方便业务大规模集群升级基础能力;解耦业务应用,能力透明化下沉,关注点分离;性能优于 Proxy Sidecar 模式;能力方便扩展可插拔,无厂商依赖。
腾讯云 Agent 现状
基于 Java Agent 的诸多优势,腾讯云微服务平台在此基础上构建了丰富的基础能力。其中比较有代表性的
能力有三个,第一个是微服务框架运行时精细的流量治理能力,包过熔断、限流、路由、全链路灰度、鉴权
等,提供用户功能强大、轻量化治理数据面;第二个是多维度的应用性能分析能力,将这些能力标准化、平
台化;最后一个就是 APM 全链路探针,提供了丰富的 Tracing 和 Metrics 信息。我们将 agent 框架做了
易用性封装,进一步降低团队学习和维护成本。
性能分析平台
我们的性能分析平台的能力矩阵包括进程内存信息分析、进程线程信息分析、Jmx Metrics、Method
Profile、CPU 火焰图、GC 日志分析。我们将所有能力标准化集成到 Paas 平台。用户只要选定 Pod 就能
够在统一的 Paas 平台抓取信息,分析信息,无需进入 Pod 或者 Vm 执行任何操作指令。
我们采用 Premain agent 实现,通过自研 sdk 抓取 cpu profile 等信息,应用通过 jvm 指令接入 Premain
性能分析探针,探针启动后开启 Socket 通信端口,用户在 Paas 平台下发指令之后,通过 socket 报文将
指令传输给 agent 探针,探针通过对应的指令调用底层自研函数,抓取 cpu、Method Profile 等信息,在
Paas 平台分析后展示。
线程分析
内存分析
cpu 火焰图
APM 全链路监控探针
我们的 APM 全链路探针是基于 OpenTelemetry 标准架构改造,整体架构分采集端 Instrumentation 探针,Collector server 端,Collector 里先通过 Receive 到 processor,包括一级二级聚合,聚合之后 exporter 输入到后端。采集端的数据协议标准化,然后通过这样标准化的协议能够输入到任何支持 opentracing 协议的后端进行分析。
我们在探针块做了一些特性增强,如全链路更加丰富的调用栈信息、探针采样率以及尾采大大降低数据传输
和存储成本、自定义特征信息 Tag:显示系统和业务自定的标签,例如服务治理的全链路灰度泳道和限流信息等等;Rpc 调用信息:client-server 通信时延;中间件链路信息:MQ、Gateway、Database;调用链与业务日志联动等。
基于 Java Agent 的 ProxyLess Mesh
腾讯云 Service Mesh 有多种数据面,有基于 sidcar Proxy 模式和基于 sdk 的 ProxyLess 模式。不同数据面之间也能实现流量治理,所有的治理规则也是基于同一套控制面下发治理 Spec。控制面制定一套公用的治理 SPEC 标准,同时下发给 SDK、Agent、Sidecar 多数据面。
腾讯云对微服务治理的标准化也做了诸多努力。我们将治理标准分为三层。
第一层是控制面传输协议标准化
第二层是治理规则 SPEC 标准化
第三层是治理数据面实现的标准化
我们也联合 NextArch 基金会和几家头部互联网企业共同制定国内微服务治理标准,希望打造符合信创自主可控的微服务治理标准化体系。
其中行业最为关注的是微服务在各个场景下治理规则 SPEC 的标准化。
服务熔断
我们支持了实例、服务、接口级别不同粒度的熔断。支持设置对熔断资源做主动探测,一旦成
功比率符合预期会立即进入半开状态,这样能够快速帮助服务跳过半开等待时间,快速恢复。
服务限流
支持了集群、单机限流。集群不可用情况下过渡到单机限流,通过 Tag engine 匹配机制控制
限流的资源范围,支持多种 RateLimit 算法符合各种应用场景。被限流后,用户能够设置默认返回报文,
这样能够保证业务比较高的 SLA。
服务路由
支持主调和被调方两个角度配置路由信息,条件匹配通过 Tag engine 控制,按照权重流量分拨。
服务鉴权
支持黑白名单,条件匹配通过 Tag engine 控制,鉴权粒度到服务接口级别。
全链路灰度
敬畏生产发布是每个业务开发的共识,灰度发布能帮助研发控制生产发布影响面,云厂商的
目标是服务全球开发者,然而不同企业技术栈不尽相同,有些可能连网关都没有。我们需用比较通用的方
案,能让全链路灰度在各种技术栈下都能运行起来。全链路灰度基于泳道设计,第一步配置泳道信息,把
一批服务版本划分到一个泳道里,设置一个泳道入口,然后配置对应的灰度规则。灰度规则匹配也是通过
Tag engine 实现,控制粒度也非常细,支持 Tag 的蓝绿灰度、金丝雀的百分比灰度。我们在泳道入口进
行校验,校验一旦命中目标泳道之后,我们把这个泳道 ID 全链路透传,控制下游流量在配置的泳道里流转,从而实现全链路灰度治理。
我们实现了一个治理 Agent 能够支持多微服务框架协议,Spring Cloud E/F/G2020、Dubbo 2.x/3.x 能够在共用一套治理 Agent。其次,我们把整个治理能力做了标准化、插件化,做了架构分层。我们的能力基本上能够无缝对接到任何框架、任何协议里。为了做到这一点,我们把整个微服务生命周期非常明确地划分出来。流量进来之后要经过流量入站 到 DNS 到流量出站等流程,在每个生命周期植入对应的治理 API。这样抽象之后,我们的治理逻辑能够非常低成本的接入任何协议框架,极大增加的框架的可扩展性,同时站在团队 ROI 的角度,能更聚焦核心能力建设,从之前繁重的协议适配层中解放出来。
我们的 Service Agent 框架分为 Agent Core、Starter、Tools、plugins、JTS 层,如下图所示。
我们还通过 Agent 解决跨线程传递问题。在链路透传的时候要透传一些信息,比如全链路灰度的时候要透
传一些规则。这个时候如果有框架用了大量异步调用过程,就会在线程传递过程中让上下文丢失。为了解决
这个问题,首先我们要通过封装原生线程类,包括 Runnable、Callable 和 TimerTask。原生 JDK 线程池
类也要做字节码修改。在使用 ThreadPoolExecutor\ForkJoinTask 的时候,涉及子线程池复用,通过字节
码插装把它主线程里的数据 Copy 下来,然后放到一个子线程内。线程执行完成以后清除数据,避免内存泄露。
运行时的字节码可观测也是一个非常重要的能力。同一个应用可能会接入多个 Agent,多次修改后字节码最后变成什么样子就不知道了。这时候我们就需要做运行时字节码观测。我们通过自研的字节码 dump 工具,Attach 一个进程,然后把 Jvm 里面实时的字节码 Dump 下来,之后通过 Class 协议解析,把它反编译成对应的源码。我们在 PaaS 平台能够看到线上运行的 ClassFile 实况,非常直观地看到线上代码执行逻辑是什么样子。
浅谈内核探针 eBPF
最后简单介绍一下内核探针 eBPF。eBPF 通过执行字节码扩展内核空间,十分高效安全。例如把治理能力
放到内核里,通过 eBPF 在内核里实现流量治理、网络链路监控等。我们把 Sidecar 的部分能力通过 eBPF 的方式放到内核里,相比 sidecar 极大节省系统资源。
事件驱动的 eBPF
总的来说,eBPF 的优势就是能够去 Sidecar,性能有极大提升,还帮我们节省大量系统资源,还可以有效
减少 Memory Copy。它的劣势也很明显,因为它是内核级的事件驱动,所以它对代码安全性要求非常严格,包括一些循环次数都有非常严格的要求,不是所有的代码都能在内核里运行,包括处理网络包大小也有很多限制。最显著的就是 eBPF 在处理七层负载时会非常困难,目前也没有完美的解决方案。基于以上特性,eBPF 在 Pod 网络监测、可观测性这方面还是有一定应用场景,当然真正应用到服务治理取代 sidecar,实现 sidecar less 目前还不太可能。
作者介绍:
童子龙--腾讯云中间件专家工程师。
在腾讯云微服务中心负责微服务中间件生态构建,腾讯开源云原生多运行时微服务框架 Femas 负责人,从 0 到 1 完成开源项目构建,在腾讯云完成并落地了多运行时微服务架构的设计,支撑了腾讯亿级规模的用户生态。专注云原生微服务架构、中间件架构领域,深耕多年,有丰富的经验和沉淀。
版权声明: 本文为 InfoQ 作者【童子龙】的原创文章。
原文链接:【http://xie.infoq.cn/article/b7c16eeda252a66cb7c6bbb91】。文章转载请联系作者。
评论