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、同步锁工具类

 package com.mic.synchrolock.util;

 import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import com.mic.constants.Constants;
import com.mic.constants.InvestType; /**
* 分布式同步锁工具类
* @author Administrator
*
*/
public class SynchrolockUtil { private final Log logger = LogFactory.getLog(getClass()); @Autowired
private RedisClientTemplate redisClientTemplate; public final String RETRYTYPE_WAIT = "1"; //加锁方法当对象已加锁时,设置为等待并轮询
public final String RETRYTYPE_NOWAIT = "0"; //加锁方法当对象已加锁时,设置为直接返回 private String requestTimeOutName = ""; //投资同步锁请求超时时间
private String retryIntervalName = ""; //投资同步锁轮询间隔
private String keyTimeoutName = ""; //缓存中key的失效时间
private String investProductSn = ""; //产品Sn
private String uuid; //对象唯一标识 private Long startTime = System.currentTimeMillis(); //首次调用时间
public Long getStartTime() {
return startTime;
} List<String> keyList = new ArrayList<String>(); //缓存key的保存集合
public List<String> getKeyList() {
return keyList;
}
public void setKeyList(List<String> keyList) {
this.keyList = keyList;
} @PostConstruct
public void init() {
uuid = UUID.randomUUID().toString();
} @PreDestroy
public void destroy() {
this.unlock();
} /**
* 根据传入key值,判断缓存中是否存在该key
* 存在-已上锁:判断retryType,轮询超时,或直接返回,返回ture
* 不存在-未上锁:将该放入缓存,返回false
* @param key
* @param retryType 当遇到上锁情况时 1:轮询;0:直接返回
* @return
*/
public boolean islocked(String key,String retryType){
boolean flag = true;
logger.info("====投资同步锁设置轮询间隔、请求超时时长、缓存key失效时长====");
//投资同步锁轮询间隔 毫秒
Long retryInterval = Long.parseLong(Constants.getProperty(retryIntervalName));
//投资同步锁请求超时时间 毫秒
Long requestTimeOut = Long.parseLong(Constants.getProperty(requestTimeOutName));
//缓存中key的失效时间 秒
Integer keyTimeout = Integer.parseInt(Constants.getProperty(keyTimeoutName)); //调用缓存获取当前产品锁
logger.info("====当前产品key为:"+key+"====");
if(isLockedInRedis(key,keyTimeout)){
if("1".equals(retryType)){
//采用轮询方式等待
while (true) {
logger.info("====产品已被占用,开始轮询====");
try {
Thread.sleep(retryInterval);
} catch (InterruptedException e) {
logger.error("线程睡眠异常:"+e.getMessage(), e);
return flag;
}
logger.info("====判断请求是否超时====");
Long currentTime = System.currentTimeMillis(); //当前调用时间
long Interval = currentTime - startTime;
if (Interval > requestTimeOut) {
logger.info("====请求超时====");
return flag;
}
if(!isLockedInRedis(key,keyTimeout)){
logger.info("====轮询结束,添加同步锁====");
flag = false;
keyList.add(key);
break;
}
}
}else{
//不等待,直接返回
logger.info("====产品已被占用,直接返回====");
return flag;
} }else{
logger.info("====产品未被占用,添加同步锁====");
flag = false;
keyList.add(key);
}
return flag;
} /**
* 在缓存中查询key是否存在
* 若存在则返回true;
* 若不存在则将key放入缓存,设置过期时间,返回false
* @param key
* @param keyTimeout key超时时间单位是秒
* @return
*/
boolean isLockedInRedis(String key,int keyTimeout){
logger.info("====在缓存中查询key是否存在====");
boolean isExist = false;
//与redis交互,查询对象是否上锁
Long result = this.redisClientTemplate.setnx(key, uuid);
logger.info("====上锁 result = "+result+"====");
if(null != result && 1 == Integer.parseInt(result.toString())){
logger.info("====设置缓存失效时长 = "+keyTimeout+"秒====");
this.redisClientTemplate.expire(key, keyTimeout);
logger.info("====上锁成功====");
isExist = false;
}else{
logger.info("====上锁失败====");
isExist = true;
}
return isExist;
} /**
* 根据传入key,对该产品进行解锁
* @param key
* @return
*/
public void unlock(){
//与redis交互,对产品解锁
if(keyList.size()>0){
for(String key : this.keyList){
String value = this.redisClientTemplate.get(key);
if(null != value && !"".equals(value)){
if(uuid.equals(value)){
logger.info("====解锁key:"+key+" value="+value+"====");
this.redisClientTemplate.del(key);
}else{
logger.info("====待解锁集合中key:"+key+" value="+value+"与uuid不匹配====");
}
}else{
logger.info("====待解锁集合中key="+key+"的value为空====");
}
}
}else{
logger.info("====待解锁集合为空====");
}
} }

2.2、业务调用模拟样例

   //获取同步锁工具类
  SynchrolockUtil synchrolockUtil = SpringUtils.getBean("synchrolockUtil");
  //获取需上锁资源的KEY
  String key = "abc";
  //查询是否上锁,上锁轮询,未上锁加锁
  boolean isLocked = synchrolockUtil.islocked(key,synchrolockUtil.RETRYTYPE_WAIT);
  //判断上锁结果
  if(isLocked){
  logger.error("同步锁请求超时并返回 key ="+key);
  }else{
  logger.info("====同步锁加锁陈功====");
  }   try {   //执行业务处理   } catch (Exception e) {
  logger.error("业务异常:"+e.getMessage(), e);
  }finally{
  //解锁
   synchrolockUtil.unlock();
  }

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

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

结合 Redis 实现同步锁的更多相关文章

  1. 基于redis 实现分布式锁的方案

    在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...

  2. 用Redis构建分布式锁-RedLock(真分布)

    在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简单的实现其实只需采用稍微增 ...

  3. Redis实现分布式锁

    http://redis.io/topics/distlock 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但 ...

  4. 基于Redis实现分布式锁(1)

    转自:http://blog.csdn.net/ugg/article/details/41894947 背景在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等.大部 ...

  5. redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  6. Redis实现分布式锁的正确姿势

    分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Re ...

  7. Java中String做为synchronized同步锁使用详解

    Java中使用String作同步锁 在Java中String是一种特殊的类型存在,在jdk中String在创建后是共享常量池的,即使在jdk1.8之后实现有所不同,但是功能还是差不多的. 借助这个特点 ...

  8. 如何优雅地用Redis实现分布式锁?

    转: 如何优雅地用Redis实现分布式锁?   BaiduSpring 01-2500:01 什么是分布式锁 在学习Java多线程编程的时候,锁是一个很重要也很基础的概念,锁可以看成是多线程情况下访问 ...

  9. 基于Redis的分布式锁真的安全吗?

    说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...

随机推荐

  1. linux less命令详情

    less 工具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极其强大.less 的用法比起 more .tail更加的有弹性.在 more 的时候,我们并没有办 ...

  2. postgresql 清空数据表数据

    在 mysql中,只需要执行: TRUNCATE table_name; 即可,数据会情况,而且自增id也会变回0: 但在 postgresql 则稍有不同,因为 postgresql 的自增id是通 ...

  3. Bootstrap in ASP.NET MVC 5

    一,新建ASP.NET MVC 5 项目 Bootstrap 文件分布 引入到页面 1.定义.注意:不要包含有.min.的文件名称,会被忽略,因为在发布的时候编译器会加载min版的文件 2.在母版页中 ...

  4. mysql的mysqli异步与php的携程

    <?php $begin = time(); //同步请求 function multi_sync(){ $host = '192.168.2.87'; $user = 'census'; $p ...

  5. PTA (Advanced Level) 1006 Sign In and Sign Out

    Sign In and Sign Out At the beginning of every day, the first person who signs in the computer room ...

  6. 描述linux系统从开机到登陆界面的启动过程

    简述:1.开机BIOS自检2.MBR引导3.grub引导菜单4.加载内核kernel5.启动init进程6.读取inittab文件,执行rc.sysinit,rc等脚本7.启动mingetty,进入系 ...

  7. elasticSearch6源码分析(4)indices模块

    1.indices概述 The indices module controls index-related settings that are globally managed for all ind ...

  8. Deep learning with Python 学习笔记(1)

    深度学习基础 Python 的 Keras 库来学习手写数字分类,将手写数字的灰度图像(28 像素 ×28 像素)划分到 10 个类别 中(0~9) 神经网络的核心组件是层(layer),它是一种数据 ...

  9. oracle中scott/tiger、sys、SYSDBA、system都是什么用

    scott 是个演示用户,是让你学习ORACLE用的 SYSDBA 不是用户,可以认为是个权限,超级权限详细点说吧            超级用户分两种 SYSDBA和SYSOPTSYSOPT 后面3 ...

  10. org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)错误几种解决方案

    报错信息: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.study.ser ...