人妖在线一区,国产日韩欧美一区二区综合在线,国产啪精品视频网站免费,欧美内射深插日本少妇

新聞動(dòng)態(tài)

redis lua腳本實(shí)戰(zhàn)秒殺和減庫存的實(shí)現(xiàn)

發(fā)布日期:2022-02-02 17:20 | 文章來源:gibhub

我們都知道redis是高性能高并發(fā)系統(tǒng)必不可少的kv中間件,它以高性能,高并發(fā)著稱,我們常常用它做緩存,將熱點(diǎn)數(shù)據(jù)或者是萬年不變的數(shù)據(jù)緩存到redis中,查詢的時(shí)候直接查詢r(jià)edis,減輕db的壓力,分布式系統(tǒng)中我們也會(huì)拿它來做分布式鎖,分布式id,冪等來解決一些分布式問題,redis也支持lua腳本,而且能夠保證lua腳本執(zhí)行過程中原子性,這就使得它的應(yīng)用場(chǎng)景很多,也很典型,在redisson這個(gè)redis客戶端中,它的各種分布式鎖底層就是使用lua來實(shí)現(xiàn)的。本文主要是學(xué)習(xí)一下redis lua腳本的編寫,以及在redisson這個(gè)redis客戶端中是怎樣使用的,實(shí)戰(zhàn)一下秒殺場(chǎng)景redis減庫存lua腳本的編寫,并偽真實(shí)環(huán)境壓測(cè)查看效果。

1.redisson介紹

redisson是一個(gè)redis的客戶端,它擁有豐富的功能,而且支持redis的各種模式,什么單機(jī),集群,哨兵的都支持,各種各樣的分布式鎖實(shí)現(xiàn),什么分布式重入鎖,紅鎖,讀寫鎖,信號(hào)量的,然后它操作redis的常用數(shù)據(jù)結(jié)構(gòu)就跟操作jdk的各種集合一樣簡(jiǎn)單。
這里我們稍微演示下,不做過多的介紹,api畢竟只是個(gè)api,有意思的都在它背后各種原理。

maven依賴

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.8.1</version>
</dependency>

創(chuàng)建RedissonClient對(duì)象

Config config = new Config();
config.useSingleServer()
        .setAddress("redis://xxx:xx");
RedissonClient redissonClient = Redisson.create(config);

它的功能超級(jí)多,下面只是列舉了一些常用的,還有什么Bloom過濾器,隊(duì)列等等。

// string
redissonClient.getBucket("name").set("zhangsan");
redissonClient.getBucket("name").get();
// hash
RMap<Object, Object> user = redissonClient.getMap("user");
user.put("name","zhangsan");
user.put("age",11);
// list
RList<String> names = redissonClient.getList("names");
names.add("zhangsan");
names.add("lisi");
names.add("wangwu");
// set
RSet<Object> nameSet = redissonClient.getSet("names");
nameSet.add("lisi");
nameSet.add("lisi");
//lock
RLock lock = redissonClient.getLock("lock");
lock.lock();
lock.unlock();
// 分布式id
RAtomicLong id = redissonClient.getAtomicLong("id");
long l = id.incrementAndGet();  

2. redis lua腳本編寫與執(zhí)行

其實(shí)redis中的lua腳本并不難,你也不需要把lua語言再去重學(xué)一遍,全憑感覺就好了,使用的時(shí)候去查下語法就ok了。
腳本中就一個(gè)redis.call() 應(yīng)該算是函數(shù)吧(方法也可以),比如我要使用lua腳本實(shí)現(xiàn)set動(dòng)作,就可以這樣寫

return redis.call('set','name','zhangsan');

其實(shí)就是跟redis交互命令一個(gè)樣子,再使用lua語言做一些條件分支,循環(huán)啥的,就完成了一些稍微復(fù)雜的邏輯。
下面自己寫一遍扣減庫存的邏輯,你就會(huì)這個(gè)玩意了。
上面是介紹了lua腳本的編寫,下面我們介紹下這個(gè)執(zhí)行。
不管是redis自己帶的那個(gè)客戶端,還是jedis,jediscluster,redistemplate,redisson這些客戶端都是支持lua腳本api的,其實(shí)就是eval,evalsha,scriptload 這幾個(gè)命令用的比較頻繁,我這里把菜鳥教程上面關(guān)于介紹redis腳本命令截圖過來

多說無益,這里直接使用redisson客戶端實(shí)踐一下。

public class RedisLua {
    private static  final Config config ;
    private static  final RedissonClient redisson ;
    static {
        config = new Config();
        config.useSingleServer()
                .setAddress("redis://ip:port");
        redisson =  Redisson.create(config);
    }
    public static void main(String[] args) throws InterruptedException {
        redisson.getBucket("name").set(11);
        RScript script = redisson.getScript();
        String result = script.eval(RScript.Mode.READ_ONLY, new StringCodec(),"return redis.call('get','name');", RScript.ReturnType.VALUE);
        System.out.println(result);
    }
}

可以看到就是使用getScript方法獲取一個(gè)script對(duì)象,然后調(diào)用script 對(duì)象的eval方法,這個(gè)script對(duì)象其實(shí)還是用好多個(gè)方法的,evalSha等等,可以自己研究下。然后就是通過lua腳本獲取我上面set進(jìn)去的name值。
我們?cè)诳瓷厦娌锁B教程對(duì)腳本命令的介紹的時(shí)候,還發(fā)現(xiàn)有key… arg…這些東西,這個(gè)我認(rèn)為就是動(dòng)態(tài)替換(傳參)的。
比如我現(xiàn)在不獲取name這個(gè)key的值了,我要獲取age 的,或者是我要直接set一個(gè)值,這個(gè)時(shí)候我就可以lua腳本中有兩個(gè)變量與你傳的參數(shù)對(duì)等起來
KEYS[1] ARGV[1]
你可以看作是數(shù)組,不過它的位置是從1開始的。

RScript script = redisson.getScript();
List<Object> keys=new ArrayList<>();
keys.add("age");
String re = script.eval(
        RScript.Mode.READ_WRITE,
        new StringCodec(),
        "return redis.call('set',KEYS[1],ARGV[1]);",
        RScript.ReturnType.VALUE,
        keys,1);
Object age = redisson.getBucket("age").get();
System.out.println(age);

KEYS[1] 就對(duì)應(yīng)這age, ARGV[1]就對(duì)應(yīng)1,同理,KEYS[2] 就對(duì)應(yīng)keys集合中的第二個(gè)元素。

3.redis減庫存lua腳本

先介紹下下單減庫存是怎么干的吧,其實(shí)一般庫存有可用庫存與預(yù)占庫存,再下單的時(shí)候,就將可用庫存減去你購買的商品數(shù)量看看是否是小于0,如果是小于0的話,說明庫存不夠了,就不讓下單購買了,如果可用庫存充足,可用庫存減去購買商品數(shù)量,預(yù)占庫存加上你購買商品數(shù)量,當(dāng)用戶超時(shí)未支付或者是手動(dòng)取消訂單的時(shí)候,就會(huì)去預(yù)占庫減去用戶購買商品數(shù),可用庫存加上商品數(shù),其實(shí)還有一個(gè)已售庫存,商家發(fā)貨,已售庫存加上商品數(shù),預(yù)占減去商品數(shù),大體上是這個(gè)邏輯。
現(xiàn)在可以想下,如果讓你來實(shí)現(xiàn)下單預(yù)占庫存的功能,你會(huì)怎么做,數(shù)據(jù)庫三個(gè)庫存字段這個(gè)不用說了。
首先你得先把可售庫存查詢出來,然后與購買商品數(shù)量進(jìn)行比較,如果是可售庫存大于這個(gè)購買商品數(shù)量,就可以購買,更新可售庫存與預(yù)占庫存。
如果上面這段代碼邏輯你不加一些特殊手段的處理,那ok,高并發(fā)場(chǎng)景下絕對(duì)會(huì)出現(xiàn)超賣現(xiàn)象。
如果是秒殺場(chǎng)景呢?血虧。
這個(gè)時(shí)候我們可能會(huì)增加一些特殊手段來解決,比如說加鎖,加分布式鎖,將這一段的業(yè)務(wù)邏輯鎖住,這個(gè)時(shí)候就不會(huì)出現(xiàn)那種超賣現(xiàn)象了, 但是這個(gè)中情況如果售罄的話,也會(huì)一直查詢數(shù)據(jù)庫。秒殺的時(shí)候,流量那么高,你不能讓這么大的流量直接查庫,如果商品售罄直接返回就可以了,不用再查詢數(shù)據(jù)庫了。
一般秒殺的時(shí)候,會(huì)將商品的庫存同步推到redis中,流量過來的時(shí)候,會(huì)先扣減redis的庫存,如果redis成功了,才扣減數(shù)據(jù)庫中的庫存,如果redis中的庫存沒了,直接返回就ok,這樣大流量就不會(huì)直接沖擊數(shù)據(jù)庫了,那么redis要實(shí)現(xiàn)這段邏輯的話,就需要lua腳本的原子性了。
接下來我們就實(shí)現(xiàn)一下lua腳本扣減庫存邏輯。

public static final String LOCK_STOCK_LUA=  "local counter = redis.call('hget',KEYS[1],ARGV[1]); \n" +
                    "local result  = counter - ARGV[2];" +
                    "if(result>=0 ) then \n" +
                    "   redis.call('hset',KEYS[1],ARGV[1],result);\n" +
                    "   redis.call('hincrby',KEYS[1],ARGV[3],ARGV[2]);\n" +
                    "   return 1;\n" +
                    "end;\n" +
                    "return 0;\n";

我這里已經(jīng)寫好了,直接貼出來。
數(shù)據(jù)設(shè)計(jì)大體是這個(gè)樣子的,使用hash數(shù)據(jù)結(jié)構(gòu)
商品:{
“可售庫存”:100,
“預(yù)占庫存”:0,
“已售庫存”:0
}

local counter = redis.call('hget',KEYS[1],ARGV[1]);

獲取可售庫存數(shù)量

local result  = counter - ARGV[2];
if(result>=0 ) then

可售庫存減去要購買的商品數(shù)量,如果是大于0的話,說明庫存還夠。

redis.call('hset',KEYS[1],ARGV[1],result);
redis.call('hincrby',KEYS[1],ARGV[3],ARGV[2]);
return 1;

重新設(shè)置可售庫存數(shù)量,增加預(yù)占庫存,然后返回1
如果是庫存不夠的話,直接返回0了就。

4.實(shí)戰(zhàn)

4.1 減庫存邏輯

減庫存邏輯其實(shí)就是先是用lua腳本減redis庫存,如果成功再去減數(shù)據(jù)庫中的真實(shí)庫存,如果減redis庫存失敗,庫存不足,就不會(huì)再走后面減真實(shí)庫存的邏輯了。
這塊的話,我是寫了一個(gè)庫存服務(wù),實(shí)現(xiàn)了這段邏輯,但是總感覺有各種數(shù)據(jù)不一致的問題,當(dāng)然不是超賣,而是少賣問題,這里就不發(fā)出來了。

4.2 壓測(cè)

我們這個(gè)實(shí)戰(zhàn)是在阿里云進(jìn)行的
redis選的是容器服務(wù),按秒計(jì)費(fèi),配置是0.5c1g
mysql也是選擇的容器服務(wù),配置是0.5c1g
庫存服務(wù)是云服務(wù)器,按小時(shí)計(jì)費(fèi)的那種,配置是2c4g,因?yàn)橐渴鸲鄠€(gè)服務(wù)跟實(shí)例,選擇的比較大。
壓測(cè)也是使用的阿里云的性能測(cè)試服務(wù)。

redis監(jiān)控,可以看到,這點(diǎn)并發(fā)對(duì)redis來說就是毛毛雨。cpu才使用7%

云服務(wù)器這塊手速有點(diǎn)慢,沒截圖出來,cpu跟內(nèi)存都在50%左右。

mysql數(shù)據(jù)庫,可以看到cpu飆上去了,內(nèi)存飆上去了。

數(shù)據(jù)庫數(shù)據(jù):

可以發(fā)現(xiàn)并沒有出現(xiàn)超賣現(xiàn)象。

到此這篇關(guān)于redis lua腳本實(shí)戰(zhàn)秒殺扣減庫存的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)redis lua戰(zhàn)秒殺扣減庫存內(nèi)容請(qǐng)搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!

香港服務(wù)器租用

版權(quán)聲明:本站文章來源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。

相關(guān)文章

實(shí)時(shí)開通

自選配置、實(shí)時(shí)開通

免備案

全球線路精選!

全天候客戶服務(wù)

7x24全年不間斷在線

專屬顧問服務(wù)

1對(duì)1客戶咨詢顧問

在線
客服

在線客服:7*24小時(shí)在線

客服
熱線

400-630-3752
7*24小時(shí)客服服務(wù)熱線

關(guān)注
微信

關(guān)注官方微信
頂部