从Discord的做法中学习 — 使用Golang进行请求合并
正如你可能之前看到的,Discord去年发布了一篇有价值的文章,讨论了他们成功存储了数万亿条消息。虽然有很多关于这篇文章的YouTube视频和文章,但我认为这篇文章中一个名为“数据服务为数据服务”的部分没有得到足够的关注。在这篇文章中,我们将讨论Discord对数据服务的方法,并探讨如何利用Golang的并发特性来减少特定情况下的数据库负载。
数据服务拯救热分区
如你所知,消息和频道是Discord中最常用的组件。让我们想象一个场景:一个拥有50万成员的频道的管理员提到@everyone。会发生什么?成千上万个同时的请求直接指向那个数据库分区,所有请求的目标都是检索相同的消息。这种模式重复发生,直到该分区无法回应其他请求。

Discord引入了一个位于Python API和数据库集群之间的中间服务 — 他们称之为数据服务。这个服务大致包含每个查询一个gRPC端点,没有任何业务逻辑。对Discord来说,这个服务的重要特性就是请求合并。
请求合并
正如我们之前讨论过的,每当在一个庞大的频道中有提及时,就会有大量类似的请求直接指向数据库分区。通过合并这些请求,如果多个用户请求相同的数据库行,我们可以将这些请求合并成一个选择查询,并执行该查询。

通过使用数据服务而不是直接连接到数据库,我们可以实现许多令人兴奋的功能,比如批量查询,这些功能可以显著减少数据库开销,并改善查询的平均值,特别是第99百分位数。
使用Golang实现简单的请求合并
与许多其他公司一样,Discord使用Python作为其主要的后端语言。无论是微服务还是单体架构,后端服务通常直接连接到数据源进行查询。虽然Python确实是一种多功能语言,但在并发性方面存在一些不足。使用Python实现并发和高吞吐量的服务可能有些挑战,而性能与用C++、Rust和Golang等编译语言编写的类似服务相比,往往会较低。
在进行任何操作之前,让我们模拟一下提到的情况。假设服务总共收到了5,000个请求,其中并发数为1,000。
- 总请求数: 5,000
- 并发数: 1,000
- 需要检索的唯一消息数: 100
type Message struct {gorm.ModelText stringUser string // some random properties that a message row may have
}func generateRandomData(db *gorm.DB) {for i := 0; i < 100; i++ {msg := &messages.Message{Text: fmt.Sprintf("Message #%d", i)}db.Save(msg)}
}
我使用Gorm构建了一个简单的数据库模型来表示**Message(消息)**表,然后向表中填充了100条虚拟消息。
e := echo.New()
e.GET("/randomMessage", func(c echo.Context) error {randomMessageID := rand.Intn(100)var msg messages.Messageif err := db.Where("id=?", randomMessageID).First(&msg).Error; err != nil {return err}return c.JSON(200, msg)
})
e.Logger.Fatal(e.Start(":1323"))
我创建了一个简单的端点来模拟对0到100之间的随机ID进行SELECT查询。现在我们可以对这个端点进行基准测试,模拟在这种情况下会发生什么。


- 平均每秒请求数 (RPS): 300
- 平均响应时间: 3.2秒
- 50% 响应时间: 546毫秒
- 99% 响应时间: 14.7秒
如果我们有10秒的超时策略,大约有2%的请求将收不到响应。现在让我们改变代码。Golang有一个名为“single flight”的内置包。这个包提供了重复函数调用抑制机制。一般来说,你给它一个键和一个函数,而不是多次运行该函数,SingleFlight会暂时保持其他调用,直到第一次调用完成其请求并以相同的结果作出响应。
var g = singleflight.Group{}
e.GET("/randomMessage", func(c echo.Context) error {randomMessageID := rand.Intn(100)msg, err, _ := g.Do(fmt.Sprint(randomMessageID), func() (interface{}, error) {var msg messages.Messageif err := db.Where("id=?", randomMessageID).First(&msg).Error; err != nil {return nil, err}return &msg, nil})if err != nil {return err}return c.JSON(200, msg)
})
func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool)
Do 执行并返回给定函数的结果,确保同一时间针对给定键只有一个执行过程。如果出现重复,重复的调用者会等待原始调用完成并接收相同的结果。返回值 shared 表示是否将 v 给了多个调用者。
现在让我们重新运行模拟并比较结果。


- 平均每秒请求数 (RPS): 2309
- 平均响应时间: 433毫秒
- 50% 响应时间: 389毫秒
- 99% 响应时间: 777毫秒
正如你所看到的,仅使用了一个简单的技术就将第99百分位数减少了14秒,新方法支持的每秒请求次数提高了7.6倍。
结论
从那时起我们就注意到,通过优化数据库查询,可以大大提高应用程序的整体性能。虽然我们讨论的方法是情景性的,但Discord已经使用了一年多,对他们有很大帮助。
你应该知道,如果你使用数据服务,你将面临其他的复杂情况。例如,你可能会有多个数据服务实例,而你的Python API必须有一种机制将类似的请求发送到同一个实例。
相关文章:
从Discord的做法中学习 — 使用Golang进行请求合并
正如你可能之前看到的,Discord去年发布了一篇有价值的文章,讨论了他们成功存储了数万亿条消息。虽然有很多关于这篇文章的YouTube视频和文章,但我认为这篇文章中一个名为“数据服务为数据服务”的部分没有得到足够的关注。在这篇文章中&#…...
【教3妹学编程-算法题】统计和小于目标的下标对数目
2哥 : 3妹,OpenAI的宫斗剧迎来了大结局!OpenAI宣布阿尔特曼复职CEO,董事会重组 3妹:啊?到底谁才是幕后操纵者啊,有咩有揪出来 2哥 : 也不是很清楚,据说在被开除的几周前,前CEO曾谴责…...
OSG粒子系统与阴影-雾效模拟(1)
虚拟现实中有很多效果,如雨效、雪效、雾效等,这些都可以通过粒子系统来实现。一个真实的粒子系统的模式能使三维场景达到更好的效果。 本章对OSG粒子系统的使用以及生成自定义粒子系统的方法进行了详细介绍最后还附带说明了阴影的使用方法。在实时的场景…...
Windows power shell for循环
有时候需要重复执行某个shell命令 for($i1;$i -lt 10;$i$i1){echo $i}如果是cmd for /l %i in (1,1,5) do echo %i...
GIT实践与常用命令---回退
实践场景 场景1 回退提交 在日常工作中,我们可能会和多个同事在同一个分支进行开发,有时候我们可能会出现一些错误提交,这些错误提交如果想撤销,可以有两种解决办法:回退( reset )、反做(revert) keywords:reset、rev…...
Python-Django的“日志功能-日志模块(logging模块)-日志输出”的功能详解
01-综述 可以使用Python内置的logging模块来实现Django项目的日志记录。 所以与其说这篇文章在讲Django的“日志功能-日志模块-日志输出”,不如说是在讲Pthon的“日志功能-日志模块-日志输出”,即Python的logging模块。 下面用一个实例来进行讲解。 …...
C现代方法(第23章)笔记——库对数值和字符数据的支持
文章目录 第23章 库对数值和字符数据的支持23.1 <float.h>: 浮点类型的特性23.2 <limits.h>: 整数类型的大小23.3 <math.h>: 数学计算(C89)23.3.1 错误23.3.2 三角函数23.3.3 双曲函数23.3.4 指数函数和对数函数23.3.5 幂函数23.3.6 就近舍入、绝对值函数和取…...
NSGA-II求解微电网多目标优化调度(MATLAB)
一、NSGA-II简介 NSGA-Ⅱ算法是Kalyanmoy Deb等人于 2002年在 NSGA 的基础上提出的,它比 NSGA算法更加优越:它采用了快速非支配排序算法,计算复杂度比 NSGA 大大的降低;采用了拥挤度和拥挤度比较算子,代替了需要指定的…...
7-9 jmu-python-班级人员信息统计
7-9 jmu-python-班级人员信息统计 分数 15 作者 郑如滨 单位 集美大学 输入a,b班的名单,并进行如下统计。 输入格式: 第1行::a班名单,一串字符串,每个字符代表一个学生,无空格,可能有重复字符。 第2行:&am…...
Doris分区与分桶(八)
接上篇----------Doris 建表示例 Doris 支持两层的数据划分。第一层是 Partition,支持 Range 和 List 的划分方式。第二层是 Bucket(Tablet),仅支持 Hash 的划分方式。 也可以仅使用一层分区。使用一层分区时,只支持…...
mac VScode 添加PHP debug
在VScode里面添加PHP Debug 插件,根据debug描述内容操作 1: 随意在index里面写个方法,然后用浏览器访问你的hello 方法,正常会进入下边的内容 class IndexController {public function index(){return 您好!这是一个[api]示例应用;}public function hello() {phpin…...
53.最大子数组和
原题链接:53.最大子数组和 思路: 只需要判断当前和小于负数 如果小于则舍弃掉子序列即可, 子序列开头从下一个下标位置开始。 全代码: class Solution { public:int maxSubArray(vector<int>& nums) {int max_len I…...
455.分发饼干
原题链接:455.分发饼干 思路: 先使用大饼干喂饱大胃口的,再到剩余的里面用大饼干喂剩下大胃口的 ,直到全部满足或者喂不了了为止。 全代码: class Solution { public:int findContentChildren(vector<int>&am…...
浏览器缓存控制讲解
缓存的作用 在你访问互联网中的任何资源其所产生的任何链路中的每一个节点几乎都会进行缓存,整个缓存体系和细节十分复杂。比如浏览器缓存,服务器缓存,代理服务器缓存,CDN缓存等。 但是缓存又十分重要,不可缺少&…...
批量插入SQL 错误 [933] [42000]: ORA-00933: SQL 命令未正确结束
使用DBeaver向【oracle数据库】插入大量数据 INSERT INTO Student(name,sex,age,address,birthday) VALUES(Nike,男,18,北京,2000-01-01) ,(Nike,男,18,北京,2000-01-01) ,(Nike,女,18,北京,2000-01-01) ,(Nike,女,18,北京,2000-01-01) ,(Nike,男,18,北京,2000-01-01) ,(Nike…...
北京数字孪生赋能工业制造,加速推进制造业数字化转型
随着新一代信息技术与实体经济深度融合进程的加快,企业数字化转型需求的提升,政策的持续支持,数字孪生将为工业制造、未来生活带来无限的可能。在制造业数字化大变革时代,以5G、大数据、物联网、人工智能等为代表的工业4.0&#x…...
【NLP】GPT 模型如何工作
介绍 2021 年,我使用 GPT 模型编写了最初的几行代码,那时我意识到文本生成已经达到了拐点。我要求 GPT-3 总结一份很长的文档,并尝试了几次提示。我可以看到结果比以前的模型先进得多,这让我对这项技术感到兴奋,并渴望…...
Linux下安装Foldseek并从蛋白质的PDB结构中获取 3Di Token 和 3Di Embedding
0. 说明: Foldseek 是由韩国国立首尔大学 (Seoul National University) 的 Martin Steinegger (MMseqs2 和 Linclust 的作者) 开发的一款用于快速地从大型蛋白质结构数据库中检索相似结构蛋白质的工具,可以用于计算两个蛋白之间的结构相似性,…...
单元测试-java.lang.NullPointerException
报错信息 java.lang.NullPointerException 空指针异常 空对象引用 来源 对Controller层进行单元测试,解决完Spring上下文报错后继续报错。 解决 在测试方法执行前要为字段完成对象的注入,否则就报空指针异常。 测试例子 不完整启动Spring框架 pub…...
机器学习数据集整理:图像、表格
前言 如果你对这篇文章感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。 表格数据 Sklearn 提供了 13 个表格型数据,且数据处理接口统一;LIBSVM 提供了 131 个表格型数据&a…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化
iOS 应用的发布流程一直是开发链路中最“苹果味”的环节:强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说,这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发(例如 Flutter、React Na…...
篇章二 论坛系统——系统设计
目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...
