騰訊贏了! 470萬荆萤!用Redis+lua教你實現(xiàn)搶紅包

近日畸冲,北京知識產(chǎn)權法院在官方微信發(fā)文稱,掌上遠景公司開發(fā)并運營了一款名為“微信自動搶紅包”軟件观腊,卓易訊暢公司運營的豌豆莢應用開發(fā)平臺提供下載該軟件邑闲。騰訊科技公司、騰訊計算機公司以不正當競爭為由梧油,將掌上遠景公司和卓易訊暢公司訴至北京知識產(chǎn)權法院苫耸。

最終,法院認定掌上遠景公司構成不正當競爭儡陨,判決掌上遠景公司賠償二原告經(jīng)濟損失450萬元及合理支出約25.4萬元褪子。雙方當事人均未提出上訴,目前該案已生效骗村。

那我們的搶紅包是怎么實現(xiàn)的呢嫌褪?我來一步一步的教你,用個實例

需求:用戶分享紅包到微信群中胚股。

每一個用戶只能領取一個紅包笼痛。

比如餓了么的紅包分享:

在設計之前,先了解一下redis的list的數(shù)據(jù)結構:

1琅拌、lpush+lpop=Stack(棧)

2缨伊、lpush+rpop=Queue(隊列)

3、lpsh+ltrim=Capped Collection(有限集合)

4进宝、lpush+brpop=Message Queue(消息隊列)

我們基于lpush+rpop進行紅包的設計刻坊。如何設計?

1党晋、當用戶點擊分享按鈕谭胚,首先會給該訂單生成若干個紅包徐块,將該紅包push到redis中。

key的設計: hb:pool:{orderId}

value : n個紅包的List隊列灾而,用來表示n個紅包的池子

2胡控、記錄哪些用戶已搶過紅包,防止重復搶:從紅包池中rpop(彈出)一個紅包绰疤,就需要記錄下來該用戶領取了紅包铜犬,這里用了hash結構舞终。

key:hb:rd:{orderId} 例如訂單號為12345轻庆,key為hb:rd:12345, 有4個用戶id分別為 11111,11112,11113,11114

hset hb:rd:12345 11111 1

hset hb:rd:12345 11112 1

hset hb:rd:12345 11113 1

hset hb:rd:12345 11114 1

如下圖:

判斷用戶11111是否領過紅包,可以根據(jù) hexists hb:rd:12345 11111 進行判斷敛劝,如果領過返回1余爆,否則返回0;

3夸盟、記錄用戶搶了多少錢:這里采用list結構進行存儲蛾方。

key:hb:detailList:{orderId}

value : 用戶搶到的紅包列表。

搶紅包流程:

保證用戶搶紅包整個流程的原子操作就必然要引入lua腳本上陕,redis+Lua可以保證多條命令組合的原子性桩砰。
lua腳本:

/** 
 * 腳本調(diào)用方式
 * Object object = jedisUtils.eval(LuaScript.getHbLua,//lua腳本 
4,//參數(shù)個數(shù) RedisKeys.getHbPoolKey(orderId),//對應腳本里的KEYS[1] 
RedisKeys.getDetailListKey(orderId),//對應腳本里的KEYS[2] 
RedisKeys.getHbRdKey(orderId),//對應腳本里的KEYS[3] 
String.valueOf(userId));//對應腳本里的KEYS[4] 
* 
* 
*/
public static String getHbLua = 
//查詢用戶是否已搶過紅包,如果用戶已搶過紅包释簿,則直接返回
 "if redis.call('hexists', KEYS[3], KEYS[4]) ~= 0 then\n" + 
//如果搶過紅包 返回“1” 
"return '1';\n" + "else\n" + 
//從紅包池取出一個小紅包
 "local hb = redis.call('rpop', KEYS[1]);\n" + 
//判斷紅包池的紅包不為空
 "if hb then\n" + 
"local x = cjson.decode(hb);\n" + 
//將紅包信息與用戶ID信息綁定亚隅,表示該用戶已搶到紅包  
"x['userId'] = KEYS[4];\n" +
 "local re = cjson.encode(x);\n" +
 //記錄用戶已搶過 比如 hset hb:rd:{orderId}  {userId}  1 
"redis.call('hset', KEYS[3], KEYS[4], '1');\n" +
//將搶紅包的結果詳情存入hb:detailList:{orderId}
 "redis.call('lpush', KEYS[2], re);\n" + 
"return re;\n" + "else\n" +
 //如果紅包已被搶完 返回“0” 
"return '0';" + 
"end\n" + 
"end\n" +
 "return nil";

生成紅包:

/**
 * 生成紅包 
* @param orderId 
*/ public void genRedpack(long orderId,int redPackCount){ 
Boolean exists = jedisUtils.exists(RedisKeys.getHbPoolKey(orderId));
 if (!exists){ 
//根據(jù)業(yè)務規(guī)則生成紅包 
int totalAmount = 2000;//總的紅包金額20元 也就是2000分 
int[] redpacks = doPartitionRedpack(totalAmount,redPackCount); 
String[] list = new String[redpacks.length]; 
//將生成的紅包push到redis中 
for (int i = 0;i < redpacks.length; i++){ 
JSONObject object = new JSONObject();
 object.put("hbId", i); //紅包ID
 object.put("amount", redpacks[i]); //紅包金額,存的是分
 list[i] = object.toJSONString(); 
} 
jedisUtils.lpush(RedisKeys.getHbPoolKey(orderId),list); 
} 
} 

/** 
* 劃分紅包 * @param totalAmount 紅包總額 單位:分
 * @param redPackCount 紅包數(shù)量
 * @return
 */ 
private int[] doPartitionRedpack(int totalAmount,int redPackCount) { 
Random random = new Random(); 
int randomMax= totalAmount - redPackCount;//每個人至少分1分錢,2000 - 6 = 1994元 也就是要隨機分的錢庶溶。
 //要把1994 隨機分成6份煮纵,我們需要向1994 這個數(shù)字中插入5個點
 // 比如 6 100 500 500 1600 這5個數(shù)字把1994分成了6份:6分 94分 400分 0分 1000分 394分
 int[] posArray = new int[redPackCount-1]; 
for (int i = 0;i < posArray.length; i++){
 int pos = random.nextInt(randomMax);
 posArray[i] = pos; 
} 
Arrays.sort(posArray);//對數(shù)組進行排序 
//生成紅包 
int[] redpacks = new int[redPackCount]; 
for (int i = 0;i <= posArray.length; i++){ 
if (i == 0){
 redpacks[i] = posArray[i] + 1;//第一份 
}else if(i == posArray.length){//如果循環(huán)到posArray.length,此時數(shù)組已越界1位偏螺,randomMax - 該值 + 1分錢=最后一份 
redpacks[i] = randomMax - posArray[i-1] + 1;
 }else {
 redpacks[i] = posArray[i] - posArray[i-1] + 1; 
} 
} 
return redpacks; 
}

上面首先給每個紅包分1分錢行疏,然后把剩下的錢通過插入(redPackCount-1)個板子,就將剩余的錢分為redPackCount份套像。每份錢加上1分錢酿联,就是每個紅包的大小。分完紅包后夺巩,將紅包push到redis中货葬。
搶紅包:

/** 
* 搶紅包 
* @param userId 
* @param orderId 
*/ 
public String snatchRedpack(long userId,long orderId){ 
Object object = jedisUtils.eval(LuaScript.getHbLua,4, 
RedisKeys.getHbPoolKey(orderId),// 
RedisKeys.getDetailListKey(orderId),// 
RedisKeys.getHbRdKey(orderId),String.valueOf(userId)); 

return (String) object;
 }

搶紅包只需要執(zhí)行一下上面的lua腳本。

測試:
運行生成紅包:這里生成5個紅包劲够,orderId為111111.

Test 
public void genRedpack(){ 
JedisUtils jedisUtils = new JedisUtils("127.0.0.1", 6379, "123456"); 
RedpackService redpackService = new RedpackService(jedisUtils);
 redpackService.genRedpack(111111,5); 
}

運行后震桶,redis中紅包池就生成好了


運行搶紅包:這里模擬了100個人,也就是100個線程征绎。這里用了CyclicBarrier蹲姐,等到所有的線程都準備好磨取,同時開搶。

@Test
 public void snatchRedpack() throws InterruptedException { 
JedisUtils jedisUtils = new JedisUtils("118.89.196.99", 6379, "123456"); 
RedpackService redpackService = new RedpackService(jedisUtils); 
IdWorker idWorker = new IdWorker(); 
int N = 100; 
CyclicBarrier barrier = new CyclicBarrier(N);
 
for (int i = 0;i<N;i++){ 
new Thread(()->{ 
long userId = idWorker.nextId(); 
try {
 System.out.println("用戶"+userId+"準備搶紅包"); 
barrier.await(); 
} catch (InterruptedException e) { 
e.printStackTrace(); 
} catch (BrokenBarrierException e) {
 e.printStackTrace();
} 
String result = redpackService.snatchRedpack(userId, 111111);
 if ("0".equals(result)){ 
System.out.println("用戶" + userId + "未搶到紅包柴墩,原因:紅包已領完"); 
}else if ("1".equals(result)){ 
System.out.println("用戶" + userId + "未搶到紅包忙厌,原因:紅包已領過");
 }else{ 
System.out.println("用戶" + userId + "搶到紅包:" + result); 
} 
},"thread"+i).start(); 
} 
Thread.sleep(Integer.MAX_VALUE);
 }

運行結果:

...... 
用戶1078994397959344128準備搶紅包 
用戶1078994397959344129準備搶紅包 
用戶1078994397959344130準備搶紅包 
用戶1078994397959344131準備搶紅包 
用戶1078994397896429573搶到紅包:{"userId":"1078994397896429573","hbId":2,"amount":126} 
用戶1078994397888040960搶到紅包:{"userId":"1078994397888040960","hbId":1,"amount":526} 
用戶1078994397896429572搶到紅包:{"userId":"1078994397896429572","hbId":5,"amount":666} 
用戶1078994397950955528未搶到紅包,原因:紅包已領完 
用戶1078994397925789696搶到紅包:{"userId":"1078994397925789696","hbId":4,"amount":490} 
用戶1078994397929984004未搶到紅包江咳,原因:紅包已領完 
用戶1078994397959344128搶到紅包:{"userId":"1078994397959344128","hbId":3,"amount":192} 
用戶1078994397955149824未搶到紅包逢净,原因:紅包已領完 
用戶1078994397900623875未搶到紅包,原因:紅包已領完 
用戶1078994397929984006未搶到紅包歼指,原因:紅包已領完 
用戶1078994397892235264未搶到紅包爹土,原因:紅包已領完 
.......

結果看出100個線程同時搶迫筑,只有5個人搶成功了饶囚。

查看redis里的數(shù)據(jù):紅包池子里的紅包已被領完顺又,對應紅包用戶信息也相應的生成逛钻。 ?????

這樣就好了扁瓢。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伐债,一起剝皮案震驚了整個濱河市嫁艇,隨后出現(xiàn)的幾起案子攀芯,更是在濱河造成了極大的恐慌附鸽,老刑警劉巖脱拼,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異坷备,居然都是意外死亡熄浓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門击你,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玉组,“玉大人,你說我怎么就攤上這事丁侄」喏ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵鸿摇,是天一觀的道長石景。 經(jīng)常有香客問我,道長拙吉,這世上最難降的妖魔是什么潮孽? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮筷黔,結果婚禮上往史,老公的妹妹穿的比我還像新娘。我一直安慰自己佛舱,他們只是感情好椎例,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布挨决。 她就那樣靜靜地躺著,像睡著了一般订歪。 火紅的嫁衣襯著肌膚如雪脖祈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天刷晋,我揣著相機與錄音盖高,去河邊找鬼。 笑死眼虱,一個胖子當著我的面吹牛喻奥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蒙幻,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼映凳,長吁一口氣:“原來是場噩夢啊……” “哼胆筒!你這毒婦竟也來了邮破?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤仆救,失蹤者是張志新(化名)和其女友劉穎抒和,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彤蔽,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡摧莽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了顿痪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片镊辕。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蚁袭,靈堂內(nèi)的尸體忽然破棺而出征懈,到底是詐尸還是另有隱情,我是刑警寧澤揩悄,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布卖哎,位于F島的核電站,受9級特大地震影響删性,放射性物質發(fā)生泄漏亏娜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一蹬挺、第九天 我趴在偏房一處隱蔽的房頂上張望维贺。 院中可真熱鬧,春花似錦巴帮、人聲如沸溯泣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽发乔。三九已至熟妓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間栏尚,已是汗流浹背起愈。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留译仗,地道東北人抬虽。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像纵菌,于是被迫代替她去往敵國和親阐污。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容