Redis--Lesson05--Redis进阶
一.Redis中的事务
在Redis中,单条命令依旧保持原子性,但是对于事务来说(命令集)不保证原子性
Redis事务的本质:一组命令的集合,一个事务中所有的命令都会被序列化,在事务的执行过程中,会按照顺序执行,一次性,顺序性,排他性!执行一些命令
如:--- 队列 set1,set2,set3 执行--
Redis事务没有隔离级别的概念,在创建Redis事务中,命令不会被直接执行,而是由exec命令发出后开始队列化执行
Redis执行事务的过程:
- 开启事务(multi)
- 命令入队(......)
- 执行事务(exec)
放弃事务:DISCARD
执行事务的两种错误情况
1.编译型异常(代码有问题,命令有错)
这种错误会导致整个事务都不会被执行
2.运行时异常(代码逻辑异常)
如果事务队列存在语法性的错误,那么执行命令的时候,其它命令都是可以正常执行的,但是语法性错误的指令肯定执行不了,丢失原子性
二.Redis中的乐观锁(监控)
悲观锁(Pessimistic Locking)
- 假设最坏的情况会发生,在读取数据时就加锁,防止其他事务修改该数据。
- 在整个事务过程中保持锁定状态,直到事务提交或回滚。
乐观锁(Optimistic Locking)
- 假设在大多数情况下不会发生冲突,因此在读取数据时不加锁。
- 在更新数据时才检查数据是否被其他事务修改过。如果没有被修改,则更新成功;如果已经被修改,则放弃本次更新或重新尝试。
watch:监视对象
当在被监视的情况下的对象发生变化,那么事务会执行失败,需要放弃原监视后,再次监视;如果事务执行成功,则自动放弃监视
执行失败的情况:
在监视的情况下,另外的一个线程率先更改值,那么被监视的事务会执行失败
另一线程率先更改
返回本线程执行事务:
本线程执行失败
三.Jedis
使用Java操作redis是官方推荐使用的一款API,Jedis是一款操作Redis的中间件,也是操作redis的基础,虽然在springBoot中已经被其它的中间件取代,但是Jedis的操作上手程度很适合新手
导入依赖:
<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>5.2.0</version> </dependency>
直接新建一个Jedis测试类,创建一个jedis对象,并且传递参数
//创建jedis对象 Jedis jedis = new Jedis("127.0.0.1",6379); //测试连接 System.out.println(jedis.ping());
String类型测试
//测试String类型 jedis.set("name","maming"); System.out.println(jedis.get("name")); //批量创建k-v jedis.mset("k1","v1","k2","v2","k3","v3"); //批量获得值 System.out.println(jedis.mget("k1", "k2", "k3"));
测试list集合
//清空数据库 jedis.flushDB(); //向list中添加元素 jedis.lpush("list","a","b","c"); //获取list中的所有元素 System.out.println(jedis.lrange("list",0,-1)); //从list中弹出最后一个元素 System.out.println(jedis.rpop("list")); //获取list中的所有元素 System.out.println(jedis.lrange("list",0,-1));
测试set集合
//清空数据库 jedis.flushDB(); //向集合中添加元素 jedis.sadd("myset","a","b","c"); //打印集合中的元素 System.out.println(jedis.smembers("myset"));
其它的命令都是和在redis上一样的,都不用测试了
Jedis事务
在Jedis中的事务和Redis中过程都是一样的,并且性质也是一样,只不过使用Java语言开启和执行事务罢了
//清空数据库 jedis.flushDB(); // 创建一个JSONObject对象 JSONObject jsonObject = new JSONObject(); // 向JSONObject对象中添加键值对 jsonObject.put("name","zhangsan"); jsonObject.put("age","18"); // 将JSONObject对象转换为字符串 String re = jsonObject.toString(); // 创建一个Transaction对象 Transaction multi = jedis.multi(); try { // 在Transaction对象中执行set操作 multi.set("user1",re); multi.set("user2",re); // 执行Transaction对象中的操作 multi.exec(); }catch (Exception e){ // 打印异常信息 e.printStackTrace(); // 放弃Transaction对象中的操作 multi.discard(); }finally { // 打印user1的值 System.out.println(jedis.get("user1")); // 打印user2的值 System.out.println(jedis.get("user2")); // 关闭jedis连接 jedis.close(); }
四.SpringBoot整合Redis
如果SpringBoot需要操作数据类型必须要使用另一个家族成员SpringData
在SpringBoot2.0以后的版本Jedis都被改为lettuce
Jedis:采用的是直连,多个线程操作是不安全的,想要避免的话就是用jedis线程池
lettuce:次啊用netty,实例可以在多个线程中共享,不存在线程不安全的情况
安装依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置Redi数据源:
spring.data.redis.host=127.0.0.1 spring.data.redis.port=6379 spring.data.redis.database=0
测试类:
@Autowired private RedisTemplate redisTemplate; @Test // 测试上下文加载 void contextLoads() { // 从Redis中获取键为"name"的值并打印 System.out.println(redisTemplate.opsForValue().get("name")); // 将字符串"a"添加到Redis中键为"list"的列表的左边 redisTemplate.opsForList().leftPush("list","a"); // 从Redis中键为"list"的列表的左边弹出元素并打印 System.out.println(redisTemplate.opsForList().leftPop("list")); }
测试结果
springBoot整合后的主要操作数据方法:
opsForValue): 用于处理简单的字符串键值对。
opsForList): 用于处理列表数据结构。
opsForSet): 用于处理无序集合数据结构。
opsForZSet): 用于处理有序集合数据结构。
opsForHash): 用于处理哈希数据结构。
Redis自动配置类:
Redis的自动配置属性(默认写入的地址和端口):
五.自定义RedisTemplate模板
在使用系统默认的模板使用的jdk转义,将传递的值进行转义后存储到redis中
测试一个使用json传递方式:
void testSer() throws JsonProcessingException { //真实的开发都使用json来传递对象 User user = new User("maming",18); String s = new ObjectMapper().writeValueAsString(user); redisTemplate.opsForValue().set("user",s); System.out.println(redisTemplate.opsForValue().get("user")); }
Redis拿取到的结果:
同时我们需要知道的是,在springBoot操作redis的时候,传递对象有两种方式,一种是传递json字符串,还有一种方式就是将对象序列化,如果对象没有序列化是传输不了的
例如我们直接传递一个对象:
void testSer() throws JsonProcessingException { //真实的开发都使用json来传递对象 redisTemplate.opsForValue().set("user",new User("maming",18)); System.out.println(redisTemplate.opsForValue().get("user")); }
报错:
org.springframework.data.redis.serializer.SerializationException: Cannot serialize
标志传递的值没有序列化,需要序列化只需要将我们的类实现一个接口Serializable就可以了
编写一个适用于自己项目的RedisTemplate模板:
@Configuration public class RedisConfig { @Bean @SuppressWarnings("all") // 抑制所有警告 public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { // 创建RedisTemplate实例,用于操作Redis RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); // 设置Redis连接工厂,以便模板能够连接到Redis服务器 redisTemplate.setConnectionFactory(redisConnectionFactory); // 创建Jackson2JsonRedisSerializer实例,用于将Java对象序列化为JSON格式存储在Redis中 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); // 创建ObjectMapper实例,用于处理JSON数据 ObjectMapper mapper = new ObjectMapper(); // 设置ObjectMapper的可见性策略,使得所有属性都能被序列化和反序列化 mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 启用默认的类型信息,以便在反序列化时能够确定确切的类类型 mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); // 将配置好的ObjectMapper设置到Jackson2JsonRedisSerializer中 jackson2JsonRedisSerializer.setObjectMapper(mapper); // 创建StringRedisSerializer实例,用于将字符串格式的key序列化到Redis中 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // 设置RedisTemplate的key序列化器为StringRedisSerializer redisTemplate.setKeySerializer(stringRedisSerializer); // 设置RedisTemplate的hash key序列化器为StringRedisSerializer redisTemplate.setHashKeySerializer(stringRedisSerializer); // 设置RedisTemplate的value序列化器为Jackson2JsonRedisSerializer redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // 设置RedisTemplate的hash value序列化器为Jackson2JsonRedisSerializer redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); // 初始化RedisTemplate,使配置生效 redisTemplate.afterPropertiesSet(); // 返回配置好的RedisTemplate实例 return redisTemplate; } }
这是模板直接复制粘贴就好了;使用新模板之后再次查看插入的数据:
简单编写utils类,用于封装对redis命令的操作
@Component public class RedisUtil { @Autowired private RedisTemplate redisTemplate; //向键值对中添加数据 public void setString(String key, String value) { redisTemplate.opsForValue().set(key, value); } //拿取键值对中的数据 public String getString(String key) { return (String)redisTemplate.opsForValue().get(key); } //刷新数据库 public void flushDB(){ try { RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); connection.flushDb(); connection.close(); }catch (Exception e){ e.printStackTrace(); } } }
简单测试封装类:
@Test void testRedisUtil() throws JsonProcessingException { // 清空Redis数据库中的所有数据 redisUtil.flushDB(); // 在Redis中设置一个字符串键值对,键为"user",值为"maming" redisUtil.setString("user", "maming"); // 从Redis中获取键为"user"的字符串值,并打印到控制台 System.out.println(redisUtil.getString("user")); }
输出:
maming
六.简单查看Redis的配置文件
1.单位篇:描述了redis支持的数据单位,并且告知我们redis单位是不区分大小写的
2.redis可以导入其它文件,引入到本文件之后一起运行
3.网络配置:默认绑定的是环回地址127.0.0.1
4.端口篇:预载了启动redis服务的默认端口
5.是否守护进程运行,默认否
6.默认的数据库个数16个,是否显示log
7.快照:如果900s内有一个key改动持久化,如果300s内有10个key改动持久化,还有60s内改动10000次
8.rdb持久化配置
9.安全验证:redis可以设置密码
127.0.0.1:6379> config get requirepass 1) "requirepass" 2) "" 127.0.0.1:6379> config set requirepass "123456" OK 127.0.0.1:6379> config get requirepass (error) NOAUTH Authentication required. 127.0.0.1:6379> set k1 v1 (error) NOAUTH Authentication required. 127.0.0.1:6379> auth 123456 OK 127.0.0.1:6379> config get requirepass 1) "requirepass" 2) "123456" 127.0.0.1:6379>
10.aof持久化,默认不开启使用的是rdb