学习

如何使用 Python 和 Redis 在 Amazon Web Services 上构建实时聊天应用程序

实时聊天消息应用正在呈指数级流行。像 WhatsApp、Facebook、Telegram、Slack、Discord 这样的移动应用已成为我们生活中“不可或缺的一部分”。用户对这些实时聊天移动应用对话非常着迷,因为它们带来了个人化的感觉并提供了实时互动。

社交媒体应用的兴起带来了社交元素,使协作、消息传递、社交互动和评论等活动成为可能。这些活动需要实时能力才能自动向用户呈现更新的信息。越来越多的开发者正在利用 Redis 的强大功能,因为它速度极快,并支持各种丰富的数据结构,例如列表 (Lists)、集合 (Sets)、有序集合 (Sorted Sets)、哈希 (Hashes) 等。Redis 还带有 Pub/Sub 消息传递功能,允许开发者通过生成多个服务器实例来扩展后端。

1. 您将构建什么?#

在本教程中,我们将了解如何使用 Flask、Socket.IO 和运行在 Amazon Web Services 上的 Redis Cloud 构建实时聊天应用。本示例结合使用了 Pub/Sub 功能和 Web-sockets 来实现客户端和服务器之间的消息通信。

2. 您需要什么?#

  • 前端 - React, Socket.IO
  • 后端 - Python(Flask),托管在 AWS 上的 Redis Enterprise Cloud

3. 入门#

步骤 1: 注册一个免费的 Redis Cloud 账户#

遵循本教程 注册一个免费的 Redis Cloud 账户。如果您已经拥有现有账户,那么您只需要您的登录凭据即可访问您的订阅。

创建新订阅时选择 AWS 作为云供应商。创建新数据库时,请确保设置自己的密码。在数据库创建过程结束时,您将获得 Redis Cloud 数据库端点和端口。请保存这些信息,稍后会用到。

提示

 您无需创建 AWS 账户即可设置您的 Redis 数据库。AWS 上的 Redis Cloud 是一个完全托管的数据库即服务,因其高性能、无限可扩展性、真正的高可用性和一流的支持而受到成千上万客户的信赖。 

步骤 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 view client 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
输出
 * 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 来保持消息结构,并简化此演示应用的实现细节。您可以选择使用哈希或 JSON。

向“General”房间填充消息#

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

Pub/Sub#

初始化后,会创建一个 Pub/Sub 订阅:SUBSCRIBE MESSAGES。同时,每个服务器实例都会在该通道上运行一个监听器来接收实时更新的消息。

同样,为简单起见,每条消息都被序列化为 JSON,我们对其进行解析,然后以与 WebSocket 消息相同的方式处理。

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

实时聊天和会话处理#

当建立 WebSocket 连接时,我们可以开始监听事件

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

使用带有 online_users 键的全局集合来维护每个用户的在线状态。因此,建立新连接时,用户 ID 会被写入该集合。

SADD online_users 1

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

之后,会向客户端广播一条消息,通知他们有新用户加入了聊天。

  • 断开连接。其工作方式类似于连接事件,不同之处在于我们需要从 online_users 集合中移除用户并通知客户端:SREM online_users 1(使 ID 为 1 的用户离线)。
  • 消息。用户发送一条消息,需要将其广播给其他客户端。Pub/Sub 也允许我们将此消息广播到所有连接到此 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 处理服务器-客户端消息传递。此外,每个服务器实例都订阅 Pub/Sub 的 MESSAGES 通道并在消息到达时分发。请注意,服务器使用单独的事件流(由服务器发送事件处理)传输 Pub/Sub 消息,这是因为需要运行独立于 socket.io 信号的 Pub/Sub 消息循环。

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

  • 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} 命令添加房间。
  • 消息存储在 key 为 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。

附加资源#