Redis事务
MULTI,EXEC,DISCARD,WATCH 四个命令是 redis 事务的四个基础命令。其中:
MULTI,告诉 redis 服务器开启一个事务。注意,只是开启,而不是执行
EXEC,告诉 redis 开始执行事务
DISCARD,告诉 redis 取消事务
WATCH,监视某一个键值对,它的作用是在事务执行之前如果监视的键值被修改,事务会被取消。
在介绍 redis 事务之前,先来展开 redis 命令队列的内部实现。
redis 允许一个客户端不间断执行多条命令:发送 MULTI 后,用户键入多条命令;再发送 EXEC 即可不间断执行之前输入的多条命令。
➜ redis_7000 redis-cli -p 7000 127.0.0.1:7000> multi OK 127.0.0.1:7000> set email helloworld QUEUED 127.0.0.1:7000> set count 10 QUEUED 127.0.0.1:7000> incrby count 2 QUEUED 127.0.0.1:7000> exec 1) OK 2) OK 3) (integer) 12 127.0.0.1:7000>
由上面给出的 redis 客户端操作,来看看 redis 服务器的状态变化:
下面示例操作,
127.0.0.1:7000> multi OK 127.0.0.1:7000> set counter 15 QUEUED 127.0.0.1:7000> incrby counter hello QUEUED 127.0.0.1:7000> incrby counter 2 QUEUED 127.0.0.1:7000> exec 1) OK 2) (error) ERR value is not an integer or out of range 3) (integer) 17 127.0.0.1:7000> get counter "17"
当执行exec时,会顺序的执行命令队列中的命令,其中一条命令incrby counter hello是错误的,但不会影响命令队列中其他命令的执行。从上面命令执行结果就可以看出,当incrby counter hello命令执行失败后,并没有影响下一条命令的执行,而是成功的自增了2,最后得到counter的结果是17,正确自增后的结果。
开启两个redis客户端,当其中一个客户端进入事务状态后,不影响另一个客户端进入事务状态。
在每个代表数据库的 redis.h/redisDb 结构类型中, 都保存了一个 watched_keys 字典, 字典的键是这个数据库被监视的键, 而字典的值则是一个链表, 链表中保存了所有监视这个键的客户端。
比如说,以下字典就展示了一个 watched_keys 字典的例子:
其中, 键 key1 正在被 client2 、 client5 和 client1 三个客户端监视, 其他一些键也分别被其他别的客户端监视着。
WATCH 命令的作用, 就是将当前客户端和要监视的键在 watched_keys 中进行关联。
举个例子, 如果当前客户端为 client10086 , 那么当客户端执行 WATCH key1 key2 时, 前面展示的 watched_keys 将被修改成这个样子:
通过 watched_keys 字典, 如果程序想检查某个键是否被监视, 那么它只要检查字典中是否存在这个键即可; 如果程序要获取监视某个键的所有客户端, 那么只要取出键的值(一个链表), 然后对链表进行遍历即可。
客户端A如下操作,
127.0.0.1:7000> watch counter OK 127.0.0.1:7000> multi OK 127.0.0.1:7000> set counter 19 QUEUED 127.0.0.1:7000> incrby counter 2
接下来在客户端B修改counter的值,
127.0.0.1:7000> set counter 18 OK
最后客户端A执行exec命令,是命令队列执行,最后看结果,
127.0.0.1:7000> exec (nil) 127.0.0.1:7000> get counter "18"
最后返回结果为nil,说明该redis事务就没有执行,正是因为watch的作用,检测到counter的值发生了变化,导致整个事务被取消。
如下示例代码,
package com.usoft.jedis.sample; import org.junit.Before; import org.junit.Test; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.Transaction; import java.util.List; /** * Created by xinxingegeya on 16/4/14. */ public class JedisTrxTest { /** * jedis连接池 */ public JedisPool jedisPool; @Before public void before() { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(10); config.setMaxIdle(5); config.setMaxWaitMillis(5000); config.setTestOnBorrow(true); jedisPool = new JedisPool(config, "127.0.0.1", 7000); } @Test public void test() { Jedis jedis = jedisPool.getResource(); /** * watch监视一个key * 当该key对应的值被其他客户端修改后,该事务被取消 */ jedis.watch("counter"); long start = System.currentTimeMillis(); Transaction tx = jedis.multi(); tx.set("counter", "10"); tx.incrBy("counter", 10); List<Object> results = tx.exec(); long end = System.currentTimeMillis(); for (Object result : results) { System.out.println(result); } System.out.println("Transaction SET: " + ((end - start) / 1000.0) + " seconds"); } }
============END============