分布式锁场景
在分布式环境下多个操作需要以原子的方式执行
首先启一个springboot项目,再引入redis依赖包:

<!-- https://mvnrepository.com/artifa ... -starter-data-redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>
以下是一个扣减库存的接口作为例子:

@RestController
public class IndexController {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;

@RequestMapping("/deduct_stock")
        public Stirng deductStock() {
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)
                if (stock > 0) {
                        int realStock = stock - 1;
                        stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)
                        System.out.println(扣减成功,剩余库存:" + realStock + "");
                } else {
                        System.out.println(扣减失败,库存不足!" );
                }
                return "end";
        }
}
1.单实例应用场景
以上代码使用JMeter压测工具进行调用,设置参数为:
Number Of Threads[users]:100
Ramp Up Period[in seconds]:0
Loop Count:2
用单个web调用,结果出现并发问题:
<ignore_js_op> 
解决方案:加入同步锁(synchronized)

@RestController
public class IndexController {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;

@RequestMapping("/deduct_stock")
        public Stirng deductStock() {
                synchronized(this) {
                        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)
                        if (stock > 0) {
                                int realStock = stock - 1;
                                stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)
                                System.out.println(扣减成功,剩余库存:" + realStock + "");
                        } else {
                                System.out.println(扣减失败,库存不足!" );
                        }
                        return "end"; 
                }
        }
}

2.多实例分布式场景
以上代码,比如有多个应用程序,用nginx做负载均衡,进行同时调用压测
两个程序存在同样的扣减,出现并发现象。
第一个应用扣减结果显示:
<ignore_js_op> 
第二个应用扣减结果显示:
<ignore_js_op> 
解决方案:redis的setnx方法(可参考SETNX的api)
多个线程setnx调用时,有且仅有一个线程会拿到这把锁,所以拿到锁的执行业务代码,最后释放掉锁,代码如下:

@RestController
public class IndexController {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;

@RequestMapping("/deduct_stock")
        public Stirng deductStock() {
                String lockkey = "lockkey";
                Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue");//jedis.setnx
                if(!result) {
                        return "";
                }
                int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)
                if (stock > 0) {
                        int realStock = stock - 1;
                        stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)
                        System.out.println(扣减成功,剩余库存:" + realStock + "");
                } else {
                        System.out.println(扣减失败,库存不足!" );
                }
                springRedisTemplate.delete(lockkey);
                return "end"; 
        }
}
调用200次,压测结果显示还是有问题,只减掉了一部分:
<ignore_js_op> 
这时,加大压测次数,结果正常了:
第一个应用扣减结果显示:
<ignore_js_op> 
第二个应用扣减结果显示:
<ignore_js_op> 
这个只是因为加大了调用次数,执行业务代码需要一点时间,这段时间拒绝了很多等待获取锁的请求。但是,还是有问题,假如redis服务挂掉了,抛出异常了,这时锁不会被释放掉,出现死锁问题,可以添加try catch处理,代码如下:

@RestController
public class IndexController {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;

@RequestMapping("/deduct_stock")
        public Stirng deductStock() {
                String lockkey = "lockkey";
                try{
                        Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue");//jedis.setnx
                        if(!result) {
                                return "";
                        }
                        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)
                        if (stock > 0) {
                                int realStock = stock - 1;
                                stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)
                                System.out.println(扣减成功,剩余库存:" + realStock + "");
                        } else {
                                System.out.println(扣减失败,库存不足!" );
                        }
                }finally{
                        springRedisTemplate.delete(lockkey);
                }
                return "end"; 
        }
}
这时,Redis服务挂掉导致死锁的问题解决了,但是,如果服务器果宕机了,又会导致锁不能被释放的现象,所以可以设置超时时间为10s,代码如下:

@RestController
public class IndexController {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;

@RequestMapping("/deduct_stock")
        public Stirng deductStock() {
                String lockkey = "lockkey";
                try{
                        Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue",10,TimeUnit.SECONDS);//jedis.setnx
                        //Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue");//jedis.setnx
                        //stringRedisTemplate.expire(lockkey,10,TimeUnit.SECONDS);
                        if(!result) {
                                return "";
                        }
                        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)
                        if (stock > 0) {
                                int realStock = stock - 1;
                                stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)
                                System.out.println(扣减成功,剩余库存:" + realStock + "");
                        } else {
                                System.out.println(扣减失败,库存不足!" );
                        }
                }finally{
                        springRedisTemplate.delete(lockkey);
                }
                return "end"; 
        }
}
这时,如果有一个线程执行需要15s,当执行到10s时第二个线程进来拿到这把锁,会出现多个线程拿到同一把锁执行,在第一个线程执行完时会释放掉第二个线程的锁,以此类推…就会导致锁的永久失效。所以,只能自己释放自己的锁,可以给当前线程取一个名字,代码如下:

@RestController
public class IndexController {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;

@RequestMapping("/deduct_stock")
        public Stirng deductStock() {
                String lockkey = "lockkey";
                String clientId = UUID.randomUUID().toString();
                try{
                        Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,clientId ,10,TimeUnit.SECONDS);//jedis.setnx
                        //Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue");//jedis.setnx
                        //stringRedisTemplate.expire(lockkey,10,TimeUnit.SECONDS);
                        if(!result) {
                                return "";
                        }
                        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)
                        if (stock > 0) {
                                int realStock = stock - 1;
                                stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)
                                System.out.println(扣减成功,剩余库存:" + realStock + "");
                        } else {
                                System.out.println(扣减失败,库存不足!" );
                        }
                }finally{
                        springRedisTemplate.delete(lockkey);
                }
                return "end"; 
        }
}

永久失效的问题解决了,但是,如果第一个线程执行15s,还是会存在多个线程拥有同一把锁的现象。所以,需要续期超时时间,当一个线程执行5s后对超时时间进行续期都10s,就可以解决了,续期设置可以借助redission工具。

Redission使用
Redission分布式锁实现原理:
<ignore_js_op>
pom.xml

<dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.6.5</version>
</dependency>

Application.java启动类

@bean
public Redission redission {
        //此为单机模式
        Config config = new Config();
        config.useSingleServer().setAddress("redis://120.0.0.1:6379").setDatabase(0);
        return (Redission)Redission.creat(config);
}

最终解决以上所有问题的代码如下:

@RestController
public class IndexController {
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
        @Autowired
        private Redissionredission;

@RequestMapping("/deduct_stock")
        public Stirng deductStock() {
                String lockkey = "lockkey";
                //String clientId = UUID.randomUUID().toString();
                RLock lock = redission.getLock();
                try{
                        //Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,clientId ,10,TimeUnit.SECONDS);//jedis.setnx
                        //Boolean result = stringRedisTemplate.opsForValue.setIfAbsent(lockkey,"lockvalue");//jedis.setnx
                        //stringRedisTemplate.expire(lockkey,10,TimeUnit.SECONDS);
                        //加锁:redission默认超时时间为30s,每10s续期一次,也可以自己设置时间
                        lock.lock(60,TimeUnit.SECONDS);
                        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get(key)
                        if (stock > 0) {
                                int realStock = stock - 1;
                                stringRedisTemplate.opsForValue.set("stock",realStock+"");//jedis.set(key,value)
                                System.out.println(扣减成功,剩余库存:" + realStock + "");
                        } else {
                                System.out.println(扣减失败,库存不足!" );
                        }
                }finally{
                        lock.unlock();
                        //springRedisTemplate.delete(lockkey);
                }
                return "end"; 
        }
}
高并发分布式锁的问题得到解决。

Redis实现高并发分布式锁的更多相关文章

  1. Redis实现高并发分布式序列号

    使用Redis实现高并发分布式序列号生成服务 序列号的构成 为建立良好的数据治理方案,作数据掌握.分析.统计.商业智能等用途,业务数据的编码制定通常都会遵循一定的规则,一般来讲,都会有自己的编码规则和 ...

  2. Redis高并发分布式锁详解

    为什么需要分布式锁 1.为了解决Java共享内存模型带来的线程安全问题,我们可以通过加锁来保证资源访问的单一,如JVM内置锁synchronized,类级别的锁ReentrantLock. 2.但是随 ...

  3. 使用Redis实现高并发分布式序列号生成服务

    序列号的构成 为建立良好的数据治理方案,作数据掌握.分析.统计.商业智能等用途,业务数据的编码制定通常都会遵循一定的规则,一般来讲,都会有自己的编码规则和自增序列构成.比如我们常见的身份证号.银行卡号 ...

  4. 使用Redis SETNX 命令实现分布式锁

    基于setnx和getset http://blog.csdn.net/lihao21/article/details/49104695 使用Redis的 SETNX 命令可以实现分布式锁,下文介绍其 ...

  5. Redis整合Spring实现分布式锁

    spring把专门的数据操作独立封装在spring-data系列中,spring-data-redis是对Redis的封装 <dependencies> <!-- 添加spring- ...

  6. 【连载】redis库存操作,分布式锁的四种实现方式[一]--基于zookeeper实现分布式锁

    一.背景 在电商系统中,库存的概念一定是有的,例如配一些商品的库存,做商品秒杀活动等,而由于库存操作频繁且要求原子性操作,所以绝大多数电商系统都用Redis来实现库存的加减,最近公司项目做架构升级,以 ...

  7. Redis 上实现的分布式锁

    转载Redis 上实现的分布式锁 由于近排很忙,忙各种事情,还有工作上的项目,已经超过一个月没写博客了,确实有点惭愧啊,没能每天或者至少每周坚持写一篇博客.这一个月里面接触到很多新知识,同时也遇到很多 ...

  8. 不懂这些高并发分布式架构、分布式系统的数据一致性解决方案,你如何能找到高新互联网工作呢?强势解析eBay BASE模式、去哪儿及蘑菇街分布式架构

    互联网行业是大势所趋,从招聘工资水平即可看出,那么如何提升自我技能,满足互联网行业技能要求?需要以目标为导向,进行技能提升,本文主要针对高并发分布式系统设计.架构(数据一致性)做了分析,祝各位早日走上 ...

  9. 关于Redis处理高并发

    Redis的高并发和快速原因 1.Redis是基于内存的,内存的读写速度非常快: 2.Redis是单线程的,省去了很多上下文切换线程的时间: 3.Redis使用多路复用技术,可以处理并发的连接.非阻塞 ...

随机推荐

  1. UVA 10534 LCS变种题

    求一个序列中 的2*n-1个数字 ,前n+1个数字为严格升序 后n+1个为严格降序,求最长的长度 一开始还没想清楚怎么解,其实就是一个LCS问题,从头到尾以及反序求一下LCS 由于 d[i]为包含了自 ...

  2. scrapy练习1

    1.建立项目: #建立名为tuto的项目 scrapy startproject tuto 2.进入项目目录: cd tuto 3.建立域名任务: #minyan任务名:后面是任务对应的域名 scra ...

  3. 白痴级教程,新手看过来,具详细实操文档 (word图片复制不过来,0202年了还有这样的不便利,下回研究一下,图片下次补)

    一.环境配置(win10): 1.配置cmd的python环境为arcmap10.2 自带的python解释器(2.7.3)(自带arcpy库) 具体操作: 1我的电脑右击属性,打开 (选中path点 ...

  4. POJ 1847:Tram

    Tram Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 11771   Accepted: 4301 Description ...

  5. 我读《DOOM启世录》——成为一个真正厉害的人

    序言 谈到游戏, 你的当然会想到几乎统治游戏市场多年的英雄联盟,你可能还会想起前段时间风头大盛的王者荣耀手游,你应该还会想起正在冲击着游戏市场的"吃鸡"类型游戏. 那么, 大家是否 ...

  6. ERNIE:知识图谱结合BERT才是「有文化」的语言模型

    自然语言表征模型最近受到非常多的关注,很多研究者将其视为 NLP 最重要的研究方向之一.例如在大规模语料库上预训练的 BERT,它可以从纯文本中很好地捕捉丰富的语义模式,经过微调后可以持续改善不同 N ...

  7. 修改自己的centos输入法

    当自己的centos连上网时,就可以修改自己的输入法了 http://jingyan.baidu.com/album/da1091fb3e7f8a027849d681.html?picindex=2

  8. Python笔记_第一篇_面向过程_第一部分_7.文件的操作(.txt)

    在平时,我们不光要对程序内的代码进行输入和输出的操作,还要对程序外的文件进行和语言之间的交换.操作和运算.在基础部分,先讲解对于外部的.txt文件的操作. 第一部分 基本内容讲解 1.   什么是文件 ...

  9. Prometheus监控系统之入门篇(一)

    1. 简介 Prometheus: (简称Prom)是由SoundCloud开发的开源监控报警系统.是大名鼎鼎的CNCF云原生基金会下的第二大开源项目.具有如下特点: 使用Go语言开发 内置时序数据库 ...

  10. 学习spring第三天

    Spring第三天笔记 今日内容 Spring的核心之一 -  AOP思想 (1) 代理模式- 动态代理 ① JDK的动态代理 (Java官方) ② CGLIB 第三方代理 (2) AOP思想在Spr ...