Redis 实现同步锁

1、技术方案

1.1、redis的基本命令

1)SETNX命令(SET if Not eXists)

语法:SETNX key value

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

2)expire命令

语法:expire KEY seconds 

功能:设置key的过期时间。如果key已过期,将会被自动删除。

3)DEL命令

语法:DEL key [KEY …]

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

1.2、实现同步锁原理

1)加锁:“锁”就是一个存储在redis里的key-value对,key是把一组投资操作用字符串来形成唯一标识,value其实并不重要,因为只要这个唯一的key-value存在,就表示这个操作已经上锁。 

2)解锁:既然key-value对存在就表示上锁,那么释放锁就自然是在redis里删除key-value对。 

3)阻塞、非阻塞:阻塞式的实现,若线程发现已经上锁,会在特定时间内轮询锁。非阻塞式的实现,若发现线程已经上锁,则直接返回。

4)处理异常情况:假设当投资操作调用其他平台接口出现等待时,自然没有释放锁,这种情况下加入锁超时机制,用redis的expire命令为key设置超时时长,过了超时时间redis就会将这个key自动删除,即强制释放锁

(此步骤需在JAVA内部设置同样的超时机制,内部超时时长应小于或等于redis超时时长)。

1.3、处理流程图

     Redis 实现同步锁_第1张图片

2、代码实现

2.1、同步锁工具类

复制代码

 
  
  1. 1 package com.mic.synchrolock.util;

  2. 2

  3. 3 import java.util.ArrayList;

  4. 4 import java.util.List;

  5. 5 import java.util.UUID;

  6. 6

  7. 7 import javax.annotation.PostConstruct;

  8. 8 import javax.annotation.PreDestroy;

  9. 9

  10. 10 import org.apache.commons.logging.Log;

  11. 11 import org.apache.commons.logging.LogFactory;

  12. 12

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

  14. 14

  15. 15 import com.mic.constants.Constants;

  16. 16 import com.mic.constants.InvestType;

  17. 17

  18. 18 /**

  19. 19 * 分布式同步锁工具类

  20. 20 * @author Administrator

  21. 21 *

  22. 22 */

  23. 23 public class SynchrolockUtil {

  24. 24

  25. 25 private final Log logger = LogFactory.getLog(getClass());

  26. 26

  27. 27 @Autowired

  28. 28 private RedisClientTemplate redisClientTemplate;

  29. 29

  30. 30 public final String RETRYTYPE_WAIT = "1"; //加锁方法当对象已加锁时,设置为等待并轮询

  31. 31 public final String RETRYTYPE_NOWAIT = "0"; //加锁方法当对象已加锁时,设置为直接返回

  32. 32

  33. 33 private String requestTimeOutName = ""; //投资同步锁请求超时时间

  34. 34 private String retryIntervalName = ""; //投资同步锁轮询间隔

  35. 35 private String keyTimeoutName = ""; //缓存中key的失效时间

  36. 36 private String investProductSn = ""; //产品Sn

  37. 37 private String uuid; //对象唯一标识

  38. 38

  39. 39 private Long startTime = System.currentTimeMillis(); //首次调用时间

  40. 40 public Long getStartTime() {

  41. 41 return startTime;

  42. 42 }

  43. 43

  44. 44 List keyList = new ArrayList(); //缓存key的保存集合

  45. 45 public List getKeyList() {

  46. 46 return keyList;

  47. 47 }

  48. 48 public void setKeyList(List keyList) {

  49. 49 this.keyList = keyList;

  50. 50 }

  51. 51

  52. 52 @PostConstruct

  53. 53 public void init() {

  54. 54 uuid = UUID.randomUUID().toString();

  55. 55 }

  56. 56

  57. 57 @PreDestroy

  58. 58 public void destroy() {

  59. 59 this.unlock();

  60. 60 }

  61. 61

  62. 62

  63. 63 /**

  64. 64 * 根据传入key值,判断缓存中是否存在该key

  65. 65 * 存在-已上锁:判断retryType,轮询超时,或直接返回,返回ture

  66. 66 * 不存在-未上锁:将该放入缓存,返回false

  67. 67 * @param key

  68. 68 * @param retryType 当遇到上锁情况时 1:轮询;0:直接返回

  69. 69 * @return

  70. 70 */

  71. 71 public boolean islocked(String key,String retryType){

  72. 72 boolean flag = true;

  73. 73 logger.info("====投资同步锁设置轮询间隔、请求超时时长、缓存key失效时长====");

  74. 74 //投资同步锁轮询间隔 毫秒

  75. 75 Long retryInterval = Long.parseLong(Constants.getProperty(retryIntervalName));

  76. 76 //投资同步锁请求超时时间 毫秒

  77. 77 Long requestTimeOut = Long.parseLong(Constants.getProperty(requestTimeOutName));

  78. 78 //缓存中key的失效时间 秒

  79. 79 Integer keyTimeout = Integer.parseInt(Constants.getProperty(keyTimeoutName));

  80. 80

  81. 81 //调用缓存获取当前产品锁

  82. 82 logger.info("====当前产品key为:"+key+"====");

  83. 83 if(isLockedInRedis(key,keyTimeout)){

  84. 84 if("1".equals(retryType)){

  85. 85 //采用轮询方式等待

  86. 86 while (true) {

  87. 87 logger.info("====产品已被占用,开始轮询====");

  88. 88 try {

  89. 89 Thread.sleep(retryInterval);

  90. 90 } catch (InterruptedException e) {

  91. 91 logger.error("线程睡眠异常:"+e.getMessage(), e);

  92. 92 return flag;

  93. 93 }

  94. 94 logger.info("====判断请求是否超时====");

  95. 95 Long currentTime = System.currentTimeMillis(); //当前调用时间

  96. 96 long Interval = currentTime - startTime;

  97. 97 if (Interval > requestTimeOut) {

  98. 98 logger.info("====请求超时====");

  99. 99 return flag;

  100. 100 }

  101. 101 if(!isLockedInRedis(key,keyTimeout)){

  102. 102 logger.info("====轮询结束,添加同步锁====");

  103. 103 flag = false;

  104. 104 keyList.add(key);

  105. 105 break;

  106. 106 }

  107. 107 }

  108. 108 }else{

  109. 109 //不等待,直接返回

  110. 110 logger.info("====产品已被占用,直接返回====");

  111. 111 return flag;

  112. 112 }

  113. 113

  114. 114 }else{

  115. 115 logger.info("====产品未被占用,添加同步锁====");

  116. 116 flag = false;

  117. 117 keyList.add(key);

  118. 118 }

  119. 119 return flag;

  120. 120 }

  121. 121

  122. 122 /**

  123. 123 * 在缓存中查询key是否存在

  124. 124 * 若存在则返回true;

  125. 125 * 若不存在则将key放入缓存,设置过期时间,返回false

  126. 126 * @param key

  127. 127 * @param keyTimeout key超时时间单位是秒

  128. 128 * @return

  129. 129 */

  130. 130 boolean isLockedInRedis(String key,int keyTimeout){

  131. 131 logger.info("====在缓存中查询key是否存在====");

  132. 132 boolean isExist = false;

  133. 133 //与redis交互,查询对象是否上锁

  134. 134 Long result = this.redisClientTemplate.setnx(key, uuid);

  135. 135 logger.info("====上锁 result = "+result+"====");

  136. 136 if(null != result && 1 == Integer.parseInt(result.toString())){

  137. 137 logger.info("====设置缓存失效时长 = "+keyTimeout+"秒====");

  138. 138 this.redisClientTemplate.expire(key, keyTimeout);

  139. 139 logger.info("====上锁成功====");

  140. 140 isExist = false;

  141. 141 }else{

  142. 142 logger.info("====上锁失败====");

  143. 143 isExist = true;

  144. 144 }

  145. 145 return isExist;

  146. 146 }

  147. 147

  148. 148 /**

  149. 149 * 根据传入key,对该产品进行解锁

  150. 150 * @param key

  151. 151 * @return

  152. 152 */

  153. 153 public void unlock(){

  154. 154 //与redis交互,对产品解锁

  155. 155 if(keyList.size()>0){

  156. 156 for(String key : this.keyList){

  157. 157 String value = this.redisClientTemplate.get(key);

  158. 158 if(null != value && !"".equals(value)){

  159. 159 if(uuid.equals(value)){

  160. 160 logger.info("====解锁key:"+key+" value="+value+"====");

  161. 161 this.redisClientTemplate.del(key);

  162. 162 }else{

  163. 163 logger.info("====待解锁集合中key:"+key+" value="+value+"与uuid不匹配====");

  164. 164 }

  165. 165 }else{

  166. 166 logger.info("====待解锁集合中key="+key+"的value为空====");

  167. 167 }

  168. 168 }

  169. 169 }else{

  170. 170 logger.info("====待解锁集合为空====");

  171. 171 }

  172. 172 }

  173. 173

  174. 174

  175. 175 }

复制代码

2.2、业务调用模拟样例

复制代码

 
  
  1. 1   //获取同步锁工具类

  2. 2   SynchrolockUtil synchrolockUtil = SpringUtils.getBean("synchrolockUtil");

  3. 3   //获取需上锁资源的KEY

  4. 4   String key = "abc";

  5. 5   //查询是否上锁,上锁轮询,未上锁加锁

  6. 6   boolean isLocked = synchrolockUtil.islocked(key,synchrolockUtil.RETRYTYPE_WAIT);

  7. 7   //判断上锁结果

  8. 8   if(isLocked){

  9. 9   logger.error("同步锁请求超时并返回 key ="+key);

  10. 10   }else{

  11. 11   logger.info("====同步锁加锁陈功====");

  12. 12   }

  13. 13

  14. 14   try {

  15. 15

  16. 16   //执行业务处理

  17. 17

  18. 18   } catch (Exception e) {

  19. 19   logger.error("业务异常:"+e.getMessage(), e);

  20. 20   }finally{

  21. 21   //解锁

  22. 22    synchrolockUtil.unlock();

  23. 23   }

复制代码

2.3、如果业务处理内部,还有嵌套加锁需求,只需将对象传入方法内部,加锁成功后将key值追加到集合中即可

ps:实际实现中还需要jedis工具类,需额外添加调用

你可能感兴趣的:(Redis 实现同步锁)