构建无服务器 Mixpanel 替代方案。第 1 部分:收集和显示事件
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
这是使用Cube.js构建分析型 Web 应用程序系列教程的第一部分。本文假设读者熟悉 Javascript、Node.js 和 React,并具备 SQL 基础知识。最终源代码可在此处获取,在线演示可在此处查看。示例应用程序是无服务器的,运行在 AWS Lambda 上。它会显示自身的使用数据。
有一类分析工具,例如 Mixpanel 或 Amplitude,它们擅长处理事件数据。这些工具非常适合衡量产品或用户互动指标,例如激活漏斗或用户留存率。它们对于衡量 A/B 测试结果也非常有用。
尽管这些工具都能完成部分工作,但它们都是专有的云端工具。如果用户关注隐私问题,或者想要自定义转化漏斗或用户留存机制,这可能会成为一个问题。虽然传统的商业智能工具,例如 Tableau 或 Power BI,理论上也可以用于运行相同的分析,但它们无法提供相同的用户体验。问题在于,它们的设计初衷是作为通用商业智能工具,而非专门针对转化漏斗、用户留存、A/B 测试等场景。
随着前端开发技术的飞速发展,快速开发复杂的用户界面成为可能。五年前需要一周才能完成的工作,如今只需一个下午就能搞定。在后端和基础设施方面,基于云的 MPP 数据库(例如 BigQuery 和 Athena)正在彻底改变行业格局。在数据库内部进行数据转换的 ELT 方法越来越受欢迎,逐渐取代了传统的 ETL 方法。无服务器架构使得应用程序的部署和扩展变得轻而易举。
所有这些都使得构建内部替代方案成为可能,以取代 Mixpanel、Amplitude 或 Kissmetrics 等现有服务。在本系列教程中,我们将构建一个功能齐全的开源事件分析系统。
它将包含以下功能:
- 数据收集;
- 仪表盘;
- 使用查询生成器进行临时分析;
- 漏斗分析;
- 留存率分析;
- 无服务器部署;
- A/B 测试;
- 实时事件监控;
下图展示了我们应用程序的架构:
在本教程的第一部分,我们将重点介绍如何收集和存储数据,并简要介绍如何基于这些数据创建简单的图表。后续部分将重点介绍如何查询数据以及构建各种分析报告功能。
收集事件
我们将使用 Snowplow CloudFront Collector 和 Javascript Tracker。我们需要将一个跟踪像素上传到 Amazon CloudFront CDN。Snowplow Tracker 通过向 Collector 发送 GET 请求来获取该像素,并将数据作为查询字符串参数传递。CloudFront Collector 使用 CloudFront 日志记录将请求(包括查询字符串)记录到 S3 存储桶中。
接下来,我们需要安装 Javascript Tracker。这里是完整指南。
但简而言之,它类似于 Google Analytics 的跟踪代码或 Mixpanel 的跟踪代码,因此我们只需要将其嵌入到我们的 HTML 页面中即可。
<script type="text/javascript">
;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
n.src=w;g.parentNode.insertBefore(n,g)}} . (window,document,"script","//d1fc8wv8zag5ca.cloudfront.net/2.10.2/sp.js","snowplow"));
window.snowplow('newTracker', 'cf', '<YOUR_CLOUDFRONT_DISTRIBUTION_URL>’, { post: false });
</script>
一旦我们将数据(即 CloudFront 日志)存储在 S3 存储桶中,就可以使用 Athena 进行查询。我们只需要创建一个用于存储 CloudFront 日志的表即可。
将以下 DDL 语句复制并粘贴到 Athena 控制台中。修改 LOCATION 以指向存储日志的 S3 存储桶。
CREATE EXTERNAL TABLE IF NOT EXISTS default.cloudfront_logs (
`date` DATE,
time STRING,
location STRING,
bytes BIGINT,
requestip STRING,
method STRING,
host STRING,
uri STRING,
status INT,
referrer STRING,
useragent STRING,
querystring STRING,
cookie STRING,
resulttype STRING,
requestid STRING,
hostheader STRING,
requestprotocol STRING,
requestbytes BIGINT,
timetaken FLOAT,
xforwardedfor STRING,
sslprotocol STRING,
sslcipher STRING,
responseresulttype STRING,
httpversion STRING,
filestatus STRING,
encryptedfields INT
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LOCATION 's3://CloudFront_bucket_name/AWSLogs/Account_ID/'
TBLPROPERTIES ( 'skip.header.line.count'='2' )
现在我们准备将 Cube.js 连接到 Athena,并开始构建我们的第一个仪表板。
构建我们的第一个图表
首先,安装 Cube.js CLI。它用于各种 Cube.js 工作流程。
$ npm install -g cubejs-cli
接下来,运行以下命令创建一个新的 Cube.js 服务。注意,这里我们指定 Athena 作为数据库(-d athena),并将模板设置为无服务器模式(-t serverless)。Cube.js 支持不同的配置,但本教程将使用无服务器模式。
$ cubejs create event-analytics-backend -d athena -t serverless
运行该create命令后,它将创建一个新的项目目录,其中包含您的新 Cube.js 项目的框架。这包括启动 Cube.js 后端所需的所有文件、用于在 React 应用中显示 Cube.js 查询结果的示例前端代码,以及一些示例模式文件,以突出显示 Cube.js 数据模式层的格式。
此项目目录中的文件.env包含相关数据库凭证的占位符。对于 Athena,您需要指定运行 Athena 查询所需的AWS 访问密钥和秘密密钥,以及存储查询结果的目标 AWS 区域和S3 输出位置。
CUBEJS_DB_TYPE=athena
CUBEJS_AWS_KEY=<YOUR ATHENA AWS KEY HERE>
CUBEJS_AWS_SECRET=<YOUR ATHENA SECRET KEY HERE>
CUBEJS_AWS_REGION=<AWS REGION STRING, e.g. us-east-1>
# You can find the Athena S3 Output location here: https://docs.aws.amazon.com/athena/latest/ug/querying.html
CUBEJS_AWS_S3_OUTPUT_LOCATION=<S3 OUTPUT LOCATION>
现在,让我们为事件模型创建一个基本的 Cube.js 模式。Cube.js 使用数据模式来生成和执行 SQL;您可以在这里了解更多信息。
创建一个schema/Events.js包含以下内容的文件。
const regexp = (key) => `&${key}=([^&]+)`;
const parameters = {
event: regexp('e'),
event_id: regexp('eid'),
page_title: regexp('page')
}
cube(`Events`, {
sql:
`SELECT
from_iso8601_timestamp(to_iso8601(date) || 'T' || "time") as time,
${Object.keys(parameters).map((key) => ( `url_decode(url_decode(regexp_extract(querystring, '${parameters[key]}', 1))) as ${key}` )).join(", ")}
FROM cloudfront_logs
WHERE length(querystring) > 1
`,
measures: {
pageView: {
type: `count`,
filters: [
{ sql: `${CUBE}.event = 'pv'` }
]
},
},
dimensions: {
pageTitle: {
sql: `page_title`,
type: `string`
}
}
});
在模式文件中,我们创建了一个 Events 立方体。它将包含所有事件信息。在基础 SQL 语句中,我们使用函数从跟踪器发送的查询字符串中提取值regexp。Cube.js 擅长执行此类转换,并且还可以将其中一些转换物化以优化性能。我们将在教程的后续部分中讨论这一点。
有了这个架构,我们就可以运行开发服务器并构建第一个图表了。
运行以下命令启动开发服务器。
$ npm dev
访问http://localhost:4000,应该会打开一个包含示例的 CodeSandbox。将renderChart函数和query变量更改为以下内容。
const renderChart = resultSet => (
<Chart height={400} data={resultSet.chartPivot()} forceFit>
<Coord type="theta" radius={0.75} />
<Axis name="Events.pageView" />
<Legend position="right" name="category" />
<Tooltip showTitle={false} />
<Geom type="intervalStack" position="Events.pageView" color="x" />
</Chart>
);
const query = {
measures: ["Events.pageView"],
dimensions: ["Events.pageTitle"]
};
现在,你应该能够看到饼图了,具体取决于你的 S3 中有哪些数据。
下一部分,我们将介绍如何构建仪表盘和动态查询构建器,类似于 Mixpanel 或 Amplitude 中的那种。第三部分将介绍如何构建转化漏斗,第四部分将介绍用户留存。最后一部分,我们将讨论如何以无服务器模式将整个应用程序部署到 AWS Lambda。
文章来源:https://dev.to/cubejs/building-a-serverless-mixpanel-alternative-part-1-collecting-and-displaying-events-43h2