背景:公司旧项目,最初访问量不多,单机部署的。后来,访问量上来了,有阵子很卡,公司决定横向扩展,后端代码部署了三台服务器。部署调整后,有用户反馈,一个订单支付了三次。
问题分析:
通过对项目日志分析,问题应该出现在,使用Redis做分布式锁,没有做到原子性操作。判断键是否存在和设置键及有效期是分两步来的,服务器卡的时候,放大了这两步操作的时间,导致了问题产生。
问题解决:
得优化这部分代码,得采用Redis调用lua脚本,实现操作的原子性。查了各种博客及Redis官方文档,推荐了ronnylt/redlock-php这个扩展,进来发现这个扩展比较老旧了,支持PHP版本太老了。
通过搜索redlock-php发现signe/redlock-php扩展应该可以满足公司项目需求。
PHP分布式测试:
本地部署了两套PHP接口代码,环境如下:
ThinkPHP8.0
PHP8
composer require ronnylt/redlock-php
测试接口代码如下:
<?php
declare (strict_types=1);
namespace app\controller;
use RedLock\RedLock;
class Index
{
public function index()
{
$servers = [
['127.0.0.1', 6379, 0.01],
];
$redLock = new RedLock($servers);
$lock = $redLock->lock('my_resource_name', 200000);
// $lock是一个数组,说明抢到临界资源,可以执行业务;上锁失败是false
if ($lock) {
// 执行业务逻辑
echo "执行业务代码...<br/>";
// 业务执行完毕,可以进行解锁操作
$redLock->unlock($lock);
} else {
echo "未抢到临界资源,继续等待...<br/>";
}
}
}
测试时注释了解锁时间,把加锁时间设置成了200秒,测试结果符合预期,下面是测试过程截图
符合预期后,把这扩展安装到现有项目,优化了现有代码,测试上线,这里记录下,防止下回碰到类似问题又去重零解决此类问题。
小结下流程:
1、PHP项目安装扩展
composer require ronnylt/redlock-php
2、分布式锁实现代码示例
<?php
declare (strict_types=1);
namespace app\controller;
use RedLock\RedLock;
class Index
{
public function index()
{
$servers = [
['127.0.0.1', 6379, 0.01],
];
$redLock = new RedLock($servers);
$lock = $redLock->lock('my_resource_name', 200000);
// $lock是一个数组,说明抢到临界资源,可以执行业务;上锁失败是false
if ($lock) {
// 执行业务逻辑
echo "执行业务代码...<br/>";
// 业务执行完毕,可以进行解锁操作
// $redLock->unlock($lock);
} else {
echo "未抢到临界资源,继续等待...<br/>";
}
}
}