首页 > 学院 > 开发设计 > 正文

Redis 事务

2019-11-08 20:37:28
字体:
来源:转载
供稿:网友
redis对事务的支持目前还比较简单。redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。由于redis是单线程来处理所有client的请求的所以做到这点是很容易的。一般情况下redis在接受到一个client发来的命令后会立即处理并 返回处理结果,但是当一个client在一个连接中发出multi命令有,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中。当从此连接受到exec命令后,redis会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给client.然后此连接就 结束事务上下文。  一、事务相关命令multi:标记一个事务块的开始。事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由 EXEC 命令原子性(atomic)地执行。exec:执行所有事务块内的命令。假如某个(或某些) key 正处于 WATCH 命令的监视之下,且事务块中有和这个(或这些) key 相关的命令,那么 EXEC 命令只在这个(或这些) key没有被其他命令所改动的情况下执行并生效,否则该事务被打断(abort)。

discard:取消事务,放弃执行事务块内的所有命令。如果正在使用 WATCH 命令监视某个(或某些) key,那么取消所有监视,等同于执行命令 UNWATCH 

watch:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断,watch不会开启事务。

unwatch:取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。因为 EXEC 命令会执行事务,因此 WATCH 命令的效果已经产生了;而 DISCARD 命令在取消事务的同时也会取消所有对 key 的监视,因此这两个命令执行之后,就没有必要执行 UNWATCH 了。

[plain] view plain copy PRint?在CODE上查看代码片./bin/redis-cli -h 192.168.36.189 -p 6379  192.168.36.189:6379> watch a  OK  192.168.36.189:6379> get a  (nil)  192.168.36.189:6379> multi  OK  192.168.36.189:6379> set a 2  QUEUED  192.168.36.189:6379> incr a  QUEUED  192.168.36.189:6379> exec  1) OK  2) (integer) 3  192.168.36.189:6379>   

二、代码实例

[java] view%20plain copy print?派生到我的代码片package cn.slimsmart.redis.demo.transaction;    import java.util.List;    import redis.clients.jedis.Jedis;  import redis.clients.jedis.Transaction;    public class TransactionTest {        public static void main(String[] args) throws InterruptedException {          @SuppressWarnings("resource")          Jedis jedis = new Jedis("192.168.36.189", 6379);          Transaction t=jedis.multi();          t.set("key1", "aaa");          Thread.sleep(1000);          t.set("key2", "bbb");          List<Object> oList=t.exec();          System.out.println(oList);                    jedis.watch("key1");          t = jedis.multi();          t.set("key2", "ccc");          t.set("key3", "ddd");          t.get("key3");          oList = t.exec();          System.out.println(oList);      }    }  

1.当事务执行t.exec()的时候,事务内的命令才会连续执行,并且中间不会插入其它client的命令。2.一个Redis的jedis 只能开启一个事务,开启多个会抛异常。3.watch操作并不一定非要在事务内操作,也可以在事物外进行watch。redis只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,比如使用的命令类型不匹配。当事务的执行过程中,如果redis意外的挂了。很遗憾只有部分命令执行了,后面的也就被丢弃了。当然如果我们使用的append-only file方式持久化,redis会用单个write操作写入整个事务内容。即是是这种方式还是有可能只部分写入了事务到磁盘。发生部分写入事务的情况下,redis重启时会检测到这种情况,然后失败退出。可以使用redis-check-aof工具进行修复,修复会删除部分写入的事务内容。修复完后就能够重新启动了。参考:http://blog.csdn.net/freebird_lb/article/details/7734008

三、总结

1.redis事物实现,multi开始,所有指令会被放入到队列中。当调用exec后,队列中所有指令会依次被执行。2.multi-exec中指令执行时,所有指令只要语法合理都会被写入队列中。队列执行时,指令有可能会执行失败,但不影响其它指令执行。3.redis事物提供了乐观锁,通过watch指令可以实现CAS操作。watch–multi–exec操作在给key加上乐观锁后,当在执行exec指令前,有其它client修改此key,此事物将执行失败。从而保证原子操作。说明:对于redis事物的应用其实需要灵活使用,上面介绍的例子是从官网翻译而来。其实在实际中可以通过watch一些标记位来保证多线程下缓存与数据库数据库的一致性。(我们的系统是分布式缓存与数据库的结合使用,缓存需要跟据数据库的一致性很重要,下面举例我们应用中的一个场景:)如一个service方法,serviceA,执行DAO方法(1),然后更新缓存(2),两个并发线程,线程一执行了方法1,此刻他需要把DAO相关的数据更新到缓存2中,多线程情况下,线程二在线程一执行1后,也同样执行1,2相关的操作,并且比线程一优先完成,这样将导致线程一在执行2时,将出现缓存数据与数据库不一致的现象。(以上是针对单帐号的多并发操作,发生的概率还是存在),对于以上问题我们的解决方案是:1. 帐号为acc,为每个acc在缓存中增加一个tag标识.2. 当线程一执行方法1前,设置标记位tag.3. 当执行方法2时,将会watch tag,并且比较tag是否发生了修改,如果一旦发生修改,则此次缓存操作不将更新,并清空此acc缓存。4. 如果tag值达到预期,则提交缓存更新,在提交缓存这段时间,如果tag发生变换,则redisexec提交时,会返回null ,这样,虽然缓存内容更新成功,但跟据返回结果,可以即时清除此acc的缓存,从而清空了缓存的脏数据。5. 通过以上事物保证了缓存数据与数据库数据不一致性的时间很短,甚至可以忽略,因为基本上在MS级别上。6. 我们的应用在缓存数据不存在acc的情况下,会尝试从数据库读取,而缓存的作用只是缓解我们系统数据库的压力,这样实现,很好的达到了我们的预期效果.

参考

1.redis事务介绍与应用


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表