1. Redis事务简介
Redis事务可以一次执行多个命令,一个事务的所有命令都会序列化并按顺序地串行化执行,而不会被其他客户端提交的命令请求插入到事务执行命令序列中。
Redis事务不具备原子性,仅仅具备隔离性,也就是说当前的事务可以不被其他事务打断。由于每一次事务操作涉及到的指令还是比较多的,为了提高执行效率,我们在使用客户端的时候,可以通过 pipeline 来优化指令的执行。
2. Redis事务命令
下面的表格节选自:https://www.runoob.com/redis/redis-transactions.html
命令 | 作用 |
---|---|
DISCARD | 取消事务,放弃执行事务块内的所有命令 |
EXEC | 执行所有事务块内的命令 |
MULTI | 标记一个事务块的开始 |
UNWATCH | 取消WATCH命令对所有key的监视 |
WATCH key [key …] | 监视一个(或多个) key,如果在事务执行之前这个(或这些key)被其他命令所改动,那么事务将被打断 |
3. 使用实例
3.1 MULTI、EXEC
通过MULTI命令开启一个事务,使用如下:
127.0.0.1:6379> MULTI
OK
在MULTI命令执行之后,我们可以继续发送命令去执行,此时的命令不会被立马执行,而是放在一个队列中,如下:
127.0.0.1:6379> SET K1 V1
QUEUED
127.0.0.1:6379> SET K2 V2
QUEUED
127.0.0.1:6379> SET K3 V3
QUEUED
当所有的命令都输入完成后,通过EXEC命令发起执行,如下:
127.0.0.1:6379> EXEC
1) OK
2) OK
3) OK
事务中的三个命令成功执行,如下:
127.0.0.1:6379> KEYS *
1) "K2"
2) "K1"
3) "K3"
3.2 DISCARD
通过DISCARD命令清空队列,放弃执行事务块的所有命令,如下:
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET K1 111
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> GET K1
"V1"
3.3 WATCH和UNWATCH
事务中的WATCH命令可以用来监控一个或多个key,如果其中至少有一个被WATCH监视的键在EXEC执行之前被修改了,那么整个事务都会被取消,EXEC返回nil来表示事务执行失败。如下:
127.0.0.1:6379> WATCH K1 K2
OK
127.0.0.1:6379> SET K1 BBB
OK
127.0.0.1:6379> GET K1
"BBB"
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET K1 AAA
QUEUED
127.0.0.1:6379> SET K2 BBB
QUEUED
127.0.0.1:6379> EXEC
(nil)
127.0.0.1:6379> MGET K1 K2
1) "BBB"
2) "V2"
EXEC执行之后之后被监控的所有键会自动执行UNWATCH。
可以手动通过UNWATCH命令,取消所有键的监控,如下:
127.0.0.1:6379> WATCH K1 K2
OK
127.0.0.1:6379> MSET K1 111 K2 222
OK
127.0.0.1:6379> MGET K1 K2
1) "111"
2) "222"
127.0.0.1:6379> UNWATCH
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> MSET K1 AAA K2 BBB
QUEUED
127.0.0.1:6379> EXEC
1) OK
127.0.0.1:6379> MGET K1 K2
1) "AAA"
2) "BBB"
4. 事务中的异常
4.1 进入队列之前就能发现的错误
进入队列之前就能发现的错误,比如使用错误的命令,对于这种错误,该命令不进入队列,Redis服务器也会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SETT K1
(error) ERR unknown command `SETT`, with args beginning with: `K1`,
127.0.0.1:6379> GET K1
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
4.2 执行EXEC之后才能发现的错误
执行EXEC之后才能发现的错误,比如对非数字字符进行数学运算,对于这种情况,Redis并没有对它们进行特别处理, 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。
127.0.0.1:6379> SET kk vv
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR kk
QUEUED
127.0.0.1:6379> SET K1 V1
QUEUED
127.0.0.1:6379> GET K1
QUEUED
127.0.0.1:6379> EXEC
1) (error) ERR value is not an integer or out of range
2) OK
3) "V1"
从这里可以看出:
Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
Redis事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
官网说明:
Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。因此不需要对回滚进行支持,使得 Redis 的内部可以保持简单且快速。
5. Redis事务总结
5.1 事务的三个阶段:
- 开启事务——以MULTI命令开启事务
- 命令入队——接收到的命令不会立即执行,而是加入等待执行的队列中。
- 执行事务——由EXEC命令执行事务,命令实际执行。
5.2 特性
- 批量的操作命令在客户端发送 EXEC 命令前被放入队列缓存。不会被实际执行。
- 不保证原子性:收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 单独的隔离操作:在事务执行过程,事务中的所有命令都会按队列顺序执行,其他客户端提交的命令请求不会插入到事务执行命令序列中。
6. Java客户端操作Redis事务
使用Jedis操作Redis事务,相关的api操作代码如下:
public class TransactionTest {
public static void main(String[] args) {
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
//连接池最大空闲数
config.setMaxIdle(300);
//最大连接数
config.setMaxTotal(1000);
//连接最大等待时间
config.setMaxWaitMillis(30000);
//在空闲时检查有效性
config.setTestOnBorrow(true);
JedisPool jedisPool=new JedisPool(config,"127.0.0.1",6379,30000,"123");
Jedis jedis = jedisPool.getResource();
Integer money = new TransactionTest().addMoney(jedis, "zhangsan", 300);
System.out.println(money);
}
private Integer addMoney(Jedis jedis,String userId, Integer money){
jedis.watch(userId);
int v=Integer.parseInt(jedis.get(userId))+money;
Transaction transaction = jedis.multi();
transaction.set(userId,String.valueOf(v));
List<Object> exec = transaction.exec();
if (exec==null){
System.out.println("userId事务操作被打断");
}
return Integer.parseInt(jedis.get(userId))+money;
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/44348.html