好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

redis分布式锁的实现原理详解

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

1.互斥性。在任意时刻,只有一个客户端能持有锁。

2.不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

3.具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

4.解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

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

?

1

2

3

4

5

6

7

8

9

< dependency >

     < groupId >org.springframework.boot</ groupId >

     < artifactId >spring-boot-starter-data-redis</ artifactId >

</ dependency >

< dependency >

     < groupId >redis.clients</ groupId >

     < artifactId >jedis</ artifactId >

     < version >3.1.0</ version >

</ dependency >

分布式锁实现代码,DistributedLock.java

?

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

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPool;

import redis.clients.jedis.JedisPoolConfig;

import redis.clients.jedis.Transaction;

import redis.clients.jedis.exceptions.JedisException;

import java.util.List;

import java.util.UUID;

/**

  * @author swadian

  * @date 2022/3/4

  * @Version 1.0

  * @describetion Redis分布式锁原理

  */

public class DistributedLock {

     //redis连接池

     private static JedisPool jedisPool;

     static {

         JedisPoolConfig config = new JedisPoolConfig();

         // 设置最大连接数

         config.setMaxTotal( 200 );

         // 设置最大空闲数

         config.setMaxIdle( 8 );

         // 设置最大等待时间

         config.setMaxWaitMillis( 1000 * 100 );

         // 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的

         config.setTestOnBorrow( true );

         jedisPool = new JedisPool(config, "192.168.3.27" , 6379 , 3000 );

     }

     /**

      * 加锁

      * @param lockName       锁的key

      * @param acquireTimeout 获取锁的超时时间

      * @param timeout        锁的超时时间

      * @return 锁标识

      * Redis Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。

      * 设置成功,返回 1 。 设置失败,返回 0 。

      */

     public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {

         Jedis jedis = null ;

         String retIdentifier = null ;

         try {

             // 获取连接

             jedis = jedisPool.getResource();

             // value值->随机生成一个String

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

             // key值->即锁名

             String lockKey = "lock:" + lockName;

             // 超时时间->上锁后超过此时间则自动释放锁 毫秒转成->秒

             int lockExpire = ( int ) (timeout / 1000 );

             // 获取锁的超时时间->超过这个时间则放弃获取锁

             long end = System.currentTimeMillis() + acquireTimeout;

             while (System.currentTimeMillis() < end) { //在获取锁时间内

                 if (jedis.setnx(lockKey, identifier) == 1 ) { //关键:设置锁

                     jedis.expire(lockKey, lockExpire);

                     // 返回value值,用于释放锁时间确认

                     retIdentifier = identifier;

                     return retIdentifier;

                 }

                 // ttl以秒为单位返回 key 的剩余过期时间,返回-1代表key没有设置超时时间,为key设置一个超时时间

                 if (jedis.ttl(lockKey) == - 1 ) {

                     jedis.expire(lockKey, lockExpire);

                 }

                 try {

                     Thread.sleep( 10 );

                 } catch (InterruptedException e) {

                     Thread.currentThread().interrupt();

                 }

             }

         } catch (JedisException e) {

             e.printStackTrace();

         } finally {

             if (jedis != null ) {

                 jedis.close();

             }

         }

         return retIdentifier;

     }

     /**

      * 释放锁

      * @param lockName   锁的key

      * @param identifier 释放锁的标识

      * @return

      */

     public boolean releaseLock(String lockName, String identifier) {

         Jedis jedis = null ;

         String lockKey = "lock:" + lockName;

         boolean retFlag = false ;

         try {

             jedis = jedisPool.getResource();

             while ( true ) {

                 // 监视lock,准备开始redis事务

                 jedis.watch(lockKey);

                 // 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁

                 if (identifier.equals(jedis.get(lockKey))) {

                     Transaction transaction = jedis.multi(); //开启redis事务

                     transaction.del(lockKey);

                     List<Object> results = transaction.exec(); //提交redis事务

                     if (results == null ) { //提交失败

                         continue ; //继续循环

                     }

                     retFlag = true ; //提交成功

                 }

                 jedis.unwatch(); //解除监控

                 break ;

             }

         } catch (JedisException e) {

             e.printStackTrace();

         } finally {

             if (jedis != null ) {

                 jedis.close();

             }

         }

         return retFlag;

     }

}

为了验证它,我们创建SkillService.java业务类

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

import lombok.extern.slf4j.Slf4j;

@Slf4j

public class SkillService {

     final DistributedLock lock = new DistributedLock();

     public static final String LOCK_KEY = "lock_resource" ;

     int n = 500 ;

     /**

      * 线程业务方法

      */

     public void seckill() {

         // 返回锁的value值,供释放锁时候进行判断

         String identifier = lock.lockWithTimeout(LOCK_KEY, 5000 , 1000 );

         log.info( "线程:" +Thread.currentThread().getName() + "获得了锁" );

         log.info( "剩余数量:{}" ,--n);

         lock.releaseLock(LOCK_KEY, identifier);

     }

}

如果找不到@Slf4j日志,在 pom.xml 文件加入下面的代码:

?

1

2

3

4

5

<!--@Slf4j日志依赖组件-->

   < dependency >

       < groupId >org.projectlombok</ groupId >

       < artifactId >lombok</ artifactId >

</ dependency >

编辑一个测试类TestLock.java

?

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

/**

  * @author swadian

  * @date 2022/3/4

  * @Version 1.0

  */

public class TestLock {

     public static void main(String[] args) {

         SkillService service = new SkillService();

         for ( int i = 10 ; i < 60 ; i++) { //开50个线程

             SkillThread skillThread = new SkillThread(service, "skillThread->" + i);

             skillThread.start();

         }

     }

}

class SkillThread extends Thread {

     private SkillService skillService;

     public SkillThread(SkillService skillService, String skillThreadName) {

         super (skillThreadName);

         this .skillService = skillService;

     }

     @Override

     public void run() {

         skillService.seckill();

     }

}

测试结果显示,加锁后剩余数量全部是顺序串行的,499,498,497...

 我们修改SkillService.java业务类,注释掉加锁逻辑

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

@Slf4j

public class SkillService {

     final DistributedLock lock = new DistributedLock();

     public static final String LOCK_KEY = "lock_resource" ;

     int n = 500 ;

     /**

      * 线程业务方法

      */

     public void seckill() {

         // 返回锁的value值,供释放锁时候进行判断

         //String identifier = lock.lockWithTimeout(LOCK_KEY, 5000, 1000);

         log.info( "线程:" +Thread.currentThread().getName() + "获得了锁" );

         log.info( "剩余数量:{}" ,--n);

         //lock.releaseLock(LOCK_KEY, identifier);

     }

}

重新执行测试,注释掉加锁逻辑后,剩余数量全部是乱序的,472,454,452...

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注的更多内容!   

原文链接:https://blog.csdn.net/swadian2008/article/details/123312714

查看更多关于redis分布式锁的实现原理详解的详细内容...

  阅读:17次