设计点赞系统时需考虑的多个关键方面,包括系统架构、库表设计、数据存储、性能优化和容灾备份。文章强调了在高流量情况下,点赞服务的独立性、异步处理、缓存机制的重要性,并提出了具体的数据库设计示例和优化策略,以确保系统的稳定性和数据的一致性。
1. 分布式架构 - 点赞功能独立成服务
首先,点赞功能应该作为独立的微服务,便于扩展和维护。可以通过 Spring Boot 创建一个独立的点赞服务,该服务将处理所有的点赞相关请求。
Service 层:
@Service
public class LikeService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
private static final String LIKE_TOPIC = "likeTopic";
/**
* 用户点赞操作,发送消息到RocketMQ
*/
public void likePost(Long postId, Long userId) {
LikeMessage message = new LikeMessage(postId, userId, System.currentTimeMillis());
rocketMQTemplate.convertAndSend(LIKE_TOPIC, message);
}
/**
* 用户取消点赞操作,发送消息到RocketMQ
*/
public void unlikePost(Long postId, Long userId) {
LikeMessage message = new LikeMessage(postId, userId, System.currentTimeMillis(), true);
rocketMQTemplate.convertAndSend(LIKE_TOPIC, message);
}
}
RocketMQ 消费者:
@Service
@RocketMQMessageListener(topic = "likeTopic", consumerGroup = "like_consumer_group")
public class LikeConsumer implements RocketMQListener<LikeMessage> {
@Autowired
private LikeRepository likeRepository;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public void onMessage(LikeMessage message) {
// 异步处理点赞
if (!message.isUnlike()) {
likeRepository.save(new LikeEntity(message.getPostId(), message.getUserId(), message.getTimestamp()));
// 更新缓存
String cacheKey = "post:likes:" + message.getPostId();
redisTemplate.opsForZSet().add(cacheKey, message.getUserId(), message.getTimestamp());
} else {
// 取消点赞
likeRepository.deleteByPostIdAndUserId(message.getPostId(), message.getUserId());
// 更新缓存
String cacheKey = "post:likes:" + message.getPostId();
redisTemplate.opsForZSet().remove(cacheKey, message.getUserId());
}
}
}
2. 缓存机制 - 利用 Redis 缓存点赞数
通过 Redis 的 ZSet 数据结构来缓存点赞用户,并定期将缓存数据同步到数据库。
缓存点赞数实现:
@Service
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 将用户点赞信息存储到缓存
*/
public void cacheLike(Long postId, Long userId, long timestamp) {
String cacheKey = "post:likes:" + postId;
redisTemplate.opsForZSet().add(cacheKey, userId, timestamp);
}
/**
* 从缓存中获取点赞数量
*/
public Long getLikeCount(Long postId) {
String cacheKey = "post:likes:" + postId;
return redisTemplate.opsForZSet().size(cacheKey);
}
/**
* 同步缓存中的数据到数据库
*/
public void syncLikesToDatabase() {
Set<String> keys = redisTemplate.keys("post:likes:*");
if (keys != null) {
for (String key : keys) {
Long postId = Long.parseLong(key.split(":")[2]);
Set<Object> userIds = redisTemplate.opsForZSet().range(key, 0, -1);
for (Object userId : userIds) {
// 将点赞数据同步到数据库
// likeRepository.save(new LikeEntity(postId, (Long) userId));
}
// 清空 Redis 中已同步的数据
redisTemplate.delete(key);
}
}
}
}
3. 库表设计
我们需要创建两张表:一张是 点赞记录表,用于存储每个用户的点赞记录;另一张是 点赞数汇总表,用于存储每个项目的总点赞数。
点赞记录表:
CREATE TABLE like_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
post_id BIGINT NOT NULL,
like_time BIGINT NOT NULL,
UNIQUE KEY unique_like (user_id, post_id)
);
点赞数汇总表:
CREATE TABLE like_summary (
post_id BIGINT PRIMARY KEY,
like_count BIGINT DEFAULT 0
);
MyBatis Mapper 接口:
@Mapper
public interface LikeRepository {
@Insert("INSERT INTO like_record (user_id, post_id, like_time) VALUES (#{userId}, #{postId}, #{likeTime})")
void save(LikeEntity likeEntity);
@Delete("DELETE FROM like_record WHERE post_id = #{postId} AND user_id = #{userId}")
void deleteByPostIdAndUserId(Long postId, Long userId);
@Select("SELECT COUNT(*) FROM like_record WHERE post_id = #{postId}")
Long countLikesByPostId(Long postId);
}
4. 异步批量处理 - 批量同步 Redis 到数据库
在高并发场景下,通过 Redis 缓存点赞数据,定期将缓存中的数据批量同步到数据库中,减少频繁的数据库写入操作。
@Service
public class SyncService {
@Autowired
private LikeRepository likeRepository;
@Scheduled(fixedDelay = 60000) // 每分钟执行一次
public void batchSyncToDatabase() {
// 从缓存中获取所有的点赞记录
Set<String> keys = redisTemplate.keys("post:likes:*");
if (keys != null) {
for (String key : keys) {
Long postId = Long.parseLong(key.split(":")[2]);
Set<Object> userIds = redisTemplate.opsForZSet().range(key, 0, -1);
for (Object userId : userIds) {
long timestamp = redisTemplate.opsForZSet().score(key, userId).longValue();
LikeEntity likeEntity = new LikeEntity(postId, (Long) userId, timestamp);
likeRepository.save(likeEntity);
}
// 清理缓存
redisTemplate.delete(key);
}
}
}
}
5. 数据库优化与容灾备份
数据库优化:
为查询添加索引来提高查询性能,特别是在高并发的情况下。
CREATE INDEX idx_post_id ON like_record(post_id);
CREATE INDEX idx_user_id ON like_record(user_id);
主从复制与容灾备份:
可以通过 MySQL 的主从复制实现数据库的高可用性和灾难恢复。下面是 MySQL 主从复制的基本步骤:
- 在主库上开启 binlog 日志。
- 在从库上配置
CHANGE MASTER TO
语句指向主库。 - 启动从库的复制进程。
- 定期进行全量和增量备份,保证数据安全。
# 主库配置
[mysqld]
log-bin=mysql-bin
server-id=1
# 从库配置
[mysqld]
server-id=2
replicate-do-db=yourdb
6. 总结
通过异步处理(RocketMQ)、Redis 缓存和批量同步策略,可以有效减少数据库的压力,并提高点赞功能的响应速度。同时,数据库的优化和主从复制也为高并发场景下的数据一致性和高可用性提供了保障。