redis中的事务及其在php使用中的问题

原创
2021/09/17 12:00
阅读数 56

redis中的事务, 不支持回滚, 只是把命令打包, 从而区别于mysql等数据库中的事务.

对于普通的命令来说, 每个命令都会返回结果, PHP演示如下:

/** @var Redis */
$redis = new Redis();
$redis->connect('localhost', 6379, 2);
$redis->select(0);

//设置一个key的值
$rt = $redis->set('name', 'aben');// 返回bool
//产品数量增加1
$goods = $redis->incrBy('goods:quantity', 1); // 返回int
//当指定的key不存在时, 设置其值为1, 有效期10s
$rt2 = $redis->set('lock_key', 1, ['nx', 'ex' => 10]); //返回bool

在PHP中我们可以把一组命令以multi开始, 而以exec执行来结束. 事务提交后, 返回的是一维数字下标数组, 顺序与事务内执行的命令顺序一致.

/** @var Redis */
$redis = new Redis();
$redis->connect('localhost', 6379, 2);
$redis->select(0);

$redis->multi();//开始事务
//设置一个key的值
$redis->set('name', 'aben');// 返回bool => 使用事务时,返回Object
//产品数量增加1
$redis->incrBy('goods:quantity', 1); // 返回int => 使用事务时,返回Object
//当指定的key不存在时, 设置其值为1, 有效期10s
$redis->set('lock_key', 1, ['nx', 'ex' => 10]); //返回bool => 使用事务时,返回Object
$status = $redis->exec();//提交事务, 返回的是一维数字下标数组, 顺序与事务内执行的命令顺序一致.
//获取每个结果:
$rt = $status[0];
$goods = $status[1];
$rt2 = $status[2];

这些看起来都很正常, 没有问题.

但是在实际并发测试中, 如果把set ex+nx操作与其他操作一起放在事务中, 会出现异常, 因为每个命令返回的值类型不是原先单个命令的返回类型, 而是Object了.

比如, 我们要在用户访问资讯详情页时, 访问量加1, 虚拟访问量增加一个随机值, 同时, 每60秒把访问量数据同步到数据库. 为了确保只有一个客户端的请求要求同步数据, 我们需要加一个锁, 并告诉客户端它是否得到了这个锁(同步数据的权限). 如果我们用下面的代码, 就会出现并发问题, 多个客户端会同时得到锁(授权), 这就是开启事务后, 返回值的类型变了造成的bug.

//上次同步时间
$lastSyncTime = $redis->get('sync_time');

$getLock = false;
$redis->multi();//开始事务
//如果上次同步时间是60秒前, 则获取锁, 并更新同步时间
if($lastSyncTime < time() - 60) {
    if ($redis->set('lock_key', 1, ['nx', 'ex' => 10])) { // TODO 这里的返回值判断就是错误的,因为是Object, 那就是true, 误判!
        $getLock = true;
        //设置最后同步时间
        $redis->set('sync_time', time());
    }
}
//真实访问量加1
$redis->incrBy('visit_times', 1);// 返回int=> 使用事务时,返回Object
//虚拟访问量加随机数
$redis->incrBy('visit_times_virtual', mt_rand(5,10)); // 返回int => 使用事务时,返回Object
$status = $redis->exec();//提交事务, 返回的是一维数字下标数组, 顺序与事务内执行的命令顺序一致.
$visitTimes = $getLock ? $status[1] : $status[0];
$visitTimesVirtual = $getLock ? $status[2] : $status[1];
return [
    'locked'=>$getLock,
    'visit_times'=>$visitTimes,
    'visit_times_virtual'=>$visitTimesVirtual
];
展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部