zoukankan      html  css  js  c++  java
  • DCS实践干货:使用Redis实现分布式锁

    场景介绍

    很多互联网场景(如商品秒杀,论坛回帖盖楼等),需要用加锁的方式,以对某种资源进行顺序访问控制。如果应用服务集群部署,则涉及到对分布式应用加锁。当前分布式加锁主要有三种方式:(磁盘)数据库、缓存数据库、Zookeeper。接下里让我们一起看看加锁实践过程。

     

    加锁实现

    1  package dcsDemo01;

    2   

    3  import java.util.UUID;

    4   

    5  import redis.clients.jedis.Jedis;

    6   

    7  public class DistributedLock {

    8      private final String host = "192.168.0.220";

    9      private final int port = 6379;

    10   

    11      private static final String SUCCESS = "OK";

    12      private static final String SET_IF_NOT_EXIST = "NX";

    13      private static final String EXPIRE_TIME = "PX";

    14   

    15      public  DistributedLock(){}

    16   

    17      /*

    18       * @param lockName      锁名

    19       * @param timeout       获取锁的超时时间

    20       * @param lockTimeout   锁的有效时间

    21       * @return              锁的标识

    22       */

    23      public String getLockWithTimeout(String lockName, long timeout, long lockTimeout) {

    24          String ret = null;

    25          Jedis jedisClient = new Jedis(host, port);

    26   

    27          try {

    28              String authMsg = jedisClient.auth("Demo@123");

    29              if (!SUCCESS.equals(authMsg)) {

    30                  System.out.println("AUTH FAILED: " + authMsg);

    31              }

    32   

    33              String identifier = UUID.randomUUID().toString();

    34              String lockKey = "DLock:" + lockName;

    35              long end = System.currentTimeMillis() + timeout;

    36   

    37              while(System.currentTimeMillis() < end) {

    38                  String result = jedisClient.set(lockKey, identifier, SET_IF_NOT_EXIST, EXPIRE_TIME, lockTimeout);

    39                  if(SUCCESS.equals(result)) {

    40                      ret = identifier;

    41                      break;

    42                  }

    43   

    44                  try {

    45                      Thread.sleep(2);

    46                  } catch (InterruptedException e) {

    47                      Thread.currentThread().interrupt();

    48                  }

    49              }

    50          }

    51          catch (Exception e) {

    52              e.printStackTrace();

    53          }finally {

    54              jedisClient.quit();

    55              jedisClient.close();

    56          }

    57   

    58          return ret;

    59      }

    60   

    61      /*

    62       * @param lockName        锁名

    63       * @param identifier    锁的标识

    64       */

    65      public void releaseLock(String lockName, String identifier) {

    66          Jedis jedisClient = new Jedis(host, port);

    67   

    68          try {

    69              String authMsg = jedisClient.auth("Demo@123");

    70              if (!SUCCESS.equals(authMsg)) {

    71                  System.out.println("AUTH FAILED: " + authMsg);

    72              }

    73   

    74              String lockKey = "DLock:" + lockName;

    75              if(identifier.equals(jedisClient.get(lockKey))) {

    76                  jedisClient.del(lockKey);

    77              }

    78          }

    79          catch (Exception e) {

    80              e.printStackTrace();

    81          }finally {

    82              jedisClient.quit();

    83              jedisClient.close();

    84          }

    85      }

    86  }

     

    测试代码

    假设20个线程对10mate10手机进行抢购:

    package dcsDemo01;

    import java.util.UUID;

     

    public class CaseTest {

        public static void main(String[] args) {

            ServiceOrder service = new ServiceOrder();

            for (int i = 0; i < 20; i++) {

                ThreadBuy client = new ThreadBuy(service);

                client.start();

            }

        }

    }

     

    class ServiceOrder {

        private final int MAX = 10;

     

        DistributedLock DLock = new DistributedLock();

     

        int n = 10;

     

        public void handleOder() {

            String userName = UUID.randomUUID().toString().substring(0,8) + Thread.currentThread().getName();

            String identifier = DLock.getLockWithTimeout("Huawei Mate 10", 10000, 2000);

            System.out.println("正在为用户:" + userName + " 处理订单");

            if(n > 0) {

                int num = MAX - n + 1;

                System.out.println("用户:"+ userName + "购买第" + num + "台,剩余" + (--n) + "");

            }else {

                System.out.println("用户:"+ userName + "无法购买,已售罄!");

            }

            DLock.releaseLock("Huawei Mate 10", identifier);

        }

    }

     

    class ThreadBuy extends Thread {

        private ServiceOrder service;

     

        public ThreadBuy(ServiceOrder service) {

            this.service = service;

        }

     

        @Override

        public void run() {

            service.handleOder();

        }

    }

     

    运行结果

    配置好实际的缓存实例连接地址、端口与连接密码,运行代码,得到以下结果:

    正在为用户:eee56fb7Thread-16 处理订单

    用户:eee56fb7Thread-16购买第1台,剩余9

    正在为用户:d6521816Thread-2 处理订单

    用户:d6521816Thread-2购买第2台,剩余8

    正在为用户:d7b3b983Thread-19 处理订单

    用户:d7b3b983Thread-19购买第3台,剩余7

    正在为用户:36a6b97aThread-15 处理订单

    用户:36a6b97aThread-15购买第4台,剩余6

    正在为用户:9a973456Thread-1 处理订单

    用户:9a973456Thread-1购买第5台,剩余5

    正在为用户:03f1de9aThread-14 处理订单

    用户:03f1de9aThread-14购买第6台,剩余4

    正在为用户:2c315ee6Thread-11 处理订单

    用户:2c315ee6Thread-11购买第7台,剩余3

    正在为用户:2b03b7c0Thread-12 处理订单

    用户:2b03b7c0Thread-12购买第8台,剩余2

    正在为用户:75f25749Thread-0 处理订单

    用户:75f25749Thread-0购买第9台,剩余1

    正在为用户:26c71db5Thread-18 处理订单

    用户:26c71db5Thread-18购买第10台,剩余0

    正在为用户:c32654dbThread-17 处理订单

    用户:c32654dbThread-17无法购买,已售罄!

    正在为用户:df94370aThread-7 处理订单

    用户:df94370aThread-7无法购买,已售罄!

    正在为用户:0af94cddThread-5 处理订单

    用户:0af94cddThread-5无法购买,已售罄!

    正在为用户:e52428a4Thread-13 处理订单

    用户:e52428a4Thread-13无法购买,已售罄!

    正在为用户:46f91208Thread-10 处理订单

    用户:46f91208Thread-10无法购买,已售罄!

    正在为用户:e0ca87bbThread-9 处理订单

    用户:e0ca87bbThread-9无法购买,已售罄!

    正在为用户:f385af9aThread-8 处理订单

    用户:f385af9aThread-8无法购买,已售罄!

    正在为用户:46c5f498Thread-6 处理订单

    用户:46c5f498Thread-6无法购买,已售罄!

    正在为用户:935e0f50Thread-3 处理订单

    用户:935e0f50Thread-3无法购买,已售罄!

    正在为用户:d3eaae29Thread-4 处理订单

    用户:d3eaae29Thread-4无法购买,已售罄!

     

    不加锁场景

    如果注释掉加锁代码,变成无锁情况,则抢购无序。

    //测试类中注释两行用于加锁的代码:
    public void handleOder() {
        String userName = UUID.randomUUID().toString().substring(0,8) + Thread.currentThread().getName();
        //加锁代码
        //String identifier = DLock.getLockWithTimeout("Huawei Mate 10", 10000, 2000);
        System.out.println("正在为用户:" + userName + " 处理订单");
        if(n > 0) {
            int num = MAX - n + 1;
            System.out.println("用户:"+ userName + "够买第" + num + "台,剩余" + (--n) + "");
        }else {
            System.out.println("用户:"+ userName + "无法够买,已售罄!");
        }
        //加锁代码
        //DLock.releaseLock("Huawei Mate 10", identifier);
    }

     

    注释加锁代码后的运行结果,可以看出处理过程是无序的:

    正在为用户:e04934ddThread-5 处理订单
    正在为用户:a4554180Thread-0 处理订单
    用户:a4554180Thread-0购买第2台,剩余8
    正在为用户:b58eb811Thread-10 处理订单
    用户:b58eb811Thread-10购买第3台,剩余7
    正在为用户:e8391c0eThread-19 处理订单
    正在为用户:21fd133aThread-13 处理订单
    正在为用户:1dd04ff4Thread-6 处理订单
    用户:1dd04ff4Thread-6购买第6台,剩余4
    正在为用户:e5977112Thread-3 处理订单
    正在为用户:4d7a8a2bThread-4 处理订单
    用户:e5977112Thread-3购买第7台,剩余3
    正在为用户:18967410Thread-15 处理订单
    用户:18967410Thread-15购买第9台,剩余1
    正在为用户:e4f51568Thread-14 处理订单
    用户:21fd133aThread-13购买第5台,剩余5
    用户:e8391c0eThread-19购买第4台,剩余6
    正在为用户:d895d3f1Thread-12 处理订单
    用户:d895d3f1Thread-12无法购买,已售罄!
    正在为用户:7b8d2526Thread-11 处理订单
    用户:7b8d2526Thread-11无法购买,已售罄!
    正在为用户:d7ca1779Thread-8 处理订单
    用户:d7ca1779Thread-8无法购买,已售罄!
    正在为用户:74fca0ecThread-1 处理订单
    用户:74fca0ecThread-1无法购买,已售罄!
    用户:e04934ddThread-5购买第1台,剩余9
    用户:e4f51568Thread-14购买第10台,剩余0
    正在为用户:aae76a83Thread-7 处理订单
    用户:aae76a83Thread-7无法购买,已售罄!
    正在为用户:c638d2cfThread-2 处理订单
    用户:c638d2cfThread-2无法购买,已售罄!
    正在为用户:2de29a4eThread-17 处理订单
    用户:2de29a4eThread-17无法购买,已售罄!
    正在为用户:40a46ba0Thread-18 处理订单
    用户:40a46ba0Thread-18无法购买,已售罄!
    正在为用户:211fd9c7Thread-9 处理订单
    用户:211fd9c7Thread-9无法购买,已售罄!
    正在为用户:911b83fcThread-16 处理订单
    用户:911b83fcThread-16无法购买,已售罄!
    用户:4d7a8a2bThread-4购买第8台,剩余2

     

    总的来说,使用DCS服务中Redis类型的缓存实例实现分布式加锁,有几大优势:

    1、加锁操作简单,使用SETGETDEL等几条简单命令即可实现锁的获取和释放。

    2、性能优越,缓存数据的读写优于磁盘数据库与Zookeeper

    3、可靠性强,DCS有主备和集群实例类型,避免单点故障。

     

    以上代码实现仅展示使用DCS服务进行加锁访问的便捷性,具体技术实现需要考虑死锁、锁的检查等情况,欢迎点击分布式缓存服务DCS了解更多。

  • 相关阅读:
    045_分页查询插件 bootstrap_pagination
    Kali中文乱码问题
    将一行很长的js代码格式化输出方便查看
    使用gcc编译c语言解码ascii码
    Netcat
    阿里云万网注册个人域名并配置解析主机
    使用阿里云服务器配置frp实现Windows系统RDP内网穿透
    mysql数据库行级锁的使用(二)
    关于mysql数据库行级锁的使用(一)
    关于volatile的可见性问题
  • 原文地址:https://www.cnblogs.com/middleware/p/9237098.html
Copyright © 2011-2022 走看看