服务端如何提升抽奖的速度和性能

先说一下我们目前情况:
我们在做一个活动平台,里面有很多活动可以上线,有很多活动可以抽奖,这些活动的抽奖与很多设定条件有关(这也是导致需要很多查询的原因)。目前服务端使用NodeJs,数据库使用MongoDB,缓存使用Redis

服务器配置
阿里云服务器1台,4核8G;MongoDB为阿里云数据库8核16G(连接数5000);阿里云Redis为4G。

前提条件

  1. 最早的时候抽奖没有使用队列,导致多人同时抽奖的时候,数据库响应不及时,奖品导致超发。
  2. 为了改正问题1,我们引入队列的形式进行抽奖,使用了一个队列,抽完一个,更新数据库的奖品使用量,然后继续抽下一个,导致瞬间只能抽奖一个人,问题是比如有100个人抽奖,也只能一个一个排着来,此做法解决了奖品超发问题,但是导致100个人同时抽奖,一个一个抽,最后一个出结果可能是几秒后了。
  3. 在问题2中,由于活动设定的抽奖条件很复杂,比如有这些设定:

    1. 活动最多可抽10次(不含通过其他渠道增加的次数),每天不超过3次
    2. 可以分享和邀约增加抽奖次数
    3. 判断是否在黑白名单内
    4. 判断是否在可抽奖,可中奖时间段内
    5. 判断当前抽奖人是否在某一次指定的次数内可中奖,比如第100个人必中一条毛巾
    6. 其他更多的限制……

遇到的问题

  1. 在某个时间段多人抽奖时,MongoDB数据库CPU直接跑满100%占用,但是MongoDB内存,服务器内存和CPU都只有30-50%左右,丝毫不受影响。数据库CPU跑满导致接口响应异常慢,正常情况一秒内返回,跑满的时候直接卡死,接口10秒都无法响应。
  2. 由于前提条件的第3点,我们在一个用户抽奖过程中,进行了很多次数据库的查询,以判断他能否中奖,能中哪个奖,大概有10次数据库查询,然后执行抽奖,再下一个……。

问题分析
我们尝试了多种办法以及压力测试,目前还没有很好的解决办法,还无法得知本质的问题是什么,是否如下:

  1. 由于很多人同时抽奖,每个人抽奖都产生多次查询和统计,导致MongoDB的CPU占用高
  2. 由于抽奖队列影响,比如排队一个一个抽,前面的人堵着,导致后面的人抽奖被堵着,从而导致接口响应变慢。
  3. 从阿里云数据库统计,从没有慢查询,即单次数据库操作超过100ms的记录。
  4. 我们各个数据表目前量也不大,数据量都在10万条以内。
  5. 服务器为弹性带宽10M,最高峰也就几M的带宽使用量,所以不是带宽问题。
  6. 服务器内存,CPU,负载指标等一切正常,目前所有问题都指向MongoDB的CPU占用率,因为MongoDB的其他指标如内存占用等一切正常,Redis指标也一切正常。

可能的解决办法

  1. 将一些用户数据保存到Redis,不去统计数据库,比如用户抽奖前需要判断的总抽奖次数,今天抽奖次数,今天分享和邀约数等,放到一个RedisHash,假设如下:

    // redis中的用户统计信息

    {

    // 用户今天分享量和抽奖次数,直接读取redis,不从MongoDB统计

    todayShareTime: 5,

    todayLuckDraw: 2

    ......

    }

    目前上面这个方案有尝试使用,但是对抽奖时MongoDB的CPU占用高没有显著解决,目前看作用不大,当然也有可能是没有将所有查询所需的数据放Redis的原因,因为那样子改动非常大,一时半会处理不了。

疑问

  1. 基于我们现在出现MongoDB CPU占用100%的情况,核心的问题在哪呢?
  2. 网上有非常多的抽奖服务,比如商城的秒杀,别人也会有有很多判断,查询,他们是怎么优化的呢?
  3. 我们使用队列一个一个去抽奖,这个思路是否正确,是否有其他更好,更优的做法去解决奖品超发的问题?
  4. 我们现在单次参与的人,预计也就几百个人抽奖数据库就扛不住了,没有所谓几万,几十万的流量。

以上是我们遇到的问题,哪位同学有遇到过,或者有对应的解决思路,或者方案,恳请指导一下,感谢!


回答:

我猜问题的本质是一次抽奖要走20~40次数据查询, 每次都算20ms好了, 走完一个人的流程也要600ms左右, 加上队列不能并发, 20人就要等10秒了.

简单的优化思路

  1. 并发的执行单次抽奖的"读"条件, 不要一次只走一个条件. 简单来说就是对所有条件发起query, 但是不要await. 然后在依次对promise await做判断. 这样改动小, 极大的加快一个人的抽奖流程.
  2. 把所有跟抽奖相关(参与抽奖人数增加, 数据量也不会变大的数据)的条件做内存缓存(不要Redis, 意义不大).可以一开始放在数据库中, 然后懒加载到内存. 这样改动也小.


回答:

亲,抽个奖而已,有必要查数据库吗?这个是根本问题哦。
比如1/100的抽奖,先抽一次,抽中了再做你现在的那一套,这都中不了就直接扔了。当然扔了之后也要扫一下尾,比如扣抽奖次数。

其实简单来说就是深入你的业务去思考,然后就是优先做排除,这样就能相对更少的进行数据库查询了。

而且,发超的那个问题在sql上是不是就能解决,update table set num = num - 1 where num > 0,然后看返回的受影响行数是0就说明已经发光了呗。


回答:

mongodb的问题不知道你使用缓存以后还做了啥。可以排查一下使用mongodb客户端的时候有没有什么问题。5000个连接数貌似有点多,减到100或者更低试试。

要避免超卖肯定要加锁的,但是加锁的过程要尽量短。比如你说的判断黑白名单、抽奖次数等等,完全不用放在队列里做,在外面先判断好了,通过的再进入抽奖队列。如果进入抽奖队列的请求最终能不能中奖也要通过中奖率之类的判断,那么也可以把这个判断挪出来,比如各个服务节点以权重随机算法判断请求是否可以中奖,而进入抽奖队列的请求则一定能中奖,这样也可以过滤掉大多数没有必要排队的请求。

如果还不够用那就考虑分库之类的,不过按你说的并发和硬件条件应该够了


回答:

看你的描述其实不一定是抽奖导致的问题, 考虑到你还无法得知本质的问题是什么, 建议采用二分法去找问题.

比如你把抽奖的代码注释掉,只保留前面的业务判断,去跑一次压测, 就可以知道是那一边的问题了.
总之先找到问题再说.

以上是 服务端如何提升抽奖的速度和性能 的全部内容, 来源链接: utcz.com/p/944238.html

回到顶部