Redis迷你版微信抢红包实战

2025-05-25 15:50

本文主要是介绍Redis迷你版微信抢红包实战,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Redis迷你版微信抢红包实战》本文主要介绍了Redis迷你版微信抢红包实战...

全部代码: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 微信抢红包内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于Redis迷你版微信抢红包实战的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154766

相关文章

Python办公自动化实战之打造智能邮件发送工具

《Python办公自动化实战之打造智能邮件发送工具》在数字化办公场景中,邮件自动化是提升工作效率的关键技能,本文将演示如何使用Python的smtplib和email库构建一个支持图文混排,多附件,多... 目录前言一、基础配置:搭建邮件发送框架1.1 邮箱服务准备1.2 核心库导入1.3 基础发送函数二、

PowerShell中15个提升运维效率关键命令实战指南

《PowerShell中15个提升运维效率关键命令实战指南》作为网络安全专业人员的必备技能,PowerShell在系统管理、日志分析、威胁检测和自动化响应方面展现出强大能力,下面我们就来看看15个提升... 目录一、PowerShell在网络安全中的战略价值二、网络安全关键场景命令实战1. 系统安全基线核查

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

Redis出现中文乱码的问题及解决

《Redis出现中文乱码的问题及解决》:本文主要介绍Redis出现中文乱码的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 问题的产生2China编程. 问题的解决redihttp://www.chinasem.cns数据进制问题的解决中文乱码问题解决总结

Java MQTT实战应用

《JavaMQTT实战应用》本文详解MQTT协议,涵盖其发布/订阅机制、低功耗高效特性、三种服务质量等级(QoS0/1/2),以及客户端、代理、主题的核心概念,最后提供Linux部署教程、Sprin... 目录一、MQTT协议二、MQTT优点三、三种服务质量等级四、客户端、代理、主题1. 客户端(Clien

在Spring Boot中集成RabbitMQ的实战记录

《在SpringBoot中集成RabbitMQ的实战记录》本文介绍SpringBoot集成RabbitMQ的步骤,涵盖配置连接、消息发送与接收,并对比两种定义Exchange与队列的方式:手动声明(... 目录前言准备工作1. 安装 RabbitMQ2. 消息发送者(Producer)配置1. 创建 Spr

java向微信服务号发送消息的完整步骤实例

《java向微信服务号发送消息的完整步骤实例》:本文主要介绍java向微信服务号发送消息的相关资料,包括申请测试号获取appID/appsecret、关注公众号获取openID、配置消息模板及代码... 目录步骤1. 申请测试系统2. 公众号账号信息3. 关注测试号二维码4. 消息模板接口5. Java测试

深度解析Spring Boot拦截器Interceptor与过滤器Filter的区别与实战指南

《深度解析SpringBoot拦截器Interceptor与过滤器Filter的区别与实战指南》本文深度解析SpringBoot中拦截器与过滤器的区别,涵盖执行顺序、依赖关系、异常处理等核心差异,并... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实