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

Ale*_*lex 7 cassandra redis

到目前为止,我已经使用 MySQL 完成了几乎所有事情,但我不喜欢手动分片数据并暂时维护所有这些。

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

在此输入图像描述

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

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

卡桑德拉非常擅长写作和阅读。但由于墓碑的原因,删除数据的情况就不那么严重了。并且您不想设置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);
Run Code Online (Sandbox Code Playgroud)

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

CREATE TABLE message_thread_by_user_ids (
    thread_id UUID,
    user1_id INT,
    user2_id INT,
    PRIMARY KEY (user1_id, user2_id)
);
Run Code Online (Sandbox Code Playgroud)

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

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

SET user:1:user:2:message_thread_id 123e4567-e89b-12d3-a456-426655440000
Run Code Online (Sandbox Code Playgroud)

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

message_thread_by_user_ids
Run Code Online (Sandbox Code Playgroud)

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

message_by_thread_id
Run Code Online (Sandbox Code Playgroud)

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

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

我想我可以通过以下方式在 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
Run Code Online (Sandbox Code Playgroud)

排序集将跟踪按 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 ...>
Run Code Online (Sandbox Code Playgroud)

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

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

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

Bes*_*2Go 1

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

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

2) 在扩展方面,redis 使用带有分片的主从拓扑,而 Cassandra 使用环形拓扑,其中每个节点都具有相同的写入和读取能力。

在我看来,我宁愿使用 Cassandra,因为它不如 Redis 快,但足够快并且非常容易扩展。

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

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

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