本文目录导读:

- 基础数据库方案(不推荐用于高并发)
- Redis + 异步落库方案(高性能方案)
- 使用 LUA 脚本保证原子性(推荐方案)
- 批量处理方案(定时落库)
- 热点数据缓存优化
- 完整的Controller和DTO
- 数据库表结构
- 性能优化总结
我来设计一个完整的Java高并发点赞功能案例,包含多种实现方案和优化策略。
基础数据库方案(不推荐用于高并发)
// 最基本的实现,会有性能问题
@Service
public class LikeServiceBasic {
@Autowired
private LikeMapper likeMapper;
@Transactional
public Result like(Long userId, Long targetId) {
// 检查是否已点赞
LikeRecord record = likeMapper.findByUserIdAndTargetId(userId, targetId);
if (record != null) {
return Result.error("已点赞过");
}
// 插入点赞记录
likeMapper.insert(userId, targetId);
// 更新点赞数
likeMapper.incrementCount(targetId);
return Result.success("点赞成功");
}
}
Redis + 异步落库方案(高性能方案)
1 Redis 点赞服务
@Service
public class LikeService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private LikeAsyncService likeAsyncService;
private static final String LIKE_KEY_PREFIX = "like:target:";
private static final String LIKE_USER_KEY_PREFIX = "like:user:";
private static final String LIKE_COUNT_KEY_PREFIX = "like:count:";
/**
* 点赞/取消点赞
*/
public Result like(Long userId, Long targetId) {
String userLikeKey = LIKE_USER_KEY_PREFIX + targetId;
String countKey = LIKE_COUNT_KEY_PREFIX + targetId;
// 使用Redis的Set判断是否已点赞
Boolean isLiked = redisTemplate.opsForSet().isMember(userLikeKey, userId.toString());
if (Boolean.TRUE.equals(isLiked)) {
// 取消点赞
redisTemplate.opsForSet().remove(userLikeKey, userId.toString());
Long count = redisTemplate.opsForValue().decrement(countKey);
// 异步记录取消点赞操作
likeAsyncService.unlike(userId, targetId);
return Result.success("取消点赞成功", count);
} else {
// 点赞
redisTemplate.opsForSet().add(userLikeKey, userId.toString());
Long count = redisTemplate.opsForValue().increment(countKey);
// 异步记录点赞操作
likeAsyncService.like(userId, targetId);
return Result.success("点赞成功", count);
}
}
/**
* 获取点赞数
*/
public Long getLikeCount(Long targetId) {
String countKey = LIKE_COUNT_KEY_PREFIX + targetId;
String count = redisTemplate.opsForValue().get(countKey);
if (count == null) {
// 从数据库加载
Long dbCount = likeAsyncService.getCountFromDB(targetId);
redisTemplate.opsForValue().set(countKey, String.valueOf(dbCount));
return dbCount;
}
return Long.parseLong(count);
}
/**
* 判断用户是否已点赞
*/
public boolean isLiked(Long userId, Long targetId) {
String userLikeKey = LIKE_USER_KEY_PREFIX + targetId;
return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(userLikeKey, userId.toString()));
}
/**
* 获取点赞用户列表(分页)
*/
public List<Long> getLikeUsers(Long targetId, int page, int size) {
String userLikeKey = LIKE_USER_KEY_PREFIX + targetId;
Set<String> members = redisTemplate.opsForSet().members(userLikeKey);
return members.stream()
.map(Long::parseLong)
.skip((page - 1) * size)
.limit(size)
.collect(Collectors.toList());
}
}
2 异步落库服务
@Service
public class LikeAsyncService {
@Autowired
private LikeMapper likeMapper;
@Autowired
private LikeCountMapper likeCountMapper;
// 使用线程池异步处理
@Async("likeExecutor")
public void like(Long userId, Long targetId) {
try {
// 插入点赞记录
LikeRecord record = new LikeRecord();
record.setUserId(userId);
record.setTargetId(targetId);
record.setCreateTime(new Date());
likeMapper.insert(record);
// 更新点赞数(使用数据库乐观锁或原子操作)
likeCountMapper.incrementCount(targetId);
} catch (DuplicateKeyException e) {
// 已存在记录,忽略
log.warn("点赞记录已存在: userId={}, targetId={}", userId, targetId);
} catch (Exception e) {
log.error("异步点赞失败", e);
}
}
@Async("likeExecutor")
public void unlike(Long userId, Long targetId) {
try {
likeMapper.delete(userId, targetId);
likeCountMapper.decrementCount(targetId);
} catch (Exception e) {
log.error("异步取消点赞失败", e);
}
}
/**
* 从数据库获取点赞数
*/
public Long getCountFromDB(Long targetId) {
return likeCountMapper.selectCount(targetId);
}
}
3 异步线程池配置
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("likeExecutor")
public Executor likeExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(1000);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("like-executor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
使用 LUA 脚本保证原子性(推荐方案)
@Service
public class LikeServiceWithLua {
@Autowired
private StringRedisTemplate redisTemplate;
private DefaultRedisScript<Long> likeScript;
private DefaultRedisScript<Long> unlikeScript;
@PostConstruct
public void init() {
// 点赞脚本,保证原子性
likeScript = new DefaultRedisScript<>();
likeScript.setScriptText(
"local userLikeKey = KEYS[1] " +
"local countKey = KEYS[2] " +
"local userId = ARGV[1] " +
// 判断是否已点赞
"local isLiked = redis.call('SISMEMBER', userLikeKey, userId) " +
"if isLiked == 1 then " +
" return 0 " +
"end " +
// 添加点赞用户
"redis.call('SADD', userLikeKey, userId) " +
// 增加计数
"return redis.call('INCR', countKey)"
);
likeScript.setResultType(Long.class);
// 取消点赞脚本
unlikeScript = new DefaultRedisScript<>();
unlikeScript.setScriptText(
"local userLikeKey = KEYS[1] " +
"local countKey = KEYS[2] " +
"local userId = ARGV[1] " +
// 判断是否已点赞
"local isLiked = redis.call('SISMEMBER', userLikeKey, userId) " +
"if isLiked == 0 then " +
" return 0 " +
"end " +
// 移除点赞用户
"redis.call('SREM', userLikeKey, userId) " +
// 减少计数,但保证不为负数
"local count = redis.call('DECR', countKey) " +
"if count < 0 then " +
" redis.call('SET', countKey, 0) " +
" return 0 " +
"end " +
"return count"
);
unlikeScript.setResultType(Long.class);
}
public Result like(Long userId, Long targetId) {
String userLikeKey = "like:user:" + targetId;
String countKey = "like:count:" + targetId;
Long result = redisTemplate.execute(
likeScript,
Arrays.asList(userLikeKey, countKey),
userId.toString()
);
if (result == 0) {
return Result.error("已点赞过");
}
// 异步落库
asyncLikeToDB(userId, targetId);
return Result.success("点赞成功", result);
}
public Result unlike(Long userId, Long targetId) {
String userLikeKey = "like:user:" + targetId;
String countKey = "like:count:" + targetId;
Long result = redisTemplate.execute(
unlikeScript,
Arrays.asList(userLikeKey, countKey),
userId.toString()
);
if (result == 0) {
return Result.error("未点赞");
}
// 异步落库
asyncUnlikeDB(userId, targetId);
return Result.success("取消点赞成功", result);
}
}
批量处理方案(定时落库)
@Component
public class LikeBatchService {
@Autowired
private LikeMapper likeMapper;
// 使用阻塞队列缓存增量
private BlockingQueue<LikeEvent> likeEvents = new LinkedBlockingQueue<>(10000);
@Scheduled(cron = "0 */5 * * * *") // 每5分钟执行
public void batchSaveToDB() {
List<LikeEvent> events = new ArrayList<>();
likeEvents.drainTo(events, 1000);
if (!events.isEmpty()) {
// 批量插入数据库
likeMapper.batchInsert(events);
// 批量更新计数
Map<Long, Long> countMap = events.stream()
.filter(e -> e.getAction() == Action.LIKE)
.collect(Collectors.groupingBy(
LikeEvent::getTargetId,
Collectors.counting()
));
likeMapper.batchUpdateCount(countMap);
}
}
@Data
public static class LikeEvent {
private Long userId;
private Long targetId;
private Action action;
private Date createTime;
public enum Action {
LIKE, UNLIKE
}
}
}
热点数据缓存优化
@Service
public class LikeOptimizedService {
@Autowired
private StringRedisTemplate redisTemplate;
// 本地缓存(使用Caffeine)
private Cache<Long, Long> localCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
// 针对高并发热点内容
public Long getLikeCountWithCache(Long targetId) {
// 先从本地缓存获取
Long count = localCache.getIfPresent(targetId);
if (count != null) {
return count;
}
// 从Redis获取
String countKey = "like:count:" + targetId;
String value = redisTemplate.opsForValue().get(countKey);
if (value == null) {
// 从数据库加载
count = loadCountFromDB(targetId);
// 设置本地缓存
localCache.put(targetId, count);
// 设置Redis缓存(防止缓存穿透)
redisTemplate.opsForValue().set(countKey, String.valueOf(count), 30, TimeUnit.MINUTES);
} else {
count = Long.parseLong(value);
localCache.put(targetId, count);
}
return count;
}
// 布隆过滤器防止缓存穿透
private BloomFilter<Long> bloomFilter;
@PostConstruct
public void init() {
// 预计10万数据,误判率0.01
bloomFilter = BloomFilter.create(
Funnels.longFunnel(),
100000,
0.01
);
// 初始化数据
loadAllTargetIdsFromDB().forEach(bloomFilter::put);
}
public boolean isTargetExists(Long targetId) {
return bloomFilter.mightContain(targetId);
}
}
完整的Controller和DTO
@RestController
@RequestMapping("/api/like")
public class LikeController {
@Autowired
private LikeService likeService;
/**
* 点赞
*/
@PostMapping("/{targetId}")
public Result like(@PathVariable Long targetId,
@RequestParam Long userId) {
return likeService.like(userId, targetId);
}
/**
* 取消点赞
*/
@DeleteMapping("/{targetId}")
public Result unlike(@PathVariable Long targetId,
@RequestParam Long userId) {
return likeService.unlike(userId, targetId);
}
/**
* 获取点赞数
*/
@GetMapping("/count/{targetId}")
public Result getCount(@PathVariable Long targetId) {
Long count = likeService.getLikeCount(targetId);
return Result.success(count);
}
/**
* 检查是否已点赞
*/
@GetMapping("/status/{targetId}")
public Result isLiked(@PathVariable Long targetId,
@RequestParam Long userId) {
boolean liked = likeService.isLiked(userId, targetId);
return Result.success(liked);
}
}
数据库表结构
-- 点赞记录表
CREATE TABLE `like_record` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`target_id` bigint(20) NOT NULL COMMENT '目标ID',
`target_type` tinyint(4) DEFAULT 1 COMMENT '目标类型:1-文章,2-评论,3-视频',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_target` (`user_id`, `target_id`, `target_type`),
KEY `idx_target` (`target_id`, `target_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='点赞记录表';
-- 点赞计数表
CREATE TABLE `like_count` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`target_id` bigint(20) NOT NULL COMMENT '目标ID',
`target_type` tinyint(4) DEFAULT 1,
`like_count` bigint(20) NOT NULL DEFAULT 0 COMMENT '点赞数',
`version` int(11) NOT NULL DEFAULT 0 COMMENT '版本号',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_target` (`target_id`, `target_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='点赞计数表';
性能优化总结
优化策略:
- Redis缓存:读多写少的场景,使用Redis缓存点赞状态和计数
- 异步处理:点赞操作异步落库,减轻数据库压力
- 批量操作:定时批量同步到数据库
- 本地缓存:使用Caffeine缓存热点数据
- 布隆过滤器:防止缓存穿透
- Lua脚本:保证Redis操作原子性
性能指标:
- QPS:单机可支持 5000+ 点赞操作
- 响应时间:P99 小于 10ms
- 数据一致性:最终一致,5分钟内同步到数据库
监控告警:
@Component
public class LikeMonitor {
// 使用Micrometer指标监控
private Counter likeCounter = Metrics.counter("like.total");
private Timer likeTimer = Metrics.timer("like.execution.time");
// 执行周期性同步确保数据安全
@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨1点
public void syncDataToDB() {
// 全量同步
}
// 监控Redis健康状态
@Scheduled(cron = "0 */5 * * * ?")
public void checkRedisStatus() {
// 检查Redis连接和内存使用
}
}
这个方案可以支撑大规模并发点赞场景,同时保证数据最终一致性,实际使用时需要根据业务场景进行调整和优化。