TeamsApp升级之路 - 大用户并发抽奖的性能分析和数据库优化

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

我们这篇文章来分析一下之前遇到的大用户并发抽奖的性能问题看看到底问题出在哪里想想看后面如何解决。

做性能分析我们需要先设想一个大用户的场景然后基于这个场景来做评估评估数据量计算量传输量存储量等等。有了正确的评估后就可以对症下药设计相应的优化方案。

之前 LuckyDraw 遇到的问题就是当一个 Teams 的 team 里有 2000 用户同时点击参与抽奖luckydraw 基本上就卡死了之前在 azure app insights 里看过错误日志基本上都是 azure storage table 的错误错误是指 storage table 返回无法处理请求。

所以这次分析我假设的场景是 3000 个用户一个 Teams 的一个 team 里或者在一个 group chat 里然后有人发了一个抽奖大家看到后纷纷点击参与抽奖所有的用户在15秒内都完成了点击参与抽奖。所以在这个场景下我们至少要能 handle 每秒处理 200 个参与抽奖的请求。

有了这个假设后我们再来看一下单个参与抽奖的操作有哪些我过了一遍代码有这些步骤

从 storage table 读取抽奖的完整信息包含目前所有的已经参与的人
判断抽奖是否已经结束如果已经开奖了那就结束这个请求
判断当前这个用户是否已经参与了本次抽奖如果是那就结束这个请求
增加当前用户到抽奖人列表然后把整个抽奖信息包含目前所有参与人保存回 storage table这里一定要包含所有参与人的原因是 storage table的设计所有信息在一条记录里所以无法分开保存
根据当前的抽奖信息生成 Teams 前端展示的 adaptive card
调用 Teams 的 bot api更新本次抽奖的 adaptive card

根据当前代码我们不难看出步骤 #2, #3, #5 是纯在内存操作#1需要从storage table 读取数据#4需要写入数据到 storage table#6需要发送http请求。所以我们要重点看 #1#4 和 #6。

结合我们假设的高并发场景加上上面的步骤我们不难发现

在 #1 中当目前参与人已经有2000人后每一个点击参与的动作我们都需要从 storage table 里读取出前 2000 个人的数据一个参与人包含了 aad object id名字参与时间所以一个人的数据量大约在 100 characters2000 人就是 100K 的数据加上一些额外的 payloadencoding之类的一个storage table 的请求返回数据量应该不会低于 120K。

在 #4 中情况和 #1 类似当目前参与人已经有2000人后每一个点击参与的动作我们都需要把 2000 多个参与人的数据传给 storage table 来保存因为是写操作对于 storage table来说肯定比 #1 要慢不少。

在 #6 中每个参与抽奖的操作要促发一次发给 teams server 的http请求每个这样的请求需要 0.5-2 秒钟才能完成这个要看是发送给那个区域的 teams service。我们假设是一秒钟再结合我们假设的场景是每秒钟有 200 个参与抽奖的请求也就是说我们的 luckydraw bot service有 200 个 http 请求在路上。这个对于单个 instance 的 app service 来说挺有压力的如果当时另一个企业还有抽奖这个数据会更高而且通过实际测试我还发现 teams 本身对于这个频率的请求会促发 throttle。

有了以上的分析针对 #1, 和 #4 我们可以有一个初步的想法开分抽奖信息的主体和参与人信息。之前 storage table 有很多的设计上的限制如果分开保存性能会很差但是 SQL DB没有这个问题这样的话我们就可以不必要把所有参与人都读出来或者同时都写入。这个可以大大提高性能

所以我们可以改成

从 SQL DB 读取抽奖的基本信息不包含参与有人。一次简单的数据库读操作
判断抽奖是否已经结束如果已经开奖了那就结束这个请求
判断当前这个用户是否已经参与了本次抽奖如果是那就结束这个请求。增加一个简单的数据库读操作判断当前用户是否已经在数据库里。
增加当前用户到抽奖人列表。一次简单的写操作只增加当前一个用户

可以看到这个优化改成了数据库的两次读和一次写三个操作数据量都很少。

实际上还可以进一步优化把 #3 和 #4 合并成一次数据库的的写操作插入当前的用户如果用户已经存在让它报错 PK conflict。所以只要把数据库表的设计做的合理就可以变成两次数据库操作

增加当前用户到抽奖人列表。一次简单的写操作只增加当前一个用户
如果第三步报错说明当前这个用户是否已经参与了本次抽奖结束这个请求

可以看到我们已经把 1 到 4 步优化成了一次读一次写还有优化的空间吗

我考虑了很久觉得在大用户量高并发的情况下用户点击参与抽奖之后可能无法立刻知道自己已经参与成功了因为 #6 刷新 adaptive card 会有一点延迟并且当有很多用户一起点击的时候我们目前显示最后2-3个用户的名字用户要到抽奖详情里才能看到自己的名字。

所以在等待过程中用户大概率会反复点击 “参与抽奖” 按钮所以上面的 #4在很多情况下会是一个普遍的情况在这种情况下#1 的读实际上就可以省掉。我们来换一个顺序看看

增加当前用户到抽奖人列表。一次简单的写操作只增加当前一个用户
如果第三步报错说明当前这个用户是否已经参与了本次抽奖结束这个请求
从 SQL DB 读取抽奖的基本信息不包含参与有人。一次简单的数据库读操作
判断抽奖是否已经结束如果已经开奖了那就结束这个请求

如果按照上面的顺序在普通情况下是数据库的一次读一次写操作但是当用户反复点击“参与按钮”的时候就变成了一次写失败的操作省掉了一次读操作。

虽然看上去省了没多少但是在高并发情况下很多用户有反复点击的习惯这就会能省下很多数据库操作在高并发情况下这种节省非常关键。

我们会在下一篇文章里介绍如何优化 #6 步看看能达到什么效果。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: 数据库