Redis迷你版微信搶紅包實(shí)戰(zhàn)
全部代碼:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/redis_demo/redpacket_demo
1 思路分析
搶紅包是一個(gè)高并發(fā)操作,且我們需要保證其原子性,同時(shí)搶紅包過程中不能加鎖,不能出現(xiàn)因?yàn)槟硞€(gè)人網(wǎng)絡(luò)卡頓,導(dǎo)致其他人無法搶紅包??。
1.1 流程
搶紅包流程:發(fā)紅包-拆紅包-搶紅包-記錄誰搶了紅包
- 發(fā)紅包:提供接口send,參數(shù):紅包總金額,紅包個(gè)數(shù)。拆完之后通過redis list結(jié)構(gòu)將紅包存入redis
- 拆紅包:split接口,根據(jù)算法將紅包合理的拆分,金額不能差距太大,比如:一個(gè)100元紅包,拆分為20個(gè),不能出現(xiàn)一個(gè)紅包里就包含99元的情況
- 搶紅包:提供rob接口,接收紅包名(要搶哪個(gè)紅包,不同人不同群發(fā)的紅包都是唯一的),接收用戶id(誰搶)
- 記錄:搶完紅包之后,記錄用戶id與所搶紅包??對(duì)應(yīng)關(guān)系,防止多搶。通過redis hset數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)。
1.2 注意點(diǎn)
①拆紅包:二倍均值算法
二倍均值算法:每次拆分后塞進(jìn)子紅包的金額 = 隨機(jī)區(qū)間(0, (剩余紅包金額M / 未被搶的剩余紅包個(gè)數(shù)N) * 2)
- 保證被拆紅包金額的差距不會(huì)太大。不會(huì)出現(xiàn)一個(gè)100元紅包,拆分為20個(gè),一個(gè)紅包里就包含99元的情況
②發(fā)紅包:list
記錄紅包被拆分為了多少份,并且每份里有多少錢
③搶紅包&記錄:hset
記錄用戶與被搶紅包的對(duì)應(yīng)關(guān)系,防止多搶
2 代碼實(shí)現(xiàn)
為了大家能看得清晰,這里我直接將所有代碼都放在了main.go,實(shí)際使用和實(shí)現(xiàn)還是應(yīng)該拆分為service、controller…
2.1 拆紅包splitRedPacket
// 拆紅包
func splitRedPacket(totalMoney, totalNum int) []int {
//1. 將紅包拆分為幾個(gè)
redpackets := make([]int, totalNum)
usedMoney := 0
for i := 0; i < totalNum; i++ {
//最后一個(gè)紅包,還剩余多少就分多少
if i == totalNum-1 {
redpackets[i] = totalMoney - usedMoney
} else {
//二倍均值算法:每次拆分后塞進(jìn)子紅包的金額 = 隨機(jī)區(qū)間(0, (剩余紅包金額M / 未被搶的剩余紅包個(gè)數(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里取出一個(gè)紅包
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)計(jì)分析,每一年搶了多少紅包,金額是多少【年度總結(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實(shí)現(xiàn)迷你版微信搶紅包
1. 發(fā)紅包
2. 拆紅包(一個(gè)紅包拆分成多少個(gè),每個(gè)紅包里有多少錢)=》二倍均值算法,將拆分后的紅包通過list放入redis
3. 搶紅包(用戶搶紅包,并記錄哪個(gè)用戶搶了多少錢,防止重復(fù)搶):hset記錄每個(gè)紅包被哪些用戶搶了
*/
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里取出一個(gè)紅包
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)計(jì)分析,每一年搶了多少紅包,金額是多少【年度總結(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. 將紅包拆分為幾個(gè)
redpackets := make([]int, totalNum)
usedMoney := 0
for i := 0; i < totalNum; i++ {
//最后一個(gè)紅包,還剩余多少就分多少
if i == totalNum-1 {
redpackets[i] = totalMoney - usedMoney
} else {
//二倍均值算法:每次拆分后塞進(jìn)子紅包的金額 = 隨機(jī)區(qū)間(0, (剩余紅包金額M / 未被搶的剩余紅包個(gè)數(shù)N) * 2)
avgMoney := ((totalMoney - usedMoney) / (totalNum - i)) * 2
money := 1 + rand.Intn(avgMoney-1)
redpackets[i] = money
usedMoney += money
}
}
return redpackets
}
演示
1.啟動(dòng)程序,調(diào)用send接口發(fā)紅包??,假設(shè)100元,拆分為3個(gè)
http://localhost:9090/send?totalMoney=100&totalNum=3

2.調(diào)用rob接口搶紅包
http://localhost:9090/rob?redPacket=b246f0cc-e9a6-11ee-a234-7a2cb90a4104&uId=1

此時(shí)如果用戶1再搶,應(yīng)當(dāng)報(bào)錯(cuò)(redis已經(jīng)有記錄該用戶已搶):

3.繼續(xù)調(diào)用rob接口,用戶2、用戶3搶紅包:


Redis中記錄:

4.此時(shí)紅包??已經(jīng)被搶完了,如果有用戶4再來搶,應(yīng)該返回來晚了,紅包被搶完了

到此這篇關(guān)于Redis迷你版微信搶紅包實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)Redis 微信搶紅包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis客戶端連接遠(yuǎn)程Redis服務(wù)器方式
這篇文章主要介紹了Redis客戶端連接遠(yuǎn)程Redis服務(wù)器方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
Redis Sentinel實(shí)現(xiàn)哨兵模式搭建小結(jié)
這篇文章主要介紹了Redis Sentinel實(shí)現(xiàn)哨兵模式搭建小結(jié),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12
Redis TTL命令實(shí)現(xiàn)數(shù)據(jù)生存時(shí)間
生存時(shí)間可以通過Redis中的不同命令來設(shè)置、查看和管理,TTL命令是其中之一,本文主要介紹了Redis TTL命令實(shí)現(xiàn)數(shù)據(jù)生存時(shí)間,具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06
Redis緩存-序列化對(duì)象存儲(chǔ)亂碼問題的解決
這篇文章主要介紹了Redis緩存-序列化對(duì)象存儲(chǔ)亂碼問題的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
聊一聊redis奇葩數(shù)據(jù)類型與集群知識(shí)
現(xiàn)在越來越多的項(xiàng)目都會(huì)利用到redis,多實(shí)例redis服務(wù)比單實(shí)例要復(fù)雜的多,這里面涉及到定位、容錯(cuò)、擴(kuò)容等技術(shù)問題,下面這篇文章主要給大家介紹了關(guān)于redis奇葩數(shù)據(jù)類型與集群知識(shí)的相關(guān)資料,需要的朋友可以參考下2022-01-01

