php 如何锁定接口,让一个接口,同一时间只处理同一人的一次请求?

目前我用的是在session中设置一个值,每次进来判断这个值是否存在,存在就表示锁定了,不执行本次请求,如果不存在,就设置这个值,并执行后续操作。

class controller{
    function index(){
        if(isset($_SESSION['lock']))
            return;
            
        $_SESSION['lock'] = 1;
        sleep(1);//这里会查询用户的资格,并给用户发送流量,如果两次同时进来,会造成两次都认为是有资格的,并且会给用户发两次相同流量
        unset($_SESSION['lock']);
    }
}

我本以为这样做是可以的,而且session存在redis里速度蛮快,直到刚才睡不着起来摆弄,发现竟然还是能让一个用户同时的多次同接口请求都执行了。

想要一个人的同时多次请求,只处理第一个。判断是否同一人,根据session来判断。

你们是用什么方法保证这个的?

阅读 10.3k
10 个回答

1,如使用Mysql,可以对Mysql进行加锁,一个请求结束以后,释放锁其它请求才能往下执行
2,可以使用计数器,在请求进来的可以往计数器里加一,请求结束以后清空计数器,较推荐这个方法
3,另外,文件的session本身是有锁的,如果同一个用户的每一次请求未结束,其它请求也会在被阻塞

不少人问session锁的问题,看下这篇文章吧
https://log.zvz.im/2016/02/27...

如果涉及到数据库用加锁吧,或者把所有的请求弄成队列一个个的处理吧

我想了想,应该写入两个东西,一个是进入时间,退出时间。
访问开始写入开始时间,退出写入退出时间。
假设第一次访问肯定是都是空的,start=02:26:30 end=02:26:50
那么这样子我重新发起访问的时候,判断一下:
<1>如果start>end,说明在访问中
这边还有一种情况,就是访问发生中断,来不及写入end的时间,所以这边就必须做一个初始化的判断,比如start>end&&start-end>100直接把end改成当前时间,则空闲,进行访问
<2>如果start<end,说明访问空闲,进行访问
注:时间戳尽量精确到微秒,方便计算。

打完一个文件,然后用文件锁 操作完成后 文件解锁 。下个用户才能继续访问 继续加锁

可以用数据库来处理啊, 设置一个标识, 用户进入该请求时先去检查数据库, 存在标识不予执行,

使用缓存把正在处理的用户的id存起来,通过过滤器操作id,用户请求进来过滤器判断id不在缓存则添加进来,处理完删除id。

方法很多,问题是你实际的需求是什么,希望你能落到最终数据的角度描述下,说不定不从API角度更简单

从你这个问法来看,你应该是提出了坑爹的解决方案。建议你说出原始需求。

最后我是用redis锁解决问题的。锁的代码如下

/**
 * redis操作
 * ps. 如果没有安装redis,会直接返回false,不会报错!
 *
 * Created by PhpStorm.
 * User: haua
 * Date: 16/10/21
 * Time: 23:02
 */
class hRedis{
    public static $host = '';//127.0.0.1
    public static $port = '';//6379
    public static $timeout = '10';
    public static $auth = '';//密码
    public static $prefix = 'act_projects_';//redis用作缓存前缀跟其它缓存做区分
    
    public static $errs = [];

    public static $redises = [];
    /**
     * 连接redis,
     * @param $db_index int|string 数据库下标
     * @return redis|bool
     */
    public static function RD($db_index='1'){
        if(!empty(self::$redises[$db_index]))
            return self::$redises[$db_index];
        if(!class_exists('redis'))
            return false;
        try{
            $redis = new redis();
            $redis->connect(self::$host, self::$port,self::$timeout);
            if(self::$auth)
                $redis->auth(self::$auth);
            
            if(is_numeric($db_index))
                $redis->select($db_index);

            self::$redises[$db_index] = $redis;

            return $redis;
        }catch(Exception $e){
            self::$errs[] = $e;
            return false;
        }
    }
    /**
     * redis锁机制
     * @param $name string 锁名
     * @param $ttl int 锁多长时间,如果超过这段时间还没解锁,就会自动解锁,0为永久,如果大于9位数,会被认为是时间戳。
     * @return bool|string 锁成功还是失败,失败返回false,成功会返回一个随机数,随机数需要作为参数传入解锁函数。锁失败的原因是,同名的锁已经锁上了
     * 
     * 锁用途:如果有一件事,只能等它上一次完成,才能再执行,那就要一个锁,
     * 每次执行的时候检查是否有锁,如果没锁,则上一把锁,然后执行业务,如果锁住了,表示这个动作正在进行,不能并行执行,所以要提示前端:服务器正在执行任务,请稍后再试。
     */
    public static function lock($name,$ttl){
        $rd = self::RD();
        if(!$rd)//如果无法链接redis,返回加锁失败
            return false;

        $ran = rand(8,9999);

        $ttl = (int)$ttl;
        $time = ['nx'];
        if($ttl>0){
            $now = time();
            if($ttl<=$now)
                $time['ex'] = $ttl;
            else
                $time['px'] = $ttl-$now;
        }
        $name = self::key($name);
        $res = $rd->set($name, $ran, $time);
        return $res?$ran:false;
    }

    /**
     * 解锁
     * @param $name string
     * @param $rand string|bool
     * @return bool 解锁成功还是失败
     */
    public static function unlock($name,$rand){
        $rd = self::RD();
        if(!$rd)//如果无法链接redis,返回加锁失败
            return false;

        $name = self::key($name);
        if ($rd->get($name) == $rand||$rand===true) {
            $rd->del($name);
        }else{
            return false;
        }
        return true;
    }
}
    
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏