我如何使用 Laravel Horizon、Redis 和极简 REST API 构建漏斗分析引擎
Source: Dev.to
核心问题
漏斗分析听起来很简单:用户先做 A,然后是 B,最后是 C。有什么比例的用户能够从 A 到达 C?他们在哪一步掉队?
实际上,要做好这件事相当棘手:
- 事件是异步到达且可能乱序
- 漏斗需要具备灵活性——不同的步骤、不同的时间范围
- 计算必须快速,即使面对成千上万的事件
- 开发者的集成工作量必须尽可能少
目标: 开发者应能在 5 分钟内通过一次 HTTP POST 开始跟踪。
架构概览
Browser/App → REST API → Event Storage → Queue → Funnel Engine → Dashboard技术栈
- 后端: Laravel 12 与模块化架构 (
nwidart/laravel-modules) - 队列: Laravel Horizon + Redis
- 前端: Next.js 14 + TypeScript
- 数据库: MariaDB
- 支付: Stripe
API层
集成故意保持最小化:
POST /api/v1/events
X-OL-Tenant-Key: your-tenant-key
X-OL-App-Key: your-app-key
Content-Type: application/json
{
"event_name": "signup_completed",
"user_identifier": "user_123",
"metadata": {
"plan": "pro",
"source": "landing_page"
}
}两个请求头,一个 JSON 正文——这就是全部契约。
控制器会验证密钥,识别租户和被跟踪的应用,持久化事件,并立即返回 200。在请求生命周期中没有繁重的处理。
漏斗引擎
当用户在仪表盘中构建漏斗时,例如 visited_pricing → started_trial → upgraded,系统必须计算每一步的转化率。
所有事件写入都会调度一个后台任务:
ProcessFunnelEngineJob::dispatch($event)->onQueue('funnels');该任务:
- 取出事件。
- 加载关联应用的所有漏斗。
- 使用滑动窗口方法重新计算每一步的转化率。
- 将结果缓存,以供仪表盘使用。
为什么使用队列?
- 性能: 无论需要重新计算多少个漏斗,API 响应都保持快速。
- 弹性: 任务失败会自动重试;对瞬时错误不会导致数据丢失。
Laravel Horizon 提供实时仪表盘,可监控任务吞吐量、失败情况和队列深度,无需额外基础设施。
多租户
Tracetics 设计上是多租户的。每个租户(公司)可以拥有多个 TrackedApps,每个都有自己的事件和漏斗。层级结构:
Tenant
└── TrackedApp (X-OL-App-Key)
└── Events
└── Funnels
└── FunnelSteps认证使用两个 Header:
X-OL-Tenant-Key— 标识租户X-OL-App-Key— 标识事件所属的应用
这使得开发者的集成保持简洁,同时在后端强制严格的数据隔离。
计划限制与计费
每个订阅计划定义了 TrackedApps、Funnels 和每月 Events 的限制。限制在控制器层面执行,在任何写入操作之前进行检查:
if ($tenant->trackedApps()->count() >= $tenant->plan->app_limit) {
return response()->json(['error' => 'App limit reached'], 403);
}Stripe 通过 webhook 处理订阅。一个值得注意的挑战是:Stripe 的新 API 将 current_period_end 移动到了 items.data[0] 中,而不是订阅根对象——这是一个细微的破坏性更改,导致我们花了一个小时进行调试。
TypeScript SDK
对于更喜欢使用类型化客户端而非原始 HTTP 的开发者,提供了一个 TypeScript SDK:
npm install tracetics-sdkimport { Tracetics } from 'tracetics-sdk';
const client = new Tracetics({
tenantKey: 'your-tenant-key',
trackedAppKey: 'your-app-key',
endpoint: 'https://tracetics.com'
});
await client.track({
event_name: 'signup_completed',
user_identifier: 'user_123'
});该 SDK 使用 tsup 构建 — 双 ESM/CJS 输出,完整的 TypeScript 类型,且零依赖。
我学到的
保持写入路径简洁快速。
验证、持久化、入队。其他所有工作都交给队列。先队列的架构立刻见效。
漏斗计算永不阻塞 API 响应,且各关注点保持良好分离。Stripe Webhook 不是可选的。
仅依赖基于重定向的确认会导致订阅状态不可靠。明确的键层次结构让多租户更容易。
明确的Tenant → App → Event所有权使权限检查变得轻而易举,数据隔离坚如磐石。
What’s Next
Tracetics 已上线,提供免费计划。TypeScript SDK 在 npm 上,名称为 tracetics-sdk。
如果您构建了类似的项目或对架构有疑问,欢迎在评论中分享您的想法。