全部代码:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/redis_demo/redpacket_demo
抢红包是一个高并发操作,且我们需要保证其原子性,同时抢红包过程中不能加锁,不能出现因为某个人网络卡顿,导致其他人无法抢红包????。
抢红包流程:发红包-拆红包-抢红包-记录谁抢了红包
二倍均值算法:每次拆分后塞进子红包的金额 = 随机区间(0, (剩余红包金额M / 未被抢的剩余红包个数N) * 2)
记录红包被拆分为了多少份,并且每份里有多少钱
记录用户与被抢红包的对应关系,防止多抢
为了大家能看得清晰,这里我直接将所有代码都放在了main.go,实际使用和实现还是应该拆分为service、controller…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// 拆红包 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 { //二倍均值算法:每次拆分后塞进子红包的金额 = 随机区间(0, (剩余红包金额M / 未被抢的剩余红包个数N) * 2) avgMoney := ((totalMoney - usedMoney) / (totalNum - i)) * 2 money := 1 + rand.Intn(avgMoney-1) redpackets[i] = money usedMoney += money } } return redpackets } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 发红包 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)) } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// 抢红包 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 { //表明已经抢过 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 { //红包已经抢完了 c.JSON(fmt.Sprintf("redpacket is empty")) return } if err != nil { panic(err) } fmt.Printf("%d rob the red packet %v\n", uId, result) //记录:后续可以异步进MySQL或者MQ做统计分析,每一年抢了多少红包,金额是多少【年度总结】 _, 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)) }
} |
1 2 3 4 5 6 7 8 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
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实现迷你版微信抢红包 1. 发红包 2. 拆红包(一个红包拆分成多少个,每个红包里有多少钱)=》二倍均值算法,将拆分后的红包通过list放入redis 3. 抢红包(用户抢红包,并记录哪个用户抢了多少钱,防止重复抢):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) }
// 发红包 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 { //表明已经抢过 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 { //红包已经抢完了 c.JSON(fmt.Sprintf("redpacket is empty")) return } if err != nil { panic(err) } fmt.Printf("%d rob the red packet %v\n", uId, result) //记录:后续可以异步进MySQL或者MQ做统计分析,每一年抢了多少红包,金额是多少【年度总结】 _, 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 { //二倍均值算法:每次拆分后塞进子红包的金额 = 随机区间(0, (剩余红包金额M / 未被抢的剩余红包个数N) * 2) avgMoney := ((totalMoney - usedMoney) / (totalNum - i)) * 2 money := 1 + rand.Intn(avgMoney-1) redpackets[i] = money usedMoney += money } } return redpackets } |
1.启动程序,调用send接口发红包????,假设100元,拆分为3个
http://localhost:9090/send?totalMoney=100&totalNum=3
2.调用rob接口抢红包
http://localhost:9090/rob?redPacket=b246f0cc-e9a6-11ee-a234-7a2cb90a4104&uId=1
此时如果用户1再抢,应当报错(redis已经有记录该用户已抢):
3.继续调用rob接口,用户2、用户3抢红包:
Redis中记录:
4.此时红包????已经被抢完了,如果有用户4再来抢,应该返回来晚了,红包被抢完了