• 关于redis实现分布式锁


    前言

    分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。本篇博客将介绍第二种方式,基于Redis实现分布式锁。虽然网上已经有各种介绍Redis分布式锁实现的博客,然而他们的实现却有着各种各样的问题,为了避免误人子弟,本篇博客将详细介绍如何正确地实现Redis分布式锁。

    可靠性

    首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

    1. 互斥性。在任意时刻,只有一个客户端能持有锁。
    2. 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
    3. 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
    4. 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

    代码实现

    组件依赖

    首先我们要通过Maven引入Jedis开源组件,在pom.xml文件加入下面的代码:

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>

    加锁代码

     1 package cn.hjf.redis.rediDemo.lock;
     2 
     3 import java.util.UUID;
     4 
     5 import redis.clients.jedis.Jedis;
     6 import redis.clients.jedis.Transaction;
     7 /**
     8  * redis setnx 实现分布式锁
     9  * 真实环境下应该是在分布式多进程的情况下
    10  * @author hjf
    11  */
    12 public class SimpleLock
    13 {
    14     Jedis conn = new Jedis("127.0.0.1", 6379);
    15     
    16     private final static String LOCK_NAME = "lock";
    17     
    18     // 获得锁  重入锁和非重入锁 
    19     // 设置超时时间 
    20     public String accequireLock(int timeOut){
    21         // 随机生成一个uuid
    22         String uuid = UUID.randomUUID().toString();
    23         // 结束时间 
    24         long end = System.currentTimeMillis() + timeOut;
    25         // 设置成功返回1  失败则返回0  
    26         // 当且仅当key不存在时将key设为value
    27         // 若给定的key已经存在 那么setnx不会做任何操作
    28         while(System.currentTimeMillis() < end){
    29             // setnx()和expire()加起来不是一个原子操作
    30             if(conn.setnx(LOCK_NAME, uuid).intValue() == 1){
    31                 // 增加redis的超时机制 一旦出现异常等情况可以自动去释放锁
    32                 conn.expire(LOCK_NAME, timeOut);   
    33                 return uuid;
    34             }
    35             
    36             // 检查是否设置超时机制(保证原子性)
    37             if(conn.ttl(LOCK_NAME) == -1){  
    38                 conn.expire(LOCK_NAME, timeOut);
    39             }
    40             
    41             try
    42             {
    43                 // 未获得锁时 休眠一段时间
    44                 Thread.sleep(1000);
    45             } catch (InterruptedException e)
    46             {
    47                 e.printStackTrace();
    48             }
    49         }
    50         
    51         return null;
    52     }
    53     
    54     // 释放锁
    55     public boolean releaseLock(String uuid){
    56         while(true){
    57             // 添加监听 一旦LOCK_NAME发生变化
    58             // 那么下面的事务有效
    59             conn.watch(LOCK_NAME);
    60             if(uuid.equals(conn.get(LOCK_NAME))){
    61                 // 通过事务来操作
    62                 Transaction transaction = conn.multi();
    63                 transaction.del(LOCK_NAME);
    64                 // 执行失败的时候会返回null
    65                 if(transaction.exec() == null){
    66                     continue;
    67                 }
    68                 // 执行成功
    69                 return true;
    70             }
    71             // 取消监听
    72             conn.unwatch();
    73             break;
    74         }
    75         
    76         return false;
    77     }
    78     
    79     public static void main(String[] args)
    80     {
    81         // 单机测试环境下 可以采用手动去删除redis库中对应的LOCK_NAME
    82         // 以便accequireLock可以获取到锁
    83         SimpleLock simpleLock = new SimpleLock();
    84         String uuid = simpleLock.accequireLock(10000);
    85         if(null != uuid){
    86             System.out.println("获取锁成功");
    87         }else{
    88             System.out.println("获取锁失败");
    89         }
    90     }
    91 }

    setnx()方法作用就是SET IF NOT EXIST,expire()方法就是给锁加一个过期时间。乍一看好像和前面的set()方法结果一样,然而由于这是两条Redis命令,不具有原子性,如果程序在执行完setnx()之后突然崩溃,导致锁没有设置过期时间。那么将会发生死锁。网上之所以有人这样实现,是因为低版本的jedis并不支持多参数的set()方法。

    可以在本地安装redis环境的前提下,进行测试。

  • 相关阅读:
    MySQL多实例配置
    MySQL8.0启动和关闭流程
    MySQL8.0初始化配置方式 ——维护使用
    MySQL多种连接方式
    MySQL 8.0用户及安全管理
    MySQL 5.7安装及版本升级到8.0
    五十六、linux 编程——UDP 编程模型
    五十五、linux 编程——TCP 连接和关闭过程及服务器的并发处理
    五十四、linux 编程——TCP 编程模型
    五十三、linux 编程——TCP 编程基本介绍
  • 原文地址:https://www.cnblogs.com/little-fly/p/9709618.html
Copyright © 2020-2023  润新知