现在我们的项目进行秒杀活动,因为用户量比较多,需要进行多线程操作,在不加锁的情况下,容易产生超卖现象,当只有一个库存时,多个用户(多线程)同时检查库存,发现库存充足,但在下单之前都没有进行排他性的校验和扣减库存操作,从而导致超卖。
初步改造是加了一把同步锁(http://t.csdnimg.cn/h6ngY),当进行秒杀业务时,第一个线程正在执行,其他的线程同时进来会产生互斥,需要排队等待,需要等持有锁的线程释放了,才可以正常的进行运行处理。但是随着用户量的如意增多,会产生服务器压力变大的现象,性能到达了瓶颈。(同步锁产生瓶颈的原因:http://t.csdnimg.cn/P0u08)。
这时,引入了Ngnix负载均衡,讲服务器进行水平扩展,通过Nfnix进行分布式集群部署,但是在压测的时候发现,吞吐量确实上来了,但是秒杀功能又会出现超卖问题。这又是什么原因呢?
经过研究发现,这个时同步锁的问题,同步锁时JVM级别的,只能够锁住单个进程,但是经分布式部署之后每台服务器只能锁住一个进程,因为有多台服务器,所以会产生超卖现象。
这时,我们可以引入分布式锁(http://t.csdnimg.cn/3gm1z)(redis和zookeeper)来解决这个问题了。这里我们讲解使用redis实现分布式锁。
咱们可以使用redis中的SetNX来实现分布式锁,SetNX的特性是:“当一个线程进来往redis的当中通过SetNX去存储一个值的时候,如果发现一个键里面(这里统称为A键)没有值得时候,他会往里面存储一个值并且返回true,当第二个线程进来准备往A键存储值的时候,发现有值,此时会返回false。”通过SetNX的这个特性,就可以实现分布式锁。
当用户请求进来的时候,通过SetNX来设置一个键,这个键里面没有的时候返回true,加锁成功(这里要加上过期时间,因为用户在请求的过程中的,如果此时服务器挂掉了,那么其他的服务器正常的请求依然会出现一个阻塞的情况。因为其他服务器的线程通过SetNX来进行上锁的时候,会发现这个键当中会一直有值,所以说会永远不会上锁成功,之前挂掉的服务器会一直持有锁,从从而造成一个死锁的现象。加上过期时间,当服务器挂掉了,可以等时间到了之后,进行锁的释放,从而不影响其他服务器的正常请求)。
通过setNX,在用户请求进来加上锁,其他所有服务器的线程此时再通过SetNX会发现已经无法进行上锁了,需要等待锁的释放。只有当第一个线程处理完之后,锁释放之后才能进行第二个线程的运行。
但是随着业务的扩展会爆露出另外的一些问题,当业务的处理时间超过了这把锁的过期时间业务还没有处理完,这把锁会自动释放,其他的线程此时就会趁虚而入,线程一处理完了之后,此时释放的是线程二的锁,其他的线程又会趁虚而入,以此类推,从而造成超卖的现象。
这里其实是两个问题:
第一个问题是当锁的过期时间到了,业务代码还没有执行完成,是一个锁的过期时间的问题。
第二个问题是在第一个问题的基础上,当业务代码处理完之后,由于其他线程的趁虚而入,而导致释放了其他线程的锁。
这里又引入了Redisson,完美解决了这个问题。