一起聊聊Redis實現秒殺的問題

本篇文章給大家帶來了關于redis的相關知識,其中主要介紹了關于實現秒殺的相關內容,包括了秒殺邏輯、存在的鏈接超時、超賣和庫存遺留的問題,下面一起來看一下,希望對大家有幫助。

一起聊聊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、超賣

在并發的情況下發生的,就是在輸出沒有庫存(秒殺結束)后還有商品售出導致庫存數量為負數。
一起聊聊Redis實現秒殺的問題

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];rn" + 					"local prodid=KEYS[2];rn" + 					"local qtkey='sk:'..prodid..":qt";rn" + 					"local usersKey='sk:'..prodid..":usr";rn" + 					"local userExists=redis.call("sismember",usersKey,userid);rn" + 					"if tonumber(userExists)==1 then rn" + 					"   return 2;rn" + 					"endrn" + 					"local num= redis.call("get" ,qtkey);rn" + 					"if tonumber(num)<=0 then rn" + 					"   return 0;rn" + 					"else rn" + 					"   redis.call("decr",qtkey);rn" + 					"   redis.call("sadd",usersKey,userid);rn" + 					"endrn" + 					"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

? 版權聲明
THE END
喜歡就支持一下吧
點贊7 分享