业务上的一些重要接口,比如提现,转账,支付等,很容易被人恶意访问此接口。
他们最常用的一种方法就是,同一时间内发送多次请求,造成瞬间高并发现象,以此来绕过金额的判断或者其他判断逻辑,即使使用事务也是不行的。
下面是我日常使用的并发锁和限流

<?php

namespace app\common\library;


/**
 * Redis Lock锁
 */
class Lock
{
    private static $_instance;
    private $_redis;

    private function __construct()
    {
        if (! extension_loaded('redis')) {
            throw new \Exception('not support: redis');
        }
        $this->_redis = new \Redis();
        $this->_redis->connect('127.0.0.1');
    }

    public static function getInstance()
    {
        if (self::$_instance instanceof self) {
            return self::$_instance;
        }
        return self::$_instance = new  self();
    }

    /**
     * 限流
     *
     * @param  string  $key
     * @param  int  $limit
     * @param  int  $second
     *
     * @return bool
     */
    public static function limitRate($key, $limit, $second = 10)
    {
        $check = self::getInstance()->_redis->exists($key);
        if ($check) {
            self::getInstance()->_redis->incr($key);  //键值递增
            $count = self::getInstance()->_redis->get($key);
            if ($count > $limit) {
                return false;
            }
        } else {
            self::getInstance()->_redis->incr($key);
            //限制时间为30秒
            self::getInstance()->_redis->expire($key, $second);
        }
        return true;
    }

    /**
     * 获取锁
     *
     * @param  String  $key  锁标识
     * @param  Int  $expire  锁过期时间
     *
     * @return Boolean
     */
    public static function lock($key, $expire = 5)
    {
        $objRedisConn = self::getInstance();
        //Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。
        //设置成功,返回 1 。 设置失败,返回 0 。
        $is_lock = $objRedisConn->_redis->setnx($key, time() + $expire);
        // 不能获取锁
        if (! $is_lock) {
            // 判断锁是否过期
            $lock_time = $objRedisConn->_redis->get($key);
            // 锁已过期,删除锁,重新获取
            if (time() > $lock_time) {
                $objRedisConn->unlock($key);
                $is_lock = $objRedisConn->_redis->setnx($key, time() + $expire);
            }
        }
        return $is_lock ? false : true;
    }

    /**
     * 释放锁
     *
     * @param  String  $key  锁标识
     *
     * @return Boolean
     */
    public static function unlock($key)
    {
        return self::getInstance()->_redis->del($key);
    }
}
最后修改:2020 年 04 月 06 日
如果觉得我的文章对你有用,请随意赞赏