Redis之利用锁机制来防止缓存过期产生的惊群现象

首先,所谓的缓存过期引起的“惊群”现象是指,在大并发情况下,我们通常会用缓存来给数据库分压,但是会有这么一种情况发生,那就是当一个缓存数据失效之后会导致同时有多个并发线程去向后端数据库发起请求去获取同一个数据,这样如果在一段时间内同时生成了大量的缓存,然后在另外一段时间内又有大量的缓存失效,这样就会导致后端数据库的压力突然增大,这种现象就可以称为“缓存过期产生的惊群现象”!

以下代码的思路,就是利用“锁机制”来防止惊群现象。先看代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class  KomaRedis{
 
     private  $redis //redis对象
     private  static  $_instance  = null;
 
     private  function  __construct( $config  array ())
     {
         if  ( empty ( $config )) {
             return  false;
         }
         $this ->redis =  new  Redis();
         $this ->redis->connect( $config [ 'server' ],  $config [ 'port' ]);
         return  $this ->redis;
     }
 
     /**
      * @param array $config
      * @return redis操作类对象
      */
     public  static  function  getInstance( $config  array ())
     {
         if  (!(self:: $_instance  instanceof  self)) {
             self:: $_instance  new  self ( $config );
         }
         return  self:: $_instance ;
     }
 
     /**
      * 获取缓存
      * @param $key string $name
      * @return array,object,number,string,boolean
      * @desc 此方法使用了锁机制来防止防止缓存过期时所产生的惊群现象,保证只有一个进程不获取数据,可以更新,其他进程仍然获取过期数据
      */
     public  function  getByLock( $key )
     {
         $sth  $this ->redis->get( $key );
         if  ( $sth  === false) {
             return  $sth ;
         else  {
             $sth  = json_decode( $sth , TRUE);
             if  ( intval ( $sth [ 'expire' ]) <= time()) {
                 $lock  $this ->redis->incr( $key  ".lock" );
                 if  ( $lock  === 1) {
                     return  false;
                 else  {
                     return  $sth [ 'data' ];
                 }
             else  {
                 return  $sth [ 'data' ];
             }
         }
     }
 
     /**
      * 设置缓存
      * @param $key string $name 缓存键
      * @param $value $string ,array,object,number,boolean $value 缓存值
      * @param null $ttl $string ,number $ttl 过期时间,如果不设置,则使用默认时间,如果为 infinity 则为永久保存
      * @return bool
      * @desc 此方法存储的数据会自动加入一些其他数据来避免惊群现象,如需保存原始数据,请使用 set
      */
     public  function  setByLock( $key $value $ttl  = null)
     {
         if  ( is_numeric ( $ttl ) &&  intval ( $ttl ) > 0) {
             $ttl  intval ( $ttl );
             $exp  = time() +  $ttl ;
             $arg  array ( "data"  =>  $value "expire"  =>  $exp );
         else  {
             $ttl  = 300;
             $exp  = time() +  $ttl ;
         }
         empty ( $ttl ) OR  $ttl  += 300;  //增加redis缓存时间,使程序有足够的时间生成缓存
         $arg  array ( "data"  =>  $value "expire"  =>  $exp );
         $rs  $this ->redis->setex( $key $ttl , json_encode( $arg , TRUE));
         $this ->redis->del( $key  ".lock" );
         return  $rs ;
     }
 
     /**
      * 返回redis对象
      * redis有非常多的操作方法,我们只封装了一部分
      * 拿着这个对象就可以直接调用redis自身方法
      */
     public  function  redis()
     {
         return  $this ->redis;
     }
}


原理就是:

首先,在存储数据的时候,设置数据的过期时间比实际设置的过期时间多300秒,然后存储的数据中,通过一个数组来存储数据,数组中一个键用来存放真实的数据,另外一个键用来存放数据的真实过期时间,这个留到后期获取数据的时候做校验,然后把对应这个数据的“锁”删除掉。

这里这么做的原因和读取数据的做法相关!

然后,在读取数据的时候,依然像平时一样直接读取,如果数据已经超过了有效期(注意:这里的有效期并非设置的有效期,而是更该之后的有效期),那么就只能去读后端数据库。如果数据依然有效,则需要去判断,判断数据“在真正的有效期内是否失效”,如果没有失效,则直接返回数据!

重点是,假如数据“在伪造的有效期内没有失效,而在真正的有效期内已经失效”,那么这时就需要去判断“数据的锁”!

通过代码“$lock = $this->redis->incr($key . ".lock");”可以获取数据的锁,“$lock === 1”表示数据没有锁,那么这一次请求需要发送到后端数据库去读取最新的数据,否则的话表示该数据已经加了锁,也就是已经有一个线程去后端读取数据了,那么后来的线程也就没有权限再去后端取数据,需要等到前面的那个线程执行结束,但是这次读取就只能读取“旧的数据”了!

通过上面的解释也就明白,为什么在存储数据的时候需要“删除数据的锁”!因为一旦数据被重新存储,那么说明已经有一个线程去后端得到了最新的数据,那么该数据的锁就可以释放,然后下一个线程在获取数据的时候如果有需要就可以得到这个锁,然后才有权限进入到后端去读取新数据!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值