Redis迷你版微信搶紅包實戰(zhàn)
全部代碼:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/redis_demo/redpacket_demo
1 思路分析
搶紅包是一個高并發(fā)操作,且我們需要保證其原子性,同時搶紅包過程中不能加鎖,不能出現(xiàn)因為某個人網(wǎng)絡(luò)卡頓,導(dǎo)致其他人無法搶紅包??。
1.1 流程
搶紅包流程:發(fā)紅包-拆紅包-搶紅包-記錄誰搶了紅包
- 發(fā)紅包:提供接口send,參數(shù):紅包總金額,紅包個數(shù)。拆完之后通過redis list結(jié)構(gòu)將紅包存入redis
- 拆紅包:split接口,根據(jù)算法將紅包合理的拆分,金額不能差距太大,比如:一個100元紅包,拆分為20個,不能出現(xiàn)一個紅包里就包含99元的情況
- 搶紅包:提供rob接口,接收紅包名(要搶哪個紅包,不同人不同群發(fā)的紅包都是唯一的),接收用戶id(誰搶)
- 記錄:搶完紅包之后,記錄用戶id與所搶紅包??對應(yīng)關(guān)系,防止多搶。通過redis hset數(shù)據(jù)結(jié)構(gòu)實現(xiàn)。
1.2 注意點
①拆紅包:二倍均值算法
二倍均值算法:每次拆分后塞進(jìn)子紅包的金額 = 隨機(jī)區(qū)間(0, (剩余紅包金額M / 未被搶的剩余紅包個數(shù)N) * 2)
- 保證被拆紅包金額的差距不會太大。不會出現(xiàn)一個100元紅包,拆分為20個,一個紅包里就包含99元的情況
②發(fā)紅包:list
記錄紅包被拆分為了多少份,并且每份里有多少錢
③搶紅包&記錄:hset
記錄用戶與被搶紅包的對應(yīng)關(guān)系,防止多搶
2 代碼實現(xiàn)
為了大家能看得清晰,這里我直接將所有代碼都放在了main.go,實際使用和實現(xiàn)還是應(yīng)該拆分為service、controller…
2.1 拆紅包splitRedPacket
// 拆紅包 func splitRedPacket(totalMoney, totalNum int) []int { //1. 將紅包拆分為幾個 redpackets := make([]int, totalNum) usedMoney := 0 for i := 0; i < totalNum; i++ { //最后一個紅包,還剩余多少就分多少 if i == totalNum-1 { redpackets[i] = totalMoney - usedMoney } else { //二倍均值算法:每次拆分后塞進(jìn)子紅包的金額 = 隨機(jī)區(qū)間(0, (剩余紅包金額M / 未被搶的剩余紅包個數(shù)N) * 2) avgMoney := ((totalMoney - usedMoney) / (totalNum - i)) * 2 money := 1 + rand.Intn(avgMoney-1) redpackets[i] = money usedMoney += money } } return redpackets }
2.2 發(fā)紅包sendRedPacket
// 發(fā)紅包 http://localhost:9090/send?totalMoney=100&totalNum=3 func sendRedPacket(c *context2.Context) { money, _ := c.URLParamInt("totalMoney") totalNum, _ := c.URLParamInt("totalNum") redPackets := splitRedPacket(money, totalNum) uuid, _ := uuid.NewUUID() k := RED_PACKGE_KEY + uuid.String() for _, r := range redPackets { _, err := RedisCli.LPush(context.TODO(), k, r).Result() if err != nil && err != redis.Nil { panic(err) } } c.JSON(fmt.Sprintf("send redpacket[%s] succ %v", k, redPackets)) }
2.3 搶紅包&記錄robRedPacket
// 搶紅包 http://localhost:9090/rob?redPacket=e3e71f56-e9a3-11ee-9ad5-7a2cb90a4104&uId=4 func robRedPacket(c *context2.Context) { //判斷是否搶過 redPacket := c.URLParam("redPacket") uId, _ := c.URLParamInt("uId") exists, err := RedisCli.HExists(context.TODO(), RED_PACKAGE_CONSUME_KEY+redPacket, fmt.Sprintf("%d", uId)).Result() if err != nil && err != redis.Nil { panic(err) } if exists { //表明已經(jīng)搶過 c.JSON(fmt.Sprintf("[%d] you have already rob", uId)) return } else if !exists { //從list里取出一個紅包 result, err := RedisCli.LPop(context.TODO(), RED_PACKGE_KEY+redPacket).Result() if err == redis.Nil { //紅包已經(jīng)搶完了 c.JSON(fmt.Sprintf("redpacket is empty")) return } if err != nil { panic(err) } fmt.Printf("%d rob the red packet %v\n", uId, result) //記錄:后續(xù)可以異步進(jìn)MySQL或者M(jìn)Q做統(tǒng)計分析,每一年搶了多少紅包,金額是多少【年度總結(jié)】 _, err = RedisCli.HSet(context.TODO(), RED_PACKAGE_CONSUME_KEY+redPacket, uId, result).Result() if err != nil && err != redis.Nil { panic(err) } c.JSON(fmt.Sprintf("[%d] rob the red packet %v", uId, result)) } }
2.4 分析(紅包被誰搶了)infoRedPacket
func infoRedPacket(c *context2.Context) { redPacket := c.URLParam("redPacket") infoMap, err := RedisCli.HGetAll(context.TODO(), RED_PACKAGE_CONSUME_KEY+redPacket).Result() if err != nil && err != redis.Nil { panic(err) } c.JSON(infoMap) }
全部代碼
Github:
https://github.com/ziyifast/ziyifast-code_instruction/tree/main/redis_demo/redpacket_demo
package main import ( "context" "fmt" "github.com/go-redis/redis/v8" "github.com/google/uuid" "github.com/kataras/iris/v12" context2 "github.com/kataras/iris/v12/context" "math/rand" "time" ) /* 通過redis實現(xiàn)迷你版微信搶紅包 1. 發(fā)紅包 2. 拆紅包(一個紅包拆分成多少個,每個紅包里有多少錢)=》二倍均值算法,將拆分后的紅包通過list放入redis 3. 搶紅包(用戶搶紅包,并記錄哪個用戶搶了多少錢,防止重復(fù)搶):hset記錄每個紅包被哪些用戶搶了 */ var ( RedisCli *redis.Client RED_PACKGE_KEY = "redpackage:" RED_PACKAGE_CONSUME_KEY = "redpackage:consume:" ) func init() { rand.Seed(time.Now().UnixNano()) RedisCli = redis.NewClient(&redis.Options{ Addr: "localhost:6379", DB: 0, }) } func main() { app := iris.New() app.Get("/send", sendRedPacket) app.Get("/rob", robRedPacket) app.Get("/info", infoRedPacket) app.Listen(":9090", nil) } // 發(fā)紅包 http://localhost:9090/send?totalMoney=100&totalNum=3 func sendRedPacket(c *context2.Context) { money, _ := c.URLParamInt("totalMoney") totalNum, _ := c.URLParamInt("totalNum") redPackets := splitRedPacket(money, totalNum) uuid, _ := uuid.NewUUID() k := RED_PACKGE_KEY + uuid.String() for _, r := range redPackets { _, err := RedisCli.LPush(context.TODO(), k, r).Result() if err != nil && err != redis.Nil { panic(err) } } c.JSON(fmt.Sprintf("send redpacket[%s] succ %v", k, redPackets)) } // 搶紅包 http://localhost:9090/rob?redPacket=e3e71f56-e9a3-11ee-9ad5-7a2cb90a4104&uId=4 func robRedPacket(c *context2.Context) { //判斷是否搶過 redPacket := c.URLParam("redPacket") uId, _ := c.URLParamInt("uId") exists, err := RedisCli.HExists(context.TODO(), RED_PACKAGE_CONSUME_KEY+redPacket, fmt.Sprintf("%d", uId)).Result() if err != nil && err != redis.Nil { panic(err) } if exists { //表明已經(jīng)搶過 c.JSON(fmt.Sprintf("[%d] you have already rob", uId)) return } else if !exists { //從list里取出一個紅包 result, err := RedisCli.LPop(context.TODO(), RED_PACKGE_KEY+redPacket).Result() if err == redis.Nil { //紅包已經(jīng)搶完了 c.JSON(fmt.Sprintf("redpacket is empty")) return } if err != nil { panic(err) } fmt.Printf("%d rob the red packet %v\n", uId, result) //記錄:后續(xù)可以異步進(jìn)MySQL或者M(jìn)Q做統(tǒng)計分析,每一年搶了多少紅包,金額是多少【年度總結(jié)】 _, err = RedisCli.HSet(context.TODO(), RED_PACKAGE_CONSUME_KEY+redPacket, uId, result).Result() if err != nil && err != redis.Nil { panic(err) } c.JSON(fmt.Sprintf("[%d] rob the red packet %v", uId, result)) } } func infoRedPacket(c *context2.Context) { redPacket := c.URLParam("redPacket") infoMap, err := RedisCli.HGetAll(context.TODO(), RED_PACKAGE_CONSUME_KEY+redPacket).Result() if err != nil && err != redis.Nil { panic(err) } c.JSON(infoMap) } // 拆紅包 func splitRedPacket(totalMoney, totalNum int) []int { //1. 將紅包拆分為幾個 redpackets := make([]int, totalNum) usedMoney := 0 for i := 0; i < totalNum; i++ { //最后一個紅包,還剩余多少就分多少 if i == totalNum-1 { redpackets[i] = totalMoney - usedMoney } else { //二倍均值算法:每次拆分后塞進(jìn)子紅包的金額 = 隨機(jī)區(qū)間(0, (剩余紅包金額M / 未被搶的剩余紅包個數(shù)N) * 2) avgMoney := ((totalMoney - usedMoney) / (totalNum - i)) * 2 money := 1 + rand.Intn(avgMoney-1) redpackets[i] = money usedMoney += money } } return redpackets }
演示
1.啟動程序,調(diào)用send接口發(fā)紅包??,假設(shè)100元,拆分為3個
http://localhost:9090/send?totalMoney=100&totalNum=3
2.調(diào)用rob接口搶紅包
http://localhost:9090/rob?redPacket=b246f0cc-e9a6-11ee-a234-7a2cb90a4104&uId=1
此時如果用戶1再搶,應(yīng)當(dāng)報錯(redis已經(jīng)有記錄該用戶已搶):
3.繼續(xù)調(diào)用rob接口,用戶2、用戶3搶紅包:
Redis中記錄:
4.此時紅包??已經(jīng)被搶完了,如果有用戶4再來搶,應(yīng)該返回來晚了,紅包被搶完了
到此這篇關(guān)于Redis迷你版微信搶紅包實戰(zhàn)的文章就介紹到這了,更多相關(guān)Redis 微信搶紅包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis和rabbitmq實現(xiàn)延時隊列的示例代碼
在高并發(fā)場景下,延遲隊列顯得尤為重要,本文主要介紹了兩種方式,redis和rabbitmq實現(xiàn)延時隊列,具有一定的參考價值,感興趣的可以了解一下2024-03-03Redis實現(xiàn)每日簽到功能(大數(shù)據(jù)量)
在面對百萬級用戶簽到情況下,傳統(tǒng)數(shù)據(jù)庫存儲和判斷會遇到瓶頸,使用Redis的二進(jìn)制數(shù)據(jù)類型可實現(xiàn)高效的簽到功能,示例代碼展示了如何調(diào)用這些功能,包括當(dāng)天簽到、補簽以及查詢簽到記錄,PHP結(jié)合Redis二進(jìn)制數(shù)據(jù)類型可有效處理大數(shù)據(jù)量下的簽到問題2024-10-10基于Redis實現(xiàn)延時隊列的優(yōu)化方案小結(jié)
本文主要介紹了基于Redis實現(xiàn)延時隊列的優(yōu)化方案小結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07詳解redis大幅性能提升之使用管道(PipeLine)和批量(Batch)操作
這篇文章主要介紹了詳解redis大幅性能提升之使用管道(PipeLine)和批量(Batch)操作 ,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2016-12-12