最近,很多小伙伴留言说,在学习高并发编程时,不太明白分布式锁是用来解决什么问题的,还有不少小伙伴甚至连分布式锁是什么都不太明白。明明在生产环境上使用了自己开发的分布式锁,为什么还会出现问题呢?同样的程序,加上分布式锁后,性能差了几个数量级!这又是为什么呢?今天,我们就来说说如何在高并发环境下实现分布式锁,不是所有的锁都是高并发的。
万字长文,带你深入解密高并发环境下的分布式锁架构,不是所有的锁都是分布式锁!!!
究竟什么样的锁才能更好的支持高并发场景呢?今天,我们就一起解密高并发环境下典型的分布式锁架构,结合【高并发】专题下的其他文章,学以致用。
锁用来解决什么问题呢?在我们编写的应用程序或者高并发程序中,不知道大家有没有想过一个问题,就是我们为什么需要引入锁?锁为我们解决了什么问题呢?
在很多业务场景下,我们编写的应用程序中会存在很多的 资源竞争 的问题。而我们在高并发程序中,引入锁,就是为了解决这些资源竞争的问题。
电商超卖问题这里,我们可以列举一个简单的业务场景。比如,在电子商务(商城)的业务场景中,提交订单购买商品时,首先需要查询相应商品的库存是否足够,只有在商品库存数量足够的前提下,才能让用户成功的下单。下单时,我们需要在库存数量中减去用户下单的商品数量,并将库存操作的结果数据更新到数据库中。整个流程我们可以简化成下图所示。
很多小伙伴也留言说,让我给出代码,这样能够更好的学习和掌握相关的知识。好吧,这里,我也给出相应的代码片段吧。我们可以使用下面的代码片段来表示用户的下单操作,我这里将商品的库存信息保存在了Redis中。
@RequestMapping("/submitOrder") public String submitOrder(){ int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); if(stock > 0){ stock -= 1; stringRedisTemplate.opsForValue().set("stock", String.valueOf(stock)); logger.debug("库存扣减成功,当前库存为:{}", stock); }else{ logger.debug("库存不足,扣减库存失败"); throw new OrderException("库存不足,扣减库存失败"); } return "success"; }注意:上述代码片段比较简单,只是为了方便大家理解,真正项目中的代码就不能这么写了。
上述的代码看似是没啥问题的,但是我们不能只从代码表面上来观察代码的执行顺序。这是因为在JVM中代码的执行顺序未必是按照我们书写代码的顺序执行的。即使在JVM中代码是按照我们书写的顺序执行,那我们对外提供的接口一旦暴露出去,就会有成千上万的客户端来访问我们的接口。所以说,我们暴露出去的接口是会被并发访问的。
试问,上面的代码在高并发环境下是线程安全的吗?答案肯定不是线程安全的,因为上述扣减库存的操作会出现并行执行的情况。
我们可以使用Apache JMeter来对上述接口进行测试,这里,我使用Apache JMeter对上述接口进行测试。
在Jmeter中,我将线程的并发度设置为3,接下来的配置如下所示。
以HTTP GET请求的方式来并发访问提交订单的接口。此时,运行JMeter来访问接口,命令行会打印出下面的日志信息。
库存扣减成功,当前库存为:49 库存扣减成功,当前库存为:49 库存扣减成功,当前库存为:49这里,我们明明请求了3次,也就是说,提交了3笔订单,为什么扣减后的库存都是一样的呢?这种现象在电商领域有一个专业的名词叫做 “超卖” 。
如果一个大型的高并发电商系统,比如淘宝、天猫、京东等,出现了超卖现象,那损失就无法估量了!架构设计和开发电商系统的人员估计就要通通下岗了。所以,作为技术人员,我们一定要严谨的对待技术,严格做好系统的每一个技术环节。
JVM中提供的锁JVM中提供的synchronized和Lock锁,相信大家并不陌生了,很多小伙伴都会使用这些锁,也能使用这些锁来实现一些简单的线程互斥功能。那么,作为立志要成为架构师的你,是否了解过JVM锁的底层原理呢?
JVM锁原理说到JVM锁的原理,我们就不得不限说说Java中的对象头了。
Java中的对象头
每个Java对象都有对象头。如果是⾮数组类型,则⽤2个字宽来存储对象头,如果是数组,则会⽤3个字宽来存储对象头。在32位处理器中,⼀个字宽是32位;在64位虚拟机中,⼀个字宽是64位。