• 结合 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、处理流程图

         

    2、代码实现

    2.1、同步锁工具类

      1 package com.mic.synchrolock.util;
      2 
      3 import java.util.ArrayList;
      4 import java.util.List;
      5 import java.util.UUID;
      6 
      7 import javax.annotation.PostConstruct;
      8 import javax.annotation.PreDestroy;
      9 
     10 import org.apache.commons.logging.Log;
     11 import org.apache.commons.logging.LogFactory;
     12 
     13 import org.springframework.beans.factory.annotation.Autowired;
     14 
     15 import com.mic.constants.Constants;
     16 import com.mic.constants.InvestType;
     17 
     18 /**
     19  * 分布式同步锁工具类
     20  * @author Administrator
     21  *
     22  */
     23 public class SynchrolockUtil {    
     24     
     25     private final Log logger = LogFactory.getLog(getClass());
     26     
     27     @Autowired
     28     private RedisClientTemplate redisClientTemplate;
     29 
     30     public final String RETRYTYPE_WAIT = "1";     //加锁方法当对象已加锁时,设置为等待并轮询
     31     public final String RETRYTYPE_NOWAIT = "0";     //加锁方法当对象已加锁时,设置为直接返回
     32     
     33     private String requestTimeOutName = "";     //投资同步锁请求超时时间
     34     private String retryIntervalName = "";         //投资同步锁轮询间隔
     35     private String keyTimeoutName = "";        //缓存中key的失效时间
     36     private String investProductSn = "";         //产品Sn
     37     private String uuid;                //对象唯一标识
     38     
     39     private Long startTime = System.currentTimeMillis(); //首次调用时间
     40     public Long getStartTime() {
     41         return startTime;
     42     }
     43 
     44     List<String> keyList = new ArrayList<String>();    //缓存key的保存集合
     45     public List<String> getKeyList() {
     46         return keyList;
     47     }
     48     public void setKeyList(List<String> keyList) {
     49         this.keyList = keyList;
     50     }
     51 
     52     @PostConstruct
     53     public void init() {
     54         uuid = UUID.randomUUID().toString();
     55     }
     56     
     57     @PreDestroy
     58     public void destroy() {
     59         this.unlock();
     60     }
     61     
     62 
     63     /**
     64      * 根据传入key值,判断缓存中是否存在该key
     65      * 存在-已上锁:判断retryType,轮询超时,或直接返回,返回ture
     66      * 不存在-未上锁:将该放入缓存,返回false
     67      * @param key
     68      * @param retryType 当遇到上锁情况时  1:轮询;0:直接返回
     69      * @return
     70      */
     71     public boolean islocked(String key,String retryType){
     72         boolean flag = true;
     73         logger.info("====投资同步锁设置轮询间隔、请求超时时长、缓存key失效时长====");
     74         //投资同步锁轮询间隔 毫秒
     75         Long retryInterval = Long.parseLong(Constants.getProperty(retryIntervalName));
     76         //投资同步锁请求超时时间 毫秒
     77         Long requestTimeOut = Long.parseLong(Constants.getProperty(requestTimeOutName));
     78         //缓存中key的失效时间 秒
     79         Integer keyTimeout =  Integer.parseInt(Constants.getProperty(keyTimeoutName));
     80         
     81         //调用缓存获取当前产品锁
     82         logger.info("====当前产品key为:"+key+"====");
     83         if(isLockedInRedis(key,keyTimeout)){
     84             if("1".equals(retryType)){
     85                 //采用轮询方式等待
     86                 while (true) {
     87                     logger.info("====产品已被占用,开始轮询====");
     88                     try {
     89                         Thread.sleep(retryInterval);
     90                     } catch (InterruptedException e) {
     91                         logger.error("线程睡眠异常:"+e.getMessage(), e);
     92                         return flag;
     93                     }
     94                     logger.info("====判断请求是否超时====");
     95                     Long currentTime = System.currentTimeMillis(); //当前调用时间
     96                     long Interval = currentTime - startTime;
     97                     if (Interval > requestTimeOut) {
     98                         logger.info("====请求超时====");
     99                         return flag;
    100                     }
    101                     if(!isLockedInRedis(key,keyTimeout)){
    102                         logger.info("====轮询结束,添加同步锁====");
    103                         flag = false;
    104                         keyList.add(key);
    105                         break;
    106                     }
    107                 }
    108             }else{
    109                 //不等待,直接返回
    110                 logger.info("====产品已被占用,直接返回====");
    111                 return flag;
    112             }
    113             
    114         }else{
    115             logger.info("====产品未被占用,添加同步锁====");
    116             flag = false;
    117             keyList.add(key);
    118         }
    119         return flag;
    120     }
    121     
    122     /**
    123      * 在缓存中查询key是否存在
    124      * 若存在则返回true;
    125      * 若不存在则将key放入缓存,设置过期时间,返回false
    126      * @param key
    127      * @param keyTimeout key超时时间单位是秒
    128      * @return
    129      */
    130     boolean isLockedInRedis(String key,int keyTimeout){
    131         logger.info("====在缓存中查询key是否存在====");
    132         boolean isExist = false; 
    133         //与redis交互,查询对象是否上锁
    134         Long result = this.redisClientTemplate.setnx(key, uuid);
    135         logger.info("====上锁 result = "+result+"====");
    136         if(null != result && 1 == Integer.parseInt(result.toString())){
    137             logger.info("====设置缓存失效时长 = "+keyTimeout+"秒====");
    138             this.redisClientTemplate.expire(key, keyTimeout);
    139             logger.info("====上锁成功====");
    140             isExist = false;
    141         }else{
    142             logger.info("====上锁失败====");
    143             isExist = true;
    144         }
    145         return isExist;
    146     }
    147     
    148     /**
    149      * 根据传入key,对该产品进行解锁
    150      * @param key
    151      * @return
    152      */
    153     public void unlock(){
    154         //与redis交互,对产品解锁
    155         if(keyList.size()>0){
    156             for(String key : this.keyList){
    157                 String value = this.redisClientTemplate.get(key);
    158                 if(null != value && !"".equals(value)){
    159                     if(uuid.equals(value)){
    160                         logger.info("====解锁key:"+key+" value="+value+"====");
    161                         this.redisClientTemplate.del(key);
    162                     }else{
    163                         logger.info("====待解锁集合中key:"+key+" value="+value+"与uuid不匹配====");
    164                     }
    165                 }else{
    166                     logger.info("====待解锁集合中key="+key+"的value为空====");
    167                 }
    168             }
    169         }else{
    170             logger.info("====待解锁集合为空====");
    171         }
    172     }
    173     
    174     
    175 }

    2.2、业务调用模拟样例

     1   //获取同步锁工具类
     2   SynchrolockUtil synchrolockUtil = SpringUtils.getBean("synchrolockUtil");
     3   //获取需上锁资源的KEY
     4   String key = "abc";
     5   //查询是否上锁,上锁轮询,未上锁加锁
     6   boolean isLocked = synchrolockUtil.islocked(key,synchrolockUtil.RETRYTYPE_WAIT);
     7   //判断上锁结果
     8   if(isLocked){
     9      logger.error("同步锁请求超时并返回 key ="+key);
    10   }else{
    11       logger.info("====同步锁加锁陈功====");
    12   }
    13 
    14   try {
    15 
    16       //执行业务处理
    17 
    18   } catch (Exception e) {
    19       logger.error("业务异常:"+e.getMessage(), e);
    20   }finally{
    21       //解锁
    22       synchrolockUtil.unlock();
    23   }

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

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

     

  • 相关阅读:
    创建数据库的那些事
    同步、异步、阻塞、非阻塞我的理解
    Openfire MultiUserChat 多用户聊天 消息发送
    JAVA 随机字符串
    OF 同步异步问题的改进
    Openfire S2S 监听与消息处理
    MySQL FEDERATED 存储引擎
    一个S2S通信中的同步、异步问题的解决
    Openfire Monitoring/jinglenodes plugin error
    Java Cache System JCS(一) 使用方法
  • 原文地址:https://www.cnblogs.com/MIC2016/p/7525560.html
Copyright © 2020-2023  润新知