一、常见客户端

在 Redis 官网中提供了各种语言的客户端
image.png

Java 常用的 Redis 客户端由如下几种
image.png

Jedis 与 lettuce 还有 Redisson 的区别如下:

  • Jedis:以 Redis 命令作为方法名称,学习成本低,简单实用。但是 Jedis 实例是线程不安全的,多线程环境下需要基于连接池来使用;
  • lettuce:基于 Netty 实现的,支持同步、异步和响应式编程方式,并且是安全的。支持 Redis 的哨兵模式、集群模式和管道线程模式;
  • Redisson:基于 Redis 实现的分布式、可伸缩的 Java 数据结构集合。包含了诸如 Map、Queue、Lock、Semaphore、AtomicLong 等强大功能;

二、SpringDataRedis 简介

在实际的开发过程中,一般情况下不会直接使用 Redis 的原生客户端(Jedis 与 lettuce)操作 Redis,而是通过 SpringData 来操作。

SpringData 是 Spring 中数据操作的模块,包含对各种数据库的集成,其中对 Redis 的集成模块就叫做 SpringDataRedis

SpringDataRedis 提供了如下特性:

  • 提供了对不同 Redis 客户端的整合(Jedis 和 lettuce);
  • 提供了 RedisTemplate 统一 API 来操作 Redis;
  • 支持 Redis 的发布订阅模型;
  • 支持 Redis 哨兵和 Redis 集群;
  • 支持基于 Lettuce 的响应式编程;
  • 支持基于 JDK、JSON、String、Object 的数据序列化及反序列化;
  • 支持基于 Redis 的 JDKCollection 实现;

SpringDataRedis 中提供了 RedisTemplate 工具类,其中封装了各种对 Redis 的操作。并且将不同数据类型的操作 API 封装到了不同的类型中。
image.png

三、SpringDataRedis 入门

SpringBoot 已经提供了对 SpringDataRedis 的支持

1、引入依赖

引入 redis 和 连接池的依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>org.example</groupId>
  7. <artifactId>Redis</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <parent>
  10. <groupId>org.springframework.boot</groupId>
  11. <artifactId>spring-boot-starter-parent</artifactId>
  12. <version>2.3.9.RELEASE</version>
  13. </parent>
  14. <properties>
  15. <maven.compiler.source>8</maven.compiler.source>
  16. <maven.compiler.target>8</maven.compiler.target>
  17. </properties>
  18. <dependencies>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter-data-redis</artifactId>
  22. </dependency>
  23. <!-- 连接池依赖:不管是 jedis 还是 lettuce 底层都是基于 commons-pool 实现的连接池 -->
  24. <dependency>
  25. <groupId>org.apache.commons</groupId>
  26. <artifactId>commons-pool2</artifactId>
  27. </dependency>
  28. <!-- jackson 依赖:redis 的 key 和 value 序列化的时候会用到,如果引入了 spring-boot-starter-web 模块则不需要单独引入此模块 -->
  29. <dependency>
  30. <groupId>com.fasterxml.jackson.core</groupId>
  31. <artifactId>jackson-databind</artifactId>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.projectlombok</groupId>
  35. <artifactId>lombok</artifactId>
  36. </dependency>
  37. <dependency>
  38. <groupId>org.springframework.boot</groupId>
  39. <artifactId>spring-boot-starter-test</artifactId>
  40. </dependency>
  41. </dependencies>
  42. </project>

2、配置文件

添加 redis 的配置信息

  1. spring:
  2. redis:
  3. host: localhost
  4. port: 6379
  5. password: 123321
  6. lettuce:
  7. pool:
  8. # 最大连接数
  9. max-active: 8
  10. # 最大空闲连接数
  11. max-idle: 8
  12. # 最小空闲连接数
  13. min-idle: 0
  14. # 连接多带多久超时
  15. max-wait: 1000

3、单元测试

添加测试类进行测试

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest(classes = Application.class)
  3. public class RedisTemplateTest {
  4. @Autowired
  5. private RedisTemplate redisTemplate;
  6. @Test
  7. public void testString() {
  8. // 写入
  9. redisTemplate.opsForValue().set("name", "zhangsan");
  10. // 获取
  11. Object name = redisTemplate.opsForValue().get("name");
  12. // 打印
  13. System.out.println(name);
  14. }
  15. }

四、最佳实践

默认情况下,在使用 RedisTemplate 将 key 和 value 写入 Redis 时,会将 key 和 value 序列化(默认是采用 JDK 序列化)为字节形式后再存入 Redis 中。

例如上述案例的代码执行后,在 Redis 中的效果如下

  1. 127.0.0.1:6379> KEYS *
  2. 1) "\xac\xed\x00\x05t\x00\x04name"
  3. 127.0.0.1:6379> GET "\xac\xed\x00\x05t\x00\x04name"
  4. "\xac\xed\x00\x05t\x00\bzhangsan"
  5. 127.0.0.1:6379>

这种形式不但可读性差,而且内存占较大

此时,可以通过指定 key 和 value 的序列化方式从而解决此问题,方案有两种,具体如下。

1、方案一

  • 自定义 RedisTemplate
  • 修改 RedisTemplate 的序列化器为

    • 采用 String 的序列化器对 key 进行序列化;
    • 采用 JSON 的序列化器对 value 进行序列化; ```java @Configuration public class RedisConfiguration {

    @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { // 1. 创建对象 RedisTemplate redisTemplate = new RedisTemplate<>(); // 2. 设置工厂 redisTemplate.setConnectionFactory(factory); // 3. 创建序列化工具 GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); // 4. key 和 hashKey 采用 String 序列化 redisTemplate.setKeySerializer(RedisSerializer.string()); // StringRedisSerializer.UTF_8 redisTemplate.setHashKeySerializer(RedisSerializer.string()); // // StringRedisSerializer.UTF_8 // 5. value 和 hashValue 采用 JSON 序列化 redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer); redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer); // 6. 返回对象 return redisTemplate; }

}

  1. 添加测试类进行测试
  2. ```java
  3. @RunWith(SpringRunner.class)
  4. @SpringBootTest(classes = Application.class)
  5. public class JsonRedisTemplateTest {
  6. @Autowired
  7. private RedisTemplate<String, Object> redisTemplate;
  8. @Test
  9. public void testString() {
  10. // 写入
  11. redisTemplate.opsForValue().set("name", "zhangsan");
  12. // 获取
  13. Object name = redisTemplate.opsForValue().get("name");
  14. // 打印
  15. System.out.println(name);
  16. }
  17. }

redis 可以正常存取

  1. 127.0.0.1:6379> KEYS *
  2. 1) "name"
  3. 127.0.0.1:6379> GET name
  4. "\"zhangsan\""
  5. 127.0.0.1:6379>

为了在反序列化时知道对象的类型,JSON 序列化器会将类的 class 类型写入 json 结果中,存入Redis,会带来额外的内存开销。
image.png

2、方案二

为了节省内存空间,我们并不会使用 JSON 序列化器来处理 value,而是统一使用 String 序列化器,当需要存储或获取 Java 对象时,手动完成对象的序列化和反序列化。

Spring 默认提供了一个 StringRedisTemplate 类,它的 key 和 value 的序列化方式默认就是 String 方式。省去了我们自定义 RedisTemplate 的过程。

  • 使用 StringRedisTemplate;
  • 写入 Redis 时,手动把对象序列化为 JSON;
  • 读取 Redis 时,手动把读取到的 JSON 反序列化为对象;

具体流程如下图
image.png

具体代码如下

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest(classes = Application.class)
  3. public class StringRedisTemplateTest {
  4. @Autowired
  5. private StringRedisTemplate redisTemplate;
  6. private static final ObjectMapper mapper = new ObjectMapper();
  7. @Test
  8. public void testString() throws JsonProcessingException {
  9. // 创建对象
  10. User user = new User("zhangsan", 18);
  11. // 手动序列化
  12. String writeUser = mapper.writeValueAsString(user);
  13. // 写入
  14. redisTemplate.opsForValue().set("example:user:101", writeUser);
  15. // 获取
  16. String var = redisTemplate.opsForValue().get("example:user:101");
  17. // 手动反序列化
  18. User readUser = mapper.readValue(var, User.class);
  19. // 打印
  20. System.out.println(readUser);
  21. }
  22. }