背景

app打点日志的上报和收集,是互联网公司的基本需求。

一.方案选择

1.1 protobuffer vs json

探究一种以最高效的方式上报和解析打点数据是一个系统性的问题,需要解决的子问题有很多,例如降低网络传输成本,减少序列化反序列化的性能开销,可靠性和高峰期的水平扩展,以及非耦合的编码等等。

很多公司的打点日志会采用比较简单通用的json格式来上报,比如"第四范式"的先荐系统就是使用json格式作为数据上报格式的,这样做便于开发和理解,但是从处理性能方面来考虑并不是最好的选择。

附上protobuffer和json的序列化反序列化性能评测对比: http://www.52im.net/thread-772-1-1.html

在2019年的数据库峰会上,腾讯广告联盟的负责人曾介绍了广告数据平台的原始日志格式,用的就是protobuffer,并且为了方便直接查原始数据格式,自研了一个名为dragon的数据存储格式。

实战:一种在http请求中使用protobuffer+nginx+lua收集打点日志的方案-LMLPHP

1.2  OpenResty (nginx+lua)

Nginx作为一款开源高性能且稳定的web服务器,经历了10年的发展,已经打败了Apache,IIS等巨头,成为了互联网界的新宠。

Nginx的异步非阻塞,以及模块化的特性,再加上lua脚本的轻量级的特性,让我们很方便的就能开发出一套可扩展且高可靠性的日志收集系统,开发人员只需要关注功能实现本身即可。

1.3 处理流程图

 实战:一种在http请求中使用protobuffer+nginx+lua收集打点日志的方案-LMLPHP

这里只画出了收集部分的步骤,通过Flume收集和处理日志的步骤请见我的另一篇博客:《将nginx收集的日志通过flume转到hive》

二.实现步骤

 2.1 定义日志格式

由于每个客户端5秒发送一批日志,可能会包含1条或者多条,为了防止重复发送uuid、客户端版本号等在一次发送周期中不会改变的数据,可以抽取这部分客户端公共的属性作为独立字段;而如点击、播放、翻页等非公共的属性才通过protobuf数组的形式发送。  

post日志的上传格式如下:

1) body就是事件体数组部分,每个事件单独一条数据;

2)其他的字段是可共用的公共属性部分,一批事件中这些属性相同。

3)token字段是信令字段,如果token错误,则可能是身份不明者伪造的上报数据。token的格式是(时间戳+密钥)的md5编码。密钥部分可以随意指定,客户端和服务端保持一致即可。出于安全考虑本处打码。

实战:一种在http请求中使用protobuffer+nginx+lua收集打点日志的方案-LMLPHP

 eventobj的格式定义:

event:{

'eventtype': 'sv', #事件类型

'pg': 'home' #事件发生的一级页面

'spg': 'recommend' #事件发生的二级页面

 'ts': 1527238632,  #timestamp 为事件发生的unix时间戳(+当前时区),精确到秒

 'arg': ''  # 字符串类型,每个事件对应的其他参数,可能0个或者多个,0个的为空字符串,多个的话用符号&链接。

 }

2.2 编写event.proto文件

本文中不会详细的介绍protobuffer的知识,只会针对该案例讲解操作步骤。如需要了解更多protobuffer的知识可以自行学习。

(有个比较坑的地方是工信部禁了developers.google.com,苦了找文档的各位童鞋。)

如下示例中指定了若干事件类型,若干一级页面和二级页面。文件名为event.proto。

syntax = "proto3";  //protobuff

option java_outer_classname = "EventsProtos";

message Event {

  enum T { // event type
    SCANV = 0; // sv,  scan video
    PLAYV = 1; // pv, play video
    LIKEV = 6;//lv,  like video
    CLIKEV = 7; // clv, canceld like video
    SHAREV = 8; //shv, share video
  }

  enum Pg{ // first level page type
    HOME = 0; //
    SEARCH = 1; //
    UPLOAD = 2; //
  }

  enum Spg{ // second level page type
    RECOMMEND = 0; //home
    FRESH = 1; // home
    HOT = 2; //home
  }

  T eventtype = 1;
  Pg pg = 2;
  Spg spg = 3;
  int32 ts = 4;
  string arg = 5;

}

message Events {
  repeated Event events = 1;
}

2.3 生成protobuffer客户端文件。

EventsProtos.java 为Android 端用, Events.pbobjc.h Events.pbobjc.m 为ios端用,

2.4 让OpenResty的lua模块支持protobuffer

1 mkdir /root/project/
2 mkdir /root/project/lua-protobuf
3 git clone https://github.com/starwing/lua-protobuf lua-protobuf/ 
4 cd lua-protobuf/
5 gcc -O2 -I/usr/local/openresty/luajit/include/luajit-2.1/ -fPIC -shared -Wl,-rpath=./   pb.c -o pb.so
6 cp pb.so /usr/local/openresty/lualib/
7 cp serpent.lua /usr/local/openresty/lualib/
8 cp protoc.lua /usr/local/openresty/lualib/
09-27 20:06