您现在的位置是:网站首页> 编程资料编程资料

利用redis实现聊天记录转存功能的全过程_Redis_

2023-05-27 495人已围观

简介 利用redis实现聊天记录转存功能的全过程_Redis_

前言

前一阵子实现了我开源项目的单聊功能,在实现过程中遇到了需要将聊天记录保存至数据库的问题,在收到消息时肯定不能直接存数据库,因为这样在高并发的场景下,数据库就炸了。

于是,我就想到了redis这个东西,第一次听说它是在2年前,但是一直没时间玩他,现在终于遇到了需要使用它的场景,在用的时候学它,本文就跟大家分享下我的实现思路以及过程,欢迎各位感兴趣的开发者阅读本文。

环境搭建

我的项目是基于SpringBoot2.x搭建的,电脑已经安装了redis,用的maven作为jar包管理工具,所以只需要在maven中添加需要的依赖包即可,如果你用的是其他管理工具,请自行查阅如何添加依赖。

org.springframework.bootspring-boot-starter-data-redisorg.springframework.bootspring-boot-starter-quartz2.3.7.RELEASE

本文需要用到依赖:Redis 、quartz,在pom.xml文件的dependencies标签下添加下述代码。

 spring: # redis配置 redis: host: 127.0.0.1 # redis地址 port: 6379 # 端口号 password: # 密码 timeout: 3000 # 连接超时时间,单位毫秒 

实现思路

在websocket的服务中,收到客户端推送的消息后,我们对数据进行解析,构造聊天记录实体类,将其保存至redis中,最后我们使用quartz设置定时任务将redis的数据定时写入mysql中。

我们将上述思路进行下整理:

  1. 解析客户端数据,构造实体类
  2. 将数据保存至redis
  3. 使用quartz将redis中的数据定时写入mysql

实现过程

实现思路很简单,难在如何将实体类数据保存至redis,我们需要把redis这一块配置好后,才能继续实现我们的业务需求。

redis支持的数据结构类型有:

  • set 集合,string类型的无序集合,元素不允许重复
  • hash 哈希表,键值对的集合,用于存储对象
  • list 列表,链表结构
  • zset有序集合
  • string 字符串,最基本的数据类型,可以包含任何数据,比如一个序列化的对象,它的字符串大小上限是512MB

redis的客户端分为jedis 和 lettuce,在SpringBoot2.x中默认客户端是使用lettuce实现的,因此我们不用做过多配置,在使用的时候通过RedisTemplate.xxx来对redis进行操作即可。

自定义RedisTemplate

在RedisTemplate中,默认是使用Java字符串序列化,将字符串存入redis后可读性很差,因此,我们需要对他进行自定义,使用Jackson 序列化,以 JSON 方式进行存储。

我们在项目的config包下,创建一个名为LettuceRedisConfig的Java文件,我们再此文件中配置其默认序列化规则,它的代码如下:

 package com.lk.config; 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.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; // 自定义RedisTemplate设置序列化器, 方便转换redis中的数据与实体类互转 @Configuration public class LettuceRedisConfig { /** * Redis 序列化配置 */ @Bean public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); // 使用GenericJackson2JsonRedisSerializer替换默认序列化 GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); // 设置 Key 和 Value 的序列化规则 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); // 初始化 RedisTemplate 序列化完成 redisTemplate.afterPropertiesSet(); return redisTemplate; } } 

封装redis工具类

做完上述操作后,通过RedisTemplate存储到redis中的数据就是json形式的了,接下来我们对其常用的操作封装成工具类,方便我们在项目中使用。

在Utils包中创建一个名为RedisOperatingUtil,其代码如下:

 package com.lk.utils; import org.springframework.data.redis.connection.DataType; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @Component // Redis操作工具类 public class RedisOperatingUtil { @Resource private RedisTemplate redisTemplate; /** * 指定 key 的过期时间 * * @param key 键 * @param time 时间(秒) */ public void setKeyTime(String key, long time) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } /** * 根据 key 获取过期时间(-1 即为永不过期) * * @param key 键 * @return 过期时间 */ public Long getKeyTime(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断 key 是否存在 * * @param key 键 * @return 如果存在 key 则返回 true,否则返回 false */ public Boolean hasKey(String key) { return redisTemplate.hasKey(key); } /** * 删除 key * * @param key 键 */ public Long delKey(String... key) { if (key == null || key.length < 1) { return 0L; } return redisTemplate.delete(Arrays.asList(key)); } /** * 获取 Key 的类型 * * @param key 键 */ public String keyType(String key) { DataType dataType = redisTemplate.type(key); assert dataType != null; return dataType.code(); } /** * 批量设置值 * * @param map 要插入的 key value 集合 */ public void barchSet(Map map) { redisTemplate.opsForValue().multiSet(map); } /** * 批量获取值 * * @param list 查询的 Key 列表 * @return value 列表 */ public List batchGet(List list) { return redisTemplate.opsForValue().multiGet(Collections.singleton(list)); } /** * 获取指定对象类型key的值 * * @param key 键 * @return 值 */ public Object objectGetKey(String key) { return redisTemplate.opsForValue().get(key); } /** * 设置对象类型的数据 * * @param key 键 * @param value 值 */ public void objectSetValue(String key, Object value) { redisTemplate.opsForValue().set(key, value); } /** * 向list的头部插入一条数据 * * @param key 键 * @param value 值 */ public Long listLeftPush(String key, Object value) { return redisTemplate.opsForList().leftPush(key, value); } /** * 向list的末尾插入一条数据 * * @param key 键 * @param value 值 */ public Long listRightPush(String key, Object value) { return redisTemplate.opsForList().rightPush(key, value); } /** * 向list头部添加list数据 * * @param key 键 * @param value 值 */ public Long listLeftPushAll(String key, List value) { return redisTemplate.opsForList().leftPushAll(key, value); } /** * 向list末尾添加list数据 * * @param key 键 * @param value 值 */ public Long listRightPushAll(String key, List value) { return redisTemplate.opsForList().rightPushAll(key, value); } /** * 通过索引设置list元素的值 * * @param key 键 * @param index 索引 * @param value 值 */ public void listIndexSet(String key, long index, Object value) { redisTemplate.opsForList().set(key, index, value); } /** * 获取列表指定范围内的list元素,正数则表示正向查找,负数则倒叙查找 * * @param key 键 * @param start 开始 * @param end 结束 * @return boolean */ public Object listRange(String key, long start, long end) { return redisTemplate.opsForList().range(key, start, end); } /** * 从列表前端开始取出数据 * * @param key 键 * @return 结果数组对象 */ public Object listPopLeftKey(String key) { return redisTemplate.opsForList().leftPop(key); } /** * 从列表末尾开始遍历取出数据 * * @param key 键 * @return 结果数组 */ public Object listPopRightKey(String key) { return redisTemplate.opsForList().rightPop(key); } /** * 获取list长度 * * @param key 键 * @return 列表长度 */ public Long listLen(String key) { return redisTemplate.opsForList().size(key); } /** * 通过索引获取list中的元素 * * @param key 键 * @param index 索引(index>=0时,0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推) * @return 列表中的元素 */ public Object listIndex(String key, long index) { return redisTemplate.opsForList().index(key, index); } /** * 移除list元素 * * @param key 键 * @param count 移除数量("负数"则从列表倒叙查找删除 count 个对应的值; "整数"则从列表正序查找删除 count 个对应的值;) * @param value 值 * @return 成功移除的个数 */ public Long listRem(String key, long count, Object value) { return redisTemplate.opsForList().remove(key, count, value); } /** * 截取指定范围内的数据, 移除不是范围内的数据 * @param key 操作的key * @param start 截取开始位置 * @param end 截取激素位置 */ public void listTrim(String key, long start, long end) { redisTemplate.opsForList().trim(key, start, end); } } 

进行单元测试

做完上述操作后,最难弄的一关我们就已经搞定了,接下来我们来对一会需要使用的方法进行单元测试,确保其能够正常运行。

创建一个名为RedisTest的Java文件,注入需要用到的相关类。

  • redisOperatingUtil为我们的redis工具类
  • subMessageMapper为聊天记录表的dao层
 @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class RedisTest { @Resource private RedisOperatingUtil redisOperatingUtil; @Resource private SubMessageMapper subMessageMapper; } 

接下来,我们看下SubMessage实体类的代码。

 package com.lk.entity; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter @NoArgsConstructor @AllArgsConstructor // 聊天记录-消息内容 public class SubMessage { private Integer id; private String msgText; // 消息内容 private String createTime; // 创建时间 private String userName; // 用户名 private String userId; // 推送方用户id private String avatarSrc; // 推送方头像 private String msgId; // 接收方用户id private Boolean status; // 消息状态 } 

测试list数据的写入与获取

在单元测试类内部加入下述代码:

 @Test public void testSerializableListRedisTemplate() { // 构造聊天记录实体类数据 SubMessage subMe