事务
事务的基本理解是,多个操作同时成功或者同时失败。
redis中单独执行命令是能够保证原子性的,但是redis中的事务不保证原子性
redis中事务的执行通过一个队列,本质上是一个命令集合通过按照顺序单独执行,在所有的单个命令入队完成之后才会挨个执行,并且不允许其他的操作干涉,并且redis事务没隔离级别的概念
操作事务有三个步骤
- 开启事务(multi)
- 命令入队
- 执行命令(exec)
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec #执行事务队列里的命令
1) OK
2) OK
3) "v2"
4) OK
放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> discard #放弃事务,整一个事务队列里的所有命令都舍弃掉
OK
127.0.0.1:6379> get k4
(nil)
如何理解redis事务不保证原子性
编译型异常
在事务队列中,直接存在命令的语法错误时,如果过命令入队,redis会抛出异常,但是事务不会停止并且该错误命令会成功入队,等到事务执行会直接导致整个事务无法正常执行,所有命令都会执行失败
运行时异常
此时的事务是不能保证原子性的,语法编译通过的命令,但是在运行时不能够正常执行,此时只有这一条命令不会执行,其他正常的命令会正常执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "v2"
redis实现悲观锁和乐观锁
悲观锁
- 悲观的认为,任何时候都一定会出现问题,所有无论做什么都加锁。悲观锁极其消耗性能
乐观锁
- 乐观的认为,认为什么时候都不会出问题,所以任何时候操作程序都不会加锁。会在更新呢数据的时候判断是否有人修改过数据
redis监视测试
redis使用watch实现乐观锁
被监视值未发生改变
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #乐观锁监视key
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby monry 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec #key没有改变,事务成功
1) (integer) -20
2) (integer) 20
监视值发生改变
- 客户端1
127.0.0.1:6379> watch money #首先监视key
OK
127.0.0.1:6379> multi #开启事务的过程中,被监视key发生了改变
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec #事务执行失败
(nil)
- 客户端2
127.0.0.1:6379> set money 1000 #在客户端1 的事务执行之前修改了key
OK
watch会监视key的value是否发生了改变,如果发生改变,会告诉操作该key的事务队列,整个事务队列都会执行失败。并且在事务执行之后(无论是否成功),都会解除对该key的监视。
在新版本的多线程redis中,执行事务队列的时候依然会保持单线程执行,在watch和exec命令之间,可以保证多线程执行命令以提高性能,但是一旦开始执行事务,就会保证单线程执行。
Jedis
jdis是redis官方推荐的Java操作redis的中间件
创建工程文件
-
创建一个空项目
-
空项目中创建一个普通maven项目,并设置jdk8和jdk8的语法检查。
-
在项目设置的javac中设置项目的编译jdk版本为jdk8
-
导入jedis依赖(fastjosn是为了方便后期测试)
<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.2</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 --> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.28</version> </dependency>
创建类测试连接
package com.zhong;
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
//地址本次测试使用本地redis测试,所以填入localhost
Jedis redis = new Jedis("localhost", 6379);
System.out.println(redis.ping());
redis.close();
}
}
控制台输出pong即为连接成功,常用api和redis原生指令一摸一样