Redis增減庫存避坑的實現(xiàn)
Redis實現(xiàn)庫存管理
查詢商品庫存數(shù)量
首先,我們可以使用Redis的String類型來存儲商品的庫存數(shù)量。每個商品對應一個key,其值為庫存數(shù)量。當需要查詢商品庫存數(shù)量時,只需要獲取相應key的值即可。
# 獲取商品庫存數(shù)量 def get_stock(product_id): redis_conn = Redis() stock = redis_conn.get(f'stock:{product_id}') return int(stock) if stock else 0
更新商品庫存數(shù)量
當有人購買商品時,我們需要更新商品的庫存數(shù)量。為了保證庫存的準確性,我們可以使用Redis的原子操作INCRBY或者DECRBY來實現(xiàn)庫存數(shù)量的增減。
# 更新商品庫存數(shù)量 def update_stock(product_id, quantity): redis_conn = Redis() redis_conn.incrby(f'stock:{product_id}', quantity)
判斷商品庫存是否充足
為了判斷商品庫存是否充足,我們只需要查詢商品的庫存數(shù)量并與購買數(shù)量進行比較即可。
# 判斷商品庫存是否充足 def check_stock(product_id, quantity): stock = get_stock(product_id) return stock >= quantity
避免超賣問題
在高并發(fā)的情況下,可能會出現(xiàn)超賣問題,即多個用戶同時購買了同一件商品,導致庫存數(shù)量出現(xiàn)負數(shù)。為了避免這個問題,我們可以使用Redis的WATCH機制來保證庫存數(shù)量的原子性。
# 避免超賣問題 def avoid_over_sell(product_id, quantity): redis_conn = Redis() with redis_conn.pipeline() as pipe: while True: try: pipe.watch(f'stock:{product_id}') stock = pipe.get(f'stock:{product_id}') stock = int(stock) if stock else 0 if stock < quantity: pipe.unwatch() raise Exception('庫存不足') pipe.multi() pipe.decrby(f'stock:{product_id}', quantity) pipe.execute() break except WatchError: continue
問題
先執(zhí)行get獲取值,判斷符合條件再執(zhí)行incr、decr操作。在臨界緩存失效的情況下,會默認賦值當前key為永不過期的0,再執(zhí)行加減法,導致程序異常。
推薦解決方案
1、限制接口頻率:先incr,執(zhí)行后值為1,說明是第一次執(zhí)行,需要額外設置過期時間,再判斷是否超過當前接口頻率限制(注意上述步驟不可調換順序)
2、使用lua腳本完整提交一次操作,腳本中的key可以保證一致。以加減庫存為例,先查詢key存在的情況下,再進行庫存變更,如果不存在無需處理,等待下次緩存加載即為最新的值
問題描述
場景1:我們緩存了一個商品的庫存,過期時間為5分鐘,根據(jù)用戶的購買和取消執(zhí)行 incr、decr 操作。代碼通常會這樣來編寫:
// 庫存存在則加一 if(redisService.get(prefix, key, Integer.class) != null){ redisService.incr(prefix, key); }
場景2:對訪問頻次進行限流,我們可以通過redis簡單實現(xiàn):
// 首先獲取當前訪問頻次 Integer count = redisService.get(prefix, key, Integer.class); // 如果頻次為空,則設置訪問次數(shù)為1 if (count == null) { redisService.set(prefix, key, 1); } else if (count < checkFrequencyCount) { // 如果頻次小于限制,則設置訪問次數(shù)加1 redisService.incr(prefix, key); } else { // 如果頻次超過限制,則限流 throw new AppException("訪問頻次過高,請稍候再試"); }
兩種場景編碼看似都沒有問題,但實際運行中卻發(fā)現(xiàn)redis中有一些key變成了永不過期的key,而且值不正確。
原因是: 因為redis的incr操作,當key不存在時, 會生成這個key并將值初始化為0, 并且默認設置key的有效時間為永久。
解決方案
1.優(yōu)化Java代碼,例如場景2。不論這個key是否存在都先加一,然后判斷其過期時間是否為永不過期,如果是永不過期則說明是新生成的key,給它設置過期時間即可,如果非永不過期則無需操作。最后再判斷一下是否值已經(jīng)大于訪問頻次了,是則限流。
long count = redisService.incr(prefix, key); // 判斷必須放在后面,否則key沒有過期時間永遠無法清除 long expire = redisService.ttl(prefix, key); if (expire == -1) { redisService.setExpire(prefix, key, accessExpireSecond); } if (count > checkFrequencyCount) { throw new AppException("訪問頻次過高,請稍候再試"); }
2.使用lua腳本執(zhí)行,保證原子性。
腳本updateStore.lua
--- 獲取key local key = KEYS[1] --- 獲取參數(shù):incr、decr local action = ARGV[1] --- 如果key存在,再執(zhí)行增加或減少的操作 if redis.call('exists', key) == 1 then redis.call(action, key) return true end return false
配置LuaConfiguration.java
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.scripting.support.ResourceScriptSource; @Configuration public class LuaConfiguration { @Bean(name = "update") public DefaultRedisScript<Boolean> redisScript() { DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("luascript/updateStore.lua"))); redisScript.setResultType(Boolean.class); return redisScript; } }
使用方法:
@Resource(name = "update") private DefaultRedisScript<Boolean> redisScript; @Resource private StringRedisTemplate stringRedisTemplate; // 執(zhí)行腳本并傳參 Boolean result = stringRedisTemplate.execute(redisScript, Arrays.asList(stockPrefix.getPrefix() + key), "incr");
到此這篇關于Redis增減庫存避坑的實現(xiàn)的文章就介紹到這了,更多相關Redis增減庫存內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
利用redis實現(xiàn)分布式鎖,快速解決高并發(fā)時的線程安全問題
這篇文章主要介紹了利用redis實現(xiàn)分布式鎖,快速解決高并發(fā)時的線程安全問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01Ubuntu系統(tǒng)中Redis的安裝步驟及服務配置詳解
本文主要記錄了Ubuntu服務器中Redis服務的安裝使用,包括apt安裝和解壓縮編譯安裝兩種方式,并對安裝過程中可能出現(xiàn)的問題、解決方案進行說明,以及在手動安裝時,服務器如何添加自定義服務的問題,需要的朋友可以參考下2024-12-12