微信小程序抢红包高并发设计

科技资讯 投稿 5300 0 评论

微信小程序抢红包高并发设计

1、背景

系统设计首先我们要考虑几个问题:

2、系统高并发瓶颈会出现在哪里?

4、如何应对羊毛党缛羊毛问题?

系统现有基座层面采用SpringCloud微服务技术框架,SLB负载,应用层面可以加多个应用节点,实现水平扩展,应用服务压力可以有效分解(应用服务基础架构方面基本固化,可改造空间有限);

 

2、解决思路

上游限流。

1、页面静态化,就是将整个页面静态化放到OSS或CDN节点中(前端是小程序,整体页面不好做静态化,只能在图片、JS、CSS等方面做点工作)。

2、防止前端操作频繁或重复提交,可以将短时间内同一个用户多次请求合并,也可以分批暂停机制或加数学验证码:用户在计算验证码结果时可以减少大量请求同时进入,减少redis, mysql,服务器的压力。

3、采用多级缓存机制,有一些不常变化字典可以缓存在前端;后端应用程序做多级缓存机制,

  b、第二级缓存:应用服务器本地缓存Ehcache本地磁盘化缓存一些关键数据;

 

例:

 boolean over = map.get(goodsId;

 if(over { return Result.error(‘库存不足’; }

当我们map通过key读取到value值为true的时候,就返回错误提示给用户,这样不管以后有多个请求进入都只运行两行代码,后面的操作无法进入。

4、防刷规则限定

5、redis预减库存

6、加入MQ消息队列

7、采用负载均衡Nginx

3、高并发设计

1、前端做好频繁重复提交策略(如合并提交),这是限流的第一步;

3、接口层面做好一些业务限流,比如同一个用户多次提交去重合并,抽过的不给再抽,可以有效防止程序机器刷单情况;

5、利用本地服务器内存标记当用作二级缓存,当redis库存<0时把一些无效请求过滤,减少redis访问压力。

 

抢红包处理流程图

前端和后端接口交互逻辑

1、前端摇一摇发起接口请求,后端接口做一些规则校验之后,快速返回并附带异步任务查询ID即:jobId。

3、暂停3/5分钟后再摇如果上次已经抽中,这次再摇,根据业务要求,后端不入抽奖队列,即不给再抽中的机会,直接返回提示给前端。如果上次没有抽中,可以再次入抽奖队列排队再抽。

 

抢红包处理流程时序图

4、代码实现

秒杀或抢红包实现代码片断,标注说明,代码中有很多业务操作(写入、查询等),当时写的代码优雅性较差,不要看代码优雅性,读者可以不管它,只需要理解高并发处理思路即可。

 /**
     * 限时红包雨,红包抽奖
     *
     * @param con     前端提交的业务参数
     * @param request 用于获取请求头信息
     */
    public ResponseData onRedPackageRain(ActivityPrizeRainCondition con, HttpServletRequest request {
        //各种校验,校验必填参数
        this.validateParam(con;
        //校验签名
        this.checkAsign(con, request;

        //活动轮数id
        String liveActivityRoundsId = con.getLiveActivityRoundsId(;
        String ip = NetworkUtils.getClientIp(request;

        //同openID或ip限流
        this.rateLimit(con.getOpenid(, ip;
        //校验是否在活动时间执行
        this.checkedActivityTime(liveActivityRoundsId;


        //所有奖品抽完,内存标识,减少Redis访问
        Assert.isTrue(!localFlag, "奖品已抽完了,请等下一轮";
        //校验当天抽奖次数
        this.checkedLotteryNum(liveActivityRoundsId, con.getOpenid(, 86400L;

        //取缓存有奖品数量的奖品 随机抽,某一轮红包轮数id前缀
        String prefix_key = GlobalConstant.WECHAT_LIVE_ACTIVITY_ROUNDS + liveActivityRoundsId + ":";
        Set<String> keys = stringRedisTemplate.keys(prefix_key + "*";
        Assert.isTrue(!CollectionUtils.isEmpty(keys, "没有奖品啦";

        //可抽奖的奖品id
        List<String> newKeys = new ArrayList<>(;
        for (String key : keys {
            String mapKey = key.substring(key.lastIndexOf(":" + 1;
            String mapValue = stringRedisTemplate.opsForValue(.get(key;
            if (StringUtils.isNotEmpty(mapValue && Integer.parseInt(mapValue > 0 {
                newKeys.add(mapKey;
            }
        }
        if (CollectionUtils.isEmpty(newKeys {
            localFlag = true;
            Assert.isTrue(false, "奖品已抽完了,请等下一轮";
        }

        /*随机抽一个奖品id*/
        Integer rand = RandomUtils.nextInt(newKeys.size(;
        String prizeLotteryId = newKeys.get(rand;
        con.setPrizeNumber(prizeLotteryId;
        con.setIp(ip;

        //符合条件的用户请求放入MQ队列
        rabbitTemplate.convertAndSend(MIAOSHA_QUEUE, con;
        LOG.info("-----------红包雨抽到奖品,加入队列:{}", con.toString(;
        return renderSuccess("具备秒杀资格";
    }
  /**
     * 异步消费抽中红包队列
     * @param con 活动请求业务参数
     * @param message MQ消息对象
     * @param channel MQ通道对象
     */
    @RabbitListener(queues = MIAOSHA_QUEUE
    public void consumeMessage(ActivityPrizeRainCondition con, Message message, Channel channel {
        
        try {
            LOG.info("rabbitmq message consume======={}", con.toString(;
            //设置最大服务消息数量,避免消息处理不过来,全部堆积在本地缓存里
            // 会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ack,则该consumer将block掉,直到有消息ack
            // channel.basicQos(0,5,false;
            //确认应答信息
            channel.basicAck(message.getMessageProperties(.getDeliveryTag(, false;
            
            //执行红包发放流程
            this.doRedPackageRain(con;
            
        }catch (Exception e{
            LOG.error("Message consume ERROR!",e;
        }
    }

    /**
     * 红包发放关键执行程序
     * @param con 业务请求参数
     */
    private void doRedPackageRain(ActivityPrizeRainCondition con{
        //初始中奖金额
        double randomMoney = 0;
        String liveActivityRoundsId= con.getLiveActivityRoundsId(;
        String prizeId = con.getPrizeNumber(;
        String prefix_key = GlobalConstant.WECHAT_LIVE_ACTIVITY_ROUNDS + liveActivityRoundsId+ ":"+prizeId;
        String prizeNum = stringRedisTemplate.opsForValue(.get(prefix_key;
        //入队后再次校验Redis是否有库存
        if(StringUtils.isEmpty(prizeNum||Integer.parseInt(prizeNum<=0{
            //再次 检查缓存所有奖品是否还有库存  有的随机抽
            // 某一轮轮数id 前缀
            String prefix_key2 = GlobalConstant.WECHAT_LIVE_ACTIVITY_ROUNDS + liveActivityRoundsId+ ":";
            Set<String> keys = stringRedisTemplate.keys(prefix_key2+"*";
            if(CollectionUtils.isEmpty(keys

编程笔记 » 微信小程序抢红包高并发设计

赞同 (27) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽