一、常见客户端
在 Redis 官网中提供了各种语言的客户端。
Java 常用的 Redis 客户端由如下几种
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 封装到了不同的类型中。
三、SpringDataRedis 入门
SpringBoot 已经提供了对 SpringDataRedis 的支持
1、引入依赖
引入 redis 和 连接池的依赖
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>Redis</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.9.RELEASE</version></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 连接池依赖:不管是 jedis 还是 lettuce 底层都是基于 commons-pool 实现的连接池 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><!-- jackson 依赖:redis 的 key 和 value 序列化的时候会用到,如果引入了 spring-boot-starter-web 模块则不需要单独引入此模块 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies></project>
2、配置文件
添加 redis 的配置信息
spring:redis:host: localhostport: 6379password: 123321lettuce:pool:# 最大连接数max-active: 8# 最大空闲连接数max-idle: 8# 最小空闲连接数min-idle: 0# 连接多带多久超时max-wait: 1000
3、单元测试
添加测试类进行测试
@RunWith(SpringRunner.class)@SpringBootTest(classes = Application.class)public class RedisTemplateTest {@Autowiredprivate RedisTemplate redisTemplate;@Testpublic void testString() {// 写入redisTemplate.opsForValue().set("name", "zhangsan");// 获取Object name = redisTemplate.opsForValue().get("name");// 打印System.out.println(name);}}
四、最佳实践
默认情况下,在使用 RedisTemplate 将 key 和 value 写入 Redis 时,会将 key 和 value 序列化(默认是采用 JDK 序列化)为字节形式后再存入 Redis 中。
例如上述案例的代码执行后,在 Redis 中的效果如下
127.0.0.1:6379> KEYS *1) "\xac\xed\x00\x05t\x00\x04name"127.0.0.1:6379> GET "\xac\xed\x00\x05t\x00\x04name""\xac\xed\x00\x05t\x00\bzhangsan"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; }
}
添加测试类进行测试```java@RunWith(SpringRunner.class)@SpringBootTest(classes = Application.class)public class JsonRedisTemplateTest {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Testpublic void testString() {// 写入redisTemplate.opsForValue().set("name", "zhangsan");// 获取Object name = redisTemplate.opsForValue().get("name");// 打印System.out.println(name);}}
redis 可以正常存取
127.0.0.1:6379> KEYS *1) "name"127.0.0.1:6379> GET name"\"zhangsan\""127.0.0.1:6379>
为了在反序列化时知道对象的类型,JSON 序列化器会将类的 class 类型写入 json 结果中,存入Redis,会带来额外的内存开销。
2、方案二
为了节省内存空间,我们并不会使用 JSON 序列化器来处理 value,而是统一使用 String 序列化器,当需要存储或获取 Java 对象时,手动完成对象的序列化和反序列化。
Spring 默认提供了一个 StringRedisTemplate 类,它的 key 和 value 的序列化方式默认就是 String 方式。省去了我们自定义 RedisTemplate 的过程。
- 使用 StringRedisTemplate;
- 写入 Redis 时,手动把对象序列化为 JSON;
- 读取 Redis 时,手动把读取到的 JSON 反序列化为对象;
具体流程如下图
具体代码如下
@RunWith(SpringRunner.class)@SpringBootTest(classes = Application.class)public class StringRedisTemplateTest {@Autowiredprivate StringRedisTemplate redisTemplate;private static final ObjectMapper mapper = new ObjectMapper();@Testpublic void testString() throws JsonProcessingException {// 创建对象User user = new User("zhangsan", 18);// 手动序列化String writeUser = mapper.writeValueAsString(user);// 写入redisTemplate.opsForValue().set("example:user:101", writeUser);// 获取String var = redisTemplate.opsForValue().get("example:user:101");// 手动反序列化User readUser = mapper.readValue(var, User.class);// 打印System.out.println(readUser);}}
