1 <?php 2 header("content-type: text/html;charset=utf8;"); 3 $start=time(); 4 $redis=new Redis(); 5 $redis->connect('192.168.95.11','6379'); 6 7 for ($i=0; $i < 100000; $i++) 8 { 9 $redis->multi(); 10 $count=$redis->get('count'); 11 $count=$count+1; 12 $redis->set('count',$count); 13 $redis->exec(); 14 } 15 $end=time(); 16 echo "this OK<br/>"; 17 echo "执行时间为:".($end-$start); 18 ?>
执行结果失败,表名使用事务不能够解决此问题。
分析原因:
我们都知道当redis开启时,事务中的命令是不执行的,而是先将命令压入队列,然后当出现exec命令的时候,才会阻塞式的将所有的命令一个接一个的执行。
所以当使用PHP中的Redis类进行redis事务的时候,所有有关redis的命令都不会真正的执行,而仅仅是将命令发送到redis中进行存储起来。
因此下图中所圈到的$count实际上不是我们想要的数据,而是一个对象,因此test.php中11行出错。
查看对象count:
4.2、原子性操作incr解决
#更新test.php文件
1 <?php 2 header("content-type: text/html;charset=utf8;"); 3 $start=time(); 4 $redis=new Redis(); 5 $redis->connect('192.168.95.11','6379'); 6 for ($i=0; $i < 100000; $i++) 7 { 8 $count=$redis->incr('count'); 9 } 10 $end=time(); 11 echo "this OK<br/>"; 12 echo "执行时间为:".($end-$start); 13 ?>
两个浏览器同时执行,耗时14、15秒,count=200000,可以解决此问题。
缺点:
仅仅只是解决这里的取出加1的问题,本质上还是没能解决问题的,在实际环境中,我们需要做的是一系列操作,不仅仅只是取出加1,因此就很有必要构建一个万能锁了。
5、构建分布式锁我们构造锁的目的就是在高并发下消除选择竞争、保持数据一致性
构造锁的时候,我们需要注意几个问题:
1、预防处理持有锁在执行操作的时候进程奔溃,导致死锁,其他进程一直得不到此锁
2、持有锁进程因为操作时间长而导致锁自动释放,但本身进程并不知道,最后错误的释放其他进程的锁
3、一个进程锁过期后,其他多个进程同时尝试获取锁,并且都成功获得锁
我们将不对test.php文件修改了,而是直接建立一个相对比较规范的面向对象Lock.class.php类文件
#建立Lock.class,php文件
1 <?php 2 #分布式锁 3 class Lock 4 { 5 private $redis=''; #存储redis对象 6 /** 7 * @desc 构造函数 8 * 9 * @param $host string | redis主机 10 * @param $port int | 端口 11 */ 12 public function __construct($host,$port=6379) 13 { 14 $this->redis=new Redis(); 15 $this->redis->connect($host,$port); 16 } 17 18 /** 19 * @desc 加锁方法 20 * 21 * @param $lockName string | 锁的名字 22 * @param $timeout int | 锁的过期时间 23 * 24 * @return 成功返回identifier/失败返回false 25 */ 26 public function getLock($lockName, $timeout=2) 27 { 28 $identifier=uniqid(); #获取唯一标识符 29 $timeout=ceil($timeout); #确保是整数 30 $end=time()+$timeout; 31 while(time()<$end) #循环获取锁 32 { 33 if($this->redis->setnx($lockName, $identifier)) #查看$lockName是否被上锁 34 { 35 $this->redis->expire($lockName, $timeout); #为$lockName设置过期时间,防止死锁 36 return $identifier; #返回一维标识符 37 } 38 elseif ($this->redis->ttl($lockName)===-1) 39 { 40 $this->redis->expire($lockName, $timeout); #检测是否有设置过期时间,没有则加上(假设,客户端A上一步没能设置时间就进程奔溃了,客户端B就可检测出来,并设置时间) 41 } 42 usleep(0.001); #停止0.001ms 43 } 44 return false; 45 } 46 47 /** 48 * @desc 释放锁 49 * 50 * @param $lockName string | 锁名 51 * @param $identifier string | 锁的唯一值 52 * 53 * @param bool 54 */ 55 public function releaseLock($lockName,$identifier) 56 { 57 if($this->redis->get($lockName)==$identifier) #判断是锁有没有被其他客户端修改 58 { 59 $this->redis->multi(); 60 $this->redis->del($lockName); #释放锁 61 $this->redis->exec(); 62 return true; 63 } 64 else 65 { 66 return false; #其他客户端修改了锁,不能删除别人的锁 67 } 68 } 69 70 /** 71 * @desc 测试 72 * 73 * @param $lockName string | 锁名 74 */ 75 public function test($lockName) 76 { 77 $start=time(); 78 for ($i=0; $i < 10000; $i++) 79 { 80 $identifier=$this->getLock($lockName); 81 if($identifier) 82 { 83 $count=$this->redis->get('count'); 84 $count=$count+1; 85 $this->redis->set('count',$count); 86 $this->releaseLock($lockName,$identifier); 87 } 88 } 89 $end=time(); 90 echo "this OK<br/>"; 91 echo "执行时间为:".($end-$start); 92 } 93 94 } 95 96 header("content-type: text/html;charset=utf8;"); 97 $obj=new Lock('192.168.95.11'); 98 $obj->test('lock_count'); 99 100 ?>
测试结果: