“分布式锁”是用来解决分布式应用中“并发冲突”的一种常用手段,实现方式一般有基于zookeeper及基于redis二种。具体到业务场景中,我们要考虑二种情况:

一、抢不到锁的请求,允许丢弃(即:忽略)

比如:一些不是很重要的场景,比如“监控数据持续上报”,某一篇文章的“已读/未读”标识位更新,对于同一个id,如果并发的请求同时到达,只要有一个请求处理成功,就算成功。

用活动图表示如下:

二、并发请求,不论哪一条都必须要处理的场景(即:不允许丢数据)

比如:一个订单,客户正在前台修改地址,管理员在后台同时修改备注。地址和备注字段的修改,都必须正确更新,这二个请求同时到达的话,如果不借助db的事务,很容易造成行锁竞争,但用事务的话,db的性能显然比不上redis轻量。

解决思路:A,B二个请求,谁先抢到分布式锁(假设A先抢到锁),谁先处理,抢不到的那个(即:B),在一旁不停等待重试,重试期间一旦发现获取锁成功,即表示A已经处理完,把锁释放了。这时B就可以继续处理了。

但有二点要注意:

a、需要设置等待重试的最长时间,否则如果A处理过程中有bug,一直卡死,或者未能正确释放锁,B就一直会等待重试,但是又永远拿不到锁。

b、等待最长时间,必须小于锁的过期时间。否则,假设锁2秒过期自动释放,但是A还没处理完(即:A的处理时间大于2秒),这时锁会因为redis key过期“提前”误释放,B重试时拿到锁,造成A,B同时处理。(注:可能有同学会说,不设置锁的过期时间,不就完了么?理论上讲,确实可以这么做,但是如果业务代码有bug,导致处理完后没有unlock,或者根本忘记了unlock,分布式锁就会一直无法释放。所以综合考虑,给分布式锁加一个“保底”的过期时间,让其始终有机会自动释放,更为靠谱)

用活动图表示如下:

写了一个简单的工具类:

package com.cnblogs.yjmyzz.redisdistributionlock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils; import java.util.UUID;
import java.util.concurrent.TimeUnit; /**
* 利用redis获取分布式锁
*
* @author 菩提树下的杨过
* @blog http://yjmyzz.cnblogs.com/
*/
public class RedisLock { private StringRedisTemplate redisTemplate; private Logger logger = LoggerFactory.getLogger(this.getClass()); /**
* simple lock尝试获取锅的次数
*/
private int retryCount = 3; /**
* 每次尝试获取锁的重试间隔毫秒数
*/
private int waitIntervalInMS = 100; public RedisLock(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
} /**
* 利用redis获取分布式锁(未获取锁的请求,允许丢弃!)
*
* @param redisKey 锁的key值
* @param expireInSecond 锁的自动释放时间(秒)
* @return
* @throws DistributionLockException
*/
public String simpleLock(final String redisKey, final int expireInSecond) throws DistributionLockException {
String lockValue = UUID.randomUUID().toString();
boolean flag = false;
if (StringUtils.isEmpty(redisKey)) {
throw new DistributionLockException("key is empty!");
}
if (expireInSecond <= 0) {
throw new DistributionLockException("expireInSecond must be bigger than 0");
}
try {
for (int i = 0; i < retryCount; i++) {
boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey, lockValue, expireInSecond, TimeUnit.SECONDS);
if (success) {
flag = true;
break;
}
try {
TimeUnit.MILLISECONDS.sleep(waitIntervalInMS);
} catch (Exception ignore) {
logger.warn("redis lock fail: " + ignore.getMessage()); }
}
if (!flag) {
throw new DistributionLockException(Thread.currentThread().getName() + " cannot acquire lock now ...");
}
return lockValue;
} catch (DistributionLockException be) {
throw be;
} catch (Exception e) {
logger.warn("get redis lock error, exception: " + e.getMessage());
throw e;
}
} /**
* 利用redis获取分布式锁(未获取锁的请求,将在timeoutSecond时间范围内,一直等待重试)
*
* @param redisKey 锁的key值
* @param expireInSecond 锁的自动释放时间(秒)
* @param timeoutSecond 未获取到锁的请求,尝试重试的最久等待时间(秒)
* @return
* @throws DistributionLockException
*/
public String lock(final String redisKey, final int expireInSecond, final int timeoutSecond) throws DistributionLockException {
String lockValue = UUID.randomUUID().toString();
boolean flag = false;
if (StringUtils.isEmpty(redisKey)) {
throw new DistributionLockException("key is empty!");
}
if (expireInSecond <= 0) {
throw new DistributionLockException("expireInSecond must be greater than 0");
}
if (timeoutSecond <= 0) {
throw new DistributionLockException("timeoutSecond must be greater than 0");
}
if (timeoutSecond >= expireInSecond) {
throw new DistributionLockException("timeoutSecond must be less than expireInSecond");
}
try {
long timeoutAt = System.currentTimeMillis() + timeoutSecond * 1000;
while (true) {
boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey, lockValue, expireInSecond, TimeUnit.SECONDS);
if (success) {
flag = true;
break;
}
if (System.currentTimeMillis() >= timeoutAt) {
break;
}
try {
TimeUnit.MILLISECONDS.sleep(waitIntervalInMS);
} catch (Exception ignore) {
logger.warn("redis lock fail: " + ignore.getMessage());
}
}
if (!flag) {
throw new DistributionLockException(Thread.currentThread().getName() + " cannot acquire lock now ...");
}
return lockValue;
} catch (DistributionLockException be) {
throw be;
} catch (Exception e) {
logger.warn("get redis lock error, exception: " + e.getMessage());
throw e;
}
} /**
* 锁释放
*
* @param redisKey
* @param lockValue
*/
public void unlock(final String redisKey, final String lockValue) {
if (StringUtils.isEmpty(redisKey)) {
return;
}
if (StringUtils.isEmpty(lockValue)) {
return;
}
try {
String currLockVal = redisTemplate.opsForValue().get(redisKey);
if (currLockVal != null && currLockVal.equals(lockValue)) {
boolean result = redisTemplate.delete(redisKey);
if (!result) {
logger.warn(Thread.currentThread().getName() + " unlock redis lock fail");
} else {
logger.info(Thread.currentThread().getName() + " unlock redis lock:" + redisKey + " successfully!");
}
}
} catch (Exception je) {
logger.warn(Thread.currentThread().getName() + " unlock redis lock error:" + je.getMessage());
}
}
}

  

然后写个spring-boot来测试一下:

package com.cnblogs.yjmyzz.redisdistributionlock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.redis.core.StringRedisTemplate; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; @SpringBootApplication
public class RedisDistributionLockApplication { private static Logger logger = LoggerFactory.getLogger(RedisDistributionLockApplication.class); public static void main(String[] args) throws InterruptedException {
ConfigurableApplicationContext applicationContext = SpringApplication.run(RedisDistributionLockApplication.class, args); //初始化
StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
RedisLock redisLock = new RedisLock(redisTemplate);
String lockKey = "lock:test"; CountDownLatch start = new CountDownLatch(1);
CountDownLatch threadsLatch = new CountDownLatch(2); final int lockExpireSecond = 5;
final int timeoutSecond = 3; Runnable lockRunnable = () -> {
String lockValue = "";
try {
//等待发令枪响,防止线程抢跑
start.await(); //允许丢数据的简单锁示例
lockValue = redisLock.simpleLock(lockKey, lockExpireSecond); //不允许丢数据的分布式锁示例
//lockValue = redisLock.lock(lockKey, lockExpireSecond, timeoutSecond); //停一会儿,故意让后面的线程抢不到锁
TimeUnit.SECONDS.sleep(2);
logger.info(String.format("%s get lock successfully, value:%s", Thread.currentThread().getName(), lockValue)); } catch (Exception e) {
e.printStackTrace();
} finally {
redisLock.unlock(lockKey, lockValue);
//执行完后,计数减1
threadsLatch.countDown();
} }; Thread t1 = new Thread(lockRunnable, "T1");
Thread t2 = new Thread(lockRunnable, "T2"); t1.start();
t2.start(); //预备:开始!
start.countDown(); //等待所有线程跑完
threadsLatch.await(); logger.info("======>done!!!"); } }

 用2个线程模拟并发场景,跑起来后,输出如下:

可以看到T2线程没抢到锁,直接抛出了预期的异常。

把44行的注释打开,即:换成不允许丢数据的模式,再跑一下:

可以看到,T1先抢到锁,然后经过2秒的处理后,锁释放,这时T2重试拿到了锁,继续处理,最终释放。

基于redis的分布式锁二种应用场景的更多相关文章

  1. 基于Redis的分布式锁两种实现方式

    最近有一个竞拍的项目会用到分布式锁,网上查到的结果是有三种途径可以实现.1.数据库锁机制,2.redis的锁,3.zookeeper.考虑到使用mysql实现会在性能这一块会受影响,zookeeper ...

  2. 基于 redis 的分布式锁实现 Distributed locks with Redis debug 排查错误

    小结: 1. 锁的实现方式,按照应用的实现架构,可能会有以下几种类型: 如果处理程序是单进程多线程的,在 python下,就可以使用 threading 模块的 Lock 对象来限制对共享变量的同步访 ...

  3. redis系列:基于redis的分布式锁

    一.介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分为两部分,一个是单机环境, ...

  4. python基于redis实现分布式锁

    阅读目录 什么事分布式锁 基于redis实现分布式锁 一.什么是分布式锁 我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的锁进行处理,并且可以完美的运行,毫无 ...

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

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

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

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

  7. 基于redis的分布式锁(转)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  8. 基于redis的分布式锁实现

    1.分布式锁介绍 在计算机系统中,锁作为一种控制并发的机制无处不在. 单机环境下,操作系统能够在进程或线程之间通过本地的锁来控制并发程序的行为.而在如今的大型复杂系统中,通常采用的是分布式架构提供服务 ...

  9. 基于redis的分布式锁(不适合用于生产环境)

    基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

随机推荐

  1. abstract抽象类和interface接口

    一.抽象类 1.抽象类不能实例化,因为有抽象方法未实现 2.可以被抽象类或非抽象类继承 3.但不是只能被继承,还可以直接拿来使用的,当然,这个使用是拿来声明,反例如下: public abstract ...

  2. PHP安装oracle的php_oci和oci8扩展

    环境:centos6.9 php5.3.3 oracle客户端:下载链接:http://www.oracle.com/technetwork/topics/linuxx86-64soft-092277 ...

  3. C语言联合体

    C语言联合体Unions实例代码教程 - 联合是一种特殊的数据类型在C语言中,使您可以存储不同的数据类型相同的内存位置. 联合是一种特殊的数据类型在C语言中,使您可以存储不同的数据类型相同的内存位置. ...

  4. 不一样视角的Glide剖析

    推荐阅读: 滴滴Booster移动App质量优化框架-学习之旅 一 Android 模块Api化演练 不一样视角的Glide剖析(一) Glide是一个快速高效的Android图片加载库,注重于平滑的 ...

  5. 全局变量 全局函数vue 方法

    定义全局变量 原理: 设置一个专用的的全局变量模块文件,模块里面定义一些变量初始状态,用export default 暴露出去,在main.js里面使用Vue.prototype挂载到vue实例上面或 ...

  6. Light oj 1125 - Divisible Group Sums (dp)

    题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1125 题意: 给你n个数,q次询问,每次询问问你取其中m个数是d的整数倍的方案 ...

  7. Ruby on rails初体验(二)

    体验一中添加了一个最基本的支架和一个简单的数据迁移,实现了一个基本的增删改查的功能列表.体验二中要在次功能上继续丰满一下功能.实现如下效果: 在每个公司中都包含有不同的部门,按照体验一中的方法,添加一 ...

  8. js 日期计算星座 根据生日的月份和日期,一行代码计算星座的js小函数(转)

    本博客根据 开源中国作者清风徐不来 的文章 根据生日的月份和日期,一行代码计算星座的js小函数(转) 原文出自CSDN 无心的专栏 的文章,知识产权归原文作者所有! 点击查看原文:js 日期计算星座

  9. linux 学习解决归档管理器打开rar和zip中文文件名乱码问题

    在ubunut下打开windows下压缩的rar文件和zip压缩文件出现中文文件名乱码的问题真的很头疼.文件名乱码其实也没有什么关系是不?至少重命名再改回来或者是使用英文命名都可以克服.不巧的是,如此 ...

  10. PHP使用frameset制作后台界面时,怎样实现通过操作左边框架,使右边框架中的页面跳转?

    左框架的链接,不仅要指定链接的文件名,还需要通过 target 属性指定要打开的目标框架名(即楼主要求的右框架名). 例:假设右框架为 <frame scr="lx1.htm" ...