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

1.并发访问限制问题

对于一些须要限制同一个用户并发访问的场景,若是用户并发请求屡次,而服务器处理没有加锁限制,用户则能够屡次请求成功。php

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

伪代码以下:redis

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

相关文章
相关标签/搜索