redis 自学笔记(二)事务

官方相关文档地址:

https://redis.io/commands/multi
https://redis.io/topics/transactions

基本命令

MULTI, EXEC, DISCARD and WATCH are the foundation of transactions in Redis. They allow the execution of a group of commands in a single step, with two important guarantees:

redis的事务由MULTI, EXEC, DISCARD 和 WATCH组成(其实还有个UNWATCH)。

  • multi

Available since 1.2.0.
Marks the start of a transaction block. Subsequent commands will be queued for atomic execution using EXEC.
Return value
Simple string reply: always OK.

作用是标记一个事务的开始。

  • exec

Available since 1.2.0.
Executes all previously queued commands in a transaction and restores the connection state to normal.
When using WATCH, EXEC will execute commands only if the watched keys were not modified, allowing for a check-and-set mechanism.
Return value
Array reply: each element being the reply to each of the commands in the atomic transaction.
When using WATCH, EXEC can return a Null reply if the execution was aborted.

简单来说,就是将之前的事务提交。它的返回值为一个数组,依次代表每一个命令的返回值。如果事务因为watch的原因无法执行,则会返回一个空值。(exec会清除之前的watch操作)

  • discard

Available since 2.0.0.
Flushes all previously queued commands in a transaction and restores the connection state to normal.
If WATCH was used, DISCARD unwatches all keys watched by the connection.
Return value
Simple string reply: always OK.

事务的代码写了一半想清空怎么办?直接再调用一次multi肯定是不行的,会包multi不可嵌套的错误;再开一条session可以,但是不好。使用discard可以清空之前写到事务中的代码。

  • WATCH key [key …]

Available since 2.2.0.
Time complexity: O(1) for every key.
Marks the given keys to be watched for conditional execution of a transaction.
Return value
Simple string reply: always OK.

对指定的key施加一个监视,如果在之后的事务的执行中发现这个key已经被更改过了,则整个事务失败,并且将数据还原到初始状态。(看上去很有乐观锁的感觉)
watch命令可以连续多次调用。

  • UNWATCH

Available since 2.2.0.
Time complexity: O(1)
Flushes all the previously watched keys for a transaction.
If you call EXEC or DISCARD, there’s no need to manually call UNWATCH.
Return value
Simple string reply: always OK.

watch的逆向操作,将之前watch的数据全部清空。

事务的特点

They allow the execution of a group of commands in a single step, with two important guarantees:

All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served in the middle of the execution of a Redis transaction. This guarantees that the commands are executed as a single isolated operation.

Either all of the commands or none are processed, so a Redis transaction is also atomic. The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the MULTI command none of the operations are performed, instead if the EXEC command is called, all the operations are performed. When using the append-only file Redis makes sure to use a single write(2) syscall to write the transaction on disk. However if the Redis server crashes or is killed by the system administrator in some hard way it is possible that only a partial number of operations are registered. Redis will detect this condition at restart, and will exit with an error. Using the redis-check-aof tool it is possible to fix the append only file that will remove the partial transaction so that the server can start again.

Starting with version 2.2, Redis allows for an extra guarantee to the above two, in the form of optimistic locking in a way very similar to a check-and-set (CAS) operation. This is documented later on this page.

看来,redis对事务的底层实现与常见的关系型数据库有着非常大的区别。它是将事务中的命令缓存到一个队列中,并在调用exec的时候一口气将队列中的命令执行完毕。而对于事务的原子性的实现,redis采用的方法则是简单粗暴的一次只允许一个事务执行。这根本就是串行化操作吗,肯定是能保证隔离性的,同时,由于redis本身超高的执行速度,也不会产生效率问题。

事务的回滚

关于事务的原子性,文档中表示Either all of the commands or none are processed, so a Redis transaction is also atomic. 。这个说法挺模糊的,在我经过测试后,发现文档中所说的原子性与其他关系型数据库是有一定区别的。
如果事务由于watch的key被修改而终止,其事务是可以回滚的。但是,若事务中的部分语句即使出现了异常,比如对非数字型值使用了incr,虽然这条命令会失败,但是事务中的其他命令依然会正常执行。
可以通过下面代码验证一下。

		Jedis jd = new Jedis("localhost", 6379);

		jd.watch("s1");
		
		Transaction tr = jd.multi();

		tr.set("s2", "s2");
		Thread.sleep(10000l);
		System.out.println(tr.incr("s1"));
		System.out.println(tr.exec());

我在执行incr之前设了一个sleep,在执行这段语句之后,迅速在另一个进程中执行incr s1 ,那么上述事务会回滚至初始状态。

		Jedis jd = new Jedis("localhost", 6379);

		Transaction tr = jd.multi();
		tr.set("t1", "q");
		tr.incr("t1");
		tr.set("t2", "t2");
		
		System.out.println(tr.exec());

很明显,tr.incr("t1"); 是会报错的,但是控制台打印出的结果却是这样的。

[OK, redis.clients.jedis.exceptions.JedisDataException: redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range, OK]

经过查询后发现,tr.set("t2", "t2"); 确实生效了。也就是说,即使事务中的一条命令出现了错误,redis依旧会继续执行其他的命令。
对此,redis给出了这样的解释
https://redis.io/topics/transactions#why-redis-does-not-support-roll-backs

If you have a relational databases background, the fact that Redis commands can fail during a transaction, but still Redis will execute the rest of the transaction instead of rolling back, may look odd to you.

However there are good opinions for this behavior:
1.Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production.
2.Redis is internally simplified and faster because it does not need the ability to roll back.

An argument against Redis point of view is that bugs happen, however it should be noted that in general the roll back does not save you from programming errors. For instance if a query increments a key by 2 instead of 1, or increments the wrong key, there is no way for a rollback mechanism to help. Given that no one can save the programmer from his or her errors, and that the kind of errors required for a Redis command to fail are unlikely to enter in production, we selected the simpler and faster approach of not supporting roll backs on errors.

这种解释看起来很任性,但是仔细一想也是有一定道理的。如果功能经过了严格的测试,那么将很少会遇到回滚。而redis也正是得益于这种功能上的缺陷,使其拥有更强大的效率。毕竟,这是一个key-value数据库,而不是传统的关系型数据库。它最需要的是速度,这也正是我们选择redis的首要原因。无法回滚造成的另一个影响就是可能会产生各种脏数据了,但是正如前面所说,如果功能确实经过了严格的测试(实际上很难做到,但我假装看不见。。。),那么理论上就不会有脏数据,或者从根本上说,redis它就不适合存储那些复杂的,强关系性的数据。回想一下在sql中使用的那些语法,如果要用redis去实现,那简直能把人逼疯。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值