在资讯类的项目中,我们常常遇到文章评论和点赞等功能实现。但是这些功能再项目中是高频出现的,如果直接操作数据库的话,对数据库压力太大。那遇到这个问题怎么解决?

java 点赞 高并发判断是否点赞 java点赞功能如何实现_java 点赞 高并发判断是否点赞

redis缓存

首先我们想到的就是添加缓存处理机制。用户点赞可以放在Redis中,然后持久化到数据库中。

点赞、取消点赞是高频次的操作,若每次都读写数据库,大量的操作会影响数据库性能,所以需要做缓存
实战

知道了解决方案,我们就要去测试效果。

redis安装不用介绍了,大家都可以安装运行。下面来讲springboot怎么解决这个问题。

导入依赖

java 点赞 高并发判断是否点赞 java点赞功能如何实现_java 点赞 高并发判断是否点赞_02

在启动类中添加缓存注解 @EnableCaching

java 点赞 高并发判断是否点赞 java点赞功能如何实现_java 点赞 高并发判断是否点赞_03

接下来我们需要配置redis

@Configuration
public class RedisConfig {
 
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
 
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
 
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(jackson2JsonRedisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
 
 
    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

这样基本环境搭建完成,我们需要做的就是redis的工具类了。

redis存储格式

现在知道了redis缓存,但是我们怎么把点赞存储到redis中。redis支持数据格式很多,我们需要把点赞人和被点赞人,点赞状态都给记录下来。

由于需要记录点赞人和被点赞人,还有点赞状态(点赞、取消点赞),还要固定时间间隔取出 Redis 中所有点赞数据,分析了下 Redis 数据格式中 Hash 最合适。

hash的存储方式我们确定了,接下来就是模仿用户点赞存储数据。

如果用户点赞,存储的键为:likedUserId::likedPostId,对应的值为 1 。取消点赞,存储的键为:likedUserId::likedPostId,对应的值为 0 。取数据时把键用 :: 切开就得到了两个id,也很方便。

java 点赞 高并发判断是否点赞 java点赞功能如何实现_java 点赞 高并发判断是否点赞_04

现在主要的实现方法就是从redis中定时从缓存中持久化到数据库中。

@Override
    public List<UserLike> getLikedDataFromRedis() {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE);
        List<UserLike> list = new ArrayList<>();
        while (cursor.hasNext()){
            Map.Entry<Object, Object> entry = cursor.next();
            String key = (String) entry.getKey();
            //分离出 likedUserId,likedPostId
            String[] split = key.split("::");
            String likedUserId = split[0];
            String likedPostId = split[1];
            Integer value = (Integer) entry.getValue();
 
            //组装成 UserLike 对象
            UserLike userLike = new UserLike(likedUserId, likedPostId, value);
            list.add(userLike);
 
            //存到 list 后从 Redis 中删除
            redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key);
        }
 
        return list;
    }
 
    @Override
    public List<LikedCountDTO> getLikedCountFromRedis() {
        Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, ScanOptions.NONE);
        List<LikedCountDTO> list = new ArrayList<>();
        while (cursor.hasNext()){
            Map.Entry<Object, Object> map = cursor.next();
            //将点赞数量存储在 LikedCountDT
            String key = (String)map.getKey();
            LikedCountDTO dto = new LikedCountDTO(key, (Integer) map.getValue());
            list.add(dto);
            //从Redis中删除这条记录
            redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, key);
        }
        return list;
    }