作者信息

长期闲置

长风破浪会有时,直挂云帆济沧海。

最近文章
oracle的用户权限有哪些357
mysql怎么解决3534无法启动318
ubuntu怎么修改mysql编码1087
视频教程分类
推荐视频教程
  • Laraval 9 学习正当时—保姆级教程,想学不会都难!Laraval 9 学习正当时—保姆级教程,想学不会都难!
  • 千万级数据并发解决方案(理论+实战)千万级数据并发解决方案(理论+实战)
  • Laravel基础与实战(模块化)Laravel基础与实战(模块化)
  • UNI-APP开发(仿饿了么)UNI-APP开发(仿饿了么)
  • 首页 >数据库 >Redis > 正文

    一起聊聊Redis实现秒杀的问题

    转载2022-05-27 21:00:06960 关注公众号:每天精选资源文章推送
    本篇文章给大家带来了关于Redis的相关知识,其中主要介绍了关于实现秒杀的相关内容,包括了秒杀逻辑、存在的链接超时、超卖和库存遗留的问题,下面一起来看一下,希望对大家有帮助。

    推荐学习:Redis视频教程

    1、秒杀逻辑

    秒杀:解决计数器和人员记录的事务操作

    1. 1.uid和proid非空判断
    2. 2.连接redis
    3. 3.拼接key
      • 库存key
      • 秒杀成功用户key
    4. 4.获取库存,如果库存为null,秒杀还没开始
    5. 5.判断用户是否重复秒杀操作
    6. 6.判断商品数量,库存数量小于1,秒杀结束
    7. 7.秒杀过程
      • 库存-1
      • 把秒杀成功用户添加清单里面

    2、存在问题

    2.1、连接超时

    原因:由于大量创建连接,十分消耗性能,并且有时获取连接不及时,出现连接超时的情况

    2.2、超卖

    在并发的情况下发生的,就是在输出没有库存(秒杀结束)后还有商品售出导致库存数量为负数。
    在这里插入图片描述

    2.3、库存遗留

    使用乐观锁解决问题2之后,出现问题3

    如果库存数量相对并发更多,由于使用乐观锁,第一个用户秒杀成功后会修改库存键的版本号,其他抢到的用户会因为版本号不同导致无法继续购买,就会有库存遗留问题

    3、解决

    3.1、连接超时

    使用连接池,工具类如下:

    public class JedisPoolUtil {
    	private static volatile JedisPool jedisPool = null;
    	private JedisPoolUtil() {
    	}
    	public static JedisPool getJedisPoolInstance() {
    		if (null == jedisPool) {
    			synchronized (JedisPoolUtil.class) {
    				if (null == jedisPool) {
    					JedisPoolConfig poolConfig = new JedisPoolConfig();
    					poolConfig.setMaxTotal(200);
    					poolConfig.setMaxIdle(32);
    					poolConfig.setMaxWaitMillis(100 * 1000);
    					poolConfig.setBlockWhenExhausted(true);
    					poolConfig.setTestOnBorrow(true);
    					jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379, 60000);
    				}
    			}
    		}
    		return jedisPool;
    	}}//使用JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();Jedis jedis = jedisPoolInstance.getResource();

    springBoot版本(pom.xml引入,application.yml配置,然后注入对象即可)

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.2.0</version></dependency>
    spring:
      redis:
        host: 127.0.0.1    port: 6379
        database: 0
        timeout: 1800000
        lettuce:
          pool:
            max-active: 20
            max-wait: -1
            max-idle: 5
            min-idle: 0
        @Autowired
        private RedisTemplate redisTemplate;

    3.2、超卖问题

    使用Redis事务,乐观锁 + watch

    //监视库存
    jedis.watch(kcKey);//中间代码忽略
    
    //7 秒杀过程
    //使用事务
    Transaction multi = jedis.multi();//组队操作
    multi.decr(kcKey);multi.sadd(userKey,uid);//执行
    List<Object> results = multi.exec();if(results == null || results.size()==0) {
        System.out.println("秒杀失败了....");
        jedis.close();
        return false;}

    3.3、乐观锁导致的库存遗留问题

    使用Lua嵌入式脚本语言

    1. 将复杂的或者多步的 Redis 操作,写为一个脚本,一次提交给Redis运行,减少反复连接 reids的次数。提升性能。
    2. LUA脚本是类似 redis 事务,有一定的原子性,不会被其他命令插队,可以完成redis事务性的操作
    3. LUA脚本功能,在Redis 2.6以上的版本才可以使用
    4. 利用 lua 脚本淘汰用户,解决超卖问题。
    5. redis 2.6 版本以后,通过 lua 脚本解决争抢问题,实际上是 redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题
    local userid=KEYS[1];				//1、2行定义两个变量,					
    local prodid=KEYS[2];
    local qtkey="sk:"..prodid..":qt";	//3,4行定义拼接key
    local usersKey="sk:"..prodid..":usr";
    local userExists=redis.call("sismember",usersKey,userid); //5-8,判断用户是否存在,不存在return 2
    if tonumber(userExists)==1 then
        return2;
    end
    local num=redis.call("get",qtkey);	//9-11,判断商品是否存在
    if tonumber(num)<=0 then
        return 0;
    else								//12-15,用户和商品操作
        redis.call("decr",qtkey);
        redis.call("sadd",usersKey,userid);
    end
    return1;  							//最后一行return 1;  秒杀成功

    完整代码如下:

    // 定义两段Lua脚本(使用Lua脚本可以解决乐观锁带来的库存遗留问题)
    	static String secKillScript =
    			"local userid=KEYS[1];\r\n" +
    					"local prodid=KEYS[2];\r\n" +
    					"local qtkey='sk:'..prodid..\":qt\";\r\n" +
    					"local usersKey='sk:'..prodid..\":usr\";\r\n" +
    					"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
    					"if tonumber(userExists)==1 then \r\n" +
    					"   return 2;\r\n" +
    					"end\r\n" +
    					"local num= redis.call(\"get\" ,qtkey);\r\n" +
    					"if tonumber(num)<=0 then \r\n" +
    					"   return 0;\r\n" +
    					"else \r\n" +
    					"   redis.call(\"decr\",qtkey);\r\n" +
    					"   redis.call(\"sadd\",usersKey,userid);\r\n" +
    					"end\r\n" +
    					"return 1" ;
     
     
    	public static boolean doSecKill(String uid,String prodid) throws IOException {
     
    		JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
    		Jedis jedis=jedispool.getResource();
    		jedis.select(2);
     
    		// 通过jedis的scriptLoad方法加载Lua脚本
    		String sha1=  jedis.scriptLoad(secKillScript);
    		//通过jedis的evalsha方法调用Lua脚本
    		Object result= jedis.evalsha(sha1, 2, uid,prodid);
     
    		String reString=String.valueOf(result);
    		if ("0".equals( reString )  ) {
    			System.err.println("已抢空!!");
    		}else if("1".equals( reString )  )  {
    			System.out.println("抢购成功!!!!");
    		}else if("2".equals( reString )  )  {
    			System.err.println("该用户已抢过!!");
    		}else{
    			System.err.println("抢购异常!!");
    		}
    		jedis.close();
    		return true;
    	}

    推荐学习:Redis视频教程

    以上就是一起聊聊Redis实现秒杀的问题的详细内容,更多请关注php中文网其它相关文章!

    高并发千万级数据库系统解决方案

    声明:本文转载于:CSDN,如有侵犯,请联系admin@php.cn删除

  • 相关标签:redis
  • 相关文章

    相关视频


    专题推荐