一种事件流埋点的实现方案

问题背景:

假定app端上有个场景,是一个有序的流程,在每个关键节点都是可能失败的。现存的方案是在每个关键节点都做一个埋点上报,有如下痛点:

  • 埋点有丢失的情况,造成漏斗分析,后面比前面多的情况
  • DA数据分析侧,不方便分析整个流程,需要查询多个事件,再做关联,SQL过于复杂
  • RD侧也不能很好的处理反馈和关注问题

埋点方案改进

方案:在app侧做整个事件流的打点,对整个事件流做一起的上报。
接口设计:整个事件流在开始节,生成id,后续这个事件流中的所有打点都记录在这个id流中,记录的顺序就是发生的顺序。暴露一个接口,提供一个默认实现,开放部分方法可重写。提供基本的方法,新建记录,插入记录,和触发上报。

数据格式: 采用json格式,大致格式如下,可自由增添加,后端用clickhouse方便处理josn数据

1
2
3
4
5
6
{
"local_id":"",
"server_id":"",
"event":[]
"extra":{}
}

问题点

事件流ID怎么确定?

由业务方指定,在newRecord的时候传入。端上的记录依赖local_id在记录产生的时候,就记录。在本地用
KV的方式存储,不过本地持久化记录的value是需要加一些状态字段的,标记这条记录的状态。server_id是因为这个事件流在后面的某个阶段才会产生一个关联的id,在后端关联查询,例如的场景就是直播连麦主播和嘉宾的链接事件关联,就可以用这个抽象的server_id

数据的一致性?

同一个事件流可能是在多个线程里面操作的, 某个线程get一次record(json)之后会,做个修改之后,会将这个更新保存下来。会存在一种情况,后面的提交修改可能会被前面的覆盖的情况。

为了解决这个问题,我们的整体的思路是一个单线程模型,安卓里面使用HanderThread,所有的操作都是在一个线程里面提交,但是也是会有覆盖的情况,因为多线程操作,它的get和set可能是分离的。如果我们可以保证每个线程里面的操作get和set是连续的,那么就能解决这个问。既然要get和set是连续的,最简单的办法就是单线程中这2个操作是连续的就可以了,所以我们的模型规定了2个模型,一个是简单的获取值,这个接口可以是同步的或异步的返回都行,不涉及值的修改。如果是涉及值的修改,只提供异步的接口,单线程队列里面去处理回调,调用方,在回调里面进行值的修改。

1
2
func sync getRecord(recordId:String):JSON
func async editRecord(recordId:String,callback:JSON)

例如一个线程post了一个修改,在消息队列处理这个消息的时候,也就是回调里面拿到的record就是最新的,此刻这个线程被这个消息占有,可以在回调里面做对这个record的各种修改,在回调结束的时候,自动做保存。这些都我们框架设计的能力,业务调用方,只需要在回调里面写自定义的修改逻辑就可以了,可以不感知具体的实现。

自定义JSON的merge

一个record的更新,其实是一个json的更新,所以这里需要定义json的合并规则,如果是数组类型的,就追加在后面,或者就是后面的替换前面的。

如何保证埋点不丢失?

可以通过以下手段来保证记录的正确的,尽量多的上报到server做统计,
app突然被kill?记录怎么办?利用的是mmap,把磁盘映射成内存的来访问,访问快,并且利用操作系统的机制,数据会尽可能的保留,计算app被突然kill,我们也可以获得一些有用信息,做问题分析。

  • 首先灵活的配置方式主动触发上报的方式。利用record的时机场景,支持配置超时。例如这个事件流,正常的情况也就最多1分钟,超过1分钟,就主动触发上报。当然也支持业务调用主动触发上报。
  • 轮训时间间隔,检查本地记录是否可以上报。
  • app启动的时候,支持定制的延时上报本地的记录。
  • 添加日期过期机制,丢掉无用的数据,减少server分析压力。