php 使用redis锁限制并发访问类

1.并发访问限制问题

对于一些需要限制同一个用户并发访问的场景,如果用户并发请求多次,而服务器处理没有加锁限制,用户则可以多次请求成功。

例如换领优惠券,如果用户同一时间并发提交换领码,在没有加锁限制的情况下,用户则可以使用同一个换领码同时兑换到多张优惠券。

伪代码如下:

1 if A(可以换领)
2     B(执行换领)
3     C(更新为已换领)
4 D(结束)

如果用户并发提交换领码,都能通过可以换领(A)的判断,因为必须有一个执行换领(B)后,才会更新为已换领(C)。因此如果用户在有一个更新为已换领之前,有多少次请求,这些请求都可以执行成功。 

2.并发访问限制方法

使用文件锁可以实现并发访问限制,但对于分布式架构的环境,使用文件锁不能保证多台服务器的并发访问限制。

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 
本文将使用其setnx方法实现分布式锁功能。setnxSet it Not eXists。 

语法:

SETNX key value

将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

时间复杂度:
O(1)
返回值:
设置成功,返回 1 。
设置失败,返回 0 。
 
RedisLock.class.php
 1 <?php
 2 /**
 3  *  Redis锁操作类
 4  *  Date:   2016-06-30
 5  *  Author: fdipzone
 6  *  Ver:    1.0
 7  *
 8  *  Func:
 9  *  public  lock    获取锁
10  *  public  unlock  释放锁
11  *  private connect 连接
12  */
13 class RedisLock { // class start
14 
15     private $_config;
16     private $_redis;
17 
18     /**
19      * 初始化
20      * @param Array $config redis连接设定
21      */
22     public function __construct($config=array()){
23         $this->_config = $config;
24         $this->_redis = $this->connect();
25     }
26 
27     /**
28      * 获取锁
29      * @param  String  $key    锁标识
30      * @param  Int     $expire 锁过期时间
31      * @return Boolean
32      */
33     public function lock($key, $expire=5){
34         $is_lock = $this->_redis->setnx($key, time()+$expire);
35 
36         // 不能获取锁
37         if(!$is_lock){
38 
39             // 判断锁是否过期
40             $lock_time = $this->_redis->get($key);
41 
42             // 锁已过期,删除锁,重新获取
43             if(time()>$lock_time){
44                 $this->unlock($key);
45                 $is_lock = $this->_redis->setnx($key, time()+$expire);
46             }
47         }
48 
49         return $is_lock? true : false;
50     }
51 
52     /**
53      * 释放锁
54      * @param  String  $key 锁标识
55      * @return Boolean
56      */
57     public function unlock($key){
58         return $this->_redis->del($key);
59     }
60 
61     /**
62      * 创建redis连接
63      * @return Link
64      */
65     private function connect(){
66         try{
67             $redis = new Redis();
68             $redis->connect($this->_config['host'],$this->_config['port'],$this->_config['timeout'],$this->_config['reserved'],$this->_config['retry_interval']);
69             if(empty($this->_config['auth'])){
70                 $redis->auth($this->_config['auth']);
71             }
72             $redis->select($this->_config['index']);
73         }catch(RedisException $e){
74             throw new Exception($e->getMessage());
75             return false;
76         }
77         return $redis;
78     }
79 
80 } // class end
81 
82 ?>
 
demo.php
 1 <?php
 2 require 'RedisLock.class.php';
 3 
 4 $config = array(
 5     'host' => 'localhost',
 6     'port' => 6379,
 7     'index' => 0,
 8     'auth' => '',
 9     'timeout' => 1,
10     'reserved' => NULL,
11     'retry_interval' => 100,
12 );
13 
14 // 创建redislock对象
15 $oRedisLock = new RedisLock($config);
16 
17 // 定义锁标识
18 $key = 'mylock';
19 
20 // 获取锁
21 $is_lock = $oRedisLock->lock($key, 10);
22 
23 if($is_lock){
24     echo 'get lock success<br>';
25     echo 'do sth..<br>';
26     sleep(5);
27     echo 'success<br>';
28     $oRedisLock->unlock($key);
29 
30 // 获取锁失败
31 }else{
32     echo 'request too frequently<br>';
33 }
34 
35 ?>
测试方法: 

打开两个不同的浏览器,同时在A,B中访问demo.php 
如果先访问的会获取到锁 
输出 
get lock success 
do sth.. 
success

另一个获取锁失败则会输出request too frequently

保证同一时间只有一个访问有效,有效限制并发访问。 


为了避免系统突然出错导致死锁,所以在获取锁的时候增加一个过期时间,如果已超过过期时间,即使是锁定状态都会释放锁,避免死锁导致的问题。 

本文转自:http://blog.csdn.net/fdipzone/article/details/51793837

 
 
 
 
 
 
原文地址:https://www.cnblogs.com/liuzhongchao/p/8144418.html