跳转到内容
View in the app

A better way to browse. Learn more.

彼岸论坛

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
欢迎抵达彼岸 彼岸花开 此处谁在 -彼岸论坛

[分享创造] 写了一个支持百万 QPS 的营销活动,欢迎大家交流沟通

发表于

这两天写了一个支持百万 QPS 的营销活动,把我想到的优化点全部用上了,甚至比一些工业级别的我感觉都优秀不少,在我自己的小水管,压不上去,如果哪位大佬有比较好的机器,欢迎压测一波,看看性能能到哪里去。欢迎大家沟通交流。

代码 github 链接

体检地址 点我 - 体验地址

优化点(难点、亮点)

代码中优化点用了 redis 预减缓存,随机比例获取奖品,高并发场景拦截大部分用户,乐观锁,mq 直接异步化发放奖品。基本上整个流程不会与数据库进行交互,瓶颈点几乎可以说是没有。这种架构,支撑百万,千万 qps 一点问题都没有。

  1. 核心发奖流程
public boolean grantPrize(String phone, String activity) {
        if (StringUtils.isAnyEmpty(activity, phone)) {
            throw new RuntimeException(ERROR_MSG);
        }

        // phone 为幂等键
        String key = StrUtil.format(ACTIVITY_PHONE_LOCK, activity, phone);
        boolean success = RedisUtils.tryLock(key, redissonClient, () -> {
            //1. 幂等处理,这里还可以优化,因为 grantId 是一个唯一索引,插入失败就是重复领取,但可能失败次数会比较多
            MktActivityPrizeGrant mktActivityPrizeGrant = mktActivityPrizeGrantDao.getMktActivityPrizeGrant(phone);
            if (mktActivityPrizeGrant != null && StringUtils.isNotEmpty(mktActivityPrizeGrant.getGrantId())) {
                throw new RuntimeException("请勿重复领取");
            }

            // 2. 这里一个优化, 随机比例获取奖品,可以随时调整
            int seed = ThreadLocalRandom.current().nextInt(0, 100) + 1; // 1-100
            int random = NumberUtils.toInt(RedisUtils.get(CACHE_MKT_ACTIVITY_PRIZE_RANDOM, stringRedisTemplate));
            if (seed > random) {
                //log.warn("随机比例被拦截 seed = {}, random = {}", seed, random);
                throw new RuntimeException("随机比例拦截 - " + ERROR_MSG);
            }


            // 3. 缓存预减库存
            Long num = RedisUtils.decr(CACHE_MKT_ACTIVITY_PRIZE_NUM, stringRedisTemplate);
            if (num == null || num < 0) {

                // 将 redis 库存加回,可做可不做,看业务需求
                RedisUtils.incr(CACHE_MKT_ACTIVITY_PRIZE_NUM, stringRedisTemplate);
                throw new RuntimeException("redis 库存不足 - " + ERROR_MSG);
            }

            MktActivityPrize activityPrize = activityCacheService.getActivityPrize();


            // 4. 真正数据库减库存,并且插入发奖记录
            // 如果 redis 预减库存成功,这里大概率会成功,基本不会失败,如果失败,放弃重试,失败重试会影响系统性能,重试次数越多,对系统性能的影响越大。
            Boolean execute = transactionTemplate.execute(status -> {
                // 4.1 扣减库存
                Integer update = mktActivityPrizeDao.occupyActivityPrize(activityPrize.getActivityId(), activityPrize.getPrizeId());
                if (update == null || update <= 0) {
                    //log.warn("mysql 扣减库存失败 update = {}", update);
                    throw new RuntimeException("mysql 库存扣减失败 - " + ERROR_MSG);
                }

                // 4.2 插入发奖记录
                MktActivityPrizeGrant grant = buildMktActivityPrizeGrant(phone, activityPrize);
                Integer insert = mktActivityPrizeGrantDao.insert(grant);
                if (insert == null || insert <= 0) {
                    //log.warn("mysql 插入发奖记录失败 insert = {}", insert);
                    throw new RuntimeException("mysql 插入发奖记录失败 - " + ERROR_MSG);
                }

                return true;
            });

            return execute;
        });


        return success;
    }

Featured Replies

No posts to show

创建帐户或登录来提出意见

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.