返回顶部
分享到

Redis迷你版微信抢红包

Mysql 来源:互联网 作者:佚名 发布时间:2025-05-25 22:32:45 人浏览
摘要

全部代码:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/redis_demo/redpacket_demo 1 思路分析 抢红包是一个高并发操作,且我们需要保证其原子性,同时抢红包过程中不能加锁,不能出现因为某

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

1 思路分析

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

1.1 流程

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

  • 发红包:提供接口send,参数:红包总金额,红包个数。拆完之后通过redis list结构将红包存入redis
  • 拆红包:split接口,根据算法将红包合理的拆分,金额不能差距太大,比如:一个100元红包,拆分为20个,不能出现一个红包里就包含99元的情况
  • 抢红包:提供rob接口,接收红包名(要抢哪个红包,不同人不同群发的红包都是唯一的),接收用户id(谁抢)
  • 记录:抢完红包之后,记录用户id与所抢红包????对应关系,防止多抢。通过redis hset数据结构实现。

1.2 注意点

①拆红包:二倍均值算法

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

  • 保证被拆红包金额的差距不会太大。不会出现一个100元红包,拆分为20个,一个红包里就包含99元的情况

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

在这里插入图片描述


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。

您可能感兴趣的文章 :

原文链接 :
相关文章
  • Mysql中的用户管理

    Mysql中的用户管理
    13. 用户管理 为什么不能只用 root:出于安全考虑,不应该所有操作都由 root 执行。 MySQL 的用户信息存储位置:mysql.user表。 13.1 用户 ???? 1
  • Redis迷你版微信抢红包

    Redis迷你版微信抢红包
    全部代码:https://github.com/ziyifast/ziyifast-code_instruction/tree/main/redis_demo/redpacket_demo 1 思路分析 抢红包是一个高并发操作,且我们需要保证其原
  • Python虚拟环境终极(含PyCharm的使用教程)

    Python虚拟环境终极(含PyCharm的使用教程)
    一、为什么需要虚拟环境? 场景 问题表现 虚拟环境解决方案 多项目依赖冲突 项目A需要Django 3.2,项目B需要Django 4.1 隔离不同项目的依赖版
  • Python中的魔术方法__new__介绍

    Python中的魔术方法__new__介绍
    一、核心意义与机制 1.1 构造过程原理 1.2 与 __init__ 对比 特性 __new__ __init__ 方法类型 静态方法 实例方法 返回值 必须返回实例对象 无返回值
  • 基于PyQt5实现的Windows定时关机工具

    基于PyQt5实现的Windows定时关机工具
    在日常使用电脑的过程中,我们经常会遇到需要定时关机的场景,比如: 夜间下载文件,想让电脑在任务完成后自动关机。 长时间运行的程
  • 宝塔安装的MySQL无法连接的情况及解决方案

    宝塔安装的MySQL无法连接的情况及解决方案
    一、错误 1130:Host xxx.xxx.xxx.xxx is not allowed to connect to this MySQL server 错误原因 此错误表示您的 IP 地址没有被授权访问宝塔服务器上的 MySQL。
  • MySQL中drop、truncate和delete的区别
    对于drop、truncate和delete,虽然简单,但是真要使用或者面试时候问到还是需要有一定的总结,今天来简单讲讲他们直接的区别。在此之前先
  • Linux搭建单机MySQL8.0.26版本的操作方法

    Linux搭建单机MySQL8.0.26版本的操作方法
    环境信息 IP 系统 规格 10.0.0.10 Ubuntu22.04 2c4g 数据库服务安装步骤 下载前置依赖 1 2 # 下载libtinfo5、libnuma1依赖 [root@lb ~]# apt update -y apt install
  • mysql中的group by高级用法
    MySQL中的GROUP BY是数据聚合分析的核心功能,主要用于将结果集按指定列分组,并结合聚合函数进行统计计算。以下从基本语法到高级用法进
  • MySQL双主搭建+keepalived高可用的实现

    MySQL双主搭建+keepalived高可用的实现
    一、测试环境准备 节点1 节点2 IP地址 192.168.101.77 192.168.101.79 MySQL版本 8.0.32 8.0.32 二、主从搭建 1.创建复制用户 节点1执行: 1 2 3 4 mysql CREA
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计