zoukankan      html  css  js  c++  java
  • redis事物

    本文档翻译自: http://redis.io/topics/transactions 。

    MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务的基础。

    事务能够一次运行多个命令。 而且带有下面两个重要的保证:

    • 事务是一个单独的隔离操作:事务中的全部命令都会序列化、按顺序地运行。事务在运行的过程中。不会被其它client发送来的命令请求所打断。

    • 事务是一个原子操作:事务中的命令要么所有被运行,要么所有都不运行。

      EXEC 命令负责触发并运行事务中的全部命令:

      • 假设client在使用 MULTI 开启了一个事务之后,却由于断线而没有成功运行 EXEC ,那么事务中的全部命令都不会被运行。
      • 还有一方面,假设client成功在开启事务之后运行 EXEC ,那么事务中的全部命令都会被运行。

      当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。

      然而,假设 Redis server由于某些原因被管理员杀死,或者遇上某种硬件故障,那么可能仅仅有部分事务命令会被成功写入到磁盘中。

      假设 Redis 在又一次启动时发现 AOF 文件出了这种问题,那么它会退出,并汇报一个错误。

      使用 redis-check-aof 程序能够修复这一问题:它会移除 AOF 文件里不完整事务的信息,确保server能够顺利启动。

    从 2.2 版本号開始,Redis 还能够通过乐观锁(optimistic lock)实现 CAS (check-and-set)操作,详细信息请參考文档的后半部分。

    使用方法

    MULTI 命令用于开启一个事务,它总是返回 OK 。

    MULTI 运行之后, client能够继续向server发送随意多条命令, 这些命令不会马上被运行, 而是被放到一个队列中, 当 EXEC 命令被调用时。 全部队列中的命令才会被运行。

    还有一方面。 通过调用 DISCARD , client能够清空事务队列。 并放弃运行事务。

    下面是一个事务样例。 它原子地添加了 foo 和 bar 两个键的值:

    > MULTI
    OK
    
    > INCR foo
    QUEUED
    
    > INCR bar
    QUEUED
    
    > EXEC
    1) (integer) 1
    2) (integer) 1
    

    EXEC 命令的回复是一个数组, 数组中的每一个元素都是运行事务中的命令所产生的回复。 当中, 回复元素的先后顺序和命令发送的先后顺序一致。

    当client处于事务状态时, 全部传入的命令都会返回一个内容为 QUEUED 的状态回复(status reply)。 这些被入队的命令将在 EXEC命令被调用时运行。

    事务中的错误

    使用事务时可能会遇上下面两种错误:

    • 事务在运行 EXEC 之前。入队的命令可能会出错。比方说,命令可能会产生语法错误(參数数量错误,參数名错误。等等),或者其它更严重的错误。比方内存不足(假设server使用 maxmemory 设置了最大内存限制的话)。
    • 命令可能在 EXEC 调用之后失败。举个样例,事务中的命令可能处理了错误类型的键。比方将列表命令用在了字符串键上面,诸如此类。

    对于发生在 EXEC 运行之前的错误。client曾经的做法是检查命令入队所得的返回值:假设命令入队时返回 QUEUED ,那么入队成功;否则,就是入队失败。

    假设有命令在入队时失败,那么大部分client都会停止并取消这个事务。

    只是。从 Redis 2.6.5 開始,server会对命令入队失败的情况进行记录,并在client调用 EXEC 命令时,拒绝运行并自己主动放弃这个事务。

    在 Redis 2.6.5 曾经。 Redis 仅仅运行事务中那些入队成功的命令,而忽略那些入队失败的命令。 而新的处理方式则使得在流水线(pipeline)中包括事务变得简单。由于发送事务和读取事务的回复都仅仅须要和server进行一次通讯。

    至于那些在 EXEC 命令运行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在运行时产生了错误, 事务中的其它命令仍然会继续运行。

    从协议的角度来看这个问题,会更easy理解一些。 下面样例中。 LPOP 命令的运行将出错。 虽然调用它的语法是正确的:

    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    
    MULTI
    +OK
    
    SET a 3
    abc
    
    +QUEUED
    LPOP a
    
    +QUEUED
    EXEC
    
    *2
    +OK
    -ERR Operation against a key holding the wrong kind of value
    

    EXEC 返回两条批量回复(bulk reply): 第一条是 OK 。而第二条是 -ERR 。 至于如何用合适的方法来表示事务中的错误。 则是由client自己决定的。

    最重要的是记住这样一条。 即使事务中有某条/某些命令运行失败了。 事务队列中的其它命令仍然会继续运行 —— Redis 不会停止运行事务中的命令。

    下面样例展示的是还有一种情况, 当命令在入队时产生错误。 错误会马上被返回给client:

    MULTI
    +OK
    
    INCR a b c
    -ERR wrong number of arguments for 'incr' command
    

    由于调用 INCR 命令的參数格式不对。 所以这个 INCR 命令入队失败。

    为什么 Redis 不支持回滚(roll back)

    假设你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续运行余下的命令”这样的做法可能会让你认为有点奇怪。

    下面是这样的做法的长处:

    • Redis 命令仅仅会由于错误的语法而失败(而且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从有用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出如今生产环境中。
    • 由于不须要对回滚进行支持。所以 Redis 的内部能够保持简单且高速。

    有种观点觉得 Redis 处理事务的做法会产生 bug , 然而须要注意的是。 在通常情况下, 回滚并不能解决编程错误带来的问题。

    举个样例, 假设你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 。 又或者对错误类型的键运行了 INCR , 回滚是没有办法处理这些情况的。

    鉴于没有不论什么机制能避免程序猿自己造成的错误。 而且这类错误通常不会在生产环境中出现, 所以 Redis 选择了更简单、更高速的无回滚方式来处理事务。

    放弃事务

    当运行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空, 而且client会从事务状态中退出:

    redis> SET foo 1
    OK
    
    redis> MULTI
    OK
    
    redis> INCR foo
    QUEUED
    
    redis> DISCARD
    OK
    
    redis> GET foo
    "1"
    

    使用 check-and-set 操作实现乐观锁

    WATCH 命令能够为 Redis 事务提供 check-and-set (CAS)行为。

    被 WATCH 的键会被监视,并会发觉这些键是否被修改过了。 假设有至少一个被监视的键在 EXEC 运行之前被修改了。 那么整个事务都会被取消。 EXEC 返回空多条批量回复(null multi-bulk reply)来表示事务已经失败。

    举个样例, 如果我们须要原子性地为某个值进行增 1 操作(如果 INCR 不存在)。

    首先我们可能会这样做:

    val = GET mykey
    val = val + 1
    SET mykey $val
    

    上面的这个实如今仅仅有一个client的时候能够运行得非常好。 可是, 当多个client同一时候对同一个键进行这种操作时。 就会产生竞争条件。

    举个样例, 假设client A 和 B 都读取了键原来的值, 比方 10 , 那么两个client都会将键的值设为 11 。 但正确的结果应该是 12才对。

    有了 WATCH , 我们就能够轻松地解决这类问题了:

    WATCH mykey
    
    val = GET mykey
    val = val + 1
    
    MULTI
    SET mykey $val
    EXEC
    

    使用上面的代码, 假设在 WATCH 运行之后, EXEC 运行之前, 有其它client改动了 mykey 的值。 那么当前client的事务就会失败。

    程序须要做的, 就是不断重试这个操作。 直到没有发生碰撞为止。

    这样的形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 而且由于大多数情况下, 不同的client会訪问不同的键, 碰撞的情况一般都非常少, 所以通常并不须要进行重试。

    了解 WATCH

    WATCH 使得 EXEC 命令须要有条件地运行: 事务仅仅能在全部被监视键都没有被改动的前提下运行。 假设这个前提不能满足的话,事务就不会被运行。

    假设你使用 WATCH 监视了一个带过期时间的键, 那么即使这个键过期了, 事务仍然能够正常运行, 关于这方面的具体情况,请看这个帖子: http://code.google.com/p/redis/issues/detail?

    id=270

    WATCH 命令能够被调用多次。 对键的监视从 WATCH 运行之后開始生效, 直到调用 EXEC 为止。

    用户还能够在单个 WATCH 命令中监视随意多个键。 就像这样:

    redis> WATCH key1 key2 key3
    OK
    

    当 EXEC 被调用时。 无论事务是否成功运行, 对全部键的监视都会被取消。

    另外。 当client断开连接时。 该client对键的监视也会被取消。

    使用无參数的 UNWATCH 命令能够手动取消对全部键的监视。 对于一些须要修改多个键的事务。 有时候程序须要同一时候对多个键进行加锁, 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时, 就能够使用 UNWATCH 命令来取消眼下对键的监视, 中途放弃这个事务, 并等待事务的下次尝试。

    使用 WATCH 实现 ZPOP

    WATCH 能够用于创建 Redis 没有内置的原子操作。

    举个样例。 下面代码实现了原创的 ZPOP 命令, 它能够原子地弹出有序集合中分值(score)最小的元素:

    WATCH zset
    element = ZRANGE zset 0 0
    MULTI
        ZREM zset element
    EXEC
    

    程序仅仅要反复运行这段代码, 直到 EXEC 的返回值不是空多条回复(null multi-bulk reply)就可以。

    Redis 脚本和事务

    从定义上来说, Redis 中的脚本本身就是一种事务, 所以不论什么在事务里能够完毕的事, 在脚本里面也能完毕。

    而且一般来说, 使用脚本要来得更简单,而且速度更快。

    由于脚本功能是 Redis 2.6 才引入的, 而事务功能则更早之前就存在了, 所以 Redis 才会同一时候存在两种处理事务的方法。

    只是我们并不打算在短时间内就移除事务功能, 由于事务提供了一种即使不使用脚本, 也能够避免竞争条件的方法。 并且事务本身的实现并不复杂。

    只是在不远的将来, 可能全部用户都会仅仅使用脚本来实现事务也说不定。 假设真的发生这样的情况的话, 那么我们将废弃并终于移除事务功能。

  • 相关阅读:
    部署iis服务器与c#程序遇到的问题小结
    Installing node-oracledb on Microsoft Windows
    Navicat:cant create OCI environment.
    socket.io 中文手册 socket.io 中文文档
    现有的情绪
    Express 4.x Node.js的Web框架
    结构与类的对象在引用上的不同之处
    Android时间戳转换为标准Datetime(yyyy-MM-dd hh:mm:ss)格式
    ASP.NET MVC4中调用WEB API的四个方法
    安恒DASCTF 四月战 WP
  • 原文地址:https://www.cnblogs.com/tlnshuju/p/6826547.html
Copyright © 2011-2022 走看看