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

首頁 > 數據庫 > Redis > 正文

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

2020-10-28 21:38:26
字體:
來源:轉載
供稿:網友

搶紅包的需求分析

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

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

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

基于redis的搶紅包方案

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

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

1.小紅包預先生成,插到數據庫里,紅包對應的用戶ID是null。生成算法見另一篇文章:http://www.companysz.com/article/98620.htm

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掛掉)會丟失一秒的數據,但是卻是一個擴展性很強,足以應付高并發的搶紅包方案。

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

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 99爱视频在线 | 欧美四级在线观看 | 91九色网| 久久久久久久久浪潮精品 | 鸳鸯谱在线观看高清 | 精品国产乱码久久久久久久 | av视在线| 午夜视频亚洲 | 国产三级a三级三级 | 免费a级作爱片免费观看欧洲 | 视频一区二区三区在线播放 | 黄wwww| 久久久午夜电影 | 99seav| 欧美性生交xxxxx免费观看 | 色婷婷久久一区二区 | 久久久久久久久久美女 | 久久毛片免费观看 | 欧美性生活久久久 | 成人富二代短视频 | 在线看一级片 | 久久伊人国产精品 | 国产精品免费久久久 | 一二区成人影院电影网 | 色蜜桃av| 欧美精品一区二区中文字幕 | 久久撸视频| h视频在线免费看 | 亚洲午夜视频在线 | 成人国产精品齐天大性 | 91在线色 | 国产精品a一 | 欧美成人精品h版在线观看 国产一级淫片在线观看 | va视频在线 | 全黄裸片武则天一级第4季 偿还电影免费看 | 国产毛片在线看 | 91丨九色丨国产在线观看 | 亚州成人在线观看 | 黄色免费影片 | 色网站在线免费观看 | 一级黄色免费观看 |