《Netty 高性能网络编程》15.3 linkd-v2 验收与复盘
验收不是再跑一遍健康检查。本节把 linkd-v2 的验收拆成功能与协议一致性清单、五类故障注入、优雅关闭、指标与日志四块,每项给出可复制命令、预期与证据;再对比卷一手写 linkd v1 与 Netty 版 linkd-v2 的差异和代价,并给出带「验收缺口」的复盘模板。
tag
验收不是再跑一遍健康检查。本节把 linkd-v2 的验收拆成功能与协议一致性清单、五类故障注入、优雅关闭、指标与日志四块,每项给出可复制命令、预期与证据;再对比卷一手写 linkd v1 与 Netty 版 linkd-v2 的差异和代价,并给出带「验收缺口」的复盘模板。
15.1 的网关能转发,但下游变慢时会拖垮自己。本节用纯 JDK 给 linkd-v2 补三层保护:每 IP 与每连接的令牌桶限流返回 429 与 Retry-After,连接、读与总预算分层超时,连续失败打开、半开探测的熔断返回 503,并给出内存上限、清理策略与压测验证命令。
3.3 的 GatewayHandler 把转发交给虚拟线程,本节把它收回 EventLoop:用路由表把 9090 的 /books/{id} 重写到 libraryd 8080,过滤 Hop-by-hop 头,基于 Bootstrap、HttpClientCodec 与 HttpObjectAggregator 做异步转发,讲清连接复用、超时与 502/504 的判据。
一个可复现的 linkd-v2 调优案例:100 条 9093 连接、8 KiB 负载,worker=1 时单条 EventLoop 接近满核,p50 12 ms、p99 33 ms。用 jcmd 线程栈、JFR ExecutionSample 与 GC 停顿排除法定位,只把 worker 从 1 改成 4,给出前后对比表与回归清单,所有数字标注单次运行、未做统计。
ab 能打 HTTP/1.1,wrk 和 hey 本机没装;9093 的自定义帧协议要用 Java 客户端压。本节给出可复制的 ab 命令、虚拟线程 FrameLoadClient、吞吐与 p50/p95/p99 口径、Little 定律、coordinated omission、预热与多轮方法,以及结果记录模板,所有数字都标注单次运行、未做统计。
调优前先固定一组能复现的起点:boss=1、worker 显式 4、backlog 1024、TCP_NODELAY、32/128 KiB 水位、PooledByteBufAllocator 与 AUTO_READ=true。本节用 BaselineProbe 打印 linkd-v2 的真实配置,说明每个值的作用、验证方式和该改的信号,强调基线不是最优值。
把 linkd-v2 变慢后的排查顺序固定为连接、线程、内存、JFR 四步,给出 lsof、netstat、jcmd Thread.print、GC.heap_info 与 JFR 的真实输出和判读方法,并说明 JFR 记录不到正在阻塞的线程这一边界。
拆开 JVM 的 MaxDirectMemorySize 与 Netty 的 io.netty.maxDirectMemory 两层限制,用 metric() 读直接内存、arena 与线程缓存,实测上限如何压缩 arena 数量,并给出 linkd-v2 的阈值告警。
把 ResourceLeakDetector 的四档级别、property 与 setLevel 的覆盖关系讲清楚,用一个故意不 release 的最小程序跑出 PARANOID 报告,逐段读创建栈与最近访问栈,并给出 linkd-v2 的限时开启与回退步骤。
整理 macOS 上排查 linkd-v2 的可用命令:lsof、netstat、tcpdump、jcmd、jstack、JFR 与 Netty 系统属性,每条给出用途、可复制命令、输出读法与风险提示,并给出从连接积压到堆外内存上涨的组合排查路径。
按 Bootstrap、Channel、EventLoop、Pipeline、ByteBuf、编解码、HTTP、WebSocket、HTTP/2 与连接池十组,列出 Netty 4.2 常用类、正确包名、方法签名与对应章节,并标出容易记错的常量与重载差异,供写 linkd-v2 时直接查表。
本节只写 Linux 部署侧:EpollEventLoopGroup 与 EpollServerSocketChannel 的配置、SO_REUSEPORT 与 TCP_FASTOPEN 的内核前提、classifier 与 glibc 兼容性,以及 Epoll.isAvailable() 为 false 时的回退。全部为部署说明,不是本机实测。
isAvailable() 为 true 不等于 server 真的跑在 kqueue 上。本节用本机探针打印 serverChannel 与 eventLoop 的实际类名,给出 KQueueEventLoopGroup 启动代码、Apple Silicon classifier,并处理 JDK 25 对 System.loadLibrary 的受限方法警告。
NIO、Epoll、KQueue 三套 transport 不是三个开关,而是同一组接口的三份实现。本节讲清 Epoll.isAvailable() 与 KQueue.isAvailable() 的探测、classifier 构件选择、NativeEventLoops 的回退顺序,以及原生传输为什么不保证更快。
gRPC 不是又一个网络框架,而是 HTTP/2、protobuf 与生成代码的组合。本节用 linkd-v2 的 9092 端口拆开这三层,给出 pom 依赖、linkd.proto、LinkdServiceImpl 与 NettyServerBuilder 服务端、NettyChannelBuilder 客户端和 deadline,并说明 shaded Netty 的版本隔离边界。
把 11.1 的 pipeline 变成 9091 上可运行的 h2c 服务端。本节给出 ServerBootstrap、Http2FrameCodec、Http2MultiplexHandler 与子 channel 的完整装配,讲 HEADERS/DATA 如何创建和关闭 stream、GOAWAY 与流量控制窗口怎么处理,并用 curl --http2-prior-knowledge 验证。
HTTP/2 帧分连接级与 stream 级两层。本节用 linkd-v2 的 9091 端口拆开 Http2FrameCodec 与 Http2MultiplexHandler 的分工,说明 preface、SETTINGS、HEADERS、DATA、WINDOW_UPDATE、GOAWAY 归谁处理,对比 h2c prior-knowledge 与 ALPN,并给出多路复用与连接池的取舍。
连接多了以后,回显要变成广播。本节用 ChannelGroup 管理会话,用 AttributeKey 绑定 userId,用 IdleStateHandler 加 ping/pong 清理死连接,给出 /ws/broadcast 的完整 WebSocketHub,并说明慢客户端过滤、广播风暴与按房间分组的扩展边界。
升级之后,入站的都是 WebSocketFrame。本节分清文本、二进制、ping、pong、close 五类帧在 WebSocketServerProtocolHandler 里的去向,讲 continuation 分片与 WebSocketFrameAggregator 的位置和上限,给出 /ws/echo 的文本与二进制回显代码,以及大帧带来的内存与背压问题。
WebSocket 仍以 HTTP/1.1 Upgrade 开始。本节按 HttpServerCodec、HttpObjectAggregator、WebSocketServerProtocolHandler 的顺序装配 9090 端口,讲路径匹配、握手完成事件与 400 失败分支,并给出可运行的 WebSocketServer 与 curl 验证命令。