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