一、日志拆分
nginx 生成的日志 access.log 默认不会拆分,会越积累越多,导致大文件不易操作。而进行离线日志分析以及计算统计结果时,一般是计算昨天的日志。试想在这种场景下,是从 access.log 一个大文件读取方便?还是拆分的零散的文件方便?
1.1 如何拆分日志
视流量情况(流量越大日志文件积累的越快),可以按天、小时、分钟来拆分。例如,将 access.log 按天拆分到某个文件夹中,如下所示:
logs_by_day/access.2021-03-09.loglogs_by_day/access.2021-03-10.loglogs_by_day/access.2021-03-11.log
拆分日志的技术实现思路:
- nginx 依然是持续的写入
access.log; - 启动定时任务,到凌晨 00:00 ,即将当前的
access.log复制一份,并命名为access.2021-03-11.log(昨天的日期); - 将当前的
access.log内容清空。这样 nginx 持续写入access.log,即新一天的日志;
1.2 定时任务
/**通用的定时表达式规则:* * * * * *┬ ┬ ┬ ┬ ┬ ┬│ │ │ │ │ ││ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)│ │ │ │ └───── month (1 - 12)│ │ │ └────────── day of month (1 - 31)│ │ └─────────────── hour (0 - 23)│ └──────────────────── minute (0 - 59)└───────────────────────── second (0 - 59, OPTIONAL) **【注意】linux crontab 不支持秒***/
1.2.1 linux crontab
在 ~ 目录进行如下实验:
- 创建一个空文件 a.txt;
- 执行
crontab -e编辑; - 加入一行代码
* * * * * echo $(date) >> /Users/bgl/a.txt,该代码会每分钟执行一次(注意文件目录),并保存退出; - 执行
crontab -l可查看当前任务; - 观察文件
cat a.txt,会发现日期每隔一分钟被写入 a.txt 文件中。
测试完成,记得删除 crontab 配置和 a.txt 文件(注:运行crontab -e命令,把之前添加的第一行代码删除即可)。
注:linux crontab 不支持秒。
1.2.2 cron 库
二、日志分析
通过分析日志,得到统计结果,并写入数据库。
在技术方案设计之前,要考虑以下事项:
- 注意 url 参数设计,和存储数据结构的设计
- 一定要使用 **,日志文件可能很大,一次性打开可能占据很大内存
- 考虑通用性和扩展性(我们要做的是一个自定义事件统计服务,分渠道统计仅仅是自定义事件统计服务的一个场景,所以自定义事件统计服务需考虑通用性和扩展性用以满足很多场景)
2.1 技术方案设计
要做一个通用的自定义事件统计服务,而不仅仅是为了作品分渠道统计。
一个详细的技术方案,需包含以下技术点:
- 需求是什么
- 需要收集哪些信息
- 需要输出什么信息
- 如何做到
2.2 需求是什么
需要统计作品在各个渠道的pv。例如,一个 h5 页,一段时间之内的统计结果如下:
- 总 pv 100
- 渠道 A pv 30
- 渠道 B pv 20
- 渠道 C pv 50
2.3 收集统计数据
收集哪些数据
以一个作品 http://182.92.168.192:8082/p/85-8d14?channel=41 为例,需要收集
- 作品 id =>
85 - 作品渠道 id =>
41
根据 nginx 收集日志的服务,通过 http://localhost:8083/event.png?xxxx 可以上报统计数据。那么上报数据的格式应该是http://localhost:8083/event.png?workId=85&channelId=41 —— 但实际情况是不能这样做,因为这样做不具备通用使用场景,变成了定制化开发。
如何收集
要做一个通用的自定义事件统计服务,而不仅仅是为了作品分渠道统计。所以,不能按照分渠道统计的需求来设计参数。
按照通用的参数设计,可以定义四个参数(很多企业内部的事件统计服务,都是这么定义的):
- category
- action
- label
- value
参照上述通用参数设计,如果要实现分渠道统计,则URL设计如下:http://localhost:8083/event.png?category=h5&action=pv&label=85&value=41,其中 85 是作品 ID,41 是渠道号。
如果再有新需求,有两个按钮“参与”和“不参与”,想统计一下用户点击了哪个按钮,则:
- 点击“参与”则发送
?category=h5&action=someButton&label=85&value=1 - 点击“不参与”则发送
?category=h5&action=someButton&label=85&value=0
通过这种方式很方便得实现了扩展。
2.4 计算结果
计算方式采用按天计算。
如何计算
采用离线计算方式。每天凌晨定时分析昨天的日志,计算出昨天的统计结果。
离线计算,要晚于拆分日志(凌晨 0:00),拆分完再计算。
- 根据日志文件名,得到昨天的日志(一个文件,或者多个文件);
- 逐行读取日志文件,累加统计结果(操作大文件,一定要用
stream,否则可能导致内存爆满。readline是 stream 的一个具体场景,即逐行读取); - 读取完毕,输出统计结果
数据结构
统计结果的格式如下。举例:eventKey 为 h5.pv.85.41,可理解为H5作品ID为85在渠道41上的pv是30。通过这种方式,就可以计算出在各层级各粒度下的pv数据。
{eventDate: '2021-03-21', // 统计结果的日期eventKey: 'h5',eventData: { pv: 10000 } // category=h5 的数据汇总},{eventDate: '2021-03-21',eventKey: 'h5.pv',eventData: { pv: 8000 } // category=h5&action=pv 的数据汇总},{eventDate: '2021-03-21',eventKey: 'h5.pv.85',eventData: { pv: 100 } // category=h5&action=pv&label=85 的数据汇总},{eventDate: '2021-03-21',eventKey: 'h5.pv.85.41',eventData: { pv: 30 } // category=h5&action=pv&label=85&value=41 的数据汇总},{eventDate: '2021-03-21',eventKey: 'h5.pv.85.42',eventData: { pv: 20 } // category=h5&action=pv&label=85&value=42 的数据汇总},{eventDate: '2021-03-21',eventKey: 'h5.pv.85.43',eventData: { pv: 50 } // category=h5&action=pv&label=85&value=43 的数据汇总}
获取结果
输入:
- 开始日期
- 结束日期
- 各个参数 category action label value 等
输出
- 日期范围之内的,所有统计数据
三、数据库存储
本次方案采用 mongodb 数据库,按照数据结构设计如下 schema:
mongoose.Schema({eventKey: String,eventData: {pv: Number,// uv: Number, // 后续可扩展 uv},eventDate: Date,},{ timestamps: true })
四、OpenAPI
OpenAPI 不是一个新词。相比 API 而言,OpenAPI 是提供了通用的第三方 API 服务(支持多方接入),而API只是针对特定场景。
但是 OpenAPI 也不是谁都可以接入,需要进行授权以及申请后才可以使用。针对这一特性可通过跨域限制来实现。
编写白名单中间件,示例代码如下:
module.exports = options => {// To do: 后期支持从数据库取到跨域的域名return async function whiteList(ctx, next) {const { corsOrigin = '*' } = options;// 非线上环境,无跨域限制if (corsOrigin === '*') {await next();} else {// 线上环境const referer = ctx.request.header.referer || '';const originArr = corsOrigin.split(',').map(site => site.trim()).filter(site => referer.indexOf(site) > -1);if (originArr.length > 0) {await next();} else {ctx.status = 401;ctx.body = {code: '40010',message: '不在允许域名单中',};}}};};
