在高并发场景中,我们需要对资源上锁。比如在秒杀商品时,大量请求过来,不上锁可能导致超卖等现象的发生。
大家都知道在java中,它自身就提供了很多锁,synchronized,lock,ReentrantLock....
它们可以工作在单机项目里面,但是在分布式项目里面,他们就不能保证对全局资源的掌控了。
那如何实现在分布式环境中对全局资源的掌控呢?(声明,此处只探讨数据库和redis实现)
1.数据库实现
数据库又分乐观锁和悲观锁
悲观锁的实现思路:使用for update来锁住记录,当有别的线程同时来修改的时候,就会去等待,是不是有点像synchronized的实现(synchronized实现原理也是多个线程一起来请求,只有一个线程可以获取锁变成owner,其他没有获取锁的线程在moniter enter区等待)
乐观锁的实现思路:在行字段中添加version字段,取出version对比。如果自己拿到的版本号和当前库里面的记录是一样的就更新记录。否则不做处理,怎么感觉有点像cas。(cas里面的实现原理简单来说就是程序自旋,如果自己手上的值和变量当前的值一样,就做update处理,否则就啥也不干)
采用数据库实现,终究服务器压力全部都到数据库上面了。不管是乐观锁还是悲观锁实现,就算数据库搞成集群模式,它的承受能力都是有限的。并不推荐使用数据库模式做锁。
2.借助第三方redis实现
这是主要要讲解的点。
其实对于锁的使用来说,终究还是要看场景的。
1)用户抢购某种商品,如果当次请求没有抢到锁,可以立即返回一个未抢到,再次请抢购的的提示。
2)用户抢购某种商品,如果当前没有获得锁,将当前线程添加到同步队列中获取锁资源,锁需要考虑过期时间限制来防止死锁。
之前有一种解决方法是 利用redis的set方法
/**
* 存储数据到缓存中,并制定过期时间和当Key存在时是否覆盖。
*
* @param key
* @param value
* @param nxxx
* nxxx的值只能取NX或者XX,如果取NX,则只有当key不存在是才进行set,如果取XX,则只有当key已经存在时才进行set
*
* @param expx expx的值只能取EX或者PX,代表数据过期时间的单位,EX代表秒,PX代表毫秒。
* @param time 过期时间,单位是expx所代表的单位。
* @return
*/
String set(String key, String value, String nxxx, String expx, long time);
由于redis是单线的的,这个set方法正好可以做分布式下面的锁。
获取锁:如果key不存在,set方法返回“ok”,当前线程添加锁成功,别的线程无法获得锁。
释放锁:通过lua脚本的原子操作来释放锁。redis.call('del',key)。
这种方案确实可以实现锁,但是它却不是可重入的。后面发现redis本身是提供可重入锁的。有点像Reentrantlock。可参考redisson的reentrant-lock