侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

设计一个支持高并发的分布式计数器服务

2025-12-12 / 0 评论 / 4 阅读

题目

设计一个支持高并发的分布式计数器服务

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

并发控制,原子操作,缓存策略,分布式系统设计

快速回答

实现高并发计数器需解决的核心问题:

  • 原子性保证:使用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分布式数据库的实时分析能力