我们在做一些活动的时候,会遇到一些分布式的情况。比如把秒杀商品。当很多人在抢商品的时候,我们就需要用分布式锁,保证在同一时间内只能由一个人来扣减库存。这个时间段内其他人只能提示系统繁忙。所以,能够锁住是非常重要的。接下来我们就说如何做。
一:引入jar包
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
二:配置文件配置application.properties
spring.redis.cluster.nodes=redis集群地址
spring.redis.password=redis集群密码
spring.redis.cluster.max-redirects=3
spring.redis.lettuce.pool.max-active=1000
spring.redis.lettuce.pool.max-wait=60s
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.min-idle=4
三:Redis配置RedisInitializer.java
/**
* Redis配置类
*/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableCaching
public class RedisInitializer extends CachingConfigurerSupport {
@Autowired
LettuceConnectionFactory redisConnectionFactory;
@Bean
public RedisTemplate<Object, Object> redisTemplate() {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// value值的序列化采用GenericJackson2JsonRedisSerializer
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
// 开启事务特性
//template.setEnableTransactionSupport(true);
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
上面就是开发前的准备工作。接下来就是核心功能。加锁和解锁。
四:Redis的工具类Util
@Component
public class RedisLockUtil {
private static final Logger logger= LoggerFactory.getLogger(RedisLockUtil.class);
/*** 分布式锁固定前缀 ***/
private static final String REDIS_LOCK = "redis_lock_";
/*** 分布式锁过期时间 ***/
private static final Integer EXPIRE_TIME = 30;
/*** 每次自旋睡眠时间 ***/
private static final Integer SLEEP_TIME = 50;
/*** 分布式锁自旋次数 ***/
private static final Integer CYCLES = 10;
@SuppressWarnings("all")
@Resource(name = "redisTemplate")
private ValueOperations<String, String> lockOperations;
/**
* 加锁
*
* @param key 加锁唯一标识
* @param value 释放锁唯一标识(建议使用线程ID作为value)
*/
public void lock(String key, String value) {
lock(key, value, EXPIRE_TIME);
}
/**
* 加锁
* @param key 加锁唯一标识
* @param value 释放锁唯一标识(建议使用线程ID作为value)
* @param timeout 超时时间(单位:S)
*/
public void lock(String key, String value, Integer timeout) {
Assert.isTrue(StringUtils.isNotBlank(key), "redis locks are identified as null.");
Assert.isTrue(StringUtils.isNotBlank(value), "the redis release lock is identified as null.");
int cycles = CYCLES;
// ----- 尝试获取锁,当获取到锁,则直接返回,否则,循环尝试获取
while (!tryLock(key, value, timeout)) {
// ----- 最多循环10次,当尝试了10次都没有获取到锁,抛出异常
if (0 == (cycles--)) {
logger.error("redis try lock fail. key: {}, value: {}", key, value);
throw new RuntimeException("redis try lock fail.");
}
try {
TimeUnit.MILLISECONDS.sleep(SLEEP_TIME);
} catch (Exception e) {
logger.error("history try lock error.", e);
}
}
}
/**
* 尝试获取锁
* @param key 加锁唯一标识
* @param value 释放锁唯一标识(建议使用线程ID作为value)
* @param timeout 超时时间(单位:S)
* @return [true: 加锁成功; false: 加锁失败]
*/
public boolean tryLock(String key, String value, Integer timeout) {
Boolean result = lockOperations.setIfAbsent(REDIS_LOCK + key, value, timeout, TimeUnit.SECONDS);
return result != null && result;
}
/**
* 释放锁
* @param key 加锁唯一标识
* @param value 释放锁唯一标识(建议使用线程ID作为value)
*/
public void unLock(String key, String value) {
Assert.isTrue(StringUtils.isNotBlank(key), "redis locks are identified as null.");
Assert.isTrue(StringUtils.isNotBlank(value), "the redis release lock is identified as null.");
key = REDIS_LOCK + key;
// ----- 通过value判断是否是该锁:是则释放;不是则不释放,避免误删
if (value.equals(lockOperations.get(key))) {
lockOperations.getOperations().delete(key);
}
}
}
五:实现商品加锁和解锁
//进入扣减商品的标识
private int i=0;
//进入扣减商品减少步骤的标识
private int j=0;
/**
* 扣减库存
* @param id 商品id
* @return
*/
public Result relase(Integer id) {
i++;
boolean flag = redisLockUtil.tryLock(Constant.REDIS_LOCK_GOODS + id, Thread.currentThread().getId() + "",10);
logger.info("加锁结果:"+flag);
if(flag){
Object obj = redisTemplate.opsForValue().get(Constant.REDIS_GOODS + id);
j++;
RedisGoods redisGoods = (RedisGoods) obj;
if(redisGoods.getRemainCount()<=0){
logger.info("没有库存了!");
return Result.sendSuccess("没有库存了!");
}
redisGoods.setRemainCount(redisGoods.getRemainCount() - 1);
redisGoodsMapperExt.updateByPrimaryKey(redisGoods);
logger.info("抽奖"+i+"次,进入抽奖里面"+j+"次还剩库存"+redisGoods.getRemainCount());
redisTemplate.opsForValue().set(Constant.REDIS_GOODS + redisGoods.getId(), redisGoods);
redisTemplate.expire(Constant.REDIS_GOODS+ redisGoods.getId(),10, TimeUnit.MINUTES);
redisLockUtil.unLock(Constant.REDIS_LOCK_GOODS+redisGoods.getId(),Thread.currentThread().getId()+"");
if(redisGoods.getRemainCount()==0){
logger.info("第"+j+"次商品卖完了");
}
RedisRecord record=new RedisRecord();
record.setCreateTime(new Date());
record.setStatus(1);
record.setComment("第"+j+"次购买商品");
redisRecordMapperExt.insert(record);
return Result.sendSuccess("操作成功!");
}else{
logger.info("还在加锁");
RedisRecord record=new RedisRecord();
record.setCreateTime(new Date());
record.setStatus(0);
record.setComment("第"+i+"次没有进入商品里面,因为还在加锁中");
redisRecordMapperExt.insert(record);
return Result.sendSuccess("太繁忙了。稍后再试!");
}
}
当我们需要使用的时候。直接调用方法就行。
参考:Redis实现分布式锁