• 第47月第24天 redis分布式锁


    1.

    抢购活动开始的一瞬间,大量的用户校验请求打到了用户服务。导致用户服务网关出现了短暂的响应延迟,有些请求的响应时长超过了10s,但由于HTTP请求的响应超时我们设置的是30s,这就导致接口一直阻塞在用户校验那里,10s后,分布式锁已经失效了,此时有新的请求进来是可以拿到锁的,也就是说锁被覆盖了。这些阻塞的接口执行完之后,又会执行释放锁的逻辑,这就把其他线程的锁释放了,导致新的请求也可以竞争到锁~这真是一个极其恶劣的循环。
    这个时候只能依赖库存校验,但是偏偏库存校验不是非原子性的,采用的是get and compare 的方式,超卖的悲剧就这样发生了~~~

    实现相对安全的分布式锁

    相对安全的定义:set、del是一一映射的,不会出现把其他现成的锁del的情况。从实际情况的角度来看,即使能做到set、del一一映射,也无法保障业务的绝对安全。因为锁的过期时间始终是有界的,除非不设置过期时间或者把过期时间设置的很长,但这样做也会带来其他问题。故没有意义。
    要想实现相对安全的分布式锁,必须依赖key的value值。在释放锁的时候,通过value值的唯一性来保证不会勿删。我们基于LUA脚本实现原子性的get and compare,如下:

    public void safedUnLock(String key, String val) {
        String luaScript = "local in = ARGV[1] local curr=redis.call('get', KEYS[1]) if in==curr then redis.call('del', KEYS[1]) end return 'OK'"";
        RedisScript<String> redisScript = RedisScript.of(luaScript);
        redisTemplate.execute(redisScript, Collections.singletonList(key), Collections.singleton(val));
    }
    复制代码

    我们通过LUA脚本来实现安全地解锁。

    实现安全的库存校验

    如果我们对于并发有比较深入的了解的话,会发现想 get and compare/ read and save 等操作,都是非原子性的。如果要实现原子性,我们也可以借助LUA脚本来实现。但就我们这个例子中,由于抢购活动一单只能下1瓶,因此可以不用基于LUA脚本实现而是基于redis本身的原子性。原因在于:

    // redis会返回操作之后的结果,这个过程是原子性的
    Long currStock = redisTemplate.opsForHash().increment("key", "stock", -1);
    复制代码

    发现没有,代码中的库存校验完全是“画蛇添足”。


    public SeckillActivityRequestVO seckillHandle(SeckillActivityRequestVO request) {
    SeckillActivityRequestVO response;
        String key = "key:" + request.getSeckillId();
        String val = UUID.randomUUID().toString();
        try {
            Boolean lockFlag = distributedLocker.lock(key, val, 10, TimeUnit.SECONDS);
            if (!lockFlag) {
                // 业务异常
            }
    
            // 用户活动校验
            // 库存校验,基于redis本身的原子性来保证
            Long currStock = stringRedisTemplate.opsForHash().increment(key + ":info", "stock", -1);
            if (currStock < 0) { // 说明库存已经扣减完了。
                // 业务异常。
                log.error("[抢购下单] 无库存");
            } else {
                // 生成订单
                // 发布订单创建成功事件
                // 构建响应
            }
        } finally {
            distributedLocker.safedUnLock(key, val);
            // 构建响应
        }
        return response;
    }
     

    https://juejin.im/post/6854573212831842311

  • 相关阅读:
    消息队列非阻塞
    外挂简介
    mfc小工具开发之定时闹钟之---二十四小时时区和时间段
    mfc小工具开发之定时闹钟之---时间获取和音频播放
    mfc小工具开发之定时闹钟之---多线程急线程同步
    mfc小工具开发之定时闹钟之---功能介绍
    8127 timeout!!! 搞死人啊
    RGB565 转换 BMP24
    linux 格式化u盘
    linq分页扩展(转)
  • 原文地址:https://www.cnblogs.com/javastart/p/13553194.html
Copyright © 2020-2023  润新知