一、前言
最近项目中,基本功能实现了,准备都访问多的接口加缓存。当然缓存就想到了redis。正好自己也查了查redis带来的一些问题:缓存穿透、缓存并发、热点缓存等。
也想到自己负责的模块涉及到资金,同一时间只能有一个人操作,想象一下,同一时间2个用户同时还款放款,一个人账户增加一个减少,为了方式同时操作数据不一致,需要锁。如果是单体服务,可以直接利用数据库的行锁或者表锁。如果是微服务集群,多个客户端同时修改一个共享数据就需要分布式锁了。
二、使用redis实现分布式锁
场景:秒杀
现在有100个商品,进行秒杀,因为商品价格便宜,所以就会有很多人来抢。首先我们要保证的就是:
1 商品最后不能成负数
2 防止超出数据库连接池数目
3 保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。
业务核心:
每个用户访问的时候,先要获取锁,如果获取到了,就可以购买商品,如果获取不到就再次访问。
/**
* 分布式锁尝试
* 每个用户访问的时候,先要获取锁,如果获取到了,就可以购买商品,如果获取不到就再次访问
*
*
* @param feeCode
* @throws Exception
*/
public void stock(String feeCode) throws Exception {
//加锁
long time = System.currentTimeMillis()+50000;
if (!redisService.lock(feeCode, String.valueOf(time))){
throw new Exception("没有获取到分布式锁呀!!");
}
//1.查询商品库存。为0则买完了
if (count<1){
throw new Exception("商品抢购结束");
}else {
//下单
count--;
Thread.sleep(1000);
log.info(String.valueOf(count));
}
//解锁
redisService.unlock(feeCode, String.valueOf(time));
}
分布式锁,加锁:
这里要注意的是,使用redis的SetNx来实现加锁。SetNx是如果key不存在就存储到redis中,并返回1,否则如果key存在,就什么也不做,返回0。
这里呢,因为很多人抢一种产品,所以key可以为productTypeId ,value为 当前时间+超时时间。
针对这个超时时间呢?大家在平常的时候很常见,就是比如在某宝、某东、12306等买东西的时候,当添加购物车后,前去结账,结账的时候,都会有一个请在30分钟内付款。否则就自动放弃。我们的这个超时时间就是类似付款时候的30分钟。这个是要根据我们具体的业务具体设定的。
如果锁过期,获取上一个锁的时间,如果锁过期,也就是存储的锁的值小于当前时间,那么使用GetSet方法把值替换掉。
/**
* 分布式锁,加锁
* @param key
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key, String value) {
//如果key存在,就返回,否则就存储
if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
String currentValue = (String) redisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间
String oldValue = (String) redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue)&&oldValue.equals(currentValue) ){
return true;
}
}
return false;
}
分布式锁,解锁:
我们先来根据
/**
* 分布式锁,解锁
* @param key
* @param value
*/
public void unlock(String key,String value){
try{
String currentValue = (String) redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue)&¤tValue.equals(value)){
redisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e){
log.error("[redis分布式锁]解锁异常,{}",e);
}
}
三、小结
保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。分布式锁就可以实现这个功能。用到了redis的两个命令,SetNx和GetSet。了解一下。