【设计题】如何涉及一个高并发的计数器
设计高并发计数器需解决性能瓶颈(高并发写入)、数据一致性(分布式场景下计数准确)、可扩展性(支持流量增长)三大核心问题。业界成熟方案基于 “分层计数、异步聚合、持久化兜底” 思路,结合本地缓存、Redis、数据库等组件实现,以下是具体设计:
一、核心设计原则
- 分层计数:本地缓存(线程级 / 进程级)承担高频写入,降低远程存储压力。
- 异步聚合:本地计数定期批量同步到全局存储(如 Redis),减少网络开销。
- 持久化兜底:全局计数定时同步到数据库,防止数据丢失。
- 水平扩展:支持多实例部署,通过分片避免单点瓶颈。
二、业界成熟方案实现
1. 单机高并发计数器(基于 LongAdder)
Java 中的 LongAdder 是单机高并发计数的首选,其通过 “分段累加”(多个单元格分摊计数)避免 CAS 竞争,性能远超 AtomicLong(适合百万级 QPS 场景)。
实现代码:
import java.util.concurrent.LongAdder;public class LocalCounter {private final LongAdder counter = new LongAdder();// 计数+1public void increment() {counter.increment();}// 计数+npublic void add(long n) {counter.add(n);}// 获取当前计数public long get() {return counter.sum();}// 重置计数(可选)public void reset() {counter.reset();}
}
适用场景:单机服务的高频计数(如接口访问量、瞬时点击量),优势是无锁、高性能,缺点是不支持分布式。
2. 分布式高并发计数器(本地 + Redis 分层设计)
分布式场景下,需结合本地计数与 Redis 实现全局计数,平衡性能与一致性。
(1)核心架构
plaintext
用户请求 → 本地计数器(LongAdder)→ 定时同步(每秒)→ Redis(INCRBY)→ 定时持久化 → 数据库
(2)实现代码
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;public class DistributedCounter {private final String redisKey; // Redis 键(如 "counter:page_view")private final StringRedisTemplate redisTemplate;private final LongAdder localCounter = new LongAdder(); // 本地计数器private final ScheduledExecutorService syncExecutor = Executors.newSingleThreadScheduledExecutor();public DistributedCounter(String redisKey, StringRedisTemplate redisTemplate) {this.redisKey = redisKey;this.redisTemplate = redisTemplate;// 启动定时任务:每秒将本地计数同步到Redisthis.syncExecutor.scheduleAtFixedRate(this::syncToRedis, 1, 1, TimeUnit.SECONDS);}// 计数+1public void increment() {localCounter.increment();}// 同步本地计数到Redis(批量累加)private void syncToRedis() {long count = localCounter.sumThenReset(); // 获取并重置本地计数if (count > 0) {redisTemplate.opsForValue().increment(redisKey, count); // Redis原子累加}}// 获取全局计数(Redis当前值 + 本地未同步值)public long getTotal() {Long redisCount = redisTemplate.opsForValue().increment(redisKey, 0); // 不修改值,仅查询return (redisCount == null ? 0 : redisCount) + localCounter.sum();}// 关闭资源public void close() {syncExecutor.shutdown();syncToRedis(); // 最后一次同步,避免数据丢失}
}
(3)关键设计
- 本地缓冲:
LongAdder处理高频写入(单机支持每秒千万级操作),避免直接操作 Redis 导致的网络瓶颈。 - 批量同步:每秒一次批量同步到 Redis(
INCRBY原子命令),减少网络请求次数(从每秒百万次降至每秒一次)。 - 实时性权衡:牺牲 1 秒内的实时性(本地计数未同步到 Redis),换取极高性能,适合允许短暂延迟的场景(如访问量统计)。
3. 超高并发场景(分片 + 预聚合)
当 QPS 超过单 Redis 实例承载能力(如每秒 100 万 +),需对计数器分片,分散压力。
(1)分片策略
- 按 key 哈希分片:将计数器 key 按哈希值分散到多个 Redis 实例(如
counter:page_view:{shardId},shardId = hash(key) % 10)。 - 实现代码(分片计数):
public class ShardedCounter {private final List<DistributedCounter> shardCounters; // 分片计数器列表private final int shardCount; // 分片数量(如10)public ShardedCounter(String baseKey, StringRedisTemplate redisTemplate, int shardCount) {this.shardCount = shardCount;this.shardCounters = new ArrayList<>(shardCount);// 初始化每个分片的计数器for (int i = 0; i < shardCount; i++) {String shardKey = baseKey + ":" + i;shardCounters.add(new DistributedCounter(shardKey, redisTemplate));}}// 计数+1:根据key哈希选择分片public void increment(String key) {int shardId = Math.abs(key.hashCode() % shardCount);shardCounters.get(shardId).increment();}// 汇总所有分片的计数public long getTotal() {long total = 0;for (DistributedCounter counter : shardCounters) {total += counter.getTotal();}return total;} }
(2)优势
- 分散 Redis 写入压力:10 个分片可将单实例压力降低 10 倍。
- 支持水平扩展:增加分片数量或 Redis 实例即可提升承载能力。
4. 持久化与高可用
- 定时持久化到数据库:通过定时任务(如每 5 分钟)将 Redis 计数同步到数据库(如 MySQL),避免 Redis 宕机导致数据丢失。
@Scheduled(cron = "0 0/5 * * * ?") // 每5分钟执行 public void persistToDb() {String redisKey = "counter:page_view";Long count = redisTemplate.opsForValue().get(redisKey);if (count != null) {counterMapper.updateCount(redisKey, count); // 写入数据库} } - Redis 高可用:采用 Redis 集群(主从 + 哨兵),确保 Redis 节点故障时自动切换,不影响计数服务。
三、业界参考案例
- Twitter 计数器:基于本地计数 + 异步聚合到 Redis,支持每秒千万级推文点赞计数。
- 阿里云 SLS 计数器:通过分片 + 预聚合实现日志访问量实时统计,支持每秒亿级操作。
- 开源方案:
Redisson RLongAdder:分布式版本的LongAdder,自动分片,适合 Java 生态。InfluxDB/Prometheus:时序数据库,内置高并发计数能力,适合监控指标场景。
四、选型建议
| 场景 | 方案 | 优势 | 局限 |
|---|---|---|---|
| 单机高频计数 | LongAdder | 无锁、高性能(千万级 QPS) | 不支持分布式 |
| 分布式一般并发 | 本地 LongAdder + Redis | 平衡性能与一致性(十万级 QPS) | 1 秒级延迟 |
| 分布式超高并发 | 分片 + 本地 + Redis | 支持百万级 QPS,可水平扩展 | 实现复杂,汇总计数有开销 |
| 监控 / 时序数据计数 | Prometheus/InfluxDB | 原生支持高并发时序计数,带聚合分析 | 依赖外部组件,适合特定场景 |
核心总结
高并发计数器的核心是 **“分层缓冲” 与 “异步聚合”**:
- 用本地计数(
LongAdder)吸收高频写入,减少远程交互; - 用 Redis 实现分布式全局计数,支持高可用;
- 用分片扩展承载能力,用定时持久化保证数据安全。根据业务的并发量、实时性要求和分布式需求选择合适方案,优先复用成熟组件(如 Redisson、Prometheus)避免重复造轮子。
