第2章 常见SDK类型与性能特征
做性能压测之前,首先得搞清楚你面对的是哪种SDK。同步还是异步?阻塞还是非阻塞?REST还是gRPC?
选错了分析方向,调优就是瞎忙活。我个人习惯一上来先给SDK分类,再针对性设计压测场景。
2.1 同步 vs 异步:最基础的分类
同步调用,说白了就是发请求后一直等。线程挂在那,直到结果回来才继续往下走。逻辑简单,但吞吐量上不去。
我记得有一次压测一个支付SDK,单机QPS卡在2000上不去。一看链路,每次调用都要等3次内部RPC全部返回。线程池被打满,后面请求全排队。
异步调用则不同。请求发出去立即返回一个Future或回调,线程可以去干别的活。等结果准备好了再通知你。
核心区别:同步占用线程等待,异步释放线程做其他事。
| 维度 | 同步 | 异步 |
|---|---|---|
| 线程占用 | 整个等待期间占用一整个线程 | 仅发起和回调时占用,其余时间释放 |
| 吞吐量与并发 | 受线程池大小限制,提高并发需扩线程 | 用少量线程即可支撑高并发 |
| 代码复杂度 | 简单,线性思维 | 需要处理回调、状态机、线程安全 |
| 常见SDK示例 | JDBC、传统HTTP客户端 | Netty、WebClient、gRPC异步Stub |
2.2 阻塞 vs 非阻塞:调用方的等待策略
这两个词经常跟同步/异步混在一起,但它们说的是另一回事。阻塞强调的是调用方是否被挂起,而非阻塞则是调用方立即拿到一个状态值,不挂起。
举个例子:你发了一个请求,socket上没数据可读。如果线程在read()上停住,那就是阻塞I/O。如果read()立即返回一个“暂无数据”的错误码,那就是非阻塞。
一个小技巧:同步不一定阻塞,异步也不一定非阻塞。Java NIO的Selector是非阻塞的,但处理流程可以是同步的(轮询)。
我曾经踩过一个坑:用非阻塞轮询去等外部任务完成,while(true)里不断检查状态。CPU直接飙到100%,还耽误了其他业务。后来改成信号量+回调,问题解决。
| 模式 | 调用方行为 | 适用场景 | 性能风险 |
|---|---|---|---|
| 同步 + 阻塞 | 线程一直等,直到数据就绪 | 低并发、简单业务 | 线程浪费,池满后请求堆积 |
| 同步 + 非阻塞 | 轮询或状态检查,不挂起 | 必须及时响应的短事务 | 轮询开销大,易CPU打满 |
| 异步 + 非阻塞 | 注册回调,事件驱动 | 高并发I/O密集型 | 回调地狱,调试困难 |
2.3 REST vs gRPC:传输协议的差异
你想想看,两个系统之间通信,最常见的两种方案就是REST和gRPC。它们底层协议不同,性能差距很大。
REST基于HTTP/1.1,文本格式(JSON/XML)。好处是通用,浏览器都能调。但每次请求都带大量HTTP头,序列化/反序列化用JSON库,CPU开销不低。
我做过一个对比压测:相同负载下,gRPC的吞吐量是REST的3~4倍,延迟也低一半。当然,这跟消息大小有关。
gRPC基于HTTP/2,使用Protobuf二进制序列化。支持流式传输、多路复用、头部压缩。如果你做微服务之间的内部调用,gRPC几乎是首选。
注意:gRPC对HTTP/2强依赖,负载均衡器必须支持。如果你用Kubernetes,需要配合service mesh或者gRPC-LB。不然建连失败让你怀疑人生。
| 对比维度 | REST (HTTP/1.1) | gRPC (HTTP/2) |
|---|---|---|
| 传输格式 | JSON/XML(文本) | Protobuf(二进制) |
| 序列化速度 | 慢,反射开销大 | 快,代码生成,零拷贝 |
| 连接复用 | 每个请求可能新建TCP连接 | 多路复用,一个连接够用 |
| 流式支持 | SSE(单向) | 双向流原生支持 |
| 调优重点 | 连接池、Keep-Alive | 流控窗口、并发流数量 |
2.4 并发模型:线程池、协程与事件驱动
SDK内部如何管理并发,直接决定了它在高负载下的表现。我见过太多人只关注协议,忽略了并发模型。
- 线程池模型:每个任务分配一个线程。简单,但线程数过多导致上下文切换爆炸。压测时CPU上不去,吞吐先掉。
- 协程/纤程模型:用户态调度,轻量级切换。我用Kotlin协程重构过一个老SDK,同样机器QPS从500升到4500。
- 事件驱动模型:单线程或少量线程,通过状态机+回调处理请求。Netty、Reactor就是代表。内存占用极低。
我个人习惯:IO密集型的SDK,首选协程或事件驱动。CPU密集型的反而适合线程池,避免协程里做计算阻塞调度器。
调优建议:压测前一定要确认SDK的并发模型,并设置合理的线程数(或协程数)。对于事件驱动模型,连接数和事件循环线程数是核心参数。
好了,这一章把常见的SDK类型和它们对应的性能特征梳理了一遍。分类清楚了,后面压测才有的放矢。你想想看,如果你的SDK是同步阻塞,却拿异步指标的期望去压,那不是自己找坑吗?