• Redis的分布式锁


    Redis的分布式锁

    1、分布式锁要解决的问题?

          比如说:某个查询数据库的接口,因为调用量比较大,所以加了缓存,并设定缓存过期后刷新,问题是当并发量比较大的时候,如果没有锁机制,那么缓存过期的瞬间,大量并发请求会穿透缓存直接查询数据库,造成雪崩效应,如果有锁机制,那么就可以控制只有一个请求去更新缓存,其它的请求视情况要么等待,要么使用过期的缓存。

         在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。其中,分布式锁是用来解决分布式应用中并发冲突的一种常用手段,实现方式有:基于zookeeper、redis、MySQL等。

         这篇文章主要分析redis分布式锁的使用以及注意事项。

    2、分布式锁设计目标

         可以保证在分布式部署的应用集群中互斥性,即同一个方法在同一操作只能被一台机器上的一个线程执行。程序出现异常锁能自动释放,避免死锁,锁超时

    3、redis的setnx

         1)基础使用

         查阅Redis的使用手册,可以看到:Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。

         基本语法:SETNX KEY_NAME VALUE

         2)容易遇到的坑

        比如下面这段代码

     1 <?php
     2 
     3 $ok = $redis->setNX($key, $value);
     4 
     5 if ($ok) {
     6     $cache->update();
     7     $redis->del($key);
     8 }
     9 

         我们来分析这样写可能会产生的问题:

        1)因为 SetNX 不具备设置过期时间的功能,所以我们需要借助 Expire 来设置,同时我们需要把两者用 Multi/Exec 包裹起来以确保请求的原子性,以免 SetNX 成功了 Expire 却失败了

    1 <?php
    2 $redis->multi();
    3 $redis->setNX($key, $value);
    4 $redis->expire($key, $ttl);
    5 $redis->exec();
    

        2)当多个请求到达时,虽然只有一个请求的 SetNX 可以成功,但是任何一个请求的 Expire 却都可以成功,如此就意味着即便获取不到锁,也可以刷新过期时间,如果请求比较密集的话,那么过期时间会一直被刷新,导致锁一直有效。于是乎我们需要在保证原子性的同时,有条件的执行 Expire,接着就需要写一个Lua 脚本来控制。

        3)但是这样一个简单的功能还需要写个Lua脚本,实在有些麻烦。其实 Redis从 2.6.12版本开始 ,SET 涵盖了 SETEX 的功能,并且 SET 本身已经包含了设置过期时间的功能,也就是说,我们前面需要的功能只用 SET 就可以实现。

     1 <?php
     2 
     3 $ok = $redis->set($key, $value, array('nx', 'ex' => $ttl));
     4 
     5 if ($ok) {
     6     $cache->update();
     7     $redis->del($key);
     8 }

    4、应用场景

         不同的应用场景,redis分布式锁的使用也是有区别的。

    1)单用户并发(对同一操作发起多次请求)

         单用户并发的场景有:支付、抽奖、领取奖励、刷新页面初始化用户数据等

         全局唯一锁(只有用户自己能拿到这把锁)

     1     public function singleTest($openid)
     2     {
     3         $redis = new RedisServer(REDIS_HOST, REDIS_PORT); //获取redis实例化对象
     4         $key = 'single_test'.$openid;  //这里openid是指用户唯一标识
     5         //设置锁
     6         $ok = $redis->set($key,1, array('nx', 'ex' => 10));
     7         if ($ok) {
     8             //更新缓存
     9             //$cache->update();
    10             if ($redis->get($key)) {
    11                 $redis->del($key);
    12             }
    13         }
    14     }

    2) 多用户并发(多用户同时对有限的公共资源进行修改)

          多用户并发的场景有:秒杀、抢购等(短时间内多用户争夺数量有限的物品)

          全局锁(也叫互斥锁、排他锁)任何一个时刻只有1人能够持有这把锁,其他人等待锁的释放,重新抢锁。

          实现:Redis setnx ,Memcached add ,MySQL 行锁、表锁。

         下面是用Redis来实现的一段代码:

     1     public function multiTest()
     2     {
     3         $redis = new RedisServer(REDIS_HOST, REDIS_PORT); //获取redis实例化对象
     4         $key = 'multi_test';
     5         $random = Common::generateRandom(); //引入一个随机值:唯一的字符串
     7         $ok = $redis->set($key, $random, array('nx', 'ex' => 10));
     8         if ($ok) {
     9             //更新缓存
    10             //...
    11 
    12             //先判断随机数,是同一个则删除锁
    13             if ($redis->get($key) == $random) {
    14                 $redis->del($key);
    15             }
    16         }
    17     }

       这里引入随机值的原因:

       如果一个请求更新缓存的时间比较长,甚至比锁的有效期还要长,导致在缓存更新过程中,锁就失效了,此时另一个请求会获取锁。

       但前一个请求在缓存更新完毕的时候, 如果不加以判直接删除锁,就会出现误删除其它请求创建的锁的情况。所以我们在创建锁的时候需要引入一个随机值

    参考链接:https://blog.huoding.com/2015/09/14/463

  • 相关阅读:
    设计模式之 观察者模式
    设计模式之 模板方法模式
    设计模式之 状态模式
    设计模式之 策略模式
    设计模式之 桥接模式
    设计模式之 外观模式
    设计模式之 代理模式
    设计模式之 装饰者模式
    设计模式之 适配器模式
    设计模式之 组合模式
  • 原文地址:https://www.cnblogs.com/hld123/p/14107053.html
Copyright © 2020-2023  润新知