Turbulenz 引擎自定义指标批处理:CustomMetricEventBatch 完整实战指南
Turbulenz 引擎自定义指标批处理:CustomMetricEventBatch 完整实战指南
在 Turbulenz 游戏框架中,自定义指标(Custom Metrics)允许开发者上报游戏内任意自定义事件(如通关用时、击杀数量),这些数据会汇总到 Turbulenz Services 服务端,并在 Hub 的游戏指标(Metrics)页面中按事件键聚合展示。本指南围绕 CustomMetricEventBatch 对象展开:它把多条自定义指标事件在客户端暂存、合并为一次 HTTP 请求批量上报,从而显著减少 API 调用次数、降低 CPU 与带宽开销。读完本文,你将掌握批量的创建、入队、计数、清空与发送全流程,并理解其底层时间戳保留机制、服务端校验规则与数据丢失风险,能够在自己的游戏中正确设计指标上报策略。
CustomMetricEventBatch 是什么
CustomMetricEventBatch 是 Turbulenz Services 客户端库(TurbulenzServices)提供的一个 JavaScript 对象,用于"把一组自定义指标事件批量暂存起来",待时机合适时一次性发送到服务器。它的核心价值在于:用一次 API 调用替代 N 次调用。
与逐条上报的 TurbulenzServices.sendCustomMetricEvent 相比,批量上报的理由非常直观——每一次 HTTP 请求都有不可忽视的固定开销。在 Working with HTTP requests 一节中,Turbulenz 官方明确列出了这些约束:
- 请求前需要建立连接,而连接建立可能耗时较长;
- 连接可能存在最大请求次数与最大存活时间的限制;
- 浏览器到同一服务器的并发连接通常只有 2~8 个;
- HTTP 请求头的体积本身就相当可观(通常超过 512 字节)。
因此,大量小于 1KB 的小请求应当被合并成几个较大的请求,以最小化请求头开销。这正是 CustomMetricEventBatch 存在的意义:把游戏中高频产生的指标(拾取道具、击杀敌人)攒成一包发送。
该对象在 TurbulenzServices 客户端库中实现,源码位于 tslib/services/turbulenzservices.ts。文档同时强调:Turbulenz 本身已经默认跟踪了一批游戏指标(如 DAU、留存等),自定义指标是在默认指标之外按你的业务需求追加的,可参考 Hub 用户指南的 Metrics 章节。
前置条件:引入所需脚本
在使用 CustomMetricEventBatch 之前,需要先加载 TurbulenzServices 的 JavaScript 库。原文档给出的引入方式为:
/*{{ javascript("jslib/services/turbulenzservices.js") }}*/
这是 Turbulenz 构建系统(基于 manage.py 与 Makefile 的资源管线)中的脚本占位语法,最终会在编译阶段替换为真实的脚本引入。从仓库结构看,对应的 TypeScript 源码位于 tslib/services/turbulenzservices.ts,其中同时定义了 CustomMetricEvent、CustomMetricEventBatch 两个类以及 TurbulenzServices 发送方法。
此外,由于指标最终与游戏会话(GameSession)关联,实际发送时还需要请求处理器(RequestHandler)与游戏会话对象,这些对象来自 TurbulenzServices 体系,可通过 TurbulenzServices.createGameSession、TurbulenzServices.createRequestHandler 等接口创建(详见 turbulenzservices_api.rst)。
对象模型:一个批次内部是怎样的
在深入各 API 方法之前,先看源码中批次对象的真实结构。源码 tslib/services/turbulenzservices.ts#L44-L85 定义了:
class CustomMetricEvent
{
key: string;
value: any;
timeOffset: number;
}
class CustomMetricEventBatch
{
events: CustomMetricEvent[];
// push / length / clear / create ...
}
可以看到,一个批次内部就是一个 CustomMetricEvent[] 数组,每个事件由三部分组成:
key:事件键(字符串);value:事件值(数字或数字数组);timeOffset:事件加入批次时的时间戳偏移,由TurbulenzEngine.time记录。
理解这个内部结构,有助于理解后续 push、length、clear 三个方法的实现原理:length() 返回的就是数组长度,clear() 直接把数组长度置 0,push() 则创建新事件对象并追加进数组。
方法一:create —— 创建新的指标批次
Summary:创建一个新的自定义指标事件批次。
语法:
var customMetricEventBatch = CustomMetricEventBatch.create();
源码实现(turbulenzservices.ts#L79-L84)非常简单:
static create() : CustomMetricEventBatch
{
var batch = new CustomMetricEventBatch();
batch.events = [];
return batch;
}
新建的批次内部 events 数组为空,length() 返回 0。之后你可以通过 push 把事件不断加入该批次,并在合适时机用 TurbulenzServices.sendCustomMetricEventBatch 发送给服务器。
方法二:push —— 向批次中压入一条指标事件
Summary:向批次中压入一条新的自定义指标事件。
语法:
customMetricEventBatch.push(eventKey, eventValue);
参数说明:
eventKey:JavaScript 字符串。用于标识这类事件的事件键,例如'levelOneCompleted'(通关关卡一)。服务端会按每个唯一键做聚合统计,因此键名应具备业务语义且保持稳定。eventValue:JavaScript 数字或数字数组。与该次事件关联的值,例如通关所耗时间'timeToComplete'。从 0.21.1 版本开始(见 release_notes_0.21.1.rst),事件值支持传入数字数组。
源码实现(turbulenzservices.ts#L60-L67):
push(key: string, value: any)
{
var event = CustomMetricEvent.create();
event.key = key;
event.value = value;
event.timeOffset = TurbulenzEngine.time;
this.events.push(event);
}
这里有一个关键细节:每次 push 时,事件都会自动记录当时的 TurbulenzEngine.time 作为 timeOffset。这意味着即使事件被延迟很久才随批次发送,服务端仍能根据入队时刻还原每条事件的真实发生时间——批量发送不会丢失时间维度上的信息。这一点在原文档的 sendCustomMetricEventBatch 说明中也有明确表述:"Each event in the batch will preserve a timestamp based on the time it is added to the batch meaning metrics from batches lose no information versus individual metrics."
方法三:length —— 获取当前批次内事件数量
Summary:获取当前批次内已暂存的自定义指标数量。
语法:
if (customMetricEventBatch.length() > maxMetricsToBatch)
{
TurbulenzServices.sendCustomMetricEventBatch(customMetricEventBatch, ...);
}
length() 是批次对象自带的"水位线",用于判断何时应该把批次刷新(flush)给服务器。典型的用法就是原文档展示的模式:设定一个阈值 maxMetricsToBatch,当 length() 超过阈值时触发发送。
源码实现(turbulenzservices.ts#L69-L72):
length() : number
{
return this.events.length;
}
方法四:clear —— 清空批次并复用对象
Summary:清空批次中所有已暂存的指标。
语法:
customMetricEventBatch.clear();
源码实现(turbulenzservices.ts#L74-L77):
clear()
{
this.events.length = 0;
}
clear() 直接截断内部 events 数组,使其长度归零。它有两个典型使用场景:
- 发送后复用:
sendCustomMetricEventBatch发送成功后,批次对象可以clear()后继续投入下一次使用,避免反复创建新对象带来的内存分配开销,对性能有积极意义; - 丢弃未发送数据:如果批次里已经压入了一些暂时不想发送(或已决定放弃)的指标,也可以用
clear()直接丢弃。
发送配合:TurbulenzServices.sendCustomMetricEventBatch
批次建好、填好数据之后,需要配合 TurbulenzServices.sendCustomMetricEventBatch 才能真正送达服务器。该方法本身属于 TurbulenzServices,详细文档见 turbulenzservices_api.rst 的 sendCustomMetricEventBatch 小节。
语法:
TurbulenzServices.sendCustomMetricEventBatch(eventBatch, requestHandler, gameSession);
参数说明:
eventBatch:一个CustomMetricEventBatch对象,即本指南创建与填充的批次;requestHandler:一个RequestHandler对象,负责处理请求生命周期;gameSession:一个GameSession对象,指标事件最终归属于该游戏会话;errorCallbackFn(可选):错误回调函数,未提供时使用TurbulenzServices.defaultErrorCallback(其签名与示例见 turbulenzservices_api.rst#L622-L645)。
发送链路与底层请求
从源码 turbulenzservices.ts#L778-L878 可以看到发送方法的完整执行流程:
- 可用性检查:若
TurbulenzServices.available()为假,直接回调"TurbulenzServices.sendCustomMetricEventBatch failed: Service not available"并返回; - 逐条校验:遍历批次内每个事件,检查:
eventKey必须是非空字符串,否则报"Event key must be a non-empty string";eventValue必须是数字或数字数组,数组中每个元素也必须是数字(且不能是NaN或无穷值),否则分别报"Event value must be a number or an array of numbers"与"Event value array elements must be numbers";eventTime必须是有效数字,防止开发者篡改时间戳,否则报"Event time offset is corrupted";
- 时间偏移重算:将每条事件的
timeOffset更新为eventTime - currentTime(即以发送时刻为基准的相对偏移),注释明确说明"服务端将用该偏移计算事件时间"; - 发起 POST 请求:
this.getService('customMetrics').request({
url: '/api/v1/custommetrics/add-event-batch/' + gameSession.gameSlug,
method: 'POST',
data: {'batch': events, 'gameSessionId': gameSession.gameSessionId},
callback: function sendCustomMetricEventBatchAjaxErrorCheck(jsonResponse, status) { ... },
requestHandler: requestHandler,
encrypt: true
}, 'custommetrics.addeventbatch');
关键实现事实:请求目标是 customMetrics 服务的 /api/v1/custommetrics/add-event-batch/{gameSlug} 端点,携带 batch(事件数组)与 gameSessionId,且 encrypt: true 表示请求会被加密传输;只有当 HTTP 状态码不是 200 时才会触发错误回调。对比单条发送方法 sendCustomMetricEvent(turbulenzservices.ts#L697-L776),它请求的是 /api/v1/custommetrics/add-event/{gameSlug},每次调用只携带一个事件——这正是批量版本省请求的根本原因。
上报策略与最佳实践
批次大小的选择
原文档与 sendCustomMetricEventBatch 说明给出的建议是:10~20 条事件组成一批通常是合理的,但理想数量取决于事件数据的体量(例如大数组 vs 单值)。结合 Working with HTTP requests 的指导:
- 请求头本身通常超过 512 字节,小于 1KB 的小请求应合并为较大请求;
- 单个超大请求又无法利用浏览器的多连接并发优势,因此也不宜无限增大单批;
- 需要根据自身事件的平均字节数,在"合并省开销"与"单请求过大"之间取平衡。
单条 vs 批量:按事件重要性分流
sendCustomMetricEventBatch 的文档给出了非常务实的策略建议:高重要性、低频的事件(如开始关卡、完成关卡)适合逐条立即上报;高频率的事件(如拾取道具、击杀敌人)适合批量上报。原因是:批量事件是延迟发送的,如果用户在游戏会话中途关闭浏览器,批次中尚未发送的事件可能全部丢失。因此关键业务指标应当即时上报,而高频次要指标可以承受一定的丢失风险。
发送后复用与内存管理
一个完整的批次生命周期通常是:
var customMetricEventBatch = CustomMetricEventBatch.create();
// 游戏循环中不断压入高频指标
customMetricEventBatch.push('pickupCollected', 1);
customMetricEventBatch.push('enemyKilled', 1);
customMetricEventBatch.push('timeToComplete', 43.2);
// 达到阈值或关键节点时发送
if (customMetricEventBatch.length() >= 10)
{
TurbulenzServices.sendCustomMetricEventBatch(
customMetricEventBatch, requestHandler, gameSession,
function errorCallbackFn(errorMsg, httpStatus)
{
console.log(errorMsg, httpStatus);
});
// 发送完成后清空并复用,避免重复创建对象
customMetricEventBatch.clear();
}
clear() 后批次对象的 length() 归零,可立即用于下一轮暂存,这一"创建一次、反复复用"的模式正是原文档强调的内存性能优化手段。
数据在服务端如何被消费
Hub 上的自定义指标
发送到服务端的事件会按事件键聚合,并展示在游戏站点的 Hub 指标页面。根据 Hub 用户指南的 Metrics 章节,对每个唯一的事件键,服务端按日计算三部分聚合值:
- EventKey : Count —— 事件发生次数(即更新该键的请求数);
- EventKey : Total Value —— 所有事件值的总和,平均值 = Total Value / Count;
- EventKey : Value Sum of Squares —— 各事件值的平方和,可用于计算平均值的标准差;该值不在页面显示,但包含在导出的 JSON/CSV 中。
需要注意两点限制(原文档明确标注):
- 自定义事件只记录在游戏站点上,Turbulenz Local Server 与 Hub 上不记录;
- 只有事件值为数字(而非数组)时,事件才计入自定义指标聚合;数组值的事件虽可上报,但不会参与上述 Count/Total/Sum of Squares 统计。
用 exportevents 导出原始事件
如果聚合指标不足以满足分析需求,可以用 exportevents 工具下载原始事件日志与匿名化用户信息。该工具的完整说明见 docs/source/tools/exportevents.rst,基本用法为:
exportevents [options] project
常用选项包括:--user/-u(Hub 登录用户名)、--password/-p(密码)、--type/-t(下载数据类型:events 或 users,默认 events)、--daterange/-d(日期或日期范围,格式 yyyy-mm-dd 或 yyyy-mm-dd : yyyy-mm-dd,默认今天)、--verbose/-v、--silent/-s 等。导出的原始事件为深度分析(例如按数组值分解统计)提供了数据基础。
小结
CustomMetricEventBatch 是 Turbulenz Services 中"省请求、保时间戳"的批量指标上报方案:通过 create 创建、push 入队、length 判满、sendCustomMetricEventBatch 发送、clear 复用,开发者可以用一次 HTTP 请求上报 10~20 条高频指标事件,同时每条事件仍保留入队时刻的时间戳,不丢失时间信息。使用时请依据事件的重要性决定"立即上报"还是"批量上报",并在发送后 clear() 复用批次对象以获得更好的内存表现。结合 Hub 的指标聚合视图与 exportevents 原始事件导出,即可构建完整的游戏业务数据观测闭环。
延伸阅读: