在当今互联网应用中,点赞功能看似简单,但当面对百万级QPS(每秒查询率)时,其背后的技术挑战不容小觑。我曾主导设计过多个千万级日活应用的互动系统,其中点赞功能的设计往往成为系统瓶颈的"重灾区"。一个优秀的点赞系统需要在用户即时反馈、数据一致性、系统可用性和资源利用率之间找到平衡点。
核心设计理念可以归纳为:用户要的是操作成功感,而非实时精确计数。这一认知从根本上改变了我们设计高并发系统的思路。在实际应用中,用户点击点赞按钮后,最关心的是立即看到"已赞"状态变化,而对于点赞总数的更新,秒级延迟是完全可接受的。这种认知解放了我们的设计约束,允许采用更高效的异步处理方案。
从用户角度看,点赞功能包含两个层次的需求:
这种差异化的需求强度为我们设计系统提供了重要依据。基于此,我们可以将系统设计原则确定为:
系统采用分层解耦架构,各层职责明确:
code复制客户端层 → 接入网关 → 服务层 → 消息队列 → 消费层 → 存储层
这种分层设计的关键优势在于:
点赞接口的核心挑战在于处理高并发写入同时保证数据正确性。我们采用"异步化+幂等"的设计方案:
java复制@PostMapping("/like")
public Result<Void> like(@RequestBody LikeRequest request) {
// 幂等校验
String uniqueKey = userId + ":" + videoId;
if (!idempotentService.tryLock(uniqueKey, 10)) {
return Result.fail("操作过于频繁");
}
// 检查当前状态
Boolean hasLiked = redisTemplate.opsForSet().isMember(likeKey, videoId);
int delta = hasLiked ? -1 : 1;
// 异步发送到Kafka
kafkaTemplate.send("like-topic", videoId, event)
.whenComplete((result, ex) -> {
if (ex != null) {
// 降级处理
}
});
// 乐观更新Redis
if (delta > 0) {
redisTemplate.opsForSet().add(likeKey, videoId);
} else {
redisTemplate.opsForSet().remove(likeKey, videoId);
}
redisTemplate.opsForHash().increment("video:like:count", videoId, delta);
return Result.ok();
}
这段代码体现了几个关键设计点:
查询接口面临的主要挑战是高并发读取和缓存一致性问题:
java复制@GetMapping("/like-count/{videoId}")
public Result<Long> getLikeCount(@PathVariable String videoId) {
// 优先读取Redis
String countStr = redisTemplate.opsForHash()
.get("video:like:count", videoId);
if (countStr != null) {
return Result.ok(Long.parseLong(countStr));
}
// 缓存穿透保护
if (bloomFilter.notContains(videoId)) {
return Result.ok(0L);
}
// 异步回源
asyncLoadToCache(videoId);
return Result.ok(0L);
}
查询接口的关键优化点包括:
消费者负责将异步消息最终持久化到数据库,采用批量合并策略显著降低数据库压力:
java复制@KafkaListener(topics = "like-topic", batch = "true",
properties = {"max.poll.records=1000", "fetch.max.wait.ms=1000"})
public void consume(List<ConsumerRecord<String, LikeEvent>> records) {
// 按videoId合并操作
Map<String, Long> videoDeltaMap = records.stream()
.collect(Collectors.groupingBy(
r -> r.value().getVideoId(),
Collectors.summingLong(r -> r.value().getDelta())
));
// 批量更新Redis
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
videoDeltaMap.forEach((videoId, delta) -> {
connection.hashCommands().hIncrBy(
"video:like:count".getBytes(),
videoId.getBytes(),
delta
);
});
return null;
});
// 批量写入MySQL
List<VideoLikeCount> entities = videoDeltaMap.entrySet().stream()
.map(e -> new VideoLikeCount(e.getKey(), e.getValue()))
.collect(Collectors.toList());
repository.batchUpsert(entities);
}
消费者设计的核心优化:
在分布式系统中,我们采用最终一致性模型,通过以下机制保证数据正确性:
缓存优化:
批量处理:
异步化设计:
当某视频突然爆火(如明星官宣),可能产生极端流量:
解决方案:
当缓存集群不可用时,系统需要优雅降级:
降级方案:
经过上述优化,系统可以达到以下性能指标:
| 指标 | 数值 | 说明 |
|---|---|---|
| 点赞接口RT | <50ms | 内存操作+异步发送 |
| 查询接口RT | <10ms | Redis单节点10万QPS |
| 写入吞吐量 | 50万QPS | Kafka3分区×单机5万TPS |
| 数据延迟 | <5s | 批量消费1s+数据库写入 |
| 系统可用性 | 99.99% | 全链路无单点 |
在实际落地过程中,我们积累了一些宝贵经验:
常见问题排查技巧:
百万级点赞系统的核心设计思想可以总结为"四化":
未来演进方向:
在实际项目中,我们通过这套架构成功支撑了单日超过10亿次的点赞操作,峰值QPS达到120万,系统保持稳定运行。这证明异步化+最终一致性的设计思路在高并发场景下的有效性。