转:https://segmentfault.com/a/1190000011421467

废话不多说,首先分享一个业务场景-抢购。一个典型的高并发问题,所需的最关键字段就是库存,在高并发的情况下每次都去数据库查询显然是不合适的,因此把库存信息存入Redis中,利用redis的锁机制来控制并发访问,是一个不错的解决方案。

首先是一段业务代码:

  1. @Transactional
  2. public void orderProductMockDiffUser(String productId){
  3. //1.查库存
  4. int stockNum = stock.get(productId);
  5. if(stocknum == 0){
  6. throw new SellException(ProductStatusEnum.STOCK_EMPTY);
  7. //这里抛出的异常要是运行时异常,否则无法进行数据回滚,这也是spring中比较基础的
  8. }else{
  9. //2.下单
  10. orders.put(KeyUtil.genUniqueKey(),productId);//生成随机用户id模拟高并发
  11. sotckNum = stockNum-1;
  12. try{
  13. Thread.sleep(100);
  14. } catch (InterruptedExcption e){
  15. e.printStackTrace();
  16. }
  17. stock.put(productId,stockNum);
  18. }
  19. }

这里有一种比较简单的解决方案,就是synchronized关键字。

  1. public synchronized void orderProductMockDiffUser(String productId)

这就是java自带的一种锁机制,简单的对函数加锁和释放锁。但问题是这个实在是太慢了,感兴趣的可以可以写个接口用apache ab压测一下。

  1. ab -n 500 -c 100 http://localhost:8080/xxxxxxx

下面就是redis分布式锁的解决方法。首先要了解两个redis指令
SETNX 和 GETSET,可以在redis中文网上找到详细的介绍。
SETNX就是set if not exist的缩写,如果不存在就返回保存value并返回1,如果存在就返回0。
GETSET其实就是两个指令GET和SET,首先会GET到当前key的值并返回,然后在设置当前Key为要设置Value。

首先我们先新建一个RedisLock类:

/***
* 加锁
* @param key
* @param value 当前时间+超时时间
* @return 锁住返回true
*/
public boolean lock(String key,String value){
if(stringRedisTemplate.opsForValue().setIfAbsent(key,value)){//setNX 返回boolean
return true;
}
//如果锁超时 ***
String currentValue = stringRedisTemplate.opsForValue().get(key);
if(!StringUtils.isEmpty(currentValue) &amp;&amp; Long.parseLong(currentValue)<System.currentTimeMillis()){
//获取上一个锁的时间
String oldvalue = stringRedisTemplate.opsForValue().getAndSet(key,value);
if(!StringUtils.isEmpty(oldvalue)&amp;&amp;oldvalue.equals(currentValue)){
return true;
}
}
return false;
}
/***
* 解锁
* @param key
* @param value
* @return
*/
public void unlock(String key,String value){
try {
String currentValue = stringRedisTemplate.opsForValue().get(key);
if(!StringUtils.isEmpty(currentValue)&amp;&amp;currentValue.equals(value)){
stringRedisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
log.error(&quot;解锁异常&quot;);
}
}

}" title="" data-original-title="复制">

  1. @Slf4j

  2. @Component

  3. public class RedisService {

  4. @Autowired

  5. private StringRedisTemplate stringRedisTemplate;
  6. <span class="hljs-comment">/***
  7.  * 加锁
  8.  * <span class="hljs-doctag">@param</span> key
  9.  * <span class="hljs-doctag">@param</span> value 当前时间+超时时间
  10.  * <span class="hljs-doctag">@return</span> 锁住返回true
  11.  */</span>
  12. <span class="hljs-keyword">public</span> boolean lock(String key,String value){
  13.     <span class="hljs-keyword">if</span>(stringRedisTemplate.opsForValue().setIfAbsent(key,value)){<span class="hljs-comment">//setNX 返回boolean</span>
  14.         <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  15.     }
  16.     <span class="hljs-comment">//如果锁超时 ***</span>
  17.     String currentValue = stringRedisTemplate.opsForValue().<span class="hljs-keyword">get</span>(key);
  18.     <span class="hljs-keyword">if</span>(!StringUtils.isEmpty(currentValue) &amp;&amp; <span class="hljs-built_in">Long</span>.parseLong(currentValue)&lt;System.currentTimeMillis()){
  19.         <span class="hljs-comment">//获取上一个锁的时间</span>
  20.         String oldvalue  = stringRedisTemplate.opsForValue().getAndSet(key,value);
  21.         <span class="hljs-keyword">if</span>(!StringUtils.isEmpty(oldvalue)&amp;&amp;oldvalue.equals(currentValue)){
  22.             <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
  23.         }
  24.     }
  25.     <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  26. }
  27. <span class="hljs-comment">/***
  28.  * 解锁
  29.  * <span class="hljs-doctag">@param</span> key
  30.  * <span class="hljs-doctag">@param</span> value
  31.  * <span class="hljs-doctag">@return</span>
  32.  */</span>
  33. <span class="hljs-keyword">public</span> void unlock(String key,String value){
  34.     <span class="hljs-keyword">try</span> {
  35.         String currentValue = stringRedisTemplate.opsForValue().<span class="hljs-keyword">get</span>(key);
  36.         <span class="hljs-keyword">if</span>(!StringUtils.isEmpty(currentValue)&amp;&amp;currentValue.equals(value)){
  37.             stringRedisTemplate.opsForValue().getOperations().delete(key);
  38.         }
  39.     } <span class="hljs-keyword">catch</span> (Exception e) {
  40.         log.error(<span class="hljs-string">"解锁异常"</span>);
  41.     }
  42. }
  43. }

这个项目是springboot的项目。首先要加入redis的pom依赖,该类只有两个功能,加锁和解锁,解锁比较简单,就是删除当前key的键值对。我们主要来说一说加锁这个功能。
首先,锁的value值是当前时间加上过期时间的时间戳,Long类型。首先看到用setiFAbsent方法也就是对应的SETNX,在没有线程获得锁的情况下可以直接拿到锁,并返回true也就是加锁,最后没有获得锁的线程会返回false。 最重要的是中间对于锁超时的处理,如果没有这段代码,当秒杀方法发生异常的时候,后续的线程都无法得到锁,也就陷入了一个死锁的情况。我们可以假设CurrentValue为A,并且在执行过程中抛出了异常,这时进入了两个value为B的线程来争夺这个锁,也就是走到了注释*的地方。currentValue==A,这时某一个线程执行到了getAndSet(key,value)函数(某一时刻一定只有一个线程执行这个方法,其他要等待)。这时oldvalue也就是之前的value等于A,在方法执行过后,oldvalue会被设置为当前的value也就是B。这时继续执行,由于oldValue==currentValue所以该线程获取到锁。而另一个线程获取的oldvalue是B,而currentValue是A,所以他就获取不到锁啦。多线程还是有些乱的,需要好好想一想。
接下来就是在业务代码中加锁啦:首要要@Autowired注入刚刚RedisLock类,不要忘记对这个类加一个@Component注解否则无法注入

  1. private static final int TIMEOUT= 10*1000;
  2. @Transactional
  3. public void orderProductMockDiffUser(String productId){
  4. long time = System.currentTimeMillions()+TIMEOUT;
  5. if(!redislock.lock(productId,String.valueOf(time)){
  6. throw new SellException(101,"换个姿势再试试")
  7. }
  8. //1.查库存
  9. int stockNum = stock.get(productId);
  10. if(stocknum == 0){
  11. throw new SellException(ProductStatusEnum.STOCK_EMPTY);
  12. //这里抛出的异常要是运行时异常,否则无法进行数据回滚,这也是spring中比较基础的
  13. }else{
  14. //2.下单
  15. orders.put(KeyUtil.genUniqueKey(),productId);//生成随机用户id模拟高并发
  16. sotckNum = stockNum-1;
  17. try{
  18. Thread.sleep(100);
  19. } catch (InterruptedExcption e){
  20. e.printStackTrace();
  21. }
  22. stock.put(productId,stockNum);
  23. }
  24. redisLock.unlock(productId,String.valueOf(time));
  25. }

大功告成了!比synchronized快了不知道多少倍,再也不会被老板骂了!

  1. </div>
posted on
2019-09-20 08:43 
duende99 
阅读(...) 
评论(...) 
编辑 
收藏

Redis分布式锁解决抢购问题的更多相关文章

  1. 使用redis分布式锁解决并发线程资源共享问题

    众所周知, 在多线程中,因为共享全局变量,会导致资源修改结果不一致,所以需要加锁来解决这个问题,保证同一时间只有一个线程对资源进行操作 但是在分布式架构中,我们的服务可能会有n个实例,但线程锁只对同一 ...

  2. redis分布式锁解决超卖问题

    redis事务 redis事务介绍:    1. redis事务可以一次执行多个命令,本质是一组命令的集合. 2.一个事务中的所有命令都会序列化,按顺序串行化的执行而不会被其他命令插入 作用:一个队列 ...

  3. 应用Redis分布式锁解决重复通知的问题

    研究背景: 这几天被支付宝充值后通知所产生的重复处理问题搞得焦头烂额, 一周连续发生两次重复充钱的杯具, 发事故邮件发到想吐..为了挽回程序员的尊严, 我用了Redis的锁机制. 事故场景: 支付宝下 ...

  4. 利用redis 分布式锁 解决集群环境下多次定时任务执行

    定时任务: @Scheduled(cron= "0 39 3 * * *") public void getAllUnSignData(){ //检查任务锁,若其它节点的相同定时任 ...

  5. 使用Redis分布式锁处理并发,解决超卖问题

    一.使用Apache ab模拟并发压测 1.压测工具介绍 $ ab -n 100 -c 100 http://www.baidu.com/ -n表示发出100个请求,-c模拟100个并发,相当是100 ...

  6. springmvc单Redis实例实现分布式锁(解决锁超时问题)

    一.前言 关于redis分布式锁, 查了很多资料, 发现很多只是实现了最基础的功能, 但是, 并没有解决当锁已超时而业务逻辑还未执行完的问题, 这样会导致: A线程超时时间设为10s(为了解决死锁问题 ...

  7. springboot+redis分布式锁-模拟抢单

    本篇内容主要讲解的是redis分布式锁,这个在各大厂面试几乎都是必备的,下面结合模拟抢单的场景来使用她:本篇不涉及到的redis环境搭建,快速搭建个人测试环境,这里建议使用docker:本篇内容节点如 ...

  8. 【分布式缓存系列】集群环境下Redis分布式锁的正确姿势

    一.前言 在上一篇文章中,已经介绍了基于Redis实现分布式锁的正确姿势,但是上篇文章存在一定的缺陷——它加锁只作用在一个Redis节点上,如果通过sentinel保证高可用,如果master节点由于 ...

  9. SpringBoot集成Redis分布式锁以及Redis缓存

    https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...

随机推荐

  1. Ant环境搭建

    1.上传安装包到linux服务器 2.解压缩 tar zxvf  apache-ant-1.10.1-bin.tar.gz 3.修改环境变量 vim /etc/profile 添加以下内容 expor ...

  2. CVE-2019-0708复现

    本人在此申明: 此次复现仅供学习使用 不可用于非法用途 一切违法后果与本人无关 复现0708第一步 github下载exp Kali里面执行命令 wget https://raw.githubuser ...

  3. JAVA基础知识|HTTP协议-发展历程

    HTTP 是基于 TCP/IP 协议的应用层协议.它不涉及数据包(packet)传输,主要规定了客户端和服务器之间的通信格式,默认使用80端口. 此文章为转载内容:http://www.ruanyif ...

  4. java随机生成6位随机数 5位随机数 4位随机数

    随机数,应用会相当广,验证数,订单号,流水号拼接. 下面是java随机数生成语句: 生成6位随机数(不会是5位或者7位,仅只有6位): System.+)*)); 同理,生成5位随机数: System ...

  5. C/C++程序基础-C++与C有什么不同

    1:C和C++的联系和区别? 答:C是一个结构化语言,它的重点在于算法和数据结构.对于语言本身而言,C是C++的子集.C程序的设计首先要考虑的是如何通过一个过程,对输入进行运算处理,得到输出.对于C+ ...

  6. sql语句中where 1=1和 0=1 的作用

    sql where 1=1和 0=1 的作用   where 1=1; 这个条件始终为True,在不定数量查询条件情况下,1=1可以很方便的规范语句. 一.不用where  1=1  在多条件查询中的 ...

  7. Nginx之共享内存与slab机制

    1. 共享内存 在 Nginx 里,一块完整的共享内存以结构体 ngx_shm_zone_t 来封装,如下: typedef struct ngx_shm_zone_s ngx_shm_zone_t; ...

  8. P3951 小凯的疑惑

    P3951 小凯的疑惑 题解 题意也就是求解不能用 ax+by 表示的最大数 ans(a,b,x,y,都是正整数) 给定 a ( =7 ) ,  b ( =3 ) 我们可以把数轴非负半轴上的数按照a的 ...

  9. ubuntu如何删除刚添加的源?

    答: sudo add-apt-repository -r <source_url> 如: sudo add-apt-repository -r ppa:linaro-maintainer ...

  10. weblogic密码重置----未做成

    1.备份DefaultAuthenticatorInit.ldift文件 [root@test4 ~]# find / -name DefaultAuthenticatorInit.ldift /ap ...