0%

Redis事务

入一行,先别惦记着赚钱,先学着让自己值钱。没有哪个行业的钱是好赚的。赚不到钱,赚知识;赚不到知识,赚经历;赚不到经历,赚阅历;以上都赚到了就不可能赚不到钱。让人迷茫的原因只有一个,那就是本该拼搏的年纪,却想太多,做太少。 —— 褚时健

一、概念

  1. 乐观锁介绍

      watch指令在redis事务中提供了CAS(check-and-set)的行为。为了检测被watch的keys在是否有多个clients同时改变引起冲突,这些keys将会被监控。如果至少有一个被监控的key在执行exec命令前被修改,整个事务将会回滚,不执行任何动作,从而保证原子性操作,并且执行exec会得到null的回复。

  1. 乐观锁工作机制

      watch命令会监视给定的每一个key,当exec时如果监视的任一个key自从调用watch后发生过变化,则整个事务会回滚,不执行任何动作。

  • watch的key是对整个连接有效的,事务也一样
  • exec/discard/unwatch命令及客户端连接关闭都会清除连接中的所有监视
  • 如果watch一个不稳定(有生命周期)的key并且此key自然过期,exec仍然会执行事务队列的指令
  1. 和MySQL对比

    • 相关命令

      • MySQL
        • begin:开启一个事务
        • commit:提交事务
        • rollback:回滚事务
      • Redis
        • multi:标记事务的开始
        • exec:执行事务的commands队列
        • discard:结束事务并清除commands队列
    • 默认状态

      • MySQL会默认开启一个事务且自动提交(set autocommit=0/1来设置),不支持rollback
      • Redis默认不会开启事务,即command会立即执行而不会排队,不支持rollback
    • 实现原理

      • MySQL
        • MySQL事务基于undo/redo日志
        • undo记录修改前状态,rollback基于undo日志实现
        • redo记录修改后的状态,commit基于redo日志实现
        • MySQL中无论是否开启事务,sql都会被立即执行并返回执行结果,只是事务开启后执行后的状态只是记录在redo日志,执行commit之后数据才会被写入磁盘
      • Redis
        • Redis事务基于commands队列,可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
        • 如果没有开启事务,command将会被立即执行并返回执行结果,并且直接写入磁盘
        • 如果事务开启,command不会被立即执行而是排入队列,并返回排队状态

二、使用

  1. 无cas案例
客户端1 客户端2 说明
>get name
“张三”
>get age
“1”
>get name
“张三”
>get age
“1”
连接两个客户端,获取age/name值
>multi
“OK”
>incr age
“QUEUED”
>set name 李四
“QUEUED”
\ 客户端1开启事务,执行命令:
1.age自增+1
2.name值改为李四
\ >incr age
“2”
>set name 王五
“王五”
客户端2执行命令,age自增+1,name改为王五
>exec
3
“OK”
>get name
“李四”
>get age
3
\ 客户端1提交事务,发现age的值为3(因为客户端2
执行了一次incr命令值变为了2),name为李四
(客户端2修改name的值被客户端1提交覆盖)。
  1. cas案例
客户端1 客户端2 说明
>get name
“张三”
>get age
“1”
>get name
“张三”
>get age
“1”
连接两个客户端,获取age/name值
>watch age name
“OK”
>multi
“OK”
>incr age
“QUEUED”
>set name 李四
“QUEUED”
\ 客户端1通过watch命令监视age和name,开启事务,执行命令:
1.age自增+1
2.name值改为李四
\ >incr age
“2”
>set name 王五
“王五”
客户端2执行命令,age自增1,name改为王五
>exec

>get name
“王五”
>get age
2
\ 客户端1提交事务,watch监控发现此期间age和name被修改过,则让事整个务回滚,不执行命令
  1. PHP实现官方demo,跑了一遍个人不太理解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
require dirname(___FILE__). '/vendor/autoload.php';

$single_server = array(
'host' => '127.0.0.1',
'port' => 6379,
);

function zpop($client, $key)
{
$element = null;
$options = array(
'cas' => true,
'watch' => $key,
'retry' => 3,
);

$client->transaction($options, function ($tx) use ($key, &$element) {
@list($element) = $tx->zrange($key, 0, 0);

if (isset($element)) {
$tx->multi();
$tx->zrem($key, $element);
}
});

return $element;
}

$client = new Predis\Client($single_server);
$zpopped = zpop($client, 'zset');

echo isset($zpopped) ? "ZPOPed $zpopped" : 'Nothing to ZPOP!', PHP_EOL;

三、参考

  1. 参考一
  2. 参考二