Spring Boot2.0之 整合Redis事务
Redis事物
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
一个事务从开始到执行会经历以下三个阶段:
开始事务。
命令入队。
执行事务。
事务的开启很简单的:
可以使用AOP封装下~
跟之前使用MySQL数据库没啥子区别的
看下目录
pom:
<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>redis</groupId>
<artifactId>com.toov5.redis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
<!-- SpringBoot web 核心组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<maimClass>
com.itmayiedu.controller.IndexController</maimClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
service:
package com.toov5.service; import java.util.Set; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; @Component public class RedisService { @Autowired private StringRedisTemplate stringRedisTemplate; //这样该方法支持多种数据类型 public void set(String key , Object object, Long time){ //开启事务权限 stringRedisTemplate.setEnableTransactionSupport(true); try { //开启事务 stringRedisTemplate.multi(); String argString =(String)object; //强转下 stringRedisTemplate.opsForValue().set(key, argString); //成功就提交 stringRedisTemplate.exec(); } catch (Exception e) { //失败了就回滚 stringRedisTemplate.discard(); } if (object instanceof String ) { //判断下是String类型不 String argString =(String)object; //强转下 //存放String类型的 stringRedisTemplate.opsForValue().set(key, argString); } //如果存放Set类型 if (object instanceof Set) { Set<String> valueSet =(Set<String>)object; for(String string:valueSet){ stringRedisTemplate.opsForSet().add(key, string); //此处点击下源码看下 第二个参数可以放好多 } } //设置有效期 if (time != null) { stringRedisTemplate.expire(key, time, TimeUnit.SECONDS); } } //做个封装 public void setString(String key, Object object){ String argString =(String)object; //强转下 //存放String类型的 stringRedisTemplate.opsForValue().set(key, argString); } public void setSet(String key, Object object){ Set<String> valueSet =(Set<String>)object; for(String string:valueSet){ stringRedisTemplate.opsForSet().add(key, string); //此处点击下源码看下 第二个参数可以放好多 } } public String getString(String key){ return stringRedisTemplate.opsForValue().get(key); } }
package com.toov5.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.toov5.service.RedisService; @RestController public class IndexController { @Autowired private RedisService redisService; @RequestMapping("/setString") public String setString(String key, String value){ redisService.set(key, value, 5000l); //超时时间5000s l表示long型 return "成功"; } @RequestMapping("get") public String get(String key){ return redisService.getString(key); } }
package com.toov5.app; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication(scanBasePackages={"com.toov5.*"}) public class app { public static void main(String[] args) { SpringApplication.run(app.class, args); } }
yml: 注意一定要是Master啊 Slave不支持写哦
spring:
redis:
database: 0
host: 192.168.91.3
port: 6379
password: 123
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000
运行结果:
Redis
补充:
对于redis来说,只满足其中的:
一致性和隔离性两个特性,其他特性是不支持的。
关于redis对ACID四个特性暂时先说这么多,在本文后面会详细说明。在详述之前,我们先来了解redis事务具体的使用和实现,这样我们接下来讨论ACID时才能更好的理解。
redis事务的使用
redis事务主要包括MULTI、EXEC、DISCARD和WATCH命令
MULTI命令
MULTI命令用来开启一个事务,当MULTI执行之后,客户端可以继续向服务器发送多条命令,这些命令会缓存在队列里面,只有当执行EXEC命令时,这些命令才会执行。
而DISCARD命令可以清空事务队列,放弃执行事务。
一个使用MULTI和EXEC执行事务的例子:
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
redis事务产生错误
使用redis事务可能会产生错误,主要分为两大类:
- 事务在执行EXEC之前,入队的命令可能出错。
- 命令可能在 EXEC 调用之后失败
redis在2.6.5之后,如果发现事务在执行EXEC之前出现错误,那么会放弃这个事务。
在EXEC命令之后产生的错误,会被忽略,其他正确的命令会被继续执行。
redis事务不支持回滚
如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。
以下是这种做法的优点:
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。 有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。
鉴于没有任何机制能避免程序员自己造成的错误, 并且这类错误通常不会在生产环境中出现, 所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。
关于WATCH命令
WATCH命令可以添加监控的键,如果这些监控的键没有被其他客户端修改,那么事务可以顺利执行,如果被修改了,那么事务就不能执行。
redis事务的ACID性质讨论
A原子性
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的
如果一个事务队列中的所有命令都被成功地执行,那么称这个事务执行成功
另一方面,如果 Redis 服务器进程在执行事务的过程中被停止 —— 比如接到 KILL 信号、宿主机器停机,等等,那么事务执行失败
事务失败时,Redis 也不会进行任何的重试或者回滚动作,不满足要么全部全部执行,要么都不执行的条件
C一致性
一致性分下面几种情况来讨论:
首先,如果一个事务的指令全部被执行,那么数据库的状态是满足数据库完整性约束的
其次,如果一个事务中有的指令有错误,那么数据库的状态是满足数据完整性约束的
最后,如果事务运行到某条指令时,进程被kill掉了,那么要分下面几种情况讨论:
- 如果当前redis采用的是内存模式,那么重启之后redis数据库是空的,那么满足一致性条件
-
如果当前采用RDB模式存储的,在执行事务时,Redis 不会中断事务去执行保存 RDB 的工作,只有在事务执行之后,保存 RDB 的工作才有可能开始。所以当 RDB 模式下的 Redis 服务器进程在事 务中途被杀死时,事务内执行的命令,不管成功了多少,都不会被保存到 RDB 文件里。 恢复数据库需要使用现有的 RDB 文件,而这个 RDB 文件的数据保存的是最近一次的数 据库快照(snapshot),所以它的数据可能不是最新的,但只要 RDB 文件本身没有因为 其他问题而出错,那么还原后的数据库就是一致的
-
如果当前采用的是AOF存储的,那么可能事务的内容还未写入到AOF文件,那么此时肯定是满足一致性的,如果事务的内容有部分写入到AOF文件中,那么需要用工具把AOF中事务执行部分成功的指令移除,这时,移除之后的AOF文件也是满足一致性的
所以,redis事务满足一致性约束
I隔离性
Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。
D持久性
因为事务不过是用队列包裹起了一组 Redis 命令,并没有提供任何额外的持久性功能,所以事务的持久性由 Redis 所使用的持久化模式决定
- 在单纯的内存模式下,事务肯定是不持久的
- 在 RDB 模式下,服务器可能在事务执行之后、RDB 文件更新之前的这段时间失败,所以 RDB 模式下的 Redis 事务也是不持久的
- 在 AOF 的“总是 SYNC ”模式下,事务的每条命令在执行成功之后,都会立即调用
fsync
或fdatasync
将事务数据写入到 AOF 文件。但是,这种保存是由后台线程进行的,主线程不会阻塞直到保存成功,所以从命令执行成功到数据保存到硬盘之间,还是有一段非常小的间隔,所以这种模式下的事务也是不持久的。 - 其他 AOF 模式也和“总是 SYNC ”模式类似,所以它们都是不持久的。