Redis(二十三)-秒杀案例之超卖和超时问题解决

Posted 码农飞哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis(二十三)-秒杀案例之超卖和超时问题解决相关的知识,希望对你有一定的参考价值。

您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦
💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通
❤️ 2. Python爬虫专栏,系统性的学习爬虫的知识点。9.9元买不了吃亏,买不了上当 。python爬虫入门进阶
❤️ 3. Ceph实战,从原理到实战应有尽有。 Ceph实战
❤️ 4. Java高并发编程入门,打卡学习Java高并发。 Java高并发编程入门
😁 5. 社区逛一逛,周周有福利,周周有惊喜。码农飞哥社区,飞跃计划
全网同名【码农飞哥】欢迎关注,个人VX: wei158556

文章目录

简介

上一篇文章我们介绍秒杀案例的基本实现 Redis(二十二)-秒杀案例的基本实现以及用ab工具模拟并发。但是在文章的最后留了两个问题没有解决,一个是高并发情况下的超卖问题,以及jedis客户端请求超时问题。这篇文章就是来解决这两篇问题。

超时问题

超时问题的现象

java.net.SocketTimeoutException: connect timed out
	at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method) ~[na:1.8.0_60]
	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85) ~[na:1.8.0_60]
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[na:1.8.0_60]
	at 

超时问题的原因

因为在上面的案例中是每次都会创建一个新的连接实例,而创建连接实例又是一个比较耗时的动作。故会出现超时问题。
那么,解决超时问题可以利用jedis的线程池,节省每次连接redis服务带来的消耗,把连接好的实例反复利用。
通过参数管理连接的行为,代码如下:

public class JedisPoolUtil 
    private static JedisPool jedisPool;
    private JedisPoolUtil() 

    
    //通过单例模式来定义jedisPool连接池
    public static JedisPool getJedisPool() 
        synchronized (JedisPoolUtil.class) 
            if (jedisPool == null) 
                JedisPoolConfig poolConfig = new JedisPoolConfig();
                poolConfig.setMaxTotal(200);
                poolConfig.setMaxIdle(32);
                poolConfig.setMaxWaitMillis(100 * 1000);
                poolConfig.setBlockWhenExhausted(true);
                poolConfig.setTestOnBorrow(true);
                //指定连接池的poolConfig,redis的IP地址,端口号,已经超时时间。
                jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379,60000);
            
        
        return jedisPool;
    
	//释放jedis连接
    public static void release(JedisPool jedisPool, Jedis jedis) 
        if (jedis != null) 
            jedisPool.returnResource(jedis);
        
    

其中连接池参数:

  1. MaxTotal :控制一个pool可以分配多少个jedis实例,通过pool.getResource()来获取,如果赋值为-1,则表示不限制,如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。
  2. MaxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
  3. MaxWaitMillis:表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;

再将原来的 Jedis jedis = new Jedis("127.0.0.1", 6379); 替换成 Jedis jedis = JedisPoolUtil.getJedisPool().getResource(); 表示从连接池中获取Jedis连接实例。
再次测试一下:

超卖问题

超卖问题的原因

之所以出现超卖的情况还是因为在并发的情况下出现了线程安全的问题,即两个线程同时需减同一个库存。主要是如下代码有问题:

       // 4.获取库存,如果库存null,秒杀还没有开始
		String skValue = jedis.get(skKey);
        if (skValue == null) 
            System.out.println("秒杀还没开始,请等待");
            return false;
        
        // 6. 判断如果商品数量,库存数量小于1,秒杀结束
        if (Integer.parseInt(skValue) <= 0) 
            System.out.println("该商品的库存不足,秒杀失败");
            return false;
        
        // 7. 秒杀过程
        //    7.1. 库存-1
        jedis.decr(skKey);
        //    7.2. 用户加入到set集合中
        jedis.sadd(userKey, uid);

比如说现在还剩下最后一个库存,现在两个线程同时获取到库存值为1,两者又同时去修改库存值,最终的库存值就会被减成负数。

超卖问题解决

要解决这个问题话,还是需要用到前面提到的乐观锁,用乐观锁去监视库存值,哪怕出现上述的情况,由于第一个线程修改库存值之后,版本号也被修改了。故第二个线程不能修改成功。所以就不会出现超卖的情况。核心代码如下:

        String skKey = "sk:" + prodid + ":qt";
	  //监视库存
        jedis.watch(skKey);
        // 4.获取库存,如果库存null,秒杀还没有开始
        String skValue = jedis.get(skKey);
        if (skValue == null) 
            System.out.println("秒杀还没开始,请等待");
            return false;
        
  //增加事务
        Transaction multi = jedis.multi();
        //组队操作
        multi.decr(skKey);
        multi.sadd(userKey, uid);
        //执行
        List<Object> results = multi.exec();
        if (results == null || results.size() == 0) 
            System.out.println("秒杀失败了。。。");
            jedis.close();
            return false;
        

这里通过watch方法监视库存的键 sk:1010:qt。然后将 减库存decr方法以及增加用户sadd方法放在事务队列,最后在通过exec方法依次执行事务队列中的命令。最终的运行结果如下:


这里给商品qt设置了30个库存,最终执行完成之后还剩下8个库存。虽然超卖的问题解决了但是还有一个新的问题:库存遗留问题。下一篇文章会接着来说下如何处理库存遗留问题。

总结

本文详细介绍了如何解决秒杀案例中的超卖以及超时问题。希望对读者朋友们有所帮助。

以上是关于Redis(二十三)-秒杀案例之超卖和超时问题解决的主要内容,如果未能解决你的问题,请参考以下文章

Redis解决秒杀微服务抢购代金券超卖和同一个用户多次抢购

Redis 学习Redis事务秒杀案例

Redis 学习Redis事务秒杀案例

Redis 学习Redis事务秒杀案例

Redis实现秒杀

Redis实现秒杀