Redis事务讲解

Redis事务是什么

Redis中的事务提供了一种将多个命令请求打包,然后一次性、顺序性执行多个命令的机制,并且在事务指向期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的请求。

Redis事务的实现

Redis事务需要用到MULTIEXEC两个命令,事务开始的时候先像Redis服务器发送 MULTI 命令开启事务,然后依次发送需要在本次事务中处理的命令,最后再发送 EXEC 命令表示事务命令结束。

正常执行

通过MUTILEXEC连个命令演示正常事务执行过程

Redis事务正常执行演示图
Redis事务正常执行演示图

从输出中可以看到,当输入 MULTI 命令后,服务器返回OK表示事务开始成功,然后依次输入需要在本次事务中执行的所有命令,每次输入一个命令服务器并不会马上执行,而是返回”QUEUED”,这表示命令已经被服务器接受并且暂时保存起来,最后输入EXEC命令后,本次事务中的所有命令才会被依次执行,可以看到最后服务器一次性返回了三个OK,这里返回的结果与发送的命令是按顺序一一对应的,这说明这次事务中的命令全都执行成功了。


如果客户端在发送EXEC命令之前断线了,则服务器会清空事务队列,事务中的所有命令都不会被执行。而一旦客户端发送了EXEC命令之后,事务中的所有命令都会被执行,即使此后客户端断线也没关系,因为服务器已经保存了事务中的所有命令。

全体连坐

语法错误会使得事务中的命令全部不执行。

Redis事务错误之全体连坐示例图
Redis事务错误之全体连坐示例图

语法错误表示命令不存在或者参数错误,这种情况需要区分Redis的版本,2.6.5之后的版本会忽略这个事务中的所有命令,都不执行。Redis 2.6.5之前的版本会忽略错误的命令,执行其他正确的命令。

冤头债主

运行错误会使得事务跳过有错的语句,仅执行无报错的任务。

Redis事务错误之冤头债主示例图
Redis事务错误之冤头债主示例图

运行错误表示命令在执行过程中出现错误,比如给非数字值增一、用GET命令获取一个散列表类型的键值等。这种错误在命令执行之前Redis是无法发现的,所以在事务里这样的命令会被Redis接受并执行。如果事务里有一条命令执行错误,其他命令依旧会执行(包括出错之后的命令)。

放弃事务

放弃事务使用 DISCARD 命令,DISCARD命令可以在 MULTI 命令执行之后,EXEC 命令执行之前取消 WATCH 命令并清空事务队列,然后从事务状态中退出。

redis放弃事务示例图
redis放弃事务示例图

WATCH监控(乐观锁)

WATCH命令是一个乐观锁,它可以在EXEC命令执行之前,监视任意数量的KEY,并在EXEC执行时,检查被监视的键是否至少有一个已经被修改过了,如果是的话,服务器将拒绝执行事务,并向客户端返回执行失败的nil回复。

Redis事务WATCH监控示例图
Redis事务WATCH监控示例图

EXEC命令执行完之后被监控的键会自动被UNWATCH,另外UNWATCH命令也可以在WATCH命令执行之后、MULTI命令执行之前取消对某个键的监控。

Redis分布式锁

天猫双11热卖过程中,对已经售罄的货物追加补货,且补货完成。客户购买热情高涨,3秒内将所有商品购 买完毕。本次补货已经将库存全部清空,如何避免最后一件商品不被多人同时购买?
使用setnx lock-key value设置一把公共锁

设置完锁之后可以使用 expire 为锁key添加时间限定,到时不释放,放弃锁,防止释放锁失败导致业务阻塞
利用setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功
对于返回设置成功的,拥有控制权,进行下一步的具体业务操作
对于返回设置失败的,不具有控制权,排队或等待 操作完毕通过del key操作释放锁

分布式锁超时时间计算规则
由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。
例如:持有锁的操作最长执行时间127ms,最短执行时间7ms。
测试百万次最长执行时间对应命令的最大耗时,测试百万次网络延迟平均耗时
锁时间设定推荐:最大耗时120%+平均网络延迟110%
如果业务最大耗时<<网络平均延迟,通常为2个数量级,取其中单个耗时较长即可

为什么Redis不支持回滚

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

以下是这种做法的优点:

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

有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。

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

Redis事务的总结

Redis事务实现分为三个阶段:

  • 开启:以MULTI开始一个事务。
  • 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面。
  • 执行:由EXEC命令触发事务。

Redis事务的三个特性:

  • 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询能看到事务里的更新,在事务外查询不能看到”的问题。
  • 不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

添加新评论

评论列表