Springboot分别使用乐观锁和分布式锁(基于redisson)完成高并发防超卖
原文 :https://blog.csdn.net/tianyaleixiaowu/article/details/90036180
乐观锁
乐观锁就是在修改时,带上version版本号。这样如果试图修改已被别人修改过的数据时,会抛出异常。在一定程度上,也可以作为防超卖的一种处理方法。我们来看一下。
我们在Goods的entity类上,加上这个字段。
@Version
private Long version;
- @Transactional
- public synchronized void mult(Long goodsId) {
- PtGoods ptGoods = ptGoodsManager.find(goodsId);
- logger.info("----amount:" + ptGoods.getAmount());
- ptGoods.setAmount(ptGoods.getAmount() + 1);
- ptGoodsManager.update(ptGoods);
- }
测试一下:
- for (int i = 0; i < 100; i++) {
- new Thread(() -> {
- goodsService.mult(1L);
- }
- ).start();
- }
可以发现,抛出了很多异常,这就是乐观锁的异常。可想而知,当高并发购买同一个商品时,会出现大量的购买失败,而不会出现超卖的情况,因为他限制了并发的访问修改。
这样其实显而易见,也是大有问题的,只适应于读多写少的情况,否则大量的失败也是有损用户体验,明明有货,却不卖出。
redission方式:
pom里加入依赖
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <dependency>
- <groupId>org.redisson</groupId>
- <artifactId>redisson-spring-boot-starter</artifactId>
- <version>3.10.6</version>
- </dependency>
redisson支持单点、集群等模式,这里选择单点的。application.yml配置好redis的连接:
- spring:
- redis:
- host: ${REDIS_HOST:127.0.0.1}
- port: ${REDIS_PORT:6379}
- password: ${REDIS_PASSWORD:}
配置redisson的客户端bean
- @Configuration
- public class RedisConfig {
- @Value("${spring.redis.host}")
- private String host;
- @Bean(name = {"redisTemplate", "stringRedisTemplate"})
- public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
- StringRedisTemplate redisTemplate = new StringRedisTemplate();
- redisTemplate.setConnectionFactory(factory);
- return redisTemplate;
- }
- @Bean
- public Redisson redisson() {
- Config config = new Config();
- config.useSingleServer().setAddress("redis://" + host + ":6379");
- return (Redisson) Redisson.create(config);
- }
- }
至于使用redisson的功能也很少,其实就是对并发访问的方法加个锁即可,方法执行完后释放锁。这样下一个请求才能进入到该方法。
我们创建一个redis锁的注解
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- /**
- * @author wuweifeng wrote on 2019/5/8.
- */
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface RedissonLock {
- /**
- * 要锁哪个参数
- */
- int lockIndex() default -1;
- /**
- * 锁多久后自动释放(单位:秒)
- */
- int leaseTime() default 10;
- }
切面类:
- import com.tianyalei.giftmall.global.annotation.RedissonLock;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.redisson.Redisson;
- import org.redisson.api.RLock;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.core.annotation.Order;
- import org.springframework.stereotype.Component;
- import javax.annotation.Resource;
- import java.util.concurrent.TimeUnit;
- /**
- * 分布式锁
- * @author wuweifeng wrote on 2019/5/8.
- */
- @Aspect
- @Component
- @Order(1) //该order必须设置,很关键
- public class RedissonLockAspect {
- private Logger log = LoggerFactory.getLogger(getClass());
- @Resource
- private Redisson redisson;
- @Around("@annotation(redissonLock)")
- public Object around(ProceedingJoinPoint joinPoint, RedissonLock redissonLock) throws Throwable {
- Object obj = null;
- //方法内的所有参数
- Object[] params = joinPoint.getArgs();
- int lockIndex = redissonLock.lockIndex();
- //取得方法名
- String key = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint
- .getSignature().getName();
- //-1代表锁整个方法,而非具体锁哪条数据
- if (lockIndex != -1) {
- key += params[lockIndex];
- }
- //多久会自动释放,默认10秒
- int leaseTime = redissonLock.leaseTime();
- int waitTime = 5;
- RLock rLock = redisson.getLock(key);
- boolean res = rLock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
- if (res) {
- log.info("取到锁");
- obj = joinPoint.proceed();
- rLock.unlock();
- log.info("释放锁");
- } else {
- log.info("----------nono----------");
- throw new RuntimeException("没有获得锁");
- }
- return obj;
- }
- }
这里解释一下,防超卖,其实是对某一个商品在被修改时进行加锁,而这个时候其他的商品是不受影响的。所以不能去锁整个方法,而应该是锁某个商品。所以我设置了一个lockIndex的参数,来指明你要锁的是方法的哪个属性,这里就是锁goodsId,如果不写,则是锁整个方法。
在切面里里面RLock.tryLock,则是最多等待5秒,托若还没取到锁就走失败,取到了则进入方法走逻辑。第二个参数是自动释放锁的时间,以避免自己刚取到锁,就挂掉了,导致锁无法释放。
测试类:
- package com.tianyalei.giftmall;
- import com.tianyalei.giftmall.core.goods.GoodsService;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.test.context.junit4.SpringRunner;
- import javax.annotation.Resource;
- import java.util.concurrent.BrokenBarrierException;
- import java.util.concurrent.CyclicBarrier;
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class GiftmallApplicationTests {
- @Resource
- private GoodsService goodsService;
- private CyclicBarrier cyclicBarrier = new CyclicBarrier(100);
- private CyclicBarrier cyclicBarrier1 = new CyclicBarrier(100);
- @Test
- public void contextLoads() {
- for (int i = 0; i < 100; i++) {
- new Thread(() -> {
- try {
- cyclicBarrier.await();
- goodsService.multi(1L);
- } catch (InterruptedException | BrokenBarrierException e) {
- e.printStackTrace();
- }
- }
- ).start();
- new Thread(() -> {
- try {
- cyclicBarrier1.await();
- goodsService.multi(2L);
- } catch (InterruptedException | BrokenBarrierException e) {
- e.printStackTrace();
- }
- }
- ).start();
- }
- try {
- Thread.sleep(6000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
这里用100并发,同时操作2个商品。
可以看到,这两个商品在各自更新各自的,互不影响。最终在5秒后,有的超时了。调大等待时间,则能保证每个都是100.
通过这种方式,即完成了分布式锁,简单也便捷。当然这里只是举例,在实际项目中,倘若要做防止超卖,以追求最大性能的话,也可以考虑使用redis来存储amount,借助于redis的increase来做数量的增减,能迅速的给出客户端是否抢到了商品的判断,之后再通过消息队列去生成订单之类的耗时操作。
Springboot分别使用乐观锁和分布式锁(基于redisson)完成高并发防超卖的更多相关文章
- MasaFramework -- 锁与分布式锁
前言 什么是锁?什么是分布式锁?它们之间有什么样的关系? 什么是锁 加锁(lock)是2018年公布的计算机科学技术名词,是指将控制变量置位,控制共享资源不能被其他线程访问.通过加锁,可以确保在同一时 ...
- Java锁?分布式锁?乐观锁?行锁?
转载自:公众号来源:码农翻身 作者:刘欣 Tomcat的锁 Tomcat是这个系统的核心组成部分, 每当有用户请求过来,Tomcat就会从线程池里找个线程来处理,有的执行登录,有的查看购物车,有的下订 ...
- 利用MySQL中的乐观锁和悲观锁实现分布式锁
背景 对于一些并发量不是很高的场景,使用MySQL的乐观锁实现会比较精简且巧妙. 下面就一个小例子,针对不加锁.乐观锁以及悲观锁这三种方式来实现. 主要是一个用户表,它有一个年龄的字段,然后并发地对其 ...
- 锁、分布式锁、无锁实战全局性ID
1.为什么要使用锁 当发生并发时,会产生多线程争夺一个资源,为保证资源的唯一性. JVM锁:对象锁,死锁,重入锁,公平锁,偏向锁 分布式锁:数据库 nosql .zookeeper 面试题:如何排查死 ...
- redis 加锁与释放锁(分布式锁1)
使用Redis的 SETNX 命令可以实现分布式锁 SETNX key value 返回值 返回整数,具体为 - 1,当 key 的值被设置 - 0,当 key 的值没被设置 分布式锁使用 impor ...
- redis 加锁与释放锁(分布式锁)
使用Redis的 SETNX 命令可以实现分布式锁 SETNX key value 返回值 返回整数,具体为 - 1,当 key 的值被设置 - 0,当 key 的值没被设置
- django celery的分布式异步之路(二) 高并发
当你跑通了前面一个demo,博客地址:http://www.cnblogs.com/kangoroo/p/7299920.html,那么你的分布式异步之旅已经起步了. 性能和稳定性是web服务的核心评 ...
- SpringBoot项目框架下ThreadPoolExecutor线程池+Queue缓冲队列实现高并发中进行下单业务
主要是自己在项目中(中小型项目) 有支付下单业务(只是办理VIP,没有涉及到商品库存),目前用户量还没有上来,目前没有出现问题,但是想到如果用户量变大,下单并发量变大,可能会出现一系列的问题,趁着空闲 ...
- springboot(十二)-分布式锁(redis)
什么是分布式锁? 要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁.进程锁. 线程锁:主要用来给方法.代码块加锁.当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段.线程锁只在同一 ...
随机推荐
- Elasticsearch - 处理冲突
http://blog.csdn.net/xifeijian/article/details/49615559
- MySQL普通索引性能试验
首先使用如下node.js脚本创建两张表,并为这两张表各自生成10000条数据: var fs = require('fs'); var nameS = "赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱 ...
- LODOP中table自动分页补线加border
LODOP中可以用ADD_PRINT_TABLE.ADD_PRINT_HTM.ADD_PRINT_HTML.ADD_PRINT_TBURL等可以输出超文本的表格,超文有超过打印项高度或纸张高度自动分页 ...
- 原生JavaScript常用本地浏览器存储方法四(HTML5 LocalStorage sessionStorage)
HTML5 LocalStorage浏览器的支持的情况如上图,IE在8.0的时候就支持了.不过需要注意的是,IE测试的时候需要服务器环境(或者localhost). 测试自然是检测浏览器是否支持本地存 ...
- ROW_NUMBER()函数使用详解
原文地址:https://blog.csdn.net/qq_30908543/article/details/74108348 注:mysql貌似不适用,本人测试未成功,mysql实现方式可参考:ht ...
- 【神经网络与深度学习】【计算机视觉】RCNN- 将CNN引入目标检测的开山之作
转自:https://zhuanlan.zhihu.com/p/23006190?refer=xiaoleimlnote 前面一直在写传统机器学习.从本篇开始写一写 深度学习的内容. 可能需要一定的神 ...
- 【OpenGL开发】关于GLEW扩展库
GLEW是一个跨平台的C++扩展库,基于OpenGL图形接口.使用OpenGL的朋友都知道,window目前只支持OpenGL1.1的涵数,但 OpenGL现在都发展到2.0以上了,要使用这些Open ...
- eNSP基于接口地址池的dhcp服务
拓扑图如下 基于接口的dhcp是最简单的一种 我们对路由器的两个端口分别设置ip地址为192.168.1.254 192.168.2.254 然后分别进入接口进行下一步配置 dhcp select i ...
- linux 文件描述符表 打开文件表 inode vnode
在Linux中,进程是通过文件描述符(file descriptors,简称fd)而不是文件名来访问文件的,文件描述符实际上是一个整数.Linux中规定每个进程能最多能同时使用NR_OPEN个文件 ...
- mysql 排除系统库的全部备份
前言: 有些时候,我们要对数据库进行备份的时候,由于GTID的缘故,导出系统库后,再次导入其他环境的数据库时,就会出问题.所以,我们需要排掉一些系统库,排除GTID对于数据库迁移的影响. 方法: ...