Mysql
主页 > 数据库 > Mysql >

Redis迷你版微信抢红包

2025-05-25 | 佚名 | 点击:

全部代码:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/redis_demo/redpacket_demo

1 思路分析

抢红包是一个高并发操作,且我们需要保证其原子性,同时抢红包过程中不能加锁,不能出现因为某个人网络卡顿,导致其他人无法抢红包????。

1.1 流程

抢红包流程:发红包-拆红包-抢红包-记录谁抢了红包

1.2 注意点

①拆红包:二倍均值算法

二倍均值算法:每次拆分后塞进子红包的金额 = 随机区间(0, (剩余红包金额M / 未被抢的剩余红包个数N) * 2)

②发红包:list

记录红包被拆分为了多少份,并且每份里有多少钱

③抢红包&记录:hset

记录用户与被抢红包的对应关系,防止多抢

2 代码实现

为了大家能看得清晰,这里我直接将所有代码都放在了main.go,实际使用和实现还是应该拆分为service、controller…

2.1 拆红包splitRedPacket

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

}

2.2 发红包sendRedPacket

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))

}

2.3 抢红包&记录robRedPacket

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))

    }

 

}

2.4 分析(红包被谁抢了)infoRedPacket

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再来抢,应该返回来晚了,红包被抢完了

在这里插入图片描述

原文链接:
相关文章
最新更新