数据库 101:适合初学者的更高级别的 Twitch Bot。
我挺喜欢写关于社交媒体+数据库的文章的,看来这里的读者也挺喜欢的。所以,我们换个方向探索吧:看看 Twitch.tv 或任何带有即时通讯功能的平台。
如果您刚刚开始接触数据库,或者特别关注数据库+社交媒体,不妨先阅读我的首篇文章《数据库101:初学者数据一致性指南》。那篇文章记录了我对数据库范式的探索,我的视野远远超出了之前只使用SQL和MySQL的经验。我会持续关注这个数据库101系列的学习内容。
目录
1. 序言
作为开发倡导者,我的任务之一是激励开发者创造和尝试新事物。我还需要挑战自己,创造出能吸引人们的有趣事物。
过去几周,我一直在准备一个题为“从开发者角度看 ScyllaDB:构建应用程序”的演讲。我的目标是构建一个简单的应用程序,该应用程序的数据库操作次数为 1k/3k 次。在本文中,我将分享我的学习成果。读完本文后,初级开发者应该能够了解如何使用一个性能强劲的数据库构建一个应用程序,并消耗大量的 IOPS(或者可能不是——请在评论区告诉我)。
读完本文后,作为一名初级开发人员(或者可能不是,请在评论中告诉我),您可以构建一个带有很酷的数据库的应用程序来消耗大量的 IO/s。
2. 项目:Twitch Sentinel
我在空闲时间真正喜欢做的事情有:
- 创建无用的 PoC(概念证明)来帮助我更多地了解计算机科学中的概念。
- 每天在 Twitch.tv (这是我的频道) 上进行现场编码。
- 撰写有关我的学习和经历的博客文章。
考虑到这一点,我决定启动一个涉及高数据摄取的 Twitch 项目;我们称之为Twitch Sentinel
。
这个想法很简单:从 Twitch.tv 上尽可能多的频道收集所有消息,存储数据,并从中检索一些指标。
你能想象如何每秒存储超过 1000 条消息吗?这听起来是个很酷的挑战。以下是一些你可以用于研究的指标:
- 特定主播每天在聊天中收到多少条消息。
- 用户每天发送多少条消息。
- 根据主播或用户的消息,他们最可能在线的时间。
- 每小时的消息峰值等等。
但是你可能会问:“如果 Twitch 给我提供了分析面板,我为什么还要创建它?”答案是:无用的 PoC,只会教你一些新东西!
你之前创建过分析仪表盘吗?用过速度超快的数据库吗?还在思考用新方法建模吗?如果答案是肯定的,那就点赞这篇文章吧!如果没有,那就让我来分享我在这方面的经验吧!
3. 做出正确的决定
当你开始一个新项目时,你应该知道代码的每个部分以及你将用来交付它的工具都会直接影响项目的未来。
此外,您选择的数据库直接影响整个应用程序的性能。
如果你读过我的数据库入门系列的第一篇文章,你可能还记得关于CAP 定理和数据库范式的讨论。现在我们将检查一些与数据库速度和一致性相关的属性。
3.1 ACID 缩写
如果您对数据建模感兴趣,您可能已经听说过 ACID。如果没有,ACID 是Atomicity
、和 的首字母缩写。在关系数据库中,这个首字母缩写词中的每个Consistency
元素都构成了所谓的。Isolation
Durability
Transaction
ATransaction
是一个单一操作,它告诉数据库,如果 ACID 中的任何一个概念失败,你的查询将不会成功。让我们进一步探讨一下:
- 原子性:交易的每个部分都是唯一的,如果其中任何一个部分失败,它就会失败并回滚到该数据部分的原始状态。
- 一致性:保证您的数据只有符合该模型的正确形状才会被接受,基于:表建模、触发器、约束等。
- 隔离性:所有事务都是独立的,不会干扰正在运行的事务。
- 持久性:如果事务完成,即使系统随后出现故障,数据也将保留下来。
如您所见,ACID 为您的所有数据提供了有趣的安全保障,但这需要一些权衡:
- 维护成本很高,因为每笔交易你都需要锁定(隔离)你的数据。这需要很多节点
- 如果您的目标是通过更高的吞吐量来加快速度,那么 ACID 将阻止您实现这一目标,因为所有这些操作都需要成功。
- 您甚至无法以灵活模式(又名“文档”)来思考,因为它破坏了一致性属性。
那又怎么样?我们直接用 JSON 或 TXT 来写数据库?好吧,这倒也没什么不好,不过我们还是用非关系型数据库(也就是NoSQL)吧。
3.2 BASE缩写
当我们进入 NoSQL 数据库时,可能最重要的是要知道:存在范例,并且每个范例对于某些特定事物都很强大。
但大多数 NoSQL 数据库范式之间有一个共同点,那就是BASE属性。这个缩写词分别代表:基本可用 (Basically Availability )、软状态 (Soft State)和最终一致性 (Endually Consistency )。
现在事情变得有趣了,因为这些属性允许你对数据进行“更少的验证”,因为你从开发人员的角度保证了数据的完整性。但在讨论这些属性之前,让我们先了解一下为什么以及它们的含义:
- 基本可用:您可以随时从数据库中读取/写入数据,但这并不意味着这些数据会被更新。
- 软状态:您可以动态地塑造您的模式,它会变成开发人员的任务,而不是数据库管理员的任务。
- 最终一致性:所有数据将在多个数据中心之间同步,直到达到所需的一致性级别。
BASE属性似乎为我们提供了一个可以接收任何类型数据的数据库,并且能够根据数据建模进行处理。此外,它始终可供查询,对于高数据量的数据提取来说是一个不错的选择。但也存在一些弊端:
- 如果您需要强大的数据一致性,这可能不是最好的方法;
- 由于数据库建模委托给开发人员,因此复杂性更高;
- 如果您需要同步集群周围的节点,数据冲突可能会很麻烦。
现在我们对数据库通常具有的一些属性有了更多的了解,所以是时候根据我们的项目来做出决定了。
3.3 做出正确的决定
ACID和BASE孰优孰劣?项目类型决定一切!通常情况下,一个项目可以使用多个数据库,所以这完全没问题。但如果只需要选择一个,请谨慎选择。需要明确的是:
- 当您需要具有事务数据一致性并且性能不是关键考虑因素时,应该选择ACID 。
- 当您对 IOPS 有较高要求并且对数据建模有把握时,应该选择BASE 。
在我们的项目中,我们将从 Twitch.tv 接收大量消息。我们需要快速存储并处理所有这些消息。所以,我们当然会放弃这个ACID 'Safety Guard'
,直接使用BASE 'Do Whatever Wisely'
xD
考虑到这一点,我决定使用CQL和ScyllaDB,因为它能够处理我们每秒接收数百万条消息的需求,同时还能保证一致性并支持ACID。既然Discord 使用 ScyllaDB 存储消息,为什么不把它用在 Twitch 上呢?:D
4. 将我们的想法建模成查询
使用 ScyllaDB 时,您需要主要关注要运行哪个查询。考虑到这一点,我们需要:
- 存储消息并在必要时阅读。
- 存储并经常从流媒体列表中读取。
- 统计特定流中聊天用户发送的所有消息;
这没什么大不了的,本来就很简单。我们需要尽可能快的吞吐量,而复杂的查询无法让我们达到这样的性能。
以下是我们可以用从该模型检索的数据运行的一些查询:
4.1 每位用户的消息数量(前 5 名)
SELECT
chatter_id,
chatter_username,
count(*) AS msg_count
FROM
dev_sentinel.messages
GROUP BY
chatter_id,
chatter_username
ORDER BY
msg_count DESC
LIMIT 5
4.2 每个流的独立用户数
SELECT
streamer_id,
COUNT(DISTINCT chatter_id) AS unique_chatters
FROM
dev_sentinel.messages
GROUP BY
streamer_id
ORDER BY
unique_chatters DESC
4.3 特定用户的最早和最新消息
SELECT
min(sent_at) AS earliest_msg,
max(sent_at) AS latest_msg
FROM
dev_sentinel.messages
WHERE
chatter_username = 'danielhe4rt'
这和我之前在文章中讨论的建模非常相似。在那篇文章中,我对一些代码片段进行了更详细的解释。欢迎大家阅读:p
5. Twitch 作为我们的有效载荷!
好的,现在我们已经对数据库概念进行了建模,接下来我们需要“真实世界”的有效负载。我不知道您是否喜欢这些教程,它们只是模拟所有数据,最终向您展示一个巨大的、毫无意义的数字……我就是不喜欢——这就是为什么我想为您带来一些真实的东西来探索。
Twitch 的直播平台上有很多 API 可以与主播聊天互动。其中最出名的就是“TMI”(Twitch 消息接口),它是一个客户端,可以直接连接到任何你想连接的 Twitch 主播聊天。以下是一些客户端的列表,你可以参考一下:
- tmi.js - NodeJS 接口
- tmi.php - PHP 接口(轻松集成到 Laravel)
- pytmi ——Python 接口
- Rust 的twitch-irc-rs接口
无论如何,所有这些客户端的思路都是一样的:你需要选择一个频道并连接到它。代码如下:
$client = new Client(new ClientOptions([
'connection' => [
'secure' => true,
'reconnect' => true,
'rejoin' => true,
],
'channels' => ['danielhe4rt']
]));
$client->on(MessageEvent::class, function (MessageEvent $e) {
print "{$e->tags['display-name']}: {$e->message}";
});
$client->connect();
每个 Twitch 有效负载都有一个“标签”数组,它为我们带来了一个包含与该特定消息相关的所有数据的 JSON:
{
"badge-info": {
"subscriber": "58"
},
"badges": {
"broadcaster": "1",
"subscriber": "3036",
"partner": "1"
},
"client-nonce": "3e00905ed814fb4d846e8b9ba6a9c1da",
"color": "#8A2BE2",
"display-name": "danielhe4rt",
"emotes": null,
"first-msg": false,
"flags": null,
"id": "b40513ae-efed-472b-9863-db34cf0baa98",
"mod": false,
"returning-chatter": false,
"room-id": "227168488",
"subscriber": true,
"tmi-sent-ts": "1686770892358",
"turbo": false,
"user-id": "227168488",
"user-type": null,
"emotes-raw": null,
"badge-info-raw": "subscriber/58",
"badges-raw": "broadcaster/1,subscriber/3036,partner/1",
"username": "danielhe4rt",
"message-type": "chat"
}
在这个有效载荷上,我们只需要:
- room-id:与特定广播频道相关的标识符。
- user-id:与发送消息的用户相关的标识符
- tmi-sent-at:消息时间戳
在 Message 接口上,您还将收到一个包含消息的字符串
这是一个简单的项目,但说真的,你可以尝试从中提炼出更多想法,然后告诉我!我很乐意帮助你创造更大的东西!
6. 让它燃烧!
正如我在本文开头所说的那样,我在这个项目中的目标是构建一个高度可扩展的应用程序,该应用程序具有一个非常酷的数据库,可以通过每秒接收大量有效载荷来满足我们的需求。
因此,我们连接到了 Twitch.tv 上访问量最大的约 2 万个聊天室,平均每秒收到 1,700 到 2,000 条消息。这意味着我们平均每小时收到600 万条消息。你曾经编写过数据量如此之高的代码吗?
当应用程序接收所有这些数据并将其发布到 ScyllaDB 时,这里是T3-Micro Cluster(AWS 最便宜的实例)的一些统计数据。
它处理每秒 1k 请求的速度轻而易举,延迟低于 P99 级毫秒。此外,这台轻量级机器每秒 1k 请求的负载仅为 8%,因此,如果您需要,可以实现更快的速度。
大多数时候,这取决于有多少流媒体连接到您的机器人以及观众每秒发送多少条消息。
7. 最后的考虑
这个项目让我深刻体会到,选择合适的工具来做合适的工作是多么重要。在这个特定的案例中,数据库需要能够帮助你在更广阔的范围内进行思考。
请记住,一个项目内拥有多个数据库完全没问题。每个数据库都用于解决开发环境中的通用或特定问题。如果有时间,请务必进行充分的研究,并使用尽可能多的工具进行 PoC!
如果您想自己构建,请查看Killercoda上的本教程,并且不要忘记在社交网站上关注我!
保持水分,很快再见。
Twitter DanielHe4rt PT-BR
Twitter Danielhe4rt EN
Twitch 频道(每日直播编码课程)
Instagram
YouTube