抢红包,1000人抢十个总金额为100的随机红包,为了支持高并发
1. 红包预生成算法
目标:提前生成10个红包金额,保证总和为100元且符合随机性要求。
- 推荐算法:二倍均值法(公平性保障)
- 初始化剩余金额=100,剩余数量=10
- 每次生成金额范围:
[0.01, 剩余金额/剩余数量 * 2]
- 最后一个红包直接取剩余金额
算法解说:
每次生成的红包金额不超过剩余金额均值的两倍
每次生成的红包金额是基于当前的剩余金额计算的,所以即使某个红包取了最大值,后续的红包金额会因为剩余金额的减少而调整,从而整体总和仍然保持正确。
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class RedPacketGenerator {
// 红包精度设置(小数点后两位)
private static final int SCALE = 2;
private static final BigDecimal MIN_AMOUNT = new BigDecimal("0.01");
private static final Random RANDOM = new Random();
/**
* 生成随机红包(二倍均值法)
* @param total 总金额
* @param count 红包数量
* @return 红包金额列表(按生成顺序)
*/
public static List<BigDecimal> generateRedPackets(BigDecimal total, int count) {
// 参数校验
validateParams(total, count);
List<BigDecimal> packets = new ArrayList<>(count);
BigDecimal remainingAmount = total;
int remainingCount = count;
// 生成前count-1个红包
for (int i = 1; i < count; i++) {
// 计算最大可选金额:剩余金额 / 剩余数量 * 2
BigDecimal max = remainingAmount.divide(
new BigDecimal(remainingCount), SCALE, RoundingMode.HALF_UP
).multiply(new BigDecimal(2));
// 生成随机金额 [0.01, max]
BigDecimal amount = generateRandomAmount(MIN_AMOUNT, max);
packets.add(amount);
remainingAmount = remainingAmount.subtract(amount);
remainingCount--;
}
// 最后一个红包直接分配剩余金额
packets.add(remainingAmount.setScale(SCALE, RoundingMode.HALF_UP));
// 打乱顺序(可选)
Collections.shuffle(packets);
return packets;
}
/**
* 生成随机金额(包含min,不超过max)
*/
private static BigDecimal generateRandomAmount(BigDecimal min, BigDecimal max) {
// 计算随机基数(0.0000~1.0000)
double factor = RANDOM.nextDouble();
// 生成金额:min + (max - min) * random
BigDecimal amount = max.subtract(min)
.multiply(BigDecimal.valueOf(factor))
.add(min)
.setScale(SCALE, RoundingMode.HALF_UP);
// 确保不小于最小值
return amount.compareTo(min) >= 0 ? amount : min;
}
private static void validateParams(BigDecimal total, int count) {
if (total.compareTo(MIN_AMOUNT.multiply(new BigDecimal(count))) < 0) {
throw new IllegalArgumentException(
"每个红包至少需要0.01元,总金额不能小于" + MIN_AMOUNT.multiply(new BigDecimal(count))
);
}
}
public static void main(String[] args) {
// 测试生成10个总金额100的红包
List<BigDecimal> packets = generateRedPackets(new BigDecimal("100.00"), 10);
// 打印结果并验证总和
BigDecimal sum = BigDecimal.ZERO;
System.out.println("红包明细:");
for (BigDecimal packet : packets) {
System.out.println(packet + "元");
sum = sum.add(packet);
}
System.out.println("红包总数:" + packets.size());
System.out.println("金额总和:" + sum.setScale(SCALE, RoundingMode.HALF_UP));
}
}
- 结果存储:将生成的10个红包金额存入Redis List结构(例如:
RPUSH red_packet_list 15.2 8.7 ...
)。
2. 高并发抢红包接口设计
核心逻辑:使用缓存原子操作确保并发安全,避免数据库瓶颈。
- 步骤:
- 请求拦截:检查用户是否已抢过(使用
Redis SETNX
记录用户ID)。 - 抢红包操作:
LPOP red_packet_list
(原子弹出预生成红包)。- 成功:返回金额,异步记录到数据库。
- 失败(返回nil):返回“已抢完”状态。
- 请求拦截:检查用户是否已抢过(使用
3. 系统优化策略
- 缓存层:
- 使用Redis Cluster分片提升吞吐量,单节点QPS可达10万+。
- 开启持久化(AOF每秒同步)防止数据丢失。
- 限流与降级:
- Nginx层限流:限制每秒最大请求数(如5000 QPS)。
- 熔断降级:通过Sentinel或Hystrix在服务不可用时返回友好提示。
- 异步处理:
- 抢红包成功后,通过消息队列(如Kafka)异步更新数据库,保证最终一致性。
4. 数据库设计
- 表结构:
CREATE TABLE red_packet_log ( id BIGINT AUTO_INCREMENT, user_id VARCHAR(64), amount DECIMAL(10,2), created_at TIMESTAMP, PRIMARY KEY(id) );
- 写入优化:
- 批量插入:MQ消费者批量处理消息,减少数据库写入次数。
- 读写分离:查询操作路由到从库,避免主库压力。
5. 安全与防刷
- 频率限制:每个用户UID限制每秒最多1次请求(Redis计数器+TTL)。
- Token验证:前端请求携带动态Token(如HMAC签名),防止重放攻击。
- IP黑名单:实时监控异常IP(如每秒超过50次请求),自动封禁。
6. 压测与监控
- 压力测试:使用JMeter模拟1000用户并发,验证Redis和接口响应时间(目标:99%请求<10ms)。
- 监控告警:
- Prometheus监控Redis内存、QPS、延迟。
- 告警规则:当红包剩余数量<2时触发低库存通知。
最终架构图
[用户请求] → (Nginx限流) → [抢红包微服务] → [Redis Cluster]
↓
[Kafka] → [消费服务] → [MySQL]