一、目标
- 调研 discord 的整体架构,发掘可为所用的设计思想
二、调研背景
- Discord作为目前比较火的一个在线聊天和语音通信平台且具有丰富的功能。另外其 "超级"群 概念号称可支持百万级群聊 以及 永久保留用户聊天记录。探究其相关技术架构与技术实现
三、产品介绍
- 目前广泛使用的在线聊天和语音通信平台。最初于2015年发布,旨在为游戏社区提供一个交流和协作的平台,但现在已经扩展到各种不同的领域。
3.1、主要功能
- 文字聊天:用户可以在频道中发送消息,与其他成员进行实时交流。这些消息可以包含文字、表情符号、图片、链接等。
- 语音通话:用户可以通过Discord内置的语音通话功能与其他成员进行语音交流。这对于组织游戏团队、进行远程会议或与朋友进行语音聊天非常有用。
- 视频通话:除了语音通话,Discord还提供了视频通话功能,使用户可以进行面对面的视频交流。
- 服务器和频道:用户可以创建自己的服务器,并在服务器内创建不同的频道,以便根据主题或目的进行组织和交流。
- 社交功能:Discord具有添加好友、私信、创建群组等社交功能,让用户可以与其他用户建立联系和交流。
- 权限和角色管理:服务器所有者可以设置不同的权限和角色,以控制成员对频道和服务器的访问和操作权限。
- 集成和插件:Discord可以与其他应用程序和服务进行集成,例如Twitch、YouTube、Spotify等,以便在聊天中共享内容或接收通知。
- Bots(机器人):用户可以添加各种机器人来执行各种任务,例如管理服务器、播放音乐、提供实用工具等。
3.2、发展历程
3.3、数据情况
3.3.1、用户及群数据
- 总用户数未知,预计1.5 亿月活跃用户,平台上有 1900 万个服务器,涵盖游戏、投资、政治、动漫等领域。2020 年,Discord 每周有 670 万服务器处于活跃状态,基本上每周都有某个给定话题的对话讨论。2021 年,Discord 每周活跃服务器数据增长到了 1900 万。
3.3.2、活跃数据
- Discord 平台上单个日活跃用户(DAU)与平台的平均互动时长,是游戏直播平台 Twitch 的两倍,同时还是 Facebook Gaming、TikTok、Reddit 以及 Snap 等头部社交平台的两倍以上。
3.3.3、收入情况
- 较少的商业化的动作,Discord 的每用户平均收入 (ARPU) 仅为 1.30 美元,在公共社交媒体公司中排名非常靠后。
四、调研方向
- 整体技术架构
- 外部集成&开放能力
- 技术栈应用情况
- 核心业务模块设计
- 核心基础组件设计与基础建设
五、调研内容来源
- 因discord是一个商业产品且并未开源,国内相关资料也较少。所以只能通过阅读官方博客与开发者平台进行分析、推导与猜想
- 官方博客:https://discord.com/blog
- 开发者平台:https://discord.com/developers/docs/reference
六、调研内容
6.1、 整体架构
discord开发团队核心理念
- 拥抱开源的同时,对开源中间件做自己的特定优化与增强
- 随时保持基建可替换
- 尽可能降低架构与业务开发复杂度
6.2、 外部集成&开放能力
6.2.1、open api
- 开放接口,其中主要包括 公会相关操作、表情符号、webhook等开放能力
6.2.1.1、公会相关操作
- 在Discord 中的公会(一般也叫"服务器")代表用户和频道的集合
- 一个公会下可以有多个频道,每个频道消息隔离,成员共享
- 提供了公会增删改查、语音状态等管理接口
6.2.1.2、表情符号
- 支持开发者可自定义表情包与符号。并提供其管理能力
6.2.1.3、webhook
- Webhooks 是一种不需要用户主动发起或者机器人交互在 Discord 中向频道发布消息的方式
- 主要用于系统消息的主动发送
6.2.2、gateway
- 允许开发者通过WebSocket对关键事件进行订阅与监听,最终推送给开发者
- 可以接收有关服务器/公会中发生的操作事件,例如更新频道或创建角色。在某些情况下,应用还会使用网关连接来更新或请求资源,例如更新语音状态时。
6.2.3、机器人
- Discord 提供了机器人用户能力,这是一种自动化的用户类型(每个类型的机器人背后都有一个运行程序,可通过sdk方式进行集成)
- 类似于微信公众号的机器人功能
- 用户可通过斜杠"/"命令与机器人进行交互。
6.2.4、GameSDK
- 通过提供GameSDK让游戏开发者进行集成,来帮助游戏开发与discord进行交互
- 如:游戏状态管理、网络组件、用户关系、邀请等相关交互功能
6.2.5、RPC(内测中)
- Discord 客户端会提供一个在本地主机上运行的 RPC 服务器,允许开发者在客户端来控制本地 Discord
- 通过本地调用无服务器方式让游戏开发的客户端可直接与本地discord客户端进行交互
- 如:rtc控制、公会/频道 管理等
6.3、 技术栈说明
6.3.1、客户端
- 提供移动端与桌面端两种 客户端形式供用户使用
- 移动端使用react native进行跨端业务实现
- 桌面端使用electron进行的web套壳实现
- 底层组件使用rust进行开发
6.3.2、服务端
6.3.2.1、语言相关
- 多种主流语言进行混合开发
- 短链api使用python进行快速迭代
- 长链gateway使用erlang的变种语言Elixir进行开发
- 核心业务早期使用Elixir与golang两种语言进行开发
- 其中golang主要实现rtc与音频相关业务服务
- Elixir主要实现im、工会等核心业务相关服务
- 其中一些性能优化,组件迭代使用rust增强实现,Elixir通过NIF方式进行调用
- 经过长久迭代,discord开发团队认为golang gc问题是个诟病,正在逐步进行迁移至rust语言
- 底层组件服务使用rust实现,以达到高性能服务提供
- 内部服务使用grpc进行通信
6.3.2.2、应用系统架构
- 整体通过响应式架构进行设计开发
- 响应式架构特点:
- "流"式编程 (连续、异步、可观察)
- 有效地处理并发请求,提高系统的吞吐量
- 消息/事件驱动,有助于系统的解耦,提高系统的扩展性和弹性。
6.3.2.3、基建相关
- ETCD:
- grpc注册中心与服务发现
- 组件集群注册管理(如消息检索es管理,后面会详细说)
- redis: es负载信息存储与缓存
- 数据存储
- Mongo -> Cassandra -> ScyllaDB ( 存在演进过程,后面会详细说*)*
6.4、核心业务模块
6.4.1、消息模块
- discord消息相关有两个比较关键的点,一个是支持非常灵活的消息样式,另外一个就是其"超级"群消息是如何扇出的
6.4.1.1、 消息组件(协议)
关键协议摘取
|-------------------|----------|---------|--------------------------------------------------------------------------------------------------|
| 字段名 | 类型 | 作用 | 描述 |
| mention_everyone | bool | 是否提及所有人 | at所有人 |
| mentions | user数组 | 提及到的人 | at的所有人 |
| attachments | 消息附件实体数组 | 附件 | 主要描述消息内携带的文件内容 |
| pinned | bool | 是否置顶 | 消息置顶 |
| message_reference | 消息实体 | 引用的消息 | 被引用的历史消息 |
| embeds | 消息嵌入实体数组 | 嵌入信息 | 主要描述消息内嵌入的图片、视频等内容 |
| type | integer | 消息类型 | 消息的类型,详情可看下文。 https://discord.com/developers/docs/resources/channel#message-object-message-types |
| content | string | 消息内容 | 消息内容 |
| components | 消息组件实体数组 | 组件 | 消息内嵌入的既定组件。下面会想说 |
- 其中components代表的是消息的定义扩展(消息组件),如消息卡片、选择按钮等都是基于此实现。其余字段均为关键业务字段。
- 此处只摘取了关键字段,次要未进行摘取。如有兴趣可祥看:https://discord.com/developers/docs/resources/channel#message-object
消息组件
- 消息组件主要由组件类型与嵌入组件两个属性,具体的组件内容不同组件类型均不一样。
- 组件可以嵌入组件。如多选下拉菜单
eg:
{
"content": "This is a message with components",
"components": [
{
"type": 1,
"components": []
}
]
}
目前支持的组件类型
- 下面会对主要组件类型进行单一分析,因为其components字段是个数组所以实际应用场景可进行组装
- 下文会对主要类型进行分析,其他组件可祥见:https://discord.com/developers/docs/interactions/message-components
组件交互
- 组件分为交互与非交互组件
- 其中交互组件会与开发者服务进行交互,交互形式有两种方式可选
- 上面提到的gateway进行监听与回复
- 上面提到的open api中的webhook进行接收与返回
Action Row (嵌入组件)
- 是其他类型组件的非action row组件的容器。
- 每条消息最多可以有 5 个Action Row
- 一个Action Row不能包含另一个Action Row
Button (按钮组件)
组件协议
说明
按钮有多种样式来传达不同类型的操作。这些样式还定义哪些字段对按钮有效。
- 非链接按钮必须 有
custom_id
,并且不能有url
- 链接按钮必须 有
url
,并且不能有custom_id
- 链接按钮在点击时不会向开发者的服务发起交互,仅会做链接跳转
按钮可选样式
eg
{
"content": "This is a message with components",
"components": [
{
"type": 1,
"components": [
{
"type": 2,
"label": "Click me!",
"style": 1,
"custom_id": "click_one"
}
]
}
]
}
Select Menus(多选下拉菜单)
组件协议
说明
- 比较关键的就是options该属性是多选下拉菜单的关键参数,该参数定义了每个选项的具体内容
option结构
eg:
// This is a message
{
"content": "Mason is looking for new arena partners. What classes do you play?",
"components": [
{
"type": 1,
"components": [
{
"type": 3,
"custom_id": "class_select_1",
"options":[
{
"label": "Rogue",
"value": "rogue",
"description": "Sneak n stab",
"emoji": {
"name": "rogue",
"id": "625891304148303894"
}
},
{
"label": "Mage",
"value": "mage",
"description": "Turn 'em into a sheep",
"emoji": {
"name": "mage",
"id": "625891304081063986"
}
},
{
"label": "Priest",
"value": "priest",
"description": "You get heals when I'm done doing damage",
"emoji": {
"name": "priest",
"id": "625891303795982337"
}
}
],
"placeholder": "Choose a class",
"min_values": 1,
"max_values": 3
}
]
}
]
}
6.4.1.2、 消息扇出流程
整体架构
说明
- 一条消息从发出到扇出的流程是:api服务将消息发送到工会服务(有状态节点,与公会进行绑定),工会服务将消息均匀发到中继节点。中继节点将消息发送给session网关。最终推送到用户手机
- 其中公会服务负责消息权限等基础功能校验(也就是校验该消息是否允许被发出)
- 公会服务校验完毕,将消息均匀分布到中继节点,由特定中继节点来处理具体的扇出流程
- 其中扇出目标用户仅为在线用户,所以在用户登录后,discord相关服务会更新相关用户所有服务器的在线列表,此时中继服务仅需对在线成员进行扇出即可
- 中继节点获取到在线成员后,会校验该用户是否有权限进行接收,最终对可接受用户将消息发送给session网关。最终推送到用户手机
ETS(项式存储)
- 因为部分公会在线成员可能较多,远程获取也会有较大的损耗,discord在初期会在中继服务中缓存每个相关公会的成员信息,但这是恐怖的,在发展后期,达到了百万计的成员。机器内存成本极高,discord开发团队对该部分进行了优化
- 其中主要是将中继服务内缓存的公会成员信息优化打到erlang 的ETS中进行存储缓存(Erlang虚拟机级别共享内存)
- 并启动worker进行对ETS 中的数据进行统一管理(inserts, updates, deletes)
6.4.2、推送模块(genstage)
背景
- discord为应对突发通知过载问题,设计了genstage模块用于消息推送
- 当时主要瓶颈在于向谷歌的 Firebase 云消息服务发送推送通知。
- Firebase 要求每个 XMPP 连接每次待处理的请求不得超过 100 个。如果有 100 个请求正在处理中,就必须等 Firebase 确认一个请求后再发送另一个请求。
- 由于一次只能有 100 个请求待处理,因此需要设计新系统,使 XMPP 连接在突发情况下不会过载。
整体架构
说明
- 将系统分为两个 GenStage 阶段。一个source,一个sink
- 阶段1 - source(推送收集器)
- 是收集推送请求的生产者。每台机器都会有一个推送收集器 Erlang 进程。
- 阶段2 - sink(推送者)
- 是一个消费者,它从推送收集器获取推送请求,并将请求推送到 Firebase。它一次只需要 100 个请求,以确保不会超过 Firebase 的待处理请求限制。每台机器上有多个 Erlang 进程。
- 阶段1 - source(推送收集器)
- GenStage 还有有两个关键功能可在突发情况下提供帮助:背压与甩负荷。
- 背压:
- source会询问sink所能处理的最大请求数。这就确保了sink待处理的推送请求数量的上限。当 Firebase 确认请求时,sink会向source提出更多请求(sink知道 Firebase XMPP 连接所能处理的确切数量)。
- 除非sink提出请求,否则source绝不会向sink发送请求。这就保证了sink永远都是无压力的
- 甩负荷:
- 由于 sink会对source产生反向压力,source就会有一个潜在的瓶颈。超大规模的突发可能会使source超载。
- 所以source 还会有一个内置功能可以处理这个问题:缓冲事件。
- 在source中,可以指定缓冲多少个推送请求。一般情况下,缓冲区是空的,但在突发通知的情况下,缓冲区就会派上用场。用于缓冲sink无法处理的事件
- 如果系统中的消息太多,也就是缓冲区达到瓶颈,source就会停止接收推送请求**(丢弃或降级)**
- 思考:此处可以基于业务策略进行降级,如部分不重要推送进行直接丢弃,重要推送进行降级缓冲
- 背压:
效果指标
Sink推送数量/分钟
Source 缓冲事件数量/分钟
6.5、核心基础组件与基础建设
6.5.1、存储演进过程与 服务架构流程
6.5.1.1、 存储db演进过程
早期:mongo单分片
- 单副本集的 MongoDB,没有使用 MongoDB 的分片,他们给出的理由是当时 MongoDB 分片很难用,而且不够稳定(这里就不去深究了)。消息数到达一亿条时,RAM 里已经存不下这么多数据和索引,MongoDB 的延时开始变得不可控。
中期:从 MongoDB 到 Cassandra
业务背景
- 2017年,消息数过亿,mongo延时变得不可控。
- 场景读取极其随机,读写比例整体大约为 50/50。不同业务场景群读取比例不同。但又不想对每个场景做独立解决方案,所以他们决定基于现有诉求,选择新的存储进行数据迁移
诉求
- 线性可扩展性: 不希望以后重新考虑解决方案或手动重新分拣数据。
- 自动故障转移: 尽可能的进行自我修复
- 维护成本低: 一旦设置好,它就能正常工作。只需在数据增长时添加更多节点即可。
- 经证明有效: 喜欢尝试新技术,但不能太新。
- 可预测的性能: 延时达到一定水位就会发出警报。并且不希望缓存消息。
- 开源: 相信自己的命运自己掌握,不想依赖第三方公司。
实施
- 基于以上诉求他们认为 Cassandra 是当时唯一能满足他们要求的数据库(后面也打脸了)
Cassandra特性
- Ap database
- 是一个KKV 存储器。主键由两个 K 组成。第一个 K 是分区键,用于确定数据所在的节点以及在磁盘上的位置。分区中包含多条记录,分区内的记录由第二个 K(即聚类键)标识。聚类键既是分区内的主键,也是行的排序方式。你可以把分区看成一个有序的字典。这些属性结合在一起,可以实现非常强大的数据建模。
- 单分区大小不建议超过 100MB。Cassandra 宣称它可以支持 2GB 分区!但虽然可以支持,但并不意味着应该支持
数据建模
- 关键数据结构
CREATE TABLE messages (
channel_id bigint,
bucket bigint,
message_id bigint,
author_id bigint,
content text,
PRIMARY KEY ((channel_id, bucket), message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);
- 基于Cassandra的特性他们将主键设计成((channel_id, bucket), message_id)
- channel_id: 服务器频道id
- bucket: 基于时间的数据分桶(基于他们的统计,大约10天的聊天消息约100MB)
- message_id: 基于雪花算法的消息id
- 这意味着在加载频道时,可以告诉 Cassandra 准确扫描消息的范围
- 消息发布时间与bucket可以通过message_id中提取【因为message_id是基于雪花算法的】
迁移期间与使用过程中遇到的问题
- 第一个遇到的就是100MB问题
- discord最初使用Cassandra存储并没有bucket概念。但在运行与迁移过程中Cassandra发出了100MB警告
- discord开发者通过分析历史消息数据分布情况,定下来100MB可存储其10天消息数据,故将数据进行分桶
- 写入顺序问题,Cassandra的数据写入处理逻辑是先读后写【本次写入会依赖上次写入结果】
- 在一个用户编辑一条消息的同时,另一个用户删除了同一条消息,由于 Cassandra 写入的所有内容都是向上插入的,因此最终会发现一条记录中除了主键和文本外,缺少其他所有数据
- 如: 在t1时间,进行了消息删除。t2时间做了消息修改(消息体),此次修改会基于上次删除结果,所以其他字段都为空了
- 他们采用的方案是启动一个反熵进程对脏数据进行清理与删除(可能是为了无锁化)
- 在一个用户编辑一条消息的同时,另一个用户删除了同一条消息,由于 Cassandra 写入的所有内容都是向上插入的,因此最终会发现一条记录中除了主键和文本外,缺少其他所有数据
迁移后指标情况
现在:从 Cassandra 到 ScyllaDB
业务背景
- 2022年,随着业务场景和消息规模的增长, Cassandra 有 177 个节点,拥有数万亿条消息 ,Cassandra 也出现了严重的性能问题
- 热分区
- 压缩问题导致请求级联延迟
- S-T-W (java)
诉求
- 可解决"热分区" 问题
- 数据压缩不会出现级联延迟
- 避免S-T-W
ScyllaDB特性
- 一般来说属于 AP database,更加侧重于可用性和分区容错性,但是 ScyllaDB 的一致性级别是可以调整的。
- 完全兼容 Cassandra,号称是Cassandra CPP实现的替代品
- CPP编写,无GC,所以也就不会出现S-T-W
- 相比Cassandra有更好的性能、更快的修复、通过每核分片架构实现更强的工作负载隔离。
- -- 避免出现级联延迟
ScyllaDB如何解决的压缩问题?
- Compaction Strategy:ScyllaDB 使用不同的算法(称为策略)来确定何时以及如何最好地运行压缩。该策略决定了写入、读取和空间放大之间的权衡。ScyllaDB Enterprise 甚至支持一种称为增量压缩策略的独特方法,该方法可以显著节省磁盘开销。
- 压缩和解压缩并行化:ScyllaDB 使用多线程并行化压缩和解压缩操作,以减少压缩和解压缩对整体性能的影响。这样可以更好地利用多核处理器的能力,并减少由于压缩和解压缩而引起的延迟。
- 压缩字典缓存:ScyllaDB 使用压缩字典缓存来提高压缩和解压缩的性能。字典缓存存储了一些常见的字符串和它们的压缩形式,这样可以减少压缩和解压缩时需要传输的数据量,从而降低延迟。
- 硬件加速:ScyllaDB 利用现代硬件特性,如 Intel 的 CPU 压缩指令集(Intel ISA-L),来加速压缩和解压缩操作。这些硬件加速技术可以显著提高压缩和解压缩的性能,从而减少级联延迟。
缺点
- 当以与表排序相反的顺序扫描数据库时,有反向查询性能不足的问题
- discord团队识别该缺点可接受
- 未解决"热分区" 问题
- 通过建立"存储服务"进行解决
迁移方案
- 因为ScyllaDB完全兼容 Cassandra,所以可以直接进行数据迁移
- 但因为其未解决"热分区" 问题。所以discord开发者基于ScyllaDB做了业务增强实现来解决该问题
- -- "存储服务"
迁移效果
- 将运行 177 个 Cassandra 节点减少到仅运行 72 个 ScyllaDB 节点。每个 ScyllaDB 节点拥有 9TB 磁盘空间,高于每个 Cassandra 节点平均 4TB 的存储空间。1774-729=60T,这么看的话他们的存储空间也节省了一些。在 Cassandra 上获取历史消息的 p99 为 40-125 毫秒,而 ScyllaDB 的延迟为 15 毫秒,消息插入性能从 Cassandra 上的 5-70 毫秒 p99 到 ScyllaDB 上稳定的 5 毫秒 p99。
- 当然此处效果与指标存储服务也功不可没。下面会说存储服务的整体设计
6.5.1.2、 存储服务架构及流程
背景
- discord开发团队为了解决数据查询"热分区"问题,Discord 采用的方案是:在 ScyllaDB 和业务服务之间加了一个中介服务(Rust 语言编写),它不包含任何业务逻辑,主要功能就是合并请求。
整体架构
架构说明
- 存储服务主要做了两部分功能。合并请求与收敛请求
- 是一个有状态 无数据 的中介服务
模块设计
合并请求
- 如果多个用户同时请求数据库的同一行,那么只会查询数据库一次。
- 第一个发出请求的用户会在该服务中启动工作任务, 后续请求将检查该任务是否存在并订阅它, 该工作任务将查询数据库并将该行返回给所有订阅者。
收敛请求
- 同时根据一致性 hash 将同类查询请求,比如同一个频道的请求,进一步收敛到中介服务,这样可以让请求合并的效果更好。
6.5.2、 消息检索架构流程
背景
- 因discord业务发展迅速,在2017年要推出消息检索功能。其技术诉求如下
- 经济高效:Discord 的核心用户体验是文字和语音聊天。搜索是一项附属功能,搜索的成本不应高于信息的实际存储成本。
- 自我修复:需要能够承受故障,只需极少的人为干预,甚至无需人为干预。
- 可线性扩展:就像存储信息一样,只需在数据增长时添加更多节点即可
- 避免繁琐的大型集群: 在集群中断的情况下,只有受影响集群中包含的 消息无法用于搜索。或者一旦整个集群的数据无法恢复,可以将其丢弃(系统可以重新索引 Discord 服务器数据)。
整体架构
- 基于以上背景,discord团队最终选用es来支持消息检索,但摒弃了es的分片能力(自行实现消息分区),因为使用es的分片能力以当时discord的数据量意味着要建立大型集群,这会带来较高的维护成本与恢复成本
说明
- 应用层进行分区,分区维度为频道id, es采用多集群方式,不同频道id绑定不同es集群 (小集群)
- 这样特定频道数据就会被分到单一的es集群
- es中采用无分片单副本的索引模式(因使用了应用分区,所以不需要es的分区功能了,副本是用于集群异常数据恢复使用)
- 启动his index worker 来用于历史消息的索引与重新索引(如es集群产生故障,旧集群被摘除使用新的集群需要重新索引消息)
- 业务 服务只负责向queue里发送消息信息,由index worker进行拉取并索引进es中
- 能力解偶、提升主流程性能、消峰
- 因检索场景大多数都是对历史消息进行检索,所以queue延迟与es的近实时特性是可接受的
- es集群注册到etcd中来实现自动发现,然后会在redis中存储每个集群的负载情况,检索sdk会使用负载最低的进群进行实时绑定
- 绑定数据使用业务db进行关系存储并使用redis进行缓存
- 因为其使用的业务db无论是cassandra还是scylladb都存在高成本读取,所以此处做了缓存 (与其前面说的不想使用缓存有出入)
- 下次产生新的消息时,直接使用既定的绑定关系进行存储即可
七、结论
通过上述对discord的调研分析, 获得的一些启发:
功能上
discord 对开发者提供了丰富的集成方式:机器人、api、网关、client RPC(亮点)、GameSDK。
设计上
- discord 使用了不同的技术栈来解决技术上的诉求,思路非常开阔
- 使用erlang开发,因其天生的actor模型,故可以完美支持其响应式架构达到高吞吐目的
- 但erlang对于某些特定场景存在性能问题,又使用rust来解决。在不变业务服务的情况通过NIF 的形式进行调用(业务无感)
- api入口使用python可以达到快速迭代,因为内部服务使用golang、rust、erlang又不会产生较大的性能问题
- 单一且高负载业务,可通过进程分离的形式进行优化。各司其职,压力分摊
- 消息扇出流程:发送与扇出分离
- 推送模块: 接收与推送分离
- 用户上线时把用户加入公会和在线状态进行绑定,维护了公会内的在线用户,消息扇出时极大的减少了消息处理量
- 拥抱开源,在开源基础上针对业务的诉求进行优化或增强
- 存储服务解决的热分区问题。不仅大大缓解了db压力,还降低了架构与业务开发复杂度 (无缓存设计)
- 消息检索业务,discord为了避免繁琐的大型集群实现了自己的分片/分区模式
- 随时保持基建可替换,降低随着业务迭代与数据发展带来的基建升级/演进 成本(架构一定是随时演进的 )
- 基础架构与组件可作为未来参考
- 响应式架构来提升单机吞吐量
- db压力过载可通过合并请求&收敛请求的方式进行优化,来缓解db压力
- 不一定非要用于存储,比如我们客户端的一些非重要请求也可以作此优化来降低带宽使用与缓解服务器压力