目录
一.什么是Redisson
Redisson 是一个用于 Redis 的 Java 客户端,它不仅提供了对 Redis 命令的访问,还提供了一套丰富且易于使用的分布式对象和服务。通过 Redisson,开发者可以轻松地在 Java 应用程序中使用 Redis 作为缓存、消息中间件或数据存储解决方案。
查看官方网站查看具体介绍 : Redisson官方介绍文档
主要特点
- 分布式对象:Redisson 提供了多种常用的 Java 对象的分布式版本,如 Map、Set、List、Queue 等等,这些对象可以在多个 JVM 实例间共享,从而支持高可用性和可扩展性。
- 分布式集合:除了基本的数据结构外,Redisson 还支持更高级的数据结构,例如 BitSets, Blooms Filters, HyperLogLogs 等,这些都是特别设计来处理大数据量和高并发场景的。
- 分布式锁:Redisson 提供了多种类型的分布式锁(如 RedLock、公平锁、联锁等),这些锁可以用来协调不同应用程序实例之间的操作,确保线程安全。
- 发布/订阅模式:支持消息的发布与订阅功能,允许不同的服务之间通过频道进行通信。
- 服务发现:Redisson 可以作为服务发现机制的一部分,帮助应用程序动态查找和连接其他服务。
- 性能优化:通过使用 Netty 框架,Redisson 能够实现高性能的异步非阻塞 I/O 操作,同时支持连接池管理和自动重连机制。
- 配置灵活:支持单机模式、主从模式、哨兵模式以及集群模式等多种部署方式,可以根据实际需求选择最适合的配置方案。
- 易于集成:Redisson 提供了 Spring、Spring Boot 等框架的支持,使得将其集成到现有项目中变得非常简单。
总之,Redisson 是一个功能强大且灵活的 Redis Java 客户端,适用于需要高性能、高可用性和易于使用的分布式应用开发。
二.为什么要使用Redisson
- 简化开发:Redisson 封装了大量的复杂逻辑,如分布式锁、分布式集合等,使得开发者可以更加专注于业务逻辑的实现,而无需深入研究底层实现细节。这大大降低了开发难度和维护成本。
- 提高效率:通过提供丰富的分布式数据结构和工具,Redisson 让开发者能够快速构建高效的应用程序。例如,使用 Redisson 的 RMap 可以很容易地创建一个分布式的哈希表,这对于需要跨多个节点共享状态的应用来说非常有用。
- 增强功能:Redisson 不仅实现了 Redis 原生命令的支持,还额外提供了许多高级特性,比如分布式锁、分布式计数器、分布式集合等,这些都是标准 Redis 客户端所不具备的功能。
- 提高可靠性:Redisson 支持多种 Redis 部署模式(单机、主从、哨兵、集群等),并且具有自动故障转移和重连机制,这有助于提高系统的稳定性和可靠性。此外,它的连接池管理功能也有助于优化资源利用,减少网络延迟。
- 易于集成:Redisson 与主流的 Java 框架(如 Spring、Spring Boot)有良好的兼容性,可以通过简单的配置就能完成集成,方便快速上手。
- 社区支持:作为一个成熟的开源项目,Redisson 拥有一个活跃的社区,这意味着用户可以获得及时的帮助和支持,同时也意味着该库会持续得到更新和完善。
- 性能优势:基于 Netty 框架构建,Redisson 在处理大量并发请求时表现出色,能够有效地提升应用的整体性能。
综上所述,如果你正在寻找一种强大、灵活且易于使用的 Redis Java 客户端,Redisson 是一个非常好的选择。无论是为了简化开发流程、提高应用性能还是增强系统稳定性,Redisson 都能为你提供强有力的支持。
三.Spring集成Redisson
1.添加依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.4</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.4</version>
</dependency>
2.添加配置信息
spring:
redis:
#这里是自己redis的ip地址
host: 192.168.169.129
#redis的端口号 默认6379
port: 6379
3.添加redisson配置类
/**
* Redisson 配置类,用于创建和配置 Redisson 客户端。
*/
@Configuration
public class RedissonConfig {
/**
* 创建并配置 Redisson 客户端。
*
* @return 配置好的 Redisson 客户端实例
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
// 创建 Redisson 配置对象
Config config = new Config();
// 配置单节点 Redis 服务器地址
// 这里使用的是单节点模式,地址为 "redis://192.168.169.129:6379" 使用自己的redis配置即可
config.useSingleServer().setAddress("redis://192.168.169.129:6379");
// 创建 Jackson ObjectMapper 实例
// ObjectMapper 是 Jackson 库提供的用于 JSON 序列化和反序列化的工具
ObjectMapper objectMapper = new ObjectMapper();
// 设置 Redisson 的编解码器为 JsonJacksonCodec
// JsonJacksonCodec 使用 Jackson 的 ObjectMapper 进行对象的序列化和反序列化
config.setCodec(new JsonJacksonCodec(objectMapper));
// 创建并返回 Redisson 客户端实例
return Redisson.create(config);
}
}
注解详解:
1.使用 @Bean 注解将该方法返回的对象注册为 Spring 容器中的 Bean。
2.使用 destroyMethod = "shutdown" 确保在 Spring 容器关闭时调用 shutdown() 方法,释放所有相关资源。
四.Redisson存取各种类型数据
在业务类注入Redisson核心接口 继续完成以下指定操作
@Autowired
private RedissonClient redisson;
1.字符串(String类型)
存储
public void setStringRedisson() {
// 获取一个名为 "stringRedisson" 的 RBucket 对象
// RBucket 是 Redisson 提供的一个接口,用于操作 Redis 中的键值对
RBucket<String> bucket = redisson.getBucket("stringRedisson");
// 设置键 "stringRedisson" 的值为 "Hello! Story"
bucket.set("Hello! Story");
}
获取
public void getStringRedisson() {
// 获取一个名为 "stringRedisson" 的 RBucket 对象
// RBucket 是 Redisson 提供的一个接口,用于操作 Redis 中的键值对
RBucket<String> bucket = redisson.getBucket("stringRedisson");
// 获取键 "stringRedisson" 的值
String stringRedisson = bucket.get();
// 打印获取到的值
System.out.println(stringRedisson);
}
2.object对象类型
确保项目中包含以下依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83_noneautotype</version>
</dependency>
1.实体类信息
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
/**
* id
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
2.存储
public void setObjectJsonRedisson() {
// 创建一个User对象
User user = new User(1, "张三", "123456");
// 获取Redis中的Bucket对象,指定键名为"ObjectRedisson"
RBucket<String> bucket = redisson.getBucket("ObjectJsonRedisson");
// 将User对象转换为JSON字符串,并设置到Bucket中
// 注意这里使用了JSON.toJSONString方法来完成对象到字符串的转换
bucket.set(JSON.toJSONString(user));
}
3.获取
/**
* 从Redis中获取并反序列化User对象
* 使用了阿里巴巴的Fastjson将JSON字符串转换为User对象
* 通过Redisson的Bucket接口从Redis中获取JSON字符串
* @return 反序列化后的User对象
*/
public User getObjectJsonRedisson() {
// 获取Redis中的Bucket对象,指定键名为"ObjectRedisson"
RBucket<String> bucket = redisson.getBucket("ObjectJsonRedisson");
// 从Bucket中获取JSON字符串
String objectJsonString = bucket.get();
// 将JSON字符串转换为User对象
// 注意这里使用了JSON.parseObject方法来完成字符串到对象的转换
User user = JSON.parseObject(objectJsonString, User.class);
// 返回反序列化后的User对象
return user;
}
3.List集合类型
注: 存取List集合类型包括两种方法
第一种是需要对象实体类实现序列化(Serializable)
第二种是不需要对象实体类实现序列化(Serializable)
为什么要实现序列化以及实现序列化会出现的问题,在文章最后有详细讲解
第一种: 需要对象实体类实现序列化
确保项目中拥有以下依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
1.实体类信息
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
/**
* id
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
2.存储
/**
* 将User对象列表存入Redis中
* 在每次调用时,先清空列表,再添加新的用户信息
*/
public void setUserListRedisson() {
// 创建两个User对象
User user1 = new User(1, "张三", "12345");
User user2 = new User(2, "李四", "12345");
// 创建一个User对象列表
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
// 获取Redis中的RList对象,指定键名为"userListRedisson"
RList<User> list = redisson.getList("userListRedisson");
// 清空列表
list.clear();
// 将User对象列表添加到RList中
list.addAll(users);
}
注: 使用list.clear() 方法会先清空列表,再实现添加操作,相当于会覆盖掉之前的数据,而不使用这个方法它会在原来的基础上添加信息,根据自己需求添加即可
3.获取
/**
* 从Redis中获取User对象列表
* 直接从Redis中的RList读取所有User对象并返回
* @return 反序列化后的User对象列表
*/
public List<User> getUserListRedisson() {
// 获取Redis中的RList对象,指定键名为"userListRedisson"
RList<User> list = redisson.getList("userListRedisson");
// 从RList中读取所有User对象
List<User> users = list.readAll();
// 返回读取到的User对象列表
return users;
}
第二种: 不需要对象实体类实现序列化
确保项目中包含以下依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<!-- Jackson库用于JSON处理,包括对象与JSON之间的序列化和反序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version> <!-- 请检查是否有更新的版本 -->
</dependency>
<!-- Apache Commons Lang库提供了许多常用的工具方法,简化了字符串处理、日期操作等任务 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
1.实体类信息
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
/**
* id
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
}
2.存储
@Autowired
private ObjectMapper objectMapper;
/**
* 将User对象列表序列化后存入Redis中
* 在每次调用时,先清空列表,再添加新的用户信息
*/
public void setUserListRedisson() {
// 创建两个User对象
User user1 = new User(1, "张三", "12345");
User user2 = new User(2, "李四", "12345");
// 创建一个User对象列表
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
// 获取Redis中的RList对象,指定键名为"userListRedisson"
RList<String> list = redisson.getList("userListRedisson");
// 清空列表
list.clear();
// 将User对象序列化并添加到列表中
for (User user : users) {
try {
String userJson = objectMapper.writeValueAsString(user);
list.add(userJson);
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to serialize User object to JSON", e);
}
}
}
注: 使用list.clear() 方法会先清空列表,再实现添加操作,相当于会覆盖掉之前的数据,而不使用这个方法它会在原来的基础上添加信息,根据自己需求添加即可
3.获取
/**
* 从Redis中获取并反序列化User对象列表
* 使用了Apache Commons Lang库进行字符串处理,以及Jackson库进行JSON解析
* @return 反序列化后的User对象列表
*/
public List<User> getUserListRedisson() {
// 创建一个User对象列表,用于存储反序列化后的User对象
List<User> users = new ArrayList<>();
// 获取Redis中的RList对象,指定键名为"userListRedisson"
RList<String> userList = redisson.getList("userListRedisson");
// 从RList中读取所有JSON字符串
List<String> userJsons = userList.readAll();
// 遍历每个JSON字符串,进行反序列化并添加到用户列表中
for (String userJson : userJsons) {
try {
// 使用Apache Commons Lang库的StringUtils.normalizeSpace方法去除字符串中的多余空格
String cleanedUserJson = StringUtils.normalizeSpace(userJson);
// 使用Jackson的ObjectMapper将JSON字符串转换为User对象
User user = objectMapper.readValue(cleanedUserJson, User.class);
// 将反序列化后的User对象添加到列表中
users.add(user);
} catch (JsonProcessingException e) {
// 捕获JSON处理异常,并将其包装为运行时异常抛出
throw new RuntimeException("Failed to parse JSON string to User object", e);
}
}
// 返回反序列化后的User对象列表
return users;
}
五.为什么要实现序列化
实现序列化(Serialization)是编程中一个非常重要的概念,尤其是在分布式系统、持久化存储和网络通信中。序列化的主要目的是将对象的状态转换为一种可以存储或传输的格式。以下是实现序列化的一些主要原因:
1.持久化存储
保存对象状态:将对象的状态保存到磁盘或其他持久化存储介质中,以便在程序重启后可以恢复这些对象的状态。
备份和恢复:在系统出现故障时,可以通过序列化和反序列化来备份和恢复数据。
历史记录:保存对象的历史状态,以便进行审计或回溯。
2. 网络传输
跨进程通信:在分布式系统中,不同进程或机器之间的通信需要将对象转换为字节流,以便通过网络传输。
远程方法调用(RMI):在 Java 中,RMI 依赖于序列化来传递对象参数和返回值。
微服务架构:在微服务架构中,服务之间通过网络进行通信,需要将对象序列化为字节流进行传输。
3. 缓存
对象缓存:将对象序列化后存储在缓存中,可以提高系统的性能和响应速度。
分布式缓存:在分布式缓存系统中,对象需要被序列化以便在不同的节点之间共享。
4. 数据交换
文件传输:将对象序列化为文件,可以在不同的系统之间进行数据交换。
消息队列:在消息队列中,消息体通常是序列化后的对象,以便在生产者和消费者之间传递。
5. 安全性和加密
数据保护:在传输或存储敏感数据时,可以先将对象序列化,然后对字节流进行加密,以保护数据的安全性。
签名和验证:序列化后的数据可以进行数字签名,确保数据的完整性和来源的可靠性。
6. 版本控制
兼容性:通过序列化和反序列化,可以确保不同版本的软件之间能够正确地交换数据。
迁移:在系统升级或迁移过程中,可以通过序列化和反序列化来确保数据的一致性和完整性。
7. 性能优化
减少内存占用:将对象序列化后存储在磁盘上,可以减少内存占用,特别是在内存资源有限的情况下。
批量处理:将多个对象序列化为一个字节流,可以减少网络传输的次数,提高性能。
8. 调试和测试
日志记录:将对象序列化后记录到日志文件中,便于调试和分析。
单元测试:在单元测试中,可以将对象序列化后保存,以便在不同的测试环境中复现问题。
六.实现序列化会导致哪些问题
实现序列化虽然带来了许多好处,但也可能导致一些问题。这些问题包括但不限于性能问题、安全问题、版本兼容性问题和内存管理问题。以下是一些常见的问题及其详细解释:
1.性能问题
序列化和反序列化的开销:序列化和反序列化过程会消耗 CPU 和内存资源,特别是对于复杂的对象图或大量数据,性能开销可能较大。
网络传输延迟:序列化后的数据量可能较大,导致网络传输延迟增加,特别是在高并发场景下。
2. 安全问题
敏感信息泄露:序列化数据可能包含敏感信息,如果这些数据被截获,可能会导致安全问题。
反序列化攻击:恶意用户可以构造特定的序列化数据,导致反序列化时执行任意代码或触发其他安全漏洞。这被称为反序列化攻击(Deserialization Attack)。
3. 版本兼容性问题
字段变更:添加、删除或修改类的字段可能会影响序列化和反序列化的过程,导致数据不一致或异常。
类结构变化:类的结构发生变化(如继承关系、方法签名等)可能影响序列化的兼容性。
serialVersionUID 管理:如果没有正确管理 serialVersionUID,可能会导致不同版本的类无法正确反序列化。
4. 内存管理问题
内存泄漏:如果序列化对象引用了大量其他对象,这些对象可能会被保留,导致内存泄漏。
临时对象:序列化和反序列化过程中可能会创建大量的临时对象,增加垃圾回收的压力。
5. 数据一致性问题
部分序列化:如果序列化过程中发生错误,可能会导致部分数据被序列化,而另一部分没有,从而导致数据不一致。
并发访问:在多线程环境下,如果多个线程同时对同一个对象进行序列化和反序列化操作,可能会导致数据不一致或竞态条件。
6. 可读性和调试困难
二进制格式:默认的序列化格式(如 Java 的二进制格式)难以阅读和调试,不利于手动检查和修改。
复杂对象图:对于复杂的对象图,序列化和反序列化的过程可能变得非常复杂,难以跟踪和调试。
7. 依赖管理问题
外部依赖:使用第三方库进行序列化(如 Jackson、Gson)时,需要确保这些依赖的版本是最新的,以避免潜在的安全漏洞和性能问题。
总结:实现序列化虽然会方便代码实现,但会造成一些性能及安全方面的问题,建议大家尽量不要序列化