麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 數據庫 > Redis > 正文

詳解利用redis + lua解決搶紅包高并發的問題

2020-03-17 12:39:57
字體:
來源:轉載
供稿:網友

搶紅包的需求分析

搶紅包的場景有點像秒殺,但是要比秒殺簡單點。

因為秒殺通常要和庫存相關。而搶紅包則可以允許有些紅包沒有被搶到,因為發紅包的人不會有損失,沒搶完的錢再退回給發紅包的人即可。

另外像小米這樣的搶購也要比淘寶的要簡單,也是因為像小米這樣是一個公司的,如果有少量沒有搶到,則下次再搶,人工修復下數據是很簡單的事。而像淘寶這么多商品,要是每一個都存在著修復數據的風險,那如果出故障了則很麻煩。

基于redis/208155.html">redis的搶紅包方案

下面介紹一種基于Redis的搶紅包方案。

把原始的紅包稱為大紅包,拆分后的紅包稱為小紅包。

1.小紅包預先生成,插到數據庫里,紅包對應的用戶ID是null。

2.每個大紅包對應兩個redis隊列,一個是未消費紅包隊列,另一個是已消費紅包隊列。開始時,把未搶的小紅包全放到未消費紅包隊列里。

未消費紅包隊列里是json字符串,如{userId:'789', money:'300'}。

3.在redis中用一個map來過濾已搶到紅包的用戶。

4.搶紅包時,先判斷用戶是否搶過紅包,如果沒有,則從未消費紅包隊列中取出一個小紅包,再push到另一個已消費隊列中,最后把用戶ID放入去重的map中。

5.用一個單線程批量把已消費隊列里的紅包取出來,再批量update紅包的用戶ID到數據庫里。

上面的流程是很清楚的,但是在第4步時,如果是用戶快速點了兩次,或者開了兩個瀏覽器來搶紅包,會不會有可能用戶搶到了兩個紅包?

為了解決這個問題,采用了lua腳本方式,讓第4步整個過程是原子性地執行。

下面是在redis上執行的Lua腳本:

-- 函數:嘗試獲得紅包,如果成功,則返回json字符串,如果不成功,則返回空 -- 參數:紅包隊列名, 已消費的隊列名,去重的Map名,用戶ID -- 返回值:nil 或者 json字符串,包含用戶ID:userId,紅包ID:id,紅包金額:money  -- 如果用戶已搶過紅包,則返回nil if rediscall('hexists', KEYS[3], KEYS[4]) ~= 0 then  return nil else  -- 先取出一個小紅包  local hongBao = rediscall('rpop', KEYS[1]);  if hongBao then   local x = cjsondecode(hongBao);   -- 加入用戶ID信息   x['userId'] = KEYS[4];   local re = cjsonencode(x);   -- 把用戶ID放到去重的set里   rediscall('hset', KEYS[3], KEYS[4], KEYS[4]);   -- 把紅包放到已消費隊列里   rediscall('lpush', KEYS[2], re);   return re;  end end return nil 

下面是測試代碼:

public class TestEval {   static String host = "localhost";   static int honBaoCount = 1_0_0000;      static int threadCount = 20;      static String hongBaoList = "hongBaoList";   static String hongBaoConsumedList = "hongBaoConsumedList";   static String hongBaoConsumedMap = "hongBaoConsumedMap";      static Random random = new Random();    // -- 函數:嘗試獲得紅包,如果成功,則返回json字符串,如果不成功,則返回空 // -- 參數:紅包隊列名, 已消費的隊列名,去重的Map名,用戶ID // -- 返回值:nil 或者 json字符串,包含用戶ID:userId,紅包ID:id,紅包金額:money   static String tryGetHongBaoScript =  //     "local bConsumed = rediscall('hexists', KEYS[3], KEYS[4]);/n" //     + "print('bConsumed:' ,bConsumed);/n"       "if rediscall('hexists', KEYS[3], KEYS[4]) ~= 0 then/n"       + "return nil/n"       + "else/n"       + "local hongBao = rediscall('rpop', KEYS[1]);/n" //     + "print('hongBao:', hongBao);/n"       + "if hongBao then/n"       + "local x = cjsondecode(hongBao);/n"       + "x['userId'] = KEYS[4];/n"       + "local re = cjsonencode(x);/n"       + "rediscall('hset', KEYS[3], KEYS[4], KEYS[4]);/n"       + "rediscall('lpush', KEYS[2], re);/n"       + "return re;/n"       + "end/n"       + "end/n"       + "return nil";   static StopWatch watch = new StopWatch();      public static void main(String[] args) throws InterruptedException { //   testEval();     generateTestData();     testTryGetHongBao();   }      static public void generateTestData() throws InterruptedException {     Jedis jedis = new Jedis(host);     jedisflushAll();     final CountDownLatch latch = new CountDownLatch(threadCount);     for(int i = 0; i < threadCount; ++i) {       final int temp = i;       Thread thread = new Thread() {         public void run() {           Jedis jedis = new Jedis(host);           int per = honBaoCount/threadCount;           JSONObject object = new JSONObject();           for(int j = temp * per; j < (temp+1) * per; j++) {             objectput("id", j);             objectput("money", j);             jedislpush(hongBaoList, objecttoJSONString());           }           latchcountDown();         }       };       threadstart();     }     latchawait();   }      static public void testTryGetHongBao() throws InterruptedException {     final CountDownLatch latch = new CountDownLatch(threadCount);     Systemerrprintln("start:" + SystemcurrentTimeMillis()/1000);     watchstart();     for(int i = 0; i < threadCount; ++i) {       final int temp = i;       Thread thread = new Thread() {         public void run() {           Jedis jedis = new Jedis(host);           String sha = jedisscriptLoad(tryGetHongBaoScript);           int j = honBaoCount/threadCount * temp;           while(true) {             Object object = jediseval(tryGetHongBaoScript, 4, hongBaoList, hongBaoConsumedList, hongBaoConsumedMap, "" + j);             j++;             if (object != null) { //             Systemoutprintln("get hongBao:" + object);             }else {               //已經取完了               if(jedisllen(hongBaoList) == 0)                 break;             }           }           latchcountDown();         }       };       threadstart();     }          latchawait();     watchstop();          Systemerrprintln("time:" + watchgetTotalTimeSeconds());     Systemerrprintln("speed:" + honBaoCount/watchgetTotalTimeSeconds());     Systemerrprintln("end:" + SystemcurrentTimeMillis()/1000);   } } 

測試結果20個線程,每秒可以搶2.5萬個,足以應付絕大部分的搶紅包場景。

如果是真的應付不了,拆分到幾個redis集群里,或者改為批量搶紅包,也足夠應付。

總結:

redis的搶紅包方案,雖然在極端情況下(即redis掛掉)會丟失一秒的數據,但是卻是一個擴展性很強,足以應付高并發的搶紅包方案。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到Redis頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 欧美毛片免费观看 | 久久精品亚洲精品国产欧美kt∨ | 久久第四色| 日本黄色一级视频 | 日韩a毛片免费观看 | 久草在线播放视频 | 国产精品资源手机在线播放 | 老a影视网站在线观看免费 国产精品久久久久久久久久尿 | 91精品欧美一区二区三区 | 免费香蕉成视频成人网 | 成人不卡在线观看 | 黄色一级片免费在线观看 | 国产精品爆操 | 国产喷白浆10p| 亚洲精品aa | 久久精品操| 国产精品午夜一区 | 欧日一级片 | 中国毛片在线观看 | 色中色综合 | 欧美一级一区二区三区 | 毛片免费在线 | 国产一区二区三区在线观看视频 | 国产精品成人一区二区三区电影毛片 | 中文在线观看www | 国产成年人视频 | 日本黄色a视频 | 亚洲国产精品二区 | 免费黄色短视频网站 | 日韩一级免费毛片 | 久草手机在线观看视频 | 麻豆视频在线观看免费网站 | 欧美在线成人影院 | 国产亚洲精品久久久久久久久 | 国产jjizz一区二区三区视频 | 免费观看视频91 | 九一成人 | 91懂色| 色综合777 | 夜夜看 | 奶子吧naiziba.cc免费午夜片在线观看 |