1. 项目背景与核心需求
在即时通讯(IM)系统中,好友管理模块是支撑用户社交关系的基础设施。这个看似简单的"添加好友-好友列表-删除好友"链条,实际上需要处理高并发读写、多端状态同步、关系链扩散等复杂问题。
以微信为例,其好友管理子系统每天需要处理:
- 超过2亿次好友添加请求
- 5000万次黑名单操作
- 3000万次备注修改
- 涉及200+维度的关系链数据同步
我们设计的这个好友管理子服务,需要实现以下核心能力:
- 关系链的原子化操作(添加/删除/拉黑)
- 多端实时状态同步
- 好友列表分级缓存
- 操作幂等性保证
- 关系变更事件通知
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 架构设计与技术选型
2.1 整体架构分层
采用微服务架构,核心分为四层:
code复制接入层 → 逻辑层 → 存储层 → 扩展层
接入层:
- 协议适配:支持TCP/WebSocket/HTTP长轮询
- 连接管理:基于Netty实现百万级长连接
- 协议编解码:Protobuf二进制协议
逻辑层:
- 关系操作服务:处理核心CRUD逻辑
- 状态同步服务:负责多端数据一致性
- 事件分发服务:通过Kafka通知其他模块
存储层:
- 关系数据:TiDB集群(强一致性需求)
- 缓存数据:Redis Cluster+本地Caffeine二级缓存
- 历史记录:Elasticsearch(操作日志检索)
扩展层:
- 风控插件:基于规则引擎的异常操作检测
- 审计模块:操作留痕与追溯
- 数据分析:用户关系图谱计算
2.2 关键设计决策
写优化设计:
- 采用"先写内存队列,后持久化"的异步写模式
- 对高频操作(如备注修改)实现合并写
- 热点用户数据特殊分片策略
读优化设计:
- 多级缓存策略:
- 本地缓存:最近操作的好友记录
- Redis缓存:完整好友列表
- 数据库:全量数据
- 缓存更新采用"写穿+定时刷新"机制
一致性保障:
- 基于TSO的分布式事务(Percolator模型)
- 变更事件通过Kafka保证最终一致性
- 客户端采用版本号冲突解决机制
3. 核心功能实现细节
3.1 好友添加流程
mermaid复制sequenceDiagram
participant C as Client
participant G as Gateway
participant F as FriendService
participant D as DB
C->>G: 发送添加请求(targetUserId, remark)
G->>F: 校验基础参数
F->>D: 检查黑名单关系(原子操作)
alt 存在黑名单
F-->>G: 返回错误(CODE_BLOCKED)
else 可添加
F->>D: 创建pending记录
F->>D: 写入操作日志
F->>G: 返回成功(带requestId)
end
G->>C: 转发响应
关键实现点:
- 防重复提交:基于客户端生成的requestId实现幂等
- 频率控制:滑动窗口限流(每个用户5次/分钟)
- 异步通知:通过MQ触发推送通知
3.2 好友列表缓存策略
采用动态加载的分页缓存方案:
java复制// 缓存数据结构
class FriendListCache {
Long userId;
List<FriendProfile> top50; // 高频访问好友
TreeMap<Long, FriendProfile> allFriends; // 按lastContactTime排序
Long version; // 数据版本号
}
缓存更新策略:
- 写穿透:任何关系变更同步更新缓存
- 定时重建:每天凌晨全量刷新
- 热点探测:对频繁访问用户启用特别缓存
3.3 多端同步方案
采用"操作日志+版本号"的同步机制:
- 每个客户端维护localVersion
- 服务端记录全局maxVersion
- 同步时携带localVersion,服务端返回[localVersion, maxVersion]之间的增量操作
冲突解决策略:
- 时间戳优先:取最新时间操作
- 客户端确认:遇到冲突时让用户选择保留哪个操作
4. 性能优化实践
4.1 存储分片策略
采用复合分片键:user_id_prefix + user_id_hash
- user_id_prefix:取用户ID前2位(00-99)
- user_id_hash:对用户ID取模(256个槽)
这样实现:
- 热点分散:明星用户不会集中在一个分片
- 范围查询:相同前缀用户的数据物理相邻
4.2 批量操作优化
对于群组好友操作(如批量添加):
python复制def batch_add_friends(request):
# 将单个大事务拆分为多个小事务
chunk_size = 50 # 每个事务处理50条记录
for chunk in split_into_chunks(request.users, chunk_size):
with transaction.atomic():
process_chunk(chunk)
time.sleep(0.1) # 控制写入速率
4.3 缓存击穿防护
采用二级缓存+互斥锁方案:
go复制func GetFriendList(userId int64) ([]Friend, error) {
// 先查本地缓存
if list := localCache.Get(userId); list != nil {
return list, nil
}
// 获取分布式锁
lockKey := fmt.Sprintf("friend_lock_%d", userId)
if ok := redis.TryLock(lockKey, 10*time.Second); !ok {
return nil, errors.New("busy")
}
defer redis.Unlock(lockKey)
// 查Redis
if list := redis.Get(userId); list != nil {
localCache.Set(userId, list)
return list, nil
}
// 查数据库
list := db.QueryFriendList(userId)
redis.SetEx(userId, list, 24*time.Hour)
localCache.Set(userId, list)
return list, nil
}
5. 生产环境踩坑记录
5.1 缓存雪崩事件
现象:某次大促期间,好友列表接口响应时间从50ms飙升到2s
根因分析:
- 大量用户同时过期缓存
- 数据库连接池被打满
- 重试机制导致恶性循环
解决方案:
- 缓存过期时间增加随机偏移(±10%)
- 实现熔断降级机制
- 引入Hystrix做资源隔离
5.2 分布式事务超时
现象:跨机房部署后,好友添加成功率下降15%
问题定位:
- 两阶段提交超时时间设置过短(默认1s)
- 机房之间网络延迟达到300ms
优化措施:
- 根据P99延迟动态调整超时
- 对非关键操作降级为最终一致
- 实现事务补偿机制
5.3 热点用户问题
案例:某明星发布动态后,其好友列表接口QPS暴涨
应对方案:
- 特殊缓存策略:对该用户启用独立缓存节点
- 请求合并:将短时间内相同查询合并处理
- 静态化处理:对粉丝数大于10万的用户生成静态列表
6. 监控与治理体系
6.1 核心监控指标
| 指标类别 | 具体指标 | 报警阈值 |
|---|---|---|
| 可用性 | 接口成功率 | <99.9% (5分钟) |
| 性能 | P99延迟 | >500ms |
| 容量 | Redis内存使用率 | >80% |
| 业务 | 每日好友添加总量 | 同比波动>30% |
6.2 灰度发布方案
采用多维度的灰度策略:
- 用户维度:按user_id取模
- 设备维度:先iOS后Android
- 地域维度:从低流量机房开始
验证流程:
code复制新版本发布 → 5%流量灰度 → 监控指标分析 → 全量发布
↘ 发现问题 → 自动回滚
6.3 应急响应机制
建立三级响应体系:
- P0级(全站不可用):15分钟响应
- P1级(核心功能受损):1小时响应
- P2级(非核心问题):24小时修复
关键预案:
- 缓存全量丢失:启动降级模式,直接读库
- DB过载:启用只读副本+限流
- 网络分区:自动切换机房
