Redis如何高效实现点赞、取消点赞功能

作者:微信小助手

发布时间:2021-11-05T17:53:59

点击关注公众号,实用技术文章及时了解

来源:blog.csdn.net/lsy0903/article/details/103949459

基于 SpringCloud, 用户发起点赞、取消点赞后先存入 Redis 中,再每隔两小时从 Redis 读取点赞数据写入数据库中做持久化存储。

点赞功能在很多系统中都有,但别看功能小,想要做好需要考虑的东西还挺多的。

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

至于多久从 Redis 取一次数据存到数据库中,根据项目的实际情况定吧,我是暂时设了两个小时。

项目需求需要查看都谁点赞了,所以要存储每个点赞的点赞人、被点赞人,不能简单的做计数。

文章分四部分介绍:

  • Redis 缓存设计及实现

  • 数据库设计

  • 数据库操作

  • 开启定时任务持久化存储到数据库

一、Redis 缓存设计及实现

1.1 Redis 安装及运行

Redis 安装请自行查阅相关教程。

说下Docker 安装运行 Redis

docker run -d -p 6379:6379 redis:4.0.8  

如果已经安装了 Redis,打开命令行,输入启动 Redis 的命令

redis-server  

1.2 Redis 与 SpringBoot 项目的整合

1.在 pom.xml 中引入依赖

<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-data-redis</artifactId>  
</dependency>  

2.在启动类上添加注释 @EnableCaching

@SpringBootApplication  
@EnableDiscoveryClient  
@EnableSwagger2  
@EnableFeignClients(basePackages = "com.solo.coderiver.project.client")  
@EnableCaching  
public class UserApplication {  
  
    public static void main(String[] args) {  
        SpringApplication.run(UserApplication.classargs);  
    }  
}  

3.编写 Redis 配置类 RedisConfig

import com.fasterxml.jackson.annotation.JsonAutoDetect;  
import com.fasterxml.jackson.annotation.PropertyAccessor;  
import com.fasterxml.jackson.databind.ObjectMapper;  
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.data.redis.connection.RedisConnectionFactory;  
import org.springframework.data.redis.core.RedisTemplate;  
import org.springframework.data.redis.core.StringRedisTemplate;  
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;  
  
import java.net.UnknownHostException;  
  
  
@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 在 SpringBoot 项目中的配置已经完成,可以愉快的使用了。

1.3 Redis 的数据结构类型

Redis 可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为String(字符串)、List(列表)、Set(集合)、Hash(散列)和 Zset(有序集合)。

下面来对这5种数据结构类型作简单的介绍:

图片

1.4 点赞数据在 Redis 中的存储格式

用 Redis 存储两种数据,一种是记录点赞人、被点赞人、点赞状态的数据,另一种是每个用户被点赞了多少次,做个简单的计数。

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

因为 Hash 里的数据都是存在一个键里,可以通过这个键很方便的把所有的点赞数据都取出。这个键里面的数据还可以存成键值对的形式,方便存入点赞人、被点赞人和点赞状态。

设点赞人的 id 为 likedPostId,被点赞人的 id 为 likedUserId ,点赞时状态为 1,取消点赞状态为 0。将点赞人 id 和被点赞人 id 作为键,两个 id 中间用 :: 隔开,点赞状态作为值。

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

在可视化工具 RDM 中看到的是这样子

图片
图片

1.5 操作 Redis

将具体操作方法封装到了 RedisService 接口里

RedisService.java

import com.solo.coderiver.user.dataobject.UserLike;  
import com.solo.coderiver.user.dto.LikedCountDTO;  
  
import java.util.List;  
  
public interface RedisService {  
  
    /**  
     * 点赞。状态为1  
     * @param likedUserId  
     * @param likedPostId  
     */
  
    void saveLiked2Redis(String likedUserId, String likedPostId);  
  
    /**  
     * 取消点赞。将状态改变为0  
     * @param likedUserId  
     * @param likedPostId  
     */
  
    void unlikeFromRedis(String likedUserId, String likedPostId);  
  
    /**  
     * 从Redis中删除一条点赞数据  
     * @param likedUserId  
     * @param likedPostId  
     */
  
    void deleteLikedFromRedis(String likedUserId, String likedPostId);  
  
    /**  
     * 该用户的点赞数加1  
     * @param likedUserId  
     */
  
    void incrementLikedCount(String likedUserId);  
  
    /**  
     * 该用户的点赞数减1  
     * @param likedUserId  
     */
  
    void decrementLikedCount(String likedUserId);  
  
    /**  
     * 获取Redis中存储的所有点赞数据  
     * @return  
     */
  
    List<UserLike> getLikedDataFromRedis();  
  
    /**  
     * 获取Redis中存储的所有点赞数量  
     * @return  
     */
  
    List<LikedCountDTO> getLikedCountFromRedis();  
  
}  

实现类 RedisServiceImpl.java

import com.solo.coderiver.user.dataobject.UserLike;  
import com.solo.coderiver.user.dto.LikedCountDTO;  
import com.solo.coderiver.user.enums.LikedStatusEnum;  
import com.solo.coderiver.user.service.LikedService;  
import com.solo.coderiver.user.service.RedisService;  
import com.solo.coderiver.user.utils.RedisKeyUtils;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.data.redis.core.Cursor;  
import org.springframework.data.redis.core.RedisTemplate;  
import org.springframework.data.redis.core.ScanOptions;  
import org.springframework.stereotype.Service;  
  
import java.util.ArrayList;  
import java.util.List;  
import java.util.Map;  
  
@Service  
@Slf4j  
public