0
点赞
收藏
分享

微信扫一扫

Redis实现分布式锁(可直接上手)


        我们在做一些活动的时候,会遇到一些分布式的情况。比如把秒杀商品。当很多人在抢商品的时候,我们就需要用分布式锁,保证在同一时间内只能由一个人来扣减库存。这个时间段内其他人只能提示系统繁忙。所以,能够锁住是非常重要的。接下来我们就说如何做。

一:引入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实现分布式锁

Redis 分布式锁|从青铜到钻石的五种演进方案


举报

相关推荐

0 条评论