0%

redis之CAS

纸上得来终觉浅,绝知此事要躬行。说到redis事务多多少少都能回答上来一点儿,然后再继续深入一点儿就不太了解了。。。

一、概念

  1. 乐观锁介绍

      &atch指令在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. demo演示
    • 3.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提交覆盖)。
  • 3.2 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被修改过,则让事整个务回滚,不执行命令

二、PHP实现

  1. 官方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. 个人理解实现