高并发下超卖和超领的一些心得

最近写电商的demo项目遇到一些比较重要的知识点进行分享一下。

高并发库存扣减超卖问题

首先很多人应该会想到乐观锁或者写sql。
这里乐观锁可以采用mybatisplus中的乐观锁,很简单的实现。但是存在ABA问题。
ABA问题:就是说你在扣减库存前,其他线程已经对该商品进行了增加,扣减的操作,当前线程再来进行扣减的操作,这个商品库存已经被修改了,当前线程不知道。
显然这个并不会对这个结果有什么影响。
写sql:就是通过在where条件语句下进行限制,不能小于0,这样扣减的时候,就会产生错误,导致执行失败。
相比之下写sql防止超卖比加版本号性能更高一些。因为减少了比较版本号的操作。

单用户超领问题

超卖我们解决了。但超领的问题还是没有解决。因为,高并发下,不做限制,就会产生一个用户领取了多张同一个优惠券,即使限制了领取张数。也就是做了判断。也会超领。
这里有人可能会想到加锁。
没错,加锁是能解决,但是加什么锁也需要我们思考!
如果加synchronize、lock等这些锁在当前进程内,集群部署下依旧存在问题。
这里就会考虑分布式锁一般由redis、zookeeper等实现。
这里如果考虑性能,大家都会首先想到redis来实现。
redis里面有setnx。它的含义就是SETifNotExists,有两个参数setnx(key,value),该方法是原子性操作
如果key不存在,则设置当前key成功,返回1;
如果当前key已经存在,则设置当前key失败,返回0。
这样的话就可以使:
得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入,调用del(key)

但是

客户端奔溃或者网络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。
也就是说用了setnx 后之后还要用expire设置过期时间。
但是
多个命令之间不是原子性操作,如setnx 和expire之间,如果setnx成功,但是expire失败,且岩机了,则这个资源就是死锁。
java可以通过
stringRedisTemplate.opsForValue().setIfAbsent(key,“1”,30, TimeUnit.SECONDS)
setnx 时直接设置过期时间。

但是

业务超时,存在其他线程误删,key30秒过期,假如线程A执行很慢超过30秒,则key就被释放了,其他线程B就得到了锁,这个时候线程A执行完成,而B还没执行完成,结果就是线程A删除了线程B加的锁
解决方案:
可以在del之前做一个判断,验证当前线程是不是自己加的,那value应该是存当前线程的标识或者uuid。

但是

进一步细化误删(get(key)要花时间不是原子性的)
当线程A获取到正常值时,返回带代码中判断期间锁过期了,线程B刚好重新设置了新值,线程A那边有判断value是自己的标识,然后调用del方法,结果就是删除了新设置的线程B的值”核心还是判断和删除命令不是原子性操作导致
解决方案:
配合lua脚本
将get命令和del命令变为原子性操作!

但是

锁的过期时间,如何实现锁的自动续期或者避免业务执行时间过长,锁过期了?
一般把锁的过期时间设置久一点,比如10分钟时间
这就需要开发者每一个加锁的地方就需要自己去设置续期等操作。就很麻烦。因为需要维护一个线程去检查这个方法执行完没有!

最终解决办法Redisson

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.10.1</version>
</dependency>
@Data
@Configuration
public class Appconfig {
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private String redisPort;
    @Value("${spring.redis.password}")
    private String redisPwd;

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        //单机方式
        config.useSingleServer().setPassword(redisPwd).setAddress("redis://"+redisHost+":"+redisPort);
//        config.useClusterServers().addNodeAddress();//集群
        RedissonClient client = Redisson.create(config);
        return client;
    }
}

Redis锁的过期时间小于业务的执行时间该如何续期?

redisson里面有个watchdog看门狗机制
负责储存这个分布式锁的Redisson节点岩机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。或者业务执
行时间过长导致锁过期,为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁
的有效期。
Redisson中客户端一旦加锁成功,就会启动一个watchdog看门狗。watchdog是一个后台线程,会每隔10秒检查一下,如果客户端还持有锁key,那么就会不断的延长锁key的生存时间默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改config.lockwatchdogTimeout来另行指定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值