TL;DR
- 场景: Java 服务接入 Memcached,需要搞清 Spymemcached 的线程模型、分片路由和序列化细节
- 结论: Spymemcached 基于 NIO+回调实现异步 IO,通过 ketama 一致性哈希做 Sharding
- 产出: 整体架构拆解、线程与 Sharding 机制说明、以及错误速查卡
Spymemcached 基本介绍
Spymemcached 是一个 memcached 的客户端,使用NIO实现。
协议支持:
- Text 协议: 基于纯文本的协议,易于调试和阅读
- Binary 协议: 二进制协议,传输效率更高
异步通信机制: 采用 NIO(Non-blocking I/O)实现高效网络通信,通过 callback(回调)机制处理响应。
集群与分片: 支持 Sharding(分片)机制,采用一致性哈希算法分配 key,支持动态增减节点。
整体设计架构
- API接口设计: 提供同步和异步两种调用方式
- 任务封装机制: 将请求封装为独立Task对象
- 路由分区策略: 采用一致性哈希算法确定目标节点
- 任务队列管理: 每个connection维护独立的任务队列
- 异步IO处理流程: Selector监控所有连接的IO事件
- 响应处理机制: 根据opaque字段匹配原始Task,执行回调
线程设计
Spymemcached 有两类线程:业务线程和Selector线程。
业务线程负责:
- 请求封装
- 对象序列化
- 协议封装
- 任务分发
Selector线程负责:
- 发送处理:队列轮询、数据发送、流量控制
- 接收处理:响应读取、结果通知
- 连接管理:故障检测、自动恢复、负载均衡
Sharding机制
路由机制
两种哈希算法:
-
arrayMod(数组取模哈希)
- 计算方式:hash(key) % 节点数量 = 目标节点索引
- 特点:节点增减时会导致大规模数据迁移
-
ketama(一致性哈希)
- 为每个物理节点生成160个虚拟节点
- 构建哈希环,将虚拟节点均匀分布
- 优势:节点增减时平均只需迁移1/N的数据
容错
三种Failover处理策略:
- Redistribute策略(推荐): 轮询/随机选择下一个可用节点
- Retry策略: 最大重试次数3次,线性增长重试间隔
- Cancel策略: 记录错误日志,抛出ConnectionException
序列化
对象以二进制形式存储在Memcached中。序列化后判断长度是否大于阈值的16384byte,如果是则进行GZip压缩。
错误速查
| 症状 | 根因定位 | 修复 |
|---|---|---|
| 高并发下大量 get 超时 | 单节点压力过大,Selector 线程处理不过来 | 降低单节点压力、调大超时配置 |
| 节点增减后缓存命中率跌到0 | 使用 arrayMod 分片 | 改用 ketama 一致性哈希 |
| 某个节点故障后大量报连接异常 | Failover 策略配置不当 | 限制最大重试次数 |
| QPS上去后Client进程内存持续增长 | 任务队列、Future或回调里持有大对象引用 | 给队列设置合理上限 |
| 某些 key 偶发 NOT_FOUND | 分片路由不一致 | 固定节点列表顺序和权重 |
| 大对象写入时CPU飙高 | 序列化+GZip压缩消耗大 | 调整压缩阈值,避免超大对象进缓存 |