开发者

Redis迷你版微信抢红包实战

目录
  • 1 思路分析
    • 1.1hCckRX 流程
    • 1.2 注意点
      • ①拆红包:二倍均值算法
      • ②发红包:list
      • ③抢红包&记录:hset
  • 2 代码实现
    • 2.1 拆红包splitRedPacket
      • 2.2 发红包sendRedPacket
        • 2.3 抢红包&记录robRedPacket
          • 2.4 分析(红包被谁抢了)infoRedPacket
          • 全部代码
            • 演示

              全部代码: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 注意点

              ①拆红包:二倍均值算javascript法

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

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

              ②发红包:list

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

              ③抢红包&记录:hset

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

              2 代码实现

              为了大家能看得清晰,这里我直接将所有代码都放在了main.go,实际使用和实现还是应该拆分为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 {
              			//二倍均值算法:每次拆分后塞进子红包的金额 = 随机区间(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

              // 发红包 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 !php= 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 {
              		//表明已经抢过
              		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

              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实现迷你版微信抢红包
              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 := R编程edisCli.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

              Redis迷你版微信抢红包实战

              2.js调用rob接口抢红包

              http://localhost:9090/rob?redPacket=b246f0cc-e9a6-11ee-a234-7a2cb90a4104&uId=1

              Redis迷你版微信抢红包实战

              此时如果用户1再抢,应当报错(redis已经有记录该用户已抢):

              Redis迷你版微信抢红包实战

              3.继续调用rob接口,用户2、用户3抢红包:

              Redis迷你版微信抢红包实战

              Redis迷你版微信抢红包实战

              Redis中记录:

              Redis迷你版微信抢红包实战

              4.此时红包已经被抢完了,如果有用户4再来抢,应该返回来晚了,红包被抢完了

              Redis迷你版微信抢红包实战

              到此这篇关于Redis迷你版微信抢红包实战的文章就介绍到这了,更多相关Redis 微信抢红包内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

              0

              上一篇:

              下一篇:

              精彩评论

              暂无评论...
              验证码 换一张
              取 消

              最新数据库

              数据库排行榜