MiniMall:使用Redis实现分布式锁,太简单不过了

1. 什么是锁

在一个进程中,当存在多个线程同时操作某个共享资源时,就需要对共享资源做同步,使其在修改这个共享资源时能够线性地执行操作。而实现同步的手段就是锁,当线程准备对共享资源做修改前,先获取锁,如果当前共享资源已经被锁,则进行等待;若没有被锁,则成功获取锁,并允许执行修改。在修改完毕后,释放锁资源,那么下一个线程也是如此,只有获取到锁,才能对共享资源修改。

2. 什么是分布式锁

分布式锁是为了防止分布式系统中多个进程之间相互干扰,从而需要一种分布式协调技术来对这些进程进行调度。

3. Redis实现分布式锁

实现分布式锁的手段有很多,比如基于数据库的悲观锁和乐观锁、基于Redis做分布式锁、基于Zookeeper做分布式锁等等。之前,我们把Redis当做缓存系统使用,今天我们再来看看如何使用Redis实现分布式锁。

Redis实现分布式锁需要用到其中的两个方法:setnx()expire()

  • setnx()

setnx的含义就是SET if Not Exists,该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。

  • expire()

expire设置过期时间,要注意的是setnx命令不能设置key的超时时间,只能通过expire来对key设置。

3.1 实现步骤

  1. setnx(key, value)如果返回0,则说明当前资源已经被锁,需要等待;如果返回1,则说明成功获取锁;
  2. expire()命令对key设置超时时间,避免死锁;
  3. 执行业务代码后,通过delete命令删除key。

需要注意的是:从解决日常工作中的需求来说,该方案已经够用。但从技术角度来说,该方案还不够严谨。比如在第一步设置setnx执行成功后,expire命令执行前出现了异常,那么就还是会出现死锁问题。但是我们这个项目中暂时不考虑这种异常情况了,在后面可能会有一个专门讨论分布式锁的专题博客,可以持续关注下。

3.2 业务背景

以商品微服务下的商品入库单业务为背景,商品入库单存在未生效已生效两种业务状态,当录入一个商品入库单保存成功之后,该商品入库单为未生效状态,此时商品还没有入库。只有在执行生效这个业务动作时,商品入库单从未生效变成已生效状态,然后再对商品的库存进行增加。从业务角度上来说,一个商品入库单是不允许重复生效的,所以在这里就需要对生效的商品入库单进行加锁,避免分布式环境下重复生效。

3.3 代码实现

com.autumn.mall.commons.utils包结构中,有一个RedisUtils工具类,用来对Redis常规操作做了简单的封装,其中我们也对获取分布式锁的操作进行了封装:

public boolean tryLock(String key) {
    return tryLock(key, null);
}
public boolean tryLock(String key, String value) {
    return tryLock(key, value, 3, TimeUnit.MINUTES);
}
public boolean tryLock(String key, String value, int timeout, TimeUnit timeUnit) {
    try {
        if (StringUtils.isBlank(value)) {
            long currTime = System.currentTimeMillis();
            // 加锁成功
            return redisTemplate.opsForValue().setIfAbsent(key, currTime);
        }
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    } finally {
        redisTemplate.expire(key, timeout, timeUnit);
    }
}

商品入库单(GoodsInboundServiceImpl)生效方法:

public void doEffect(String uuid) {
    // 获取分布式锁
    try {
        while (redisUtils.tryLock(getLockKeyPrefix() + uuid) == false) {
            TimeUnit.SECONDS.sleep(3);
        }
    } catch (Exception e) {
        MallExceptionCast.cast(CommonsResultCode.TRY_LOCKED_ERROR);
    }
    Optional<GoodsInbound> optional = goodsInboundRepository.findById(uuid);
    if (optional.isPresent() == false) {
        MallExceptionCast.cast(CommonsResultCode.ENTITY_IS_NOT_EXIST);
    }
    if (optional.get().getState().equals(BizState.effect)) {
        MallExceptionCast.cast(ProductResultCode.ENTITY_IS_EQUALS_TARGET_STATE);
    }
    // 生效入库单
    GoodsInbound entity = optional.get();
    entity.setState(BizState.effect);
    getRepository().save(entity);
    // 商品入库
    List<GoodsInboundDetail> details = goodsInboundDetailRepository.findAllByGoodsInboundUuidOrderByLineNumber(entity.getUuid());
    List<Stock> stocks = new ArrayList<>();
    details.stream().forEach(detail -> {
        Stock stock = new Stock();
        stock.setEntityKey(MallModuleKeyPrefixes.PRODUCT_KEY_PREFIX_OF_GOODS + detail.getGoodsUuid());
        stock.setWarehouse(entity.getWarehouse());
        stock.setQuantity(detail.getQuantity());
        stocks.add(stock);
    });
    ResponseResult responseResult = stockClient.inbound(stocks);
    // 删除分布式锁
    redisUtils.remove(getLockKeyPrefix() + uuid);
}
——End——
更多精彩分享,可扫码关注微信公众号哦。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值