Pinpoint Redis Lettuce 插件:配置、字节码增强原理与链路追踪能力全解析
【免费下载链接】pinpointAPM, (Application Performance Management) tool for large-scale distributed systems.项目地址: https://gitcode.com/gh_mirrors/pi/pinpoint
本文基于 Pinpoint 仓库中 agent-module/plugins/redis-lettuce/README.md 展开,系统讲解 Pinpoint 对 Lettuce 客户端(io.lettuce/lettuce-core)的 APM 追踪支持:从支持版本范围、Pinpoint 配置项,到字节码增强的底层实现与验证方式。读完本文,你将掌握如何开启/调优 Lettuce 追踪、理解端点在调用链中的传播机制,并能在本地用测试 Web 应用快速验证追踪效果。
一、插件概览:引入版本与支持范围
Lettuce 是 Java 生态中广泛使用的 Redis 客户端,其底层基于 Netty,支持同步、异步(RedisFuture)与响应式(Reactive Streams)三种命令执行模型。Pinpoint 为此专门提供了pinpoint-redis-lettuce-plugin插件。
根据 agent-module/plugins/redis-lettuce/README.md 的官方说明:
- 引入版本(Since):Pinpoint 1.8.1 起内置支持;
- 支持范围(Range):
io.lettuce/lettuce-core的[5.0.0.RELEASE, 5.1.2.RELEASE],即5.0.0.RELEASE <= x <= 5.1.2.RELEASE。
从源码结构看,插件对版本的适配并不局限于上述区间:在 LettucePlugin.java 的RedisClientTransform中,针对newStatefulRedisConnection、connectStatefulAsync等内部方法分别探测了5.0、5.1、6.0/6.x等多套方法签名,再决定注入哪个拦截器——这意味着实现对更高版本的方法签名变化做了兼容处理。
插件模块本身的信息同样可以在 pom.xml 中确认:
- 插件以
provided方式依赖lettuce-core(避免与业务应用实际使用的版本冲突); - 依赖
pinpoint-reactor-seam-support,并通过 maven-shade-plugin 将其类重定位到插件包名下,避免与其它响应式插件在应用类加载器中产生重复类定义(LinkageError)。
在链路数据层面,插件使用的服务类型常量定义在 LettuceConstants.java:REDIS_LETTUCE用于记录命令调用,REDIS_LETTUCE_INTERNAL用于内部方法,Span 作用域名为redisLettuceScope。
二、追踪覆盖面:同步 / 异步 / 响应式 / Pub-Sub
插件并非只追踪某个单一入口,而是对 Lettuce 的整条命令执行链路做字节码增强。从 LettucePlugin.java 的setup()可以看出,它分为四层注入:
- 客户端层:对
io.lettuce.core.RedisClient与io.lettuce.core.cluster.RedisClusterClient注入EndPointAccessor字段,并在构造方法上挂载拦截器,捕获RedisURI中的 host:port; - 连接层:对
StatefulRedisConnectionImpl、StatefulRedisClusterConnectionImpl、StatefulRedisMasterSlaveConnectionImpl、StatefulRedisSentinelConnectionImpl、StatefulRedisPubSubConnectionImpl、StatefulRedisClusterPubSubConnectionImpl等 6 种连接实现统一注入端点字段; - 命令层:对以下命令实现类批量注入方法级拦截器——
- 异步:
AbstractRedisAsyncCommands、RedisAsyncCommandsImpl、RedisAdvancedClusterAsyncCommandsImpl、RedisClusterPubSubAsyncCommandsImpl、RedisPubSubAsyncCommandsImpl; - 响应式:
AbstractRedisReactiveCommands、RedisAdvancedClusterReactiveCommandsImpl、RedisClusterPubSubReactiveCommandsImpl、RedisPubSubReactiveCommandsImpl、RedisReactiveCommandsImpl、RedisSentinelReactiveCommandsImpl;
- 异步:
- Publisher 层:对
io.lettuce.core.RedisPublisher注入AsyncContextAccessor,并在subscribe(Subscriber)方法上挂载FluxAndMonoOperatorSubscribeInterceptor,用于承接响应式订阅的异步上下文。
命令层的方法筛选由 LettuceMethodNameFilter.java 控制:只接受public 且非 static / 非 abstract / 非 native的方法,并显式排除clone、equals、toString等 Object 方法,以及dispatch、getConnection、setAutoFlushCommands、setTimeout、createMono、createDissolvingFlux等框架内部/不应单独成 span 的方法。
命令 Span 记录了哪些信息
以 LettuceMethodInterceptor.java 为例,每次 Redis 命令调用都会记录:
- ServiceType:
REDIS_LETTUCE(doInBeforeTrace中记录); - API 描述符:具体执行的命令方法(如
get、set); - EndPoint:目标 Redis 的
host:port(来源见第三节); - DestinationId:
REDIS_LETTUCE名称; - 异常:若命令抛出异常则记录异常信息;
- 异步上下文:当返回结果是
AsyncContextAccessor时,为其注入AsyncContext,使异步命令的后续回调能延续同一条调用链。
三、Pinpoint 配置详解
README 给出的开关配置位于pinpoint.config:
# Enable/Disable # Default value is true. profiler.redis.lettuce.enable=true结合 LettucePluginConfig.java 的解析逻辑,该插件实际支持的配置项共有 4 个,完整配置块如下:
########################################################### # Redis Lettuce ########################################################### # 总开关:是否启用 Lettuce 插件追踪 # 默认值:true profiler.redis.lettuce.enable=true # 是否追踪 Redis Pub/Sub 监听器(RedisPubSubListener.message 回调) # 默认值:true profiler.redis.lettuce.trace.pubsub-listener=true # 额外扫描的 Pub/Sub 监听器实现所在的基础包列表(逗号分隔) # 默认值:空;默认只扫描 io.lettuce.core.pubsub.RedisPubSubReactiveCommandsImpl$ 包 profiler.redis.lettuce.pubsub-listener.base-packages= # 是否以“包装 Publisher”的方式传递异步上下文(替换向返回对象注入 AsyncContext 的方式) # 默认值:false(即默认直接向返回的异步结果对象注入 AsyncContext) profiler.redis.lettuce.wrap.publisher=false各配置项的源码依据如下:
enable:在 LettucePlugin.java 的setup()中读取,为false时插件直接跳过全部类变换;trace.pubsub-listener与pubsub-listener.base-packages:控制 addRedisPubSubListener() 是否扫描并增强RedisPubSubListener实现类(默认覆盖io.lettuce.core.pubsub.RedisPubSubReactiveCommandsImpl$包,业务自定义监听器可通过 base-packages 追加);wrap.publisher:通过 lettuceMethodInterceptor() 决定命令层使用LettuceMethodInterceptor还是WrappingLettuceMethodInterceptor——后者会把 reactor 的Mono/Flux返回值包装为携带AsyncContext的SeamPublisherWrapper,而非直接注入到返回对象上(见 WrappingLettuceMethodInterceptor.java)。
注意:配置修改后需重启被注入 Agent 的应用进程才能生效;
pinpoint.config通常位于 Agent 分发目录的profiles/<profile>/或 Agent 根目录下。
四、实现原理:EndPoint 的捕获与逐层传播
追踪数据中“目标 Redis 地址”的获取是理解本插件的关键。它的流转路径在源码中非常清晰:
客户端构造时捕获地址:
RedisClientConstructorInterceptor在RedisClient(ClientResources, RedisURI)构造方法执行前读取RedisURI.getHost()与getPort(),用HostAndPort.toHostAndPortString拼成host:port,写入 RedisClient 上的EndPointAccessor字段(RedisClientConstructorInterceptor.java);Cluster 客户端则由RedisClusterClientConstructorInterceptor处理Iterable<RedisURI>参数。连接对象上附着地址:
AttachEndPointInterceptor拦截newStatefulRedisConnection、newStatefulRedisPubSubConnection、newStatefulRedisSentinelConnection等连接创建方法,在返回的连接对象上写入同一个 EndPoint(AttachEndPointInterceptor.java)。命令 Span 读取地址:
LettuceMethodInterceptor.toEndPoint()先把命令对象强制转换为StatefulConnectionGetter,通过_$PINPOINT$_getConnection()拿到连接,再通过EndPointAccessor取出host:port,最终写入 SpanEvent(LettuceMethodInterceptor.java)。读取不到时回退为字符串"Unknown"。Pub/Sub 独立入口:
RedisPubSubListenerInterceptor会在收到消息时若无当前 Trace 则创建以STAND_ALONE为根类型的独立调用链,记录/类名/方法名形式的 RPC 名称,并通过唯一 scope##LETTUCE_PUBSUB_LISTENER_TRACE保证嵌套消息不产生重复 Trace(RedisPubSubListenerInterceptor.java)。
这套“客户端捕获 → 连接附着 → 命令读取”的链路设计,使得同一连接上发出的所有命令 Span 都能关联到同一个真实的 Redis 节点地址,为服务端地图(Server Map)和调用关系分析提供了可靠依据。
五、已知限制与演进方向
README 的 TODO 部分明确列出了早期版本的三项规划,原文与当前仓库中的实现状态对比如下:
| README 中的规划 | 原文描述 | 当前仓库的实现状态(从源码推断) |
|---|---|---|
| 异步追踪(Asynchronous tracking) | 探索应用java.util.concurrent.CompletableFuture的方案 | 已实现:LettuceMethodInterceptor通过AsyncContextAccessor为RedisFuture等异步结果注入异步上下文 |
| 响应式追踪(Reactive feature tracking) | 探索支持 projectreactor.io 的方案 | 已实现:对RedisPublisher注入AsyncContextAccessor,并在subscribe上挂载FluxAndMonoOperatorSubscribeInterceptor,另有wrap.publisher包装模式 |
| IO 读写时间追踪 | 读耗时难以测量,仍在寻找方案 | 截至当前源码,仍未发现独立的读写耗时测量实现,属于持续演进中的能力 |
可以理解为:README 中的 TODO 属于插件 1.8.1 时代的早期规划,而当前仓库中的实现已覆盖异步与响应式两条主线;IO 读写耗时这类需要深入 Netty 管线内部的能力仍不在现有 Span 记录范围之内。
六、快速验证:内置测试 Web 应用
仓库在 agent-module/agent-testweb/redis-lettuce-plugin-testweb 提供了专门的验证工程,采用Spring Boot WebFlux + Spring Data Redis Reactive(底层连接器即 Lettuce),其依赖见 pom.xml。
RedisLettucePluginController.java 中暴露了可直达的 HTTP 端点,覆盖了插件支持的主要场景:
/basic/get:同步StringRedisTemplate的 set/get;/basic/callback:RedisCallback回调方式执行命令;/pipe:pipelining 批量执行(rPop);/stream/read:Redis Stream 读取;/reactive/get、/reactive/set:响应式模板的读写;/reactive/pub、/reactive/sub:响应式 Pub/Sub 发布与订阅。
使用方式:为该模块配置-javaagent指向 Pinpoint Agent 并启动后,逐一访问上述端点,即可在 Pinpoint Web 的调用链页面中看到以REDIS_LETTUCE为 ServiceType 的 Span 及其关联的 Redishost:port。若需要本地 Redis 环境,参考测试类 RedisServerTest.java,它使用 Testcontainers 拉起redis:5.0.14-alpine容器(该测试默认@Disabled,需在具备 Docker 的环境中手动启用)。
小结
本文从 agent-module/plugins/redis-lettuce/README.md 出发,结合 LettucePlugin.java 及其拦截器实现,完整梳理了 Lettuce 插件的支持范围、4 个配置项、Endpoint 传播链路与异步/响应式追踪原理。核心要点可归结为:
- 通过
profiler.redis.lettuce.enable(默认 true)一键开关,另有 pubsub-listener 与 wrap.publisher 等精细化选项; - 追踪覆盖同步、异步、响应式与 Pub/Sub 四类命令场景,Span 记录 ServiceType、API、EndPoint、DestinationId 与异常;
- 地址信息按“构造捕获 → 连接附着 → 命令读取”三级传播,保证同一连接的命令归属到正确的 Redis 节点;
- README 中记载的异步与响应式 TODO 在当前源码中已有对应实现,读写耗时测量仍属未实现能力。
【免费下载链接】pinpointAPM, (Application Performance Management) tool for large-scale distributed systems.项目地址: https://gitcode.com/gh_mirrors/pi/pinpoint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考