您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
Redis事务及CAS(Check-And-Set)机制
 
作者:小武
  2637  次浏览      29
2020-11-17
 
编辑推荐:
本文是主要介绍了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"

 

   
2637 次浏览       29
相关文章

基于EA的数据库建模
数据流建模(EA指南)
“数据湖”:概念、特征、架构与案例
在线商城数据库系统设计 思路+效果
 
相关文档

Greenplum数据库基础培训
MySQL5.1性能优化方案
某电商数据中台架构实践
MySQL高扩展架构设计
相关课程

数据治理、数据架构及数据标准
MongoDB实战课程
并发、大容量、高性能数据库设计与优化
PostgreSQL数据库实战培训
最新活动计划
DeepSeek大模型应用开发 6-12[厦门]
人工智能.机器学习TensorFlow 6-22[直播]
基于 UML 和EA进行分析设计 6-30[北京]
嵌入式软件架构-高级实践 7-9[北京]
用户体验、易用性测试与评估 7-25[西安]
图数据库与知识图谱 8-23[北京]
 
最新文章
InfluxDB概念和基本操作
InfluxDB TSM存储引擎之数据写入
深度漫谈数据系统架构——Lambda architecture
Lambda架构实践
InfluxDB TSM存储引擎之数据读取
最新课程
Oracle数据库性能优化、架构设计和运行维护
并发、大容量、高性能数据库设计与优化
NoSQL数据库(原理、应用、最佳实践)
企业级Hadoop大数据处理最佳实践
Oracle数据库性能优化最佳实践
更多...   
成功案例
某金融公司 Mysql集群与性能优化
北京 并发、大容量、高性能数据库设计与优化
知名某信息通信公司 NoSQL缓存数据库技术
北京 oracle数据库SQL优化
中国移动 IaaS云平台-主流数据库及存储技术
更多...