学习

如何使用 Redis 构建聊天应用

Ajeet Raina
作者
Ajeet Raina, Redis 前开发者增长经理

实时聊天应用是一种在线沟通渠道,允许您进行实时对话。越来越多的开发者正在利用 Redis 的强大功能,因为它速度极快,并且支持 Lists、Sets、Sorted Sets、Hashes 等多种丰富的数据结构。Redis 还具备 Pub/Sub 消息发布订阅功能,允许开发者通过生成多个服务器实例来扩展后端。

警告

虽然关于 Redis 的传统聊天应用教程仍然富有启发性,但我们鼓励您深入学习我们的AI 聊天机器人教程,以释放现代对话体验的潜力。

注意

请注意,此代码是开源的。您可以在本教程末尾找到链接。

在本教程中,我们将学习如何使用 Flask、Socket.IO 和 Redis 开发实时消息应用。此示例利用 Redis 的 Pub/Sub 功能结合 websockets 来实现客户端和服务器之间的实时聊天应用通信。

注意

请注意,此代码是开源的,并实现了实时聊天应用的基本功能。您可以在本教程末尾找到链接。

步骤 1. 前提条件#

为了开发这个即时消息应用,您需要以下软件

  • Python 3.6+

步骤 2. 克隆仓库#

首先,我们将克隆实现基本聊天功能的项目。

git clone https://github.com/redis-developer/basic-redis-chat-app-demo-python

步骤 3. 安装所需的包#

cd client
yarn install

步骤 4. 启动前端#

要运行聊天应用的前端,请运行以下命令

yarn start
You can now access a chat window in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://192.168.1.9:3000

步骤 5. 安装所需的 Python 模块#

cd ..
pip3 install -r requirements.txt

步骤 6. 运行后端#

要启动完整的聊天应用,请运行以下命令

python3 -m venv venv/
source venv/bin/activate
python3 app.py
python3 app.py
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 220-696-610
(8122) wsgi starting up on http://127.0.0.1:5000

工作原理#

此即时消息应用服务器作为一个基本的 REST API 工作,它涉及保持会话并处理聊天室中的用户状态(除了 WebSocket/实时部分)。服务器启动时,会执行初始化步骤。首先,建立一个新的 Redis 连接,并检查是否需要加载演示数据。

初始化#

为简单起见,会检查键 `total_users` 的值:如果它不存在,我们将使用初始数据填充 Redis 数据库。`EXISTS total_users`(检查键是否存在)演示数据初始化分多个步骤处理

创建演示用户#

我们创建一个新的用户 ID:`INCR total_users`。然后通过用户名设置用户 ID 查找键,例如:

SET username:nick user:1

最后,其余数据写入哈希集合

示例

  HSET user:1 username "nick" password "bcrypt_hashed_password".

此外,每个用户都被添加到默认的“General”房间。为了处理每个用户的聊天室,我们有一个集合来保存聊天室 ID。下面是添加房间的命令示例

 SADD user:1:rooms "0"

填充用户之间的私信。首先,创建私聊房间:如果需要建立私聊房间,会为每个用户生成一个房间 ID,例如 `room:1:2`,其中数字对应按升序排列的用户 ID。

例如:创建 2 个用户之间的私聊房间

 SADD user:1:rooms 1:2 and SADD user:2:rooms 1:2

然后通过写入有序集合向该房间添加消息

 ZADD room:1:2 1615480369 "{'from': 1, 'date': 1615480369, 'message': 'Hello', 'roomId': '1:2'}"

我们使用 JSON 字符串来保存消息结构,并简化此演示应用的实现细节。

向“General”房间填充消息。消息被添加到 ID 为 `room:0` 的有序集合中

发布/订阅 (Pub/sub)#

初始化后,会创建一个发布/订阅订阅:`SUBSCRIBE MESSAGES`。同时,每个服务器实例将在此通道上运行一个监听器,以接收实时更新。

同样,为简单起见,每条消息都序列化为 JSON,我们解析并以与 WebSocket 消息相同的方式处理。

发布/订阅允许连接用不同平台编写的多个服务器,而无需考虑每个服务器的实现细节。

实时聊天和会话处理#

实例化 WebSocket/实时服务器时,它会监听以下事件

  • 连接。新用户连接。此时,捕获用户 ID 并保存到会话(在 Redis 中缓存)。请注意,会话缓存是特定于语言/库的,此处纯粹用于持久性和在服务器重新加载之间维护状态。

使用键为 `online_users` 的全局集合来保存每个用户的在线状态。因此,在新连接时,用户 ID 会被写入该集合

 SADD online_users 1

这里我们将 ID 为 1 的用户添加到了 `online_users` 集合中

之后,向客户端广播消息,通知他们新用户已加入聊天。

  • 断开连接。其工作方式与连接事件类似,不同之处在于我们需要从 `online_users` 集合中移除用户并通知客户端:`SREM online_users 1`(将 ID 为 1 的用户标记为离线)。
  • 消息。用户发送消息,需要广播给其他客户端。发布/订阅还允许我们将此消息广播给连接到此 Redis 的所有服务器实例:
 PUBLISH message "{'serverId': 4132, 'type':'message', 'data': {'from': 1, 'date': 1615480369, 'message': 'Hello', 'roomId': '1:2'}}"

请注意,我们发送了与消息类型和服务器 ID 相关的额外数据。服务器 ID 用于发送消息的服务器实例丢弃这些消息,因为它连接到同一个 `MESSAGES` 通道。

序列化 JSON 的 `type` 字段对应于我们用于实时通信的实时方法(连接/断开连接/消息)。

`data` 是方法特定的信息。在上面的示例中,它与新消息相关。

数据如何存储?#

Redis 主要用作数据库来保存用户/消息数据,并用于在连接的服务器之间发送消息。

实时功能由 Socket.IO 处理,用于服务器-客户端消息传递。此外,每个服务器实例都会订阅发布/订阅的 `MESSAGES` 通道,并在消息到达后进行分发。请注意,服务器通过独立的事件流(由 Server Sent Events 处理)传输发布/订阅消息,这是因为需要独立于 socket.io 信号运行发布/订阅消息循环。

聊天数据存储在各种键和各种数据类型中。用户数据存储在哈希集合中,每个用户条目包含以下值

  • username:唯一用户名;

  • password:哈希密码

  • 此外,用户还关联了一组聊天室

  • 房间是有序集合,其中包含消息,分数字段是每条消息的时间戳

  • 每个聊天室都有一个关联的名称

  • “online”集合是所有用户的全局集合,用于跟踪哪些用户在线。

  • 每个用户哈希集合通过键 user:{userId} 访问。其数据使用 HSET key field data 命令存储。用户 ID 通过递增 total_users 键 (INCR total_users) 计算得到。

  • 用户名存储为独立的键 (username:{username}),返回用户 ID 以便更快访问,并使用 SET username:{username} {userId} 命令存储。

  • 用户所属的房间存储在 user:{userId}:rooms 键下,是一个聊天室 ID 集合。房间通过 SADD user:{userId}:rooms {roomId} 命令添加。

  • 消息存储在 room:{roomId} 键下的有序集合中(如上所述)。它们通过 ZADD room:{roomId} {timestamp} {message} 命令添加。消息被序列化为应用特定的 JSON 字符串。

数据如何访问?#

获取用户:`HGETALL user:{id}`。

 HGETALL user:2

其中我们获取 ID 为 2 的用户数据。

  • 在线用户:`SMEMBERS online_users`。这将返回在线用户的 ID
  • 获取用户的房间 ID:`SMEMBERS user:{id}:rooms`。例如:
 SMEMBERS user:2:rooms

这将返回 ID 为 2 的用户的聊天室 ID

  • 获取消息列表: ZREVRANGE room:{roomId} {offset_start} {offset_end}。例如:
 ZREVRANGE room:1:2 0 50

它将返回 ID 为 1 和 2 的用户之间的私聊房间的 50 条消息,偏移量为 0。