几种常用的 Redis 的 java 客户端:

Jedis : 以 Redis 命令作为方法名称,学习成本低,简单实用。但是 Jedis 实例是线程不安全的,多线程环境下需要基于连接池来使用

Lettuce : Lettuce 是基于 Netty 实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持 Redis 的哨兵模式、集群模式和管道模式。

Redisson : Redisson 是一个基于 Redis 实现的分布式、可伸缩的 Java 数据结构集合。包含了诸如 Map、Queue、Lock、 Semaphore、AtomicLong 等强大功能

# Jedis

  1. 引入依赖

    1
    2
    3
    4
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    </dependency>

  2. 建立链接

    如果我们没有密码的话,我们就把第二条注释掉就可以了,否则就会报错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        private Jedis jedis;

    @BeforeEach//测试类中任何一个测试方法执行之前都先执行该注解标注的方法
    void contextLoads() {
    // 1.建立连接
    jedis = new Jedis("127.0.0.1",6379);
    // 2.设置密码
    jedis.auth("root");
    // 3.选择库 如果我们不选择的话,默认就是选择的0号库
    jedis.select(0);
    }

  3. 测试 String

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    void testString(){
    // 存入数据
    String result = jedis.set("name","asialee");
    System.out.println("result:"+result);
    // 获取数据
    String name = jedis.get("name");
    System.out.println("name:"+name);
    }

  4. 释放资源

    1
    2
    3
    4
    5
    6
    7
    @AfterEach
    void tearDown(){
    // 释放资源
    if(jedis !=null){
    jedis.close();
    }
    }

Jedis 使用的基本步骤:

  1. 引入依赖
  2. 创建 Jedis 对象,建立连接
  3. 使用 Jedis,方法名与 Redis 命令一致
  4. 释放资源

# Jedis 连接池

Jedis 本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用 Jedis 连接池代替 Jedis 的直连方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class JedisConnectionFactory {
private static final JedisPool jedisPool ;

static {
// 1.配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 1.1配置最大连接数 最多配置8个连接
poolConfig.setMaxTotal(8);
// 1.2 配置最大空闲连接 代表就算没有人访问这个池子,但我也会最多预备8个连接,这样的话一旦有人来了就可以直接使用不用临时创建
poolConfig.setMaxIdle(8);
// 1.3 最小空闲连接 代表着如果长时间没有访问这个池子,那我们的池子就会释放,一直释放到0为止
poolConfig.setMinIdle(0);
// 1.4 等待时长 代表当连接池里没有连接可以使用的时候,我们要不要等待,等待多长时间,默认是-1也就是没有时间限制一直等待到有空闲连接为止
// 我们这里配置一个最多等待1000毫秒
poolConfig.setMaxWaitMillis(1000);

// 创建连接池对象 (如果有密码的话,可以在参数列表后面加上密码 这里的1000是超时时间
jedisPool = new JedisPool(poolConfig,"127.0.0.1",6379,1000);


}

public static Jedis getJedis(){
return jedisPool.getResource();
}
}

然后我们再改一下我们的建立连接池

1
2
3
4
5
6
7
8
9
10
11
@BeforeEach//测试类中任何一个测试方法执行之前都先执行该注解标注的方法
void contextLoads() {
// 1.建立连接
// jedis = new Jedis("127.0.0.1",6379);
jedis = JedisConnectionFactory.getJedis();
// 2.设置密码
// jedis.auth("123321");
// 3.选择库 如果我们不选择的话,默认就是选择的0号库
jedis.select(0);
}

到现在的小伙伴可能在想,那我们最后执行 close 方法的时候,不还是销毁了吗?

答案:不是销毁了

我们看一下 close 的原码文件,当判断我们有连接池的时候就是进入到第一个 if,底层并不会真正的 close,而是归还 return 出去了

1
2
3
pool.returnBrokenResource(this);
或者是
pool.returnResource(this);

image-20240603212706622

# SpringDataRedis

SpringData 是 Spring 中数据操作的模块,包含对各种数据库的集成,其中对 Redis 的集成模块就叫做 SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis

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

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

API返回值类型说明
redisTemplate.opsForValue()ValueOperations操作 String 类型数据
redisTemplate.opsForHash()HashOperations操作 Hash 类型数据
redisTemplate.opsForList()ListOperations操作 List 类型数据
redisTemplate.opsForSet()SetOperations操作 Set 类型数
redisTemplate.opsForZSet()ZSetOperations操作 SortedSet 类型数据
redisTemplate通用的命令

SpringBoot 已经提供了对 SpringDataRedis 的支持,使用非常简单:

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!--redis依赖-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--common-pool连接池依赖-->
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    </dependency>

  2. 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    spring:
    redis:
    host: 192.168.150.101
    port: 6379
    password: 123321
    lettuce:
    pool:
    max-active: 8 # 最大连接
    max-idle: 8 # 最大空闲连接
    min-idle: 0 # 最小空闲连接
    max-wait: 100ms # 连接等待时间

  3. 注入 RedisTemplate

    1
    2
    @Autowired
    private RedisTemplate redisTemplate;

  4. 编写测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @SpringBootTest
    public class RedisTest {
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void testString(){
    redisTemplate.opsForValue().set("name","asialee");
    Object name = redisTemplate.opsForValue().get("name");
    System.out.println("name = " + name);
    }
    }

SpringDataRedis 的使用步骤:

  1. 引入 spring-boot-starter-data-redis 依赖
  2. 在 application.yml 配置 Redis 信息
  3. 注入 RedisTemplate

# SpringDataRedis 的序列化方式

RedisTemplate 可以接收任意 Object 作为值写入 Redis,只不过写入前会把 Object 序列化为字节形式,默认是采用 JDK 序列化,得到的结果是这样的:

image-20240603214340200

缺点:

  • 可读性差
  • 内存占用较大

我们可以自定义 RedisTemplate 的序列化方式,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
// 创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(connectionFactory);
// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 设置Key和HashKey的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置Value和HashValue的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
// 返回
return template;
}
}

# StringRedisTemplate

尽管 JSON 的序列化方式可以满足我们的需求,但依然存在一些问题,如图:

image-20240603214842199

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

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

image-20240603215005814

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Autowired
private StringRedisTemplate stringRedisTemplate;
//json工具
private static final ObjectMapper mapper = new ObjectMapper();

@Test
void testSaveUser() throws JsonProcessingException {
// 创建对象
User user = new User("虎哥", 21);
// 手动序列化
String json = mapper.writeValueAsString(user);
// 写入数据
stringRedisTemplate.opsForValue().set("user:200", json);

// 获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
// 手动反序列化
User user1 = mapper.readValue(jsonUser, User.class);
System.out.println("user1 = " + user1);
}

@Test
//hash test
void testHash() {
stringRedisTemplate.opsForHash().put("user:400", "name", "虎哥");
stringRedisTemplate.opsForHash().put("user:400", "age", "21");

Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:400");
System.out.println("entries = " + entries);
}

RedisTemplate 的两种序列化实践方案:

方案一:

  1. 自定义 RedisTemplate
  2. 修改 RedisTemplate 的序列化器为 GenericJackson2JsonRedisSerializer

方案二:

  1. 使用 StringRedisTemplate
  2. 写入 Redis 时,手动把对象序列化为 JSON
  3. 读取 Redis 时,手动把读取到的 JSON 反序列化为对象