首先分享一个业务场景-抢购。一个典型的高并发问题,所需的最关键字段就是库存,在高并发的情况下每次都去数据库查询显然是不合适的,因此把库存信息存入Redis中,利用redis的锁机制来控制并发访问,是一个不错的解决方案。
首先是一段业务代码:
@Transactional
public void orderProductMockDiffUser(String productId){
这里有一种比较简单的解决方案,就是synchronized关键字。
public synchronized void orderProductMockDiffUser(String productId)
这就是java自带的一种锁机制,简单的对函数加锁和释放锁。但问题是这个实在是太慢了,感兴趣的可以可以写个接口用apache ab压测一下。
ab -n 500 -c 100 http:
下面就是redis分布式锁的解决方法。首先要了解两个redis指令 SETNX 和 GETSET,可以在redis中文网上找到详细的介绍。 SETNX就是set if not exist的缩写,如果不存在就返回保存value并返回1,如果存在就返回0。 GETSET其实就是两个指令GET和SET,首先会GET到当前key的值并返回,然后在设置当前Key为要设置Value。
首先我们先新建一个RedisLock类:
@Slf4j
@Component
public class RedisService { @Autowired private StringRedisTemplate stringRedisTemplate;
这个项目是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注解否则无法注入
private static final int TIMEOUT= 10*1000; @Transactional public void orderProductMockDiffUser(String productId){ long time = System.currentTimeMillions()+TIMEOUT; if(!redislock.lock(productId,String.valueOf(time)){ throw new SellException(101,"换个姿势再试试") }
|