题目
设计一个支持高并发的分布式计数器服务
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
并发控制,原子操作,缓存策略,分布式系统设计
快速回答
实现高并发计数器需解决的核心问题:
- 原子性保证:使用Redis的INCR/DECR或Lua脚本
- 缓存击穿防护:采用互斥锁或缓存预热
- 数据一致性:结合异步持久化与补偿机制
- 水平扩展:通过分片键分散压力
最终方案:Redis分片 + Lua原子操作 + 双写队列 + 定时持久化
解析
问题场景
在千万级日活的电商系统中,需要实时统计商品点击量,峰值QPS超过10,000。传统MySQL方案会导致:
- 行锁竞争造成大量连接超时
- 磁盘I/O瓶颈导致响应延迟飙升
- 单点故障引发服务雪崩
核心解决方案
1. 原子操作实现(Redis)
// 使用Lua脚本保证原子性
$lua = <<<LUA
local key = KEYS[1]
local change = tonumber(ARGV[1])
local current = redis.call('GET', key) or '0'
current = tonumber(current)
local newval = current + change
redis.call('SET', key, newval)
return newval
LUA;
$redis->eval($lua, ["product:1234:views"], [1]);原理说明:Redis单线程模型+Lua脚本确保操作原子性,避免竞态条件
2. 缓存架构设计

- 本地缓存:APCu存储节点级计数(有效期5s)
- 分布式缓存:Redis Cluster分片存储(分片键=商品ID%1024)
- 持久层:MySQL分库分表(按日分表)
3. 数据同步策略
// 双写队列示例
class CounterQueue {
public static function push($productId, $count) {
// 写入Redis
$redis->incrBy("product:{$productId}:views", $count);
// 异步写入Kafka
$kafka->produce([
'type' => 'view_count',
'data' => ['product_id' => $productId, 'delta' => $count]
]);
}
}
// 消费者处理MySQL持久化
$consumer->consume(function($msg) {
$db->query("UPDATE counts SET views=views+{$msg['delta']}
WHERE product_id={$msg['product_id']}");
});4. 容灾机制
- 缓存雪崩防护:随机过期时间+热点Key检测
- 数据补偿:每小时扫描Redis与MySQL差异
- 降级方案:故障时切换至本地计数+批量上报
最佳实践
- 分片策略:采用一致性哈希减少扩容时的数据迁移
- 批量提交:每100ms聚合一次本地计数再提交
- 监控指标:Redis内存碎片率、Kafka堆积量、MySQL更新延迟
常见错误
| 错误方案 | 后果 | 改进方案 |
|---|---|---|
| 直接写MySQL | 高峰期连接数打满 | 读写分离+连接池 |
| Redis单点部署 | 单点故障导致数据丢失 | Cluster模式+持久化 |
| 先读后写 | 并发更新导致数据错误 | 使用原子操作替代 |
扩展知识
- HyperLogLog:适用于允许误差的基数统计(如UV)
- Redis模块:RedisBloom实现概率计数器
- NewSQL方案:TiDB分布式数据库的实时分析能力