亚马逊AWS官方博客

高并发自定义移动推送(Mobile Push)解决方案

关于加勒比熊猫

加勒比熊猫成立于 2019 年,是一家专注海外知识分享以及休闲游戏市场,集产品研发,运营和发行于一体的企业。成立三年以来,公司一直专注创作精品工具类 App 和休闲类游戏,并持续探索全球市场潜力。至今公司自研产品海外累计下载量已突破 1.2 亿人次,其中不仅有多款游戏获得 Apple App Store 及 Google Play 全球推荐,同时也不乏多种不同类型产品,取得全球重点国家下载榜前列的成绩。

概述

移动推送(Mobile Push)是一种将通知信息从服务器端推送到移动设备上的技术,常用于向用户推送移动 APP 实时信息、系统更新、营销活动等。在亚马逊云科技提供的服务中,您通常需要使用 Amazon SNS 来实现一站式移动推送。您可以查看此文档了解如何使用 Amazon SNS 实现移动推送。

然而,使用 Amazon SNS 实现移动推送时无法灵活设置用户分组与自定义推送内容。您必须在推送之前预先设计好用户分组,并且为同一组用户推送相同的内容。如果您希望每次推送时设计不同的用户分组,并且为不同的用户推送不同的内容,那么使用 Amazon SNS 可能无法满足您的要求。自建集群实现高并发推送时也可能遇到成本高、延迟高的问题。

针对上述高并发自定义移动推送的需求,结合使用 Amazon StepFunctions Distributed Map 功能和 Amazon Lambda 服务即可轻松实现,并且整个架构采用无服务器技术栈,按需付费,低成本,低延迟,可扩展。

思路

Amazon StepFunctions Distributed Map 功能可以将 Amazon S3 上某一路径下的每一个文件都映射到一个计算资源上进行处理。比如您可以将 100 万用户信息分片保存到 1000 个文件中,每个文件保存 1000 个用户的信息。然后使用 Amazon StepFunctions Distributed Map 功能,基于这 1000 个文件启动 1000 个 Amazon Lambda 函数实例,每个实例处理一个文件(1000 条用户信息)。得益于 Amazon Lambda 快速扩展的特性,这 1000 个实例可以被快速拉起并执行推送任务。开发者可以在 Amazon Lambda 上面编写自定义脚本,实现自定义的推送逻辑,比如过滤不需要被推送的用户,或者即席生成推送消息。

当然,在启动推送任务之前,如果您的用户信息是保存在数据库中的,您还需要提前把用户信息导出到 Amazon S3 上并做好分片。需要注意的是:Amazon S3 对同一前缀下的文件请求有速率限制,同一前缀下每秒可承受 5500 次 GET 请求,详见 Amazon S3 官方文档。所以将数据导出到 Amazon S3 时请注意设计良好的前缀结构,或联系与您对接的架构师获取建议。

Amazon S3,Amazon StepFunctions 与 Amazon Lambda 都是亚马逊云科技提供的无服务器技术栈的服务,均具备按需付费、无需管理服务器、高可用和高可扩展的特性,并且上述所涉及的所有操作均可通过 API 或 SDK 实现编程管理与自动化。

实现

编写 Amazon Lambda 函数

您首先需要在 Amazon Lambda 控制台创建一个函数。这些函数可以被您手动触发执行,也可以被各种服务触发执行,比如被 Amazon StepFunctions Distributed Map 执行。

当被 Amazon StepFunctions Distributed Map 调用时,Amazon Lambda 函数中的 event 参数将会包含对应 Amazon S3 上的文件名,您需要在 Amazon Lambda 函数中读取对应的 Amazon S3 文件,拿到对应的用户列表,然后根据您的自定义逻辑,发出 HTTP 请求。请确保您的 Amazon Lambda 函数拥有读取 Amazon S3 文件的权限。另外为了提升效率,请使用多线程或类似的并发技术,在 Amazon Lambda 函数中并行处理 HTTP 请求,而非顺序串行处理。

以下是一个使用 Node.js 20.x 运行时编写的示例函数(您也可以使用其他编程语言实现类似的效果):

// import axios from "axios";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"

const s3 = new S3Client();

export const handler = async (event) => {
  const res = await s3.send(new GetObjectCommand({
    Bucket: 'YOUR_BUCKET_NAME',
    Key: event.Key
  }));
  const body = await res.Body.transformToString();

  await Promise.all(body.split('\n').map(line => {
    // TODO: check line here
    console.log(line);

    // TODO: send API request to the mobile push provider
    // axios.get("https://example.com")
  }));
};

在此示例函数中,首先根据 event.Key 从 Amazon S3 上读取了文件,然后将文件的每一行视为一条用户信息,并使用 Promise 进行并行处理。由于 Node.js 的 fetch 函数在 v21 才稳定,此示例中没有使用 fetch 函数,而是使用了 axios 库。您需要将依赖库打包上传至 Amazon Lambda 才能够运行相关代码。

创建 Amazon StepFunctions 状态机

接下来您需要创建 Amazon StepFunctions 状态机,负责将 Amazon S3 上某一路径下的每一个文件都映射到一个 Amazon Lambda 实例上。通过使用 Distributed Map 功能,您可以轻松实现这样的需求。

访问您的 Amazon StepFunctions 控制台,参考下图创建状态机。您只需要一个 Map 状态和一个 Amazon Lambda Invoke 状态即可。如果您有额外的需求也可以自行添加额外的状态和工作流。

编辑 Map 状态,选择 Distributed 模式,数据源为 Amazon S3 的 list 模式,并输入对应的桶名和前缀,其他保持默认。

在 Amazon Lambda Invoke 中填写您的函数名,其他保持默认,保存状态机。

如果您是新建的状态机,那么状态机将被自动授权访问 Amazon S3 和触发 Amazon Lambda 函数的权限。如果您在执行状态机时遇到权限错误,请检查状态机对应的 IAM 角色相关权限,或联系与您对接的架构师获取建议。

执行

您可以在 Amazon StepFunctions 控制台上启动状态机,或者使用 SDK 或 CLI 调用 Amazon StepFunctions 服务的 StartExecution 操作即可。详情可以参考 Amazon StepFunctions API 文档

总结

本文介绍了加勒比熊猫在亚马逊云科技上采用 Amazon StepFunctions 和 Amazon Lambda 等无服务器技术栈的服务来构建大规模高并发自定义移动推送的需求,这种架构设计充分利用了无服务器服务的按需付费、无需管理服务器、高可用和高可扩展的特性,是大规模移动端推送场景的一个很好的实践案例。

本篇作者

李凌

加勒比熊猫中台研发总监,负责后端研发、大数据管理和运维架构设计。擅长研发高可用的后端系统,通过应用先进的技术和工具,确保系统的可靠性和可扩展性,为用户提供稳定流畅的服务体验。

陈汉卿

亚马逊云科技解决方案架构师,负责基于亚马逊云科技云计算方案的咨询、架构设计及落地,拥有多年移动互联网研发经验,在云原生微服务以及云迁移等方向有丰富的实践经验。

王景辉

亚马逊云科技无服务器解决方案架构师,负责基于亚马逊云科技无服务器计算与应用集成领域的方案咨询与架构设计,同时致力于亚马逊云科技云服务知识体系的传播与普及。

薛佳庆

亚马逊云科技解决方案架构师,负责为互联网出海客户提供云上服务的解决方案;在无服务器,容器技术,数据分析等方面有丰富的经验。