编辑推荐: |
本文是主要介绍了Redis事务概念及CAS(Check-And-Set)机制示例等,希望对您的学习有所帮助。
本文来自雷迪斯,由火龙果软件Linda编辑、推荐。 |
|
Redis事务
在网络/活动中见过事务机制保证发证券对对券码存量校正,这是典型的并发,理解操作的实例。
Redis的事务机制交易通过四个命令来完成:MULTI, EXEC, DISCARD and WATCH,建议精读链接文章对Redis事务机制有详细介绍。
Redis事务机制特性
事务(transaction)的定义从multi开始,到exec结束。
同一个事务内的多个命令,具有原子性,不会被打断
在Redis事务的执行过程中,永远不会发生另一个客户端发出的请求。这样可以确保将命令作为单个隔离操作执行。
实例一:multi和exec之间的命令不会被其他并发处理打断,事务原子性
clientA:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set hello a
QUEUED // clientB cocurrently set hello to b 127.0.0.1:6379> get hello
QUEUED
127.0.0.1:6379> exec
1) OK
2) "a" clientB:
127.0.0.1:6379> set hello b
OK
|
实例二:注意理解incr和set操作的区别
clientA:
127.0.0.1:6379> set hello 1
OK 127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr hello //
QUEUED
// clientB cocurrently set hello to 22
127.0.0.1:6379> get hello
QUEUED
127.0.0.1:6379> exec
1) (integer) 23
2) "23" // clientB:
127.0.0.1:6379> set hello 22
OK |
事务内部的命令或全部执行(仅指入带执行的命令编码成功),或者都不执行
所有命令都将被处理,或者不处理任何命令,因此Redis事务也是原子的。
multi执行之前分段连接,则全部命令不会执行;exec执行之后,所有命令操作都会被执行。
注意:这里仅指指命令的执行,但同时事务内部命令执行与否,并不意味着执行成功(存在命令执行后,返回异常的情况)。
事务期间的错误情况
命令入待执行命令队列失败,在此时exec前就会有错误:命令语法错误,内存不足等极端情况,此时一般会中断事务,自动discard事务。
127.0.0.1:6379>
multi
OK
127.0.0.1:6379> set hello 1
QUEUED
127.0.0.1:6379> lget hello // 非法的命令导致错误
(error) ERR unknown command 'lget'
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because
of previous errors.
|
在执行exec后部分命令可能失败:甚至部分命令失败,其他命令仍将正常执行完成
实例三:在执行exec后部分命令可能失败
127.0.0.1:6379>
multi
OK
127.0.0.1:6379> set hello 1
QUEUED
127.0.0.1:6379> lset hello 0 1 // 该命令可被执行,但执行失败
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding
the wrong kind of value
127.0.0.1:6379> get hello // 事务中的第一步执行成功
"1" |
CAS(Check-And-Set)支持
watch已监视的密钥,只允许在当前终端的multi和exec见被修改,其他情况的修改都将导致watch和此事务的失败。
实例:CAS的主要通过watch命令完成,依次在watch一个键后,其他终端修改此键的值时,都将触发当前事务的失败。
// clientA
127.0.0.1:6379> watch hello
OK
127.0.0.1:6379> multi
OK
// clientB cocurrently set hello to 33
127.0.0.1:6379> set hello 2
QUEUED
127.0.0.1:6379> exec // watched hello changed,
exec will fail
(nil) // clientB:
127.0.0.1:6379> set hello 33
OK // redis monitor
1496247607.225085 [0 127.0.0.1:43168] "watch"
"hello"
1496247611.476334 [0 127.0.0.1:43168] "multi"
1496247626.275239 [0 127.0.0.1:43216] "set"
"hello" "33"
1496247632.092549 [0 127.0.0.1:43168] "exec"
|
注意:当前终端的watch和multi之间对密钥的修改,也会触发事务的失败详细。
predis的CAS使用
Laravel项目中有phpredis和predis两个扩展支持redis的,phpredis是?实现扩展,需要编译安装,性能卓越;predis是PHP实现扩展,安装方便(composer
install),线上部署无需变更
这里对predis下使用Redis的事务机制展开说明
Check-And-Set:
示例代码
function
zpop($client, $key)
{
$element = null;
$options = array(
'cas' => true, // Initialize with support
for CAS operations
'watch' => $key, // Key that needs to be
WATCHed to detect changes
'retry' => 3, // Number of retries on aborted
transactions, after
// which the client bails out with an exception.
);
$client->transaction($options, function ($tx)
use ($key, &$element) {
@list($element) = $tx->zrange($key, 0, 0);
if (isset($element)) {
$tx->multi(); // With CAS, MULTI *must* be
explicitly invoked.
$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; |
在cas模式下,predis使用的注意事项:
必须显式的调用$tx->multi()
在调用multi之前必须显式的调用任何命令$tx->validRedisCmd(),否则直接调用multi并不会执行multi指令
cas的目的是监视某个键在此期间不会变化,所以应用场景应该是:取某个键的值,做一些操作,再进行键执行其他命令
此处适当的引用下php.net对memcached :: cas()的解释来辅助理解
Memcached :: cas()执行“检查并设置”操作,以便仅在自从此客户端上次获取该项目以来没有其他客户端对其进行更新的情况下,该项目才会被存储。
cas =检查并设置,某项将在没有其他终端修改的替代下保存,即当前终端是最新一次获取该值
实例:Lravel下通过predis的事务的CAS(check-and-set)机制保证竞争条件下的数据一致性
use Illuminate\Support\Facades\Redis; $keyTotal = 'key-total';
$keyOneUser = 'key-one-user';
$total = 10;
$oneUserLimit = 2;
$options = [
"cas" => true,
"watch" => [$keyTotal, $keyOneUser],
// watch multi keys
"retry" => 3
];
try {
$redis = Redis::connection('redis_conn');
$transFunc = function ($tx) use ($keyTotal,
$keyOneUser, $total, $oneUserLimit) {
$curTotal = $tx->get($keyTotal);
$curLimit = $tx->get($keyOneUser);
if ($curTotal >= $total || $curLimit >=
$oneUserLimit) {
\Log::info('[Rule ruleUpdateCasTransaction]
out of limit, ' . "$keyTotal:$curTotal>=$total,$keyOneUser:$curLimit>=$oneUserLimit");
return false;
}
$tx->multi(); // cas模式下必须调用multi,调用multi前必须执行其他redis命令初始化配置
$tx->incr($keyTotal);
$tx->incr($keyOneUser);
};
$redis->transaction($options, $transFunc);
return true;
} catch (\Exception $e) {
\Log::error("[Rule ruleCntDec]Fail to $type
for total and limit, e:" . $e);
return false;
}
|
Redis monitor命令下的cas事务执行过程:
1496242439.690785
[0 127.0.0.1:43126] "AUTH" "e3b8d1e0xxxxxxxxxxxxxxVpEi"
1496242439.691136 [0 127.0.0.1:43126] "SELECT"
"4"
1496242439.691356 [4 127.0.0.1:43126] "WATCH"
"key-total" "key-one-user"
1496242439.691493 [4 127.0.0.1:43126] "GET"
"key-total"
1496242439.691643 [4 127.0.0.1:43126] "GET"
"key-one-user"
1496242439.691834 [4 127.0.0.1:43126] "MULTI"
1496242439.692136 [4 127.0.0.1:43126] "INCR"
"key-total"
1496242439.692146 [4 127.0.0.1:43126] "INCR"
"key-one-user"
1496242439.692152 [4 127.0.0.1:43126] "EXEC" |
|