我应该或不应该如何一起使用 Cassandra 和 Redis 来构建可扩展的一对一聊天应用程序?

Posted

技术标签:

【中文标题】我应该或不应该如何一起使用 Cassandra 和 Redis 来构建可扩展的一对一聊天应用程序?【英文标题】:How should I or should not use Cassandra and Redis together to build a scalable one on one chat application? 【发布时间】:2017-11-14 02:03:00 【问题描述】:

到目前为止,我几乎都使用 mysql 来完成所有工作,但我不喜欢手动分片我的数据并暂时维护所有这些的想法。

我想构建一个像 Facebook 和 WhatsApp 一样的一对一聊天应用程序,如下图所示:

所以我们这里有两个部分。右侧部分是聊天线程中的所有消息,左侧部分显示聊天线程,包含最后一条消息的信息,以及您的聊天伙伴信息,例如姓名和图像等。

到目前为止,这是我所拥有的:

Cassandra 非常擅长写作和阅读。但由于墓碑,在删除数据方面并没有那么多。并且您不想将 gc_grace_seconds 设置为 0,因为如果一个节点出现故障并发生删除,那么在修复完成后,删除的行可能会恢复活力。因此,我们最终可能会在节点进入集群之前删除节点中的所有数据。无论如何,据我所知,Cassandra 非常适合这个聊天应用程序的正确部分。由于消息将按插入时间存储和排序,并且排序永远不会改变。所以你只需要写和读。这是 Cassandra 擅长的。

我有这些表来存储正确部分的消息:

CREATE TYPE user_data_for_message (
    from_id INT,
    to_id INT,
    from_username TEXT,
    to_username TEXT,
    from_image_name TEXT,
    to_image_name TEXT
);

CREATE TABLE message_by_thread_id (
    message_id TIMEUUID,
    thread_id UUID,
    user_data FROZEN <user_data_for_message>,
    message TEXT,
    created_time INT,
    is_viewed BOOLEAN,
    PRIMARY KEY (thread_id, message_id)
) WITH CLUSTERING ORDER BY (message_id DESC);

在我插入新消息之前,如果客户端没有提供thread_id,我可以检查两个用户之间是否存在线程。我可以像这样存储这些信息:

CREATE TABLE message_thread_by_user_ids (
    thread_id UUID,
    user1_id INT,
    user2_id INT,
    PRIMARY KEY (user1_id, user2_id)
);

我可以为 user1 和 user2 顺序相反的每个线程存储两行,因此我只需要执行 1 次读取来检查是否存在。由于我不想在每次插入之前检查线程是否存在,我可以先检查 Redis 中用户之间是否存在线程,因为它在内存中并且速度更快。

我也可以像这样在 Redis 中保存上面相同的信息(不是我在 Cassandra 中所做的两种方式,而是一种节省内存的方式。我们可以执行两个 GET 来检查它):

SET user:1:user:2:message_thread_id 123e4567-e89b-12d3-a456-426655440000

所以在我发送消息之前,我可以先在 Redis 中检查两个用户之间是否存在线程。如果在 Redis 中找不到,我可以检查 Cassandra,(以防 Redis 服务器在某个时候关闭并且没有保存它),如果存在线程,只需使用该 thread_id 插入新消息,如果没有,则创建线程,然后将其插入表中:

message_thread_by_user_ids

使用上面的 SET 命令将其插入 Redis。然后最后将消息插入:

message_by_thread_id

好的,现在是棘手的部分。聊天的左侧部分没有静态排序顺序。顺序一直在变化。如果对话有新消息,则该对话会转到顶部。所以我还没有找到一种在 Cassandra 中建模而不进行删除和插入的好方法。我必须删除一行,然后将其插入,以便表对行重新排序。并且删除一行并在表中插入一行,每次发送消息对我来说听起来都不是一个好主意,但我可能错了,我对 Cassandra 没有经验。

所以我的想法是我可以在左侧使用 Redis,但唯一的问题是如果 Redis 服务器出现故障,那么左侧最近的聊天对话将会丢失,即使聊天本身也会丢失保存在卡桑德拉。用户需要重新发送消息才能再次出现对话。

我认为我可以通过以下方式在 Redis 中执行此操作:

每次用户发送消息时,例如,如果用户 1 向用户 2 发送消息,我可以这样做:

ZADD user:1:message_thread_ids 1510624312 123e4567-e89b-12d3-a456-426655440000

ZADD user:2:message_thread_ids 1510624312 123e4567-e89b-12d3-a456-426655440000

排序集将跟踪具有按 unix 时间戳排序的最近活动对话的线程的 ID。

但另一个问题是,每次加载此窗口时,我都必须执行 ZRANGE,例如在左侧获取 20 个最近的对话,然后在 Cassandra 中执行 20 个带有 LIMIT 1 的单个 SELECT 语句以获得关于发送的最后一条消息的信息,这可能不是那么有效。我想我可以使用 HMSET 在 redis 中保存 20 个最近活动对话的最后一条消息的信息,其中包含最相关的信息,例如消息本身只修剪到 60 个字符、last_message 时间戳、from_username、to_username、from_id、to_id、from_image、 to_image 和 message_id。

HMSET thread:123e4567-e89b-12d3-a456-426655440000 <... message info ...>

但是现在我必须跟踪并从 Redis 中删除不相关的哈希映射,因为我不想保留超过最近的 20 个,因为它会很快耗尽内存。我将从 Redis 和内存中获取最新的 20 个,如果用户向下滚动,我将一次从 Cassandra 获取 10 个。另一个问题是,如果 Redis 服务器出现故障,我可能会从应用程序的左侧丢失一个对话,如果对话是一个全新的对话。

我认为通过这种方法,我可以通过添加新节点每秒在 Cassandra 端获得大量写入,而 Redis 每秒可以执行 200 000 - 800 000 次操作,因此可以执行删除操作并将内容添加到排序集不应成为限制。由于 Redis 服务器会有一些来回,我可以尝试管道化 Redis 命令或编写 Lua 脚本,以便我可以一次性将指令发送到 Redis。

这是个好主意吗?如何解决显示活动对话的应用程序左侧的这个问题?像我建议的那样在 Redis 中执行此操作是一个好主意,还是应该以不同的方式执行?

【问题讨论】:

【参考方案1】:

两者都是很好的解决方案。但瓶颈可能在哪里?

1) Redis 受限于内存,不能超过。此外,当服务器关闭时,您的数据也会丢失。

2) 在扩展方面,redis 使用带有分片的主从拓扑,而 Cassandra 使用环形拓扑,其中每个节点都可以读写。

在我看来,我宁愿使用 Cassandra,因为它没有 Redis 快,但速度足够快且非常易于扩展。

这是个好主意吗?如何解决显示活动对话的应用程序左侧的这个问题?像我建议的那样在 Redis 中执行此操作是一个好主意,还是应该以不同的方式执行?

你的用户是如何相互写作的,我想你是用 websocket 来做的,不是吗?如果是,只需跟踪套接字 ID 并在套接字断开连接时将其删除。

另一个问题是,您在哪里以及如何检索某个人的朋友 ID(图片左侧)?

【讨论】:

以上是关于我应该或不应该如何一起使用 Cassandra 和 Redis 来构建可扩展的一对一聊天应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

我应该在 cassandra.yaml 中为 listen_address 使用啥地址?

我应该在 cassandra.yaml 中为 broadcast_rpc_address 使用啥地址

用户可以或不应该拥有多个电子邮件地址和电话号码? [关闭]

如何在 google-dataflow 中读取 cassandra

如何使用Apache Flink阅读Cassandra?

我应该如何与摩卡和猫鼬一起使用?