Bootstrap

PHP项目添加分布式锁,这里是ThinkPHP8框架实现分布式锁

背景:公司旧项目,最初访问量不多,单机部署的。后来,访问量上来了,有阵子很卡,公司决定横向扩展,后端代码部署了三台服务器。部署调整后,有用户反馈,一个订单支付了三次。

问题分析:
通过对项目日志分析,问题应该出现在,使用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/>";
        }
    }
}

概念补充:

分布式锁:

保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。

实现分布式锁一般有三种实现方式:

1. 数据库乐观锁
2. 基于Redis的分布式锁
3. 基于Zookeeper的分布式锁

确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

1. 互斥性。在任意时刻,只有一个客户端能持有锁。

2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

3. 安全性。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了,即不能误解锁。

4. 容错性。只要大多数Redis节点正常运行,客户端就能够获取和释放锁。
;