vlambda博客
学习文章列表

Redis 系列 -- SpringBoot中 基于 Redis 实现分布式锁

分布式锁应用场景

锁的应用常出现在一些多线程、高并发的场景中,如秒杀、抢购、12306抢票,还有一些商品库存管理等。在一般的单体应用中,我们通常使用 synchronized、ReentrantLock 等来进行共享变量的管理,从而实现线程锁功能。但随着互联网发展、用户的剧增,单体应用已无法满足需求,多服务器部署、分布式应用越来越被广泛使用。但问题也出现了,对于之前的基于本地变量锁的形式,在不同服务器间无法共享。所以分布式锁应运而生,如基于中间件 redis 、zookeeper等实现的分布式锁。

为什么使用redis 分布式锁

1、redis 读写快

与mysql等传统数据库进行内存、磁盘I\O 流操作过程相比,redis 基于内存操作和I\O多路复用具有更快的读写效率。

2、redis 单线程 操作简单

基于单线程设计,所以redis在处理一些并发问题上具有天生的优势。

使用命令

SETNX命令

SETNX key value

当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

GETSET命令

GETSET key value

将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。

 GET命令

GET key

返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。

DEL命令

DEL key

删除给定的一个或多个 key ,不存在的 key 会被忽略。

 SpringBoot 中 代码实现

redis锁对象

 
   
   
 
  1. package com.tcwong.redis.util;


  2. import java.util.concurrent.TimeUnit;


  3. /**

  4. * redis锁

  5. */

  6. public interface RedisLock {


  7. /**

  8. * 获取锁

  9. * [@param] tryMillSecondsTime 尝试时间 秒

  10. * [@return]

  11. */

  12. boolean tryLock(long tryMillSecondsTime);


  13. /**

  14. * 获取锁

  15. * [@param] tryTime 尝试时间

  16. * [@param] timeUnit 时间类型

  17. * [@return]

  18. */

  19. boolean tryLock(long tryTime, TimeUnit timeUnit);


  20. /**

  21. * 释放锁

  22. */

  23. void unLock();


  24. /**

  25. * 重制锁

  26. */

  27. void reInit();

  28. }



  29. package com.tcwong.redis.util;


  30. import org.springframework.data.redis.core.RedisTemplate;

  31. import org.springframework.data.redis.core.ValueOperations;


  32. import java.util.concurrent.TimeUnit;


  33. /**

  34. * redis锁

  35. */

  36. public class RedisLockImpl implements RedisLock {


  37. private RedisTemplate redisTemplate;


  38. private String key;


  39. private long expireTime;


  40. private long tryTime;


  41. private long maxTryTime;


  42. private ValueOperations valueOperations;


  43. public RedisLockImpl(RedisTemplate redisTemplate, String key, Long expireTime, Long maxTryTime) {

  44. this.redisTemplate = redisTemplate;

  45. this.key = key;

  46. this.expireTime = expireTime;

  47. this.maxTryTime = maxTryTime;

  48. this.valueOperations = redisTemplate.opsForValue();

  49. }


  50. /**

  51. * 获取锁

  52. * @return

  53. */

  54. private boolean tryLock() {

  55. while (this.tryTime >= 0) {

  56. Long expires = System.currentTimeMillis() + this.expireTime ;

  57. String expiresValue = String.valueOf(expires);

  58. Boolean ifAbsentFlag = this.valueOperations.setIfAbsent(this.key, expiresValue);

  59. if (ifAbsentFlag) {

  60. redisTemplate.expire(this.key, this.expireTime, TimeUnit.MILLISECONDS);

  61. return true;

  62. }

  63. String lastTimeValue = (String)this.valueOperations.get(this.key);

  64. if (lastTimeValue != null && Long.valueOf(lastTimeValue) < System.currentTimeMillis()) {

  65. String oldTimeValue = (String) this.valueOperations.getAndSet(this.key, expiresValue);

  66. this.redisTemplate.expire(this.key, this.expireTime, TimeUnit.MILLISECONDS);

  67. if (oldTimeValue != null && oldTimeValue.equals(lastTimeValue)) {

  68. return true;

  69. }

  70. }

  71. this.tryTime -= 100;


  72. try {

  73. TimeUnit.MILLISECONDS.sleep(100);

  74. } catch (InterruptedException e) {

  75. }

  76. }

  77. return false;

  78. }


  79. /**

  80. * 获取锁

  81. * @param tryMillSecondsTime 尝试时长

  82. * @return

  83. */

  84. @Override

  85. public boolean tryLock(long tryMillSecondsTime) {

  86. this.tryTime = tryMillSecondsTime > this.maxTryTime ? maxTryTime : tryMillSecondsTime;

  87. return this.tryLock();

  88. }


  89. /**

  90. * 获取锁

  91. * @param tryTime 尝试时间

  92. * @param timeUnit 时间类型

  93. * @return

  94. */

  95. @Override

  96. public boolean tryLock(long tryTime, TimeUnit timeUnit) {

  97. return this.tryLock(timeUnit.toMillis(tryTime));

  98. }


  99. /**

  100. * 释放锁

  101. */

  102. @Override

  103. public void unLock() {

  104. this.redisTemplate.delete(this.key);

  105. }


  106. /**

  107. * 重制锁

  108. */

  109. @Override

  110. public void reInit() {

  111. this.redisTemplate.expire(this.key, 0 , TimeUnit.MILLISECONDS);

  112. }

  113. }

获取redis 锁
 
   
   
 
  1. package com.tcwong.redis.util;


  2. import java.util.concurrent.TimeUnit;


  3. /**

  4. * 获取锁对象

  5. */

  6. public interface RedisLockFactory {

  7. /**

  8. * 获取锁

  9. * @param key redis锁key

  10. * @return

  11. */

  12. RedisLock getLock(String key);


  13. /**

  14. * 获取锁

  15. * @param key redis锁key

  16. * @param tryTime 尝试时间

  17. * @param timeUnit 时间类型

  18. * @return

  19. */

  20. RedisLock getLock(String key, int tryTime, TimeUnit timeUnit);

  21. }



  22. package com.tcwong.redis.util;


  23. import org.springframework.beans.factory.annotation.Autowired;

  24. import org.springframework.data.redis.core.RedisTemplate;

  25. import org.springframework.stereotype.Component;


  26. import java.util.concurrent.TimeUnit;


  27. /**

  28. * 获取锁对象

  29. */

  30. @Component

  31. public class RedisLockFactoryImpl implements RedisLockFactory {


  32. @Autowired

  33. private RedisTemplate redisTemplate;


  34. private long expireTime = 0 ;


  35. private long maxTryTime = 1000 ;


  36. private static final String REDIS_KEY_PRE = "REDIS_KEY_PRE";


  37. /**

  38. * 获取锁

  39. * @param key redis锁key

  40. * @return

  41. */

  42. @Override

  43. public RedisLock getLock(String key) {

  44. return new RedisLockImpl(this.redisTemplate,REDIS_KEY_PRE + key,this.expireTime,this.maxTryTime);

  45. }


  46. /**

  47. * 获取锁

  48. * @param key redis锁key

  49. * @param tryTime 尝试时间

  50. * @param timeUnit 时间类型

  51. * @return

  52. */

  53. @Override

  54. public RedisLock getLock(String key, int tryTime, TimeUnit timeUnit) {

  55. return new RedisLockImpl(this.redisTemplate, REDIS_KEY_PRE + key,timeUnit.toMillis(tryTime),this.maxTryTime);

  56. }

  57. }


《 完 》


谢谢大家持续关注TC

你们的每一个交流和【在看】都是TC持续更新的动力