面试官:"请设计一个支持千万级用户同时点赞/收藏的系统,要求高可用、高性能、强一致性。"
点赞/收藏功能看似简单,但在高并发场景下却是一个典型的系统设计挑战。今天我们来深入剖析如何构建一个稳健的高并发互动系统。
一、需求分析与核心难点
核心功能需求:
- 点赞/取消点赞操作
- 实时计数查询
- 用户点赞列表查询
- 内容被点赞列表查询
非功能性需求:
- 高可用性(99.99%)
- 低延迟(<50ms)
- 高吞吐量(>10w TPS)
- 数据最终一致性
- 水平扩展能力
核心难点:
arduino
/**
* 高并发点赞系统核心挑战
*/
public class CoreChallenges {
// 1. 写热点问题:热门内容瞬间千万级点赞请求
// 2. 计数准确性:防重复、防丢失、实时性要求
// 3. 列表查询性能:百万级粉丝列表分页查询
// 4. 数据一致性:缓存与数据库的一致性保证
// 5. 系统扩展性:应对业务快速增长
}
二、整体架构设计
分层架构:
客户端 → API网关 → 业务服务层 → 缓存层 → 持久层
↓ ↓ ↓
监控系统 消息队列 大数据平台
组件职责:
- API网关:限流、鉴权、负载均衡
- 业务服务:点赞操作、查询服务
- 缓存层:Redis集群,存储热点数据
- 持久层:MySQL集群,数据持久化
- 消息队列:异步处理、削峰填谷
三、详细设计方案
3.1 数据模型设计
Redis数据结构:
ini
// 点赞关系存储
String likeKey = "like:content:" + contentId; // Set<userId>
String userLikeKey = "user:like:" + userId; // Hash<contentId, timestamp>
// 计数器设计
String countKey = "count:content:" + contentId; // String计数
String hotKey = "hot:content"; // ZSet热门排行
MySQL表设计:
kotlin
/**
* 点赞关系表
*/
@Entity
@Table(name = "user_like_relation")
public class UserLikeRelation {
private Long id;
private Long userId;
private Long contentId;
private Integer contentType;
private Date createTime;
private Integer status; // 1:有效, 0:取消
}
/**
* 内容计数表
*/
@Entity
@Table(name = "content_like_count")
public class ContentLikeCount {
private Long id;
private Long contentId;
private Long likeCount;
private Date updateTime;
}
3.2 核心业务实现
点赞服务核心代码:
typescript
@Service
@Slf4j
public class LikeServiceImpl implements LikeService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 点赞操作 - 原子性实现
*/
@Override
public boolean like(Long userId, Long contentId, Integer contentType) {
// 1. 防重检查
String likeKey = "like:content:" + contentId;
if (Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(likeKey, userId))) {
return false;
}
// 2. 原子性操作
redisTemplate.execute(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) {
operations.multi();
// 添加点赞关系
operations.opsForSet().add(likeKey, userId);
// 增加计数
operations.opsForValue().increment("count:content:" + contentId);
// 记录用户行为
operations.opsForHash().put("user:like:" + userId,
contentId.toString(), System.currentTimeMillis());
return operations.exec();
}
});
// 3. 异步持久化
sendLikeMessage(userId, contentId, contentType, 1);
return true;
}
/**
* 获取点赞计数
*/
public long getLikeCount(Long contentId) {
String countKey = "count:content:" + contentId;
Object count = redisTemplate.opsForValue().get(countKey);
if (count != null) {
return Long.parseLong(count.toString());
}
// 缓存未命中,查询数据库
Long dbCount = likeDao.selectLikeCount(contentId);
if (dbCount != null) {
redisTemplate.opsForValue().set(countKey, dbCount, 1, TimeUnit.HOURS);
return dbCount;
}
return 0L;
}
}
3.3 异步处理机制
消息队列处理:
less
@Component
@Slf4j
public class LikeMessageConsumer {
@KafkaListener(topics = "like-topic", groupId = "like-group")
public void consumeLikeMessage(String message) {
try {
LikeMessage likeMessage = JSON.parseObject(message, LikeMessage.class);
// 批量写入数据库
likeDao.batchInsertLikeRelations(Collections.singletonList(likeMessage));
// 更新计数
likeDao.updateLikeCount(likeMessage.getContentId(),
likeMessage.getAction() == 1 ? 1 : -1);
} catch (Exception e) {
log.error("处理点赞消息失败: {}", message, e);
// 进入死信队列或重试机制
}
}
}
四、高性能优化策略
多级缓存设计:
kotlin
/**
* 多级缓存实现
*/
@Service
public class MultiLevelCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 本地缓存
private Cache<Long, Long> localCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
/**
* 获取计数 - 多级缓存查询
*/
public long getCountWithMultiCache(Long contentId) {
// 1. 查询本地缓存
Long count = localCache.getIfPresent(contentId);
if (count != null) {
return count;
}
// 2. 查询Redis
String countKey = "count:content:" + contentId;
Object redisCount = redisTemplate.opsForValue().get(countKey);
if (redisCount != null) {
count = Long.parseLong(redisCount.toString());
localCache.put(contentId, count);
return count;
}
// 3. 查询数据库
count = likeDao.selectLikeCount(contentId);
if (count != null) {
// 回写缓存
redisTemplate.opsForValue().set(countKey, count, 1, TimeUnit.HOURS);
localCache.put(contentId, count);
return count;
}
return 0L;
}
}
批量处理优化:
ini
/**
* 批量操作优化
*/
@Service
public class BatchOperationService {
/**
* 批量点赞计数查询
*/
public Map<Long, Long> batchGetCounts(List<Long> contentIds) {
Map<Long, Long> result = new HashMap<>();
List<String> keys = contentIds.stream()
.map(id -> "count:content:" + id)
.collect(Collectors.toList());
// 批量Redis查询
List<Object> counts = redisTemplate.opsForValue().multiGet(keys);
for (int i = 0; i < contentIds.size(); i++) {
Long contentId = contentIds.get(i);
Object count = counts.get(i);
if (count != null) {
result.put(contentId, Long.parseLong(count.toString()));
} else {
// 异步加载缺失数据
asyncLoadMissingData(contentId);
}
}
return result;
}
}
五、高可用保障
限流降级策略:
kotlin
/**
* 限流降级服务
*/
@Service
public class RateLimitService {
/**
* 用户级别限流
*/
public boolean canUserLike(Long userId, Long contentId) {
String limitKey = "limit:user:" + userId + ":" + contentId;
Long count = redisTemplate.opsForValue().increment(limitKey);
redisTemplate.expire(limitKey, 1, TimeUnit.MINUTES);
// 1分钟内最多操作5次
return count <= 5;
}
/**
* 服务降级策略
*/
@HystrixCommand(fallbackMethod = "getCountFallback")
public long getCountWithCircuitBreaker(Long contentId) {
return getLikeCount(contentId);
}
public long getCountFallback(Long contentId) {
// 返回缓存中的近似值或默认值
return 0L;
}
}
数据一致性保障:
less
/**
* 数据同步保障
*/
@Component
@Slf4j
public class DataConsistencyService {
@Scheduled(fixedRate = 300000) // 5分钟同步一次
public void syncRedisToDB() {
// 扫描Redis中的计数数据
Set<String> keys = redisTemplate.keys("count:content:*");
for (String key : keys) {
Long contentId = extractIdFromKey(key);
Object count = redisTemplate.opsForValue().get(key);
if (count != null) {
// 批量更新数据库
likeDao.updateCountFromCache(contentId, Long.parseLong(count.toString()));
}
}
}
}
六、扩展性设计
分库分表方案:
arduino
/**
* 分片策略
*/
public class ShardingStrategy {
/**
* 按用户ID分片
*/
public String getShardKey(Long userId) {
int shardCount = 16;
return "db_" + (userId % shardCount);
}
/**
* 全局ID生成
*/
public Long generateId() {
// 雪花算法生成分布式ID
return IdGenerator.nextId();
}
}
七、监控与告警
关键监控指标:
- 点赞操作QPS/TPS
- 接口响应时间P99
- Redis缓存命中率
- 消息队列堆积情况
- 数据库连接池使用率
八、面试深度问答
Q1:为什么选择Redis作为主要存储?
A: Redis具有极高的读写性能(10w+ QPS),丰富的数据结构支持,原子操作能力,非常适合点赞这种高频写场景。
Q2:如何保证数据不丢失?
A: 采用多级保障:1)Redis持久化机制 2)消息队列持久化 3)数据库最终落盘 4)定期数据校对
Q3:如何处理热点Key问题?
A: 采用多级缓存、本地计数、Key分片、异步合并等策略,对极端热点启用特殊处理流程。
Q4:系统的扩展性如何?
A: 支持水平扩展:无状态服务层、Redis集群扩展、数据库分库分表、消息队列分区消费。
Q5:如何保证数据一致性?
A: 采用最终一致性模型,通过消息队列、定时任务、重试机制保证数据最终一致。
面试技巧:
- 从业务场景出发阐述设计思路
- 重点突出解决的核心难点
- 展示对性能、可用性、一致性的权衡思考
- 结合实际经验分享实战教训
- 准备数据支撑和性能指标
本文由微信公众号"程序员小胖"整理发布,转载请注明出处。