抢红包,1000人抢十个总金额为100的随机红包,为了支持高并发

1. 红包预生成算法

目标:提前生成10个红包金额,保证总和为100元且符合随机性要求。

  • 推荐算法:二倍均值法(公平性保障)
    1. 初始化剩余金额=100,剩余数量=10
    2. 每次生成金额范围:[0.01, 剩余金额/剩余数量 * 2]
    3. 最后一个红包直接取剩余金额
      算法解说:
      每次生成的红包金额不超过剩余金额均值的两倍
      每次生成的红包金额是基于当前的剩余金额计算的,所以即使某个红包取了最大值,后续的红包金额会因为剩余金额的减少而调整,从而整体总和仍然保持正确。
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. 高并发抢红包接口设计

核心逻辑:使用缓存原子操作确保并发安全,避免数据库瓶颈。

  • 步骤
    1. 请求拦截:检查用户是否已抢过(使用Redis SETNX记录用户ID)。
    2. 抢红包操作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]

参考资料

posted @ 2025-03-11 14:46  向着朝阳  阅读(92)  评论(0)    收藏  举报