[关闭]
@leptune 2017-03-02T07:07:56.000000Z 字数 13615 阅读 10578

统计系统交接

唯中科技


目录

1. 需求

1.1 目的

从海量的业务日志中,挖掘出统计数据。

1.2 举例

从下列原始数据
doanload
要加工得到:
doanload
这就是统计系统做的事!

2. 存储架构

2.1 架构图

doanload

2.2 原始数据集结构

用redis的List(列表)来存储,一个api对应一个列表。api有:

doanload

2.3 分析数据集结构

用redis的Hash(哈希表)来存储,一个task(任务)对应一个哈希表。任务集有:

doanload
doanload
doanload

3. 总体流程时序图

3.1 写入

img

3.2 导入旧数据

img

3.3 分析历史数据

img

3.4 分析实时数据

img

3.5 Web展示分析

img

4. 代码结构

  1. ├── ApiServer // 统计底层相关
  2.    ├── backup // 日志备份目录
  3.       ├── ... // 众多日志
  4.    ├── data
  5.       ├── cronfile // 计划任务,通过crontab cronfile命令来装载任务
  6.       ├── GeoIP.conf // 用来获取ip地址库的配置文件
  7.       ├── GeoLite2-City.mmdb // ip国家库
  8.       └── GeoLite2-Country.mmdb // ip城市库
  9.    ├── pid // 众多进程的pid文件(暂时没用到,后续可以用于监控进程)
  10.       ├── ... // pid文件
  11.    ├── log // 今日日志(众多进程的,如实时分析进程,分析历史数据进程,校验数据进程等)
  12.       ├── ... // 众多日志
  13.    ├── process // 第三方工具安装文件(方便迁移时可以直接使用)
  14.       ├── geoipupdate // 更新ip地址库
  15.          ├── ...
  16.       ├── mmonit // 监控工具WEB界面(暂时尚未布置)
  17.          ├── ...
  18.       └── monit // 监控工具(暂时尚未布置)
  19.       ├── ...
  20.    ├── src // 【统计核心目录】--统计源码!
  21.       ├── Base // 顶级文件
  22.          ├── Base.php
  23.          ├── ConsumerBase.php
  24.          ├── ConsumerImportBase.php
  25.          └── TasksBase.php
  26.       ├── Common // 公共文件
  27.          └── config.php
  28.       ├── Consumer // 消费者
  29.          ├── ConsumerHistory.php // 分析历史数据
  30.          └── ConsumerRealTime.php // 分析实时数据
  31.       ├── GetApiFuncs // 提供给open平台、推广平台等的Api
  32.          ├── ...
  33.       ├── Hooks // 没用到
  34.          └── Hooks.php
  35.       ├── Import // 导入历史数据
  36.          ├──...
  37.       ├── Org // 第三方插件
  38.          ├── ...
  39.          └── Sync.php // redis里的预定义变量
  40.       ├── Producter // 生产者
  41.          ├── GetApi.php // 读取数据的Api处理
  42.          └── Producter.php // Api顶级文件,接收客户端请求
  43.       ├── Scheduled // 计划任务程序
  44.          └── check_and_fix.php // 定时校验并修复数据
  45.       └── Tasks // 统计任务集
  46.       ├── submit_download // 下载
  47.          ├── ...
  48.       ├── submit_login // SDK登录
  49.          ├── ...
  50.       ├── submit_online_time // 在线时长
  51.          ├── ...
  52.       ├── submit_order // SDK订单
  53.          ├── ...
  54.       ├── submit_order_other // SDK订单的补单、退单等
  55.          ├── ...
  56.       ├── submit_regist // 说玩注册_SDK
  57.          ├── ...
  58.       ├── submit_search // 搜索
  59.          ├── ...
  60.       ├── submit_shuowan_game_role // SDK注册
  61.          ├── ...
  62.       ├── submit_shuowan_login // 说玩登录
  63.          ├── ...
  64.       └── submit_shuowan_regist // 说玩注册
  65.          ├── ...
  66.    ├── test // 测试文件目录(只测试用)
  67.       ├── ...
  68.    └── tools // 【统计核心目录】--维护统计项目脚本(密码:查看check.sh文件)
  69.    ├── auto_reload.sh // 【计划任务】--自动重载php-fpm(防止其内存溢出)(每分钟执行)
  70.    ├── backup_log.sh // 【计划任务】--备份日志(每天凌晨)
  71.    ├── restartApi.sh // 【计划任务】--重新分析某API的全部数据
  72.    ├── restartImport.sh // 【计划任务】--重新导入某API的历史数据
  73.    ├── sync_geolite_date.sh // 【计划任务】--定时更新ip地址库(每周一凌晨1点执行)
  74.    ├── check.sh // 【辅助脚本】--脚本环境校验,用于其他脚本调用
  75.    ├── restartConsumeRealTimeProcess.sh // 【辅助脚本】--重启实时分析进程(更改实时分析代码或统计任务时,必须运行此脚本)
  76.    ├── restartTask.sh // 【辅助脚本】--重新分析某任务
  77.    ├── run_consumer.sh // 【辅助脚本】--运行某php程序为守护进程
  78.    ├── count.sh // 【无需了解】--统计redis某列表类型长度
  79.    └── update_json.sh // 【无需了解】--统计API文档
  80.    ├── init.sh // 【慎用!】--初始化统计系统
  81.    ├── restart.sh // 【慎用!】--重新分析所有统计任务
  82. ├── App // 【统计核心目录】--统计后台管理
  83.    ├── ... // Thinkphp框架文件
  84. ├── bower_components // 前端插件
  85.    ├── ...
  86. ├── bower.json // 前端bower管理插件配置文件
  87. ├── crossdomain.xml // 跨域策略文件
  88. ├── favicon.ico // 网站图标
  89. ├── index.php // TP入口文件
  90. ├── Libs // TP底层
  91.    ├── ...
  92. ├── Public // TP public文件
  93.    ├── ...
  94. ├── robots.txt // 防止百度等蜘蛛文件
  95. ├── .htaccess // nginx重定向路由文件
  96. ├── static // 资源文件
  97.    ├──...

5. 用到的技术

6. 用到的账号

  • 数据库:请查看ApiServer/src/Common/config.php文件
  • 统计后台:http://admin.dbtrix.com admin 密码查看ApiServer/tools/check.sh文件
  • 统计终端:请联系运维获知
  • git仓库地址:ssh://gituser@125.208.12.83:1283/data/git/tongji (密码请联系运维获知)

7. 统计任务管理

7.1 增加统计任务

  • 假设给SDK订单增加个统计任务,要统计每天每个游戏的失败订单量及失败订单额

7.1.1 增加任务代码

首先,在`ApiServer/src/Tasks/submit_order`文件夹增加`TasksFail.php`文件
  1. <?php
  2. namespace submit_order; // 哪个api的任务就写那个api
  3. require_once __DIR__ . "/../../Base/TasksBase.php"; // 引入base文件
  4. class TasksFail extends \TasksBase { // 请保证类名和文件名一致!
  5. public function execute() { // 任务入口函数,每个任务文件都必须有该函数!
  6. return $this->_execute(function ($data) { // 调用base文件的_execute函数,来遍历要统计的数据
  7. if ($data["pay_status"] == 0) { // 失败的订单
  8. $fields = []; // $fields数组为要自增的数据集
  9. $fields["{$data['gameid']}:count"] = 1; // 每来一笔失败订单就自增1,统计订单数
  10. $fields["{$data['gameid']}:amount"] = $data['amount']; // 每来一笔失败订单就自增amount,统计订单额
  11. return $fields;
  12. }
  13. return false; // 如果不是失败的,则不处理这条数据
  14. }, true);
  15. // _execute函数接收三个参数:
  16. // 【$func】:回调函数
  17. // 【$inCreByFloat】:是否以浮点数模式自增,默认false
  18. // 【$timeFormat】:统计任务集名字后缀的时间格式,默认'Ymd'(即统计集按每天划分)
  19. }
  20. }

7.1.2 生成统计数据

  1. cd /data/www/wztj # 切换到统计目录
  2. sudo chmod 0777 -R . # 更改文件权限
  3. sudo chown www:www -R . # 更改文件拥有者
  4. php ApiServer/src/Consumer/ConsumerHistory.php submit_order TasksFail # 生成统计数据

7.1.3 确认统计数据有无生成

  1. redis-cli keys '*:submit_order:TasksFail:*' # 确认有无生成统计数据
  2. redis-cli hgetall analyze:submit_order:TasksFail:20160725 # 查看某天的统计数据

7.1.4 在统计后台页面上展示

7.1.4.1 增加前台代码

在`App/Admin/View/Analyze/order.html`文件中的`<script></script>`里在最后一行加入:
  1. // drawEs函数是封装的用于生成统计图表的函数
  2. drawEs({
  3. type:'table', // 图表类型,详见static/js/draw_es.js的genEsOption函数
  4. id:'fail_count', // 后台Controller对应的函数名
  5. name:'失败订单分析', // 统计图表名字
  6. cols:12, // 统计图表宽度
  7. });
保存后,刷新页面,打开统计后台的`分析图表-SDK订单分析`最下面,就会出现`失败订单分析`了

7.1.4.2 给图表添加数据

在`App/Admin/Controller/AnalyzeOrderController.class.php`文件的最下面增加代码:
  1. public function fail_count() {
  2. $Ymd = date('Ymd', strtotime(I('get.Ymd', date('Y-m-d')))); // 默认今天
  3. $data = $this->redis->hGetAll("analyze:{$this->api}:TasksFail:{$Ymd}"); // 取出统计数据
  4. $res = [];
  5. foreach ($data as $key => $count) {
  6. // key格式为:gameid:count或gameid:amount
  7. list($gameid, $type) = explode(':', $key);
  8. $res[$gameid][$type] += $count;
  9. }
  10. $gameid2name = $this->sdkGameId2Name(array_keys($res)); // 从redis缓存里获取gameid对应的游戏名,不从缓存获取也可,可以直接查数据库
  11. foreach ($res as $gameid => $d) {
  12. $res[$gameid]['game_name'] = $gameid2name[$gameid]; // 获取游戏名
  13. }
  14. // 此为统计图表table所需的表结构,含义分别为:
  15. // name => 字段名
  16. // desc => 字段描述
  17. // sorttype => 排序方式
  18. // width => 该字段要显示的宽度
  19. $model = [
  20. ['name' => 'game_name' , 'desc' => "游戏名" , 'sorttype' => 'text' , 'width' => '30'] ,
  21. ['name' => 'count' , 'desc' => '交易量' , 'sorttype' => 'number' , 'width' => '15'] ,
  22. ['name' => 'amount' , 'desc' => '交易额' , 'sorttype' => 'number' , 'width' => '15'] ,
  23. ];
  24. return $this->ajaxReturn(['status' => 200, 'data' => [
  25. 'data' => array_values($res), // 统计数据
  26. 'model' => $model,
  27. ]]);
  28. }
好了,现在刷新页面看看,已经有数据了!至此,增加一个统计任务的流程已结束!

7.2 修改统计任务

  • 下面,就修改上面增加的统计任务,让失败订单分析统计增加渠道名输出,并增加搜索渠道名功能吧!

7.2.1 去除老的任务数据

  1. # 先把该任务从实时任务集里给移除
  2. redis-cli srem tasks:submit_order TasksFail
  3. # 先确认要删除的key(每个任务集都会生成个isEnd:history:submit_order:TasksFail类似的任务,用来检测php ApiServer/src/Consumer/ConsumerHistory.php submit_order TasksFail该命令是否结束,此任务必须删除)
  4. r keys '*submit_order:TasksFail*'
  5. # 删除任务遗留数据(注:得先知道你写的任务会生成哪些统计key,然后再谨慎删除!)
  6. r del `r keys '*submit_order:TasksFail*'`

7.2.2 修改任务代码

将上面的`ApiServer/src/Tasks/submit_order/TasksFail.php`修改如下:
  1. <?php
  2. namespace submit_order;
  3. require_once __DIR__ . "/../../Base/TasksBase.php";
  4. class TasksFail extends \TasksBase {
  5. public function execute() {
  6. return $this->_execute(function ($data) {
  7. if ($data["pay_status"] == 0) {
  8. $fields = [];
  9. // 将下面两行改为如下:(加入了渠道标识,好展示渠道名)
  10. $fields["{$data['chan_no']}:{$data['gameid']}:count"] = 1;
  11. $fields["{$data['chan_no']}:{$data['gameid']}:amount"] = $data['amount'];
  12. return $fields;
  13. }
  14. return false;
  15. }, true);
  16. }
  17. }

7.2.3 修改后台代码

  1. public function fail_count() {
  2. $Ymd = date('Ymd', strtotime(I('get.Ymd', date('Y-m-d'))));
  3. $searchChanNo = I('get.key'.__function__.'0_id', NULL);
  4. $data = $this->redis->hGetAll("analyze:{$this->api}:TasksFail:{$Ymd}");
  5. $res = [];
  6. // 增加下面两行
  7. $gameids = [];
  8. $chanNos = [];
  9. // 修改此foreach为:
  10. foreach ($data as $key => $count) {
  11. // key格式为:chan_no:gameid:count或chan_no:gameid:amount
  12. list($chanNo, $gameid, $type) = explode(':', $key);
  13. if ($searchChanNo && $chanNo != $searchChanNo) {
  14. continue;
  15. }
  16. $res["{$chanNo}:{$gameid}"][$type] += $count;
  17. $gameids []= $gameid;
  18. $chanNos []= $chanNo;
  19. }
  20. // 修改下面两行为:
  21. $gameid2name = $this->sdkGameId2Name($gameids);
  22. $channo2name = $this->channelId2Name($chanNos);
  23. // 修改此foreach为:
  24. foreach ($res as $key => $d) {
  25. list($chanNo, $gameid) = explode(':', $key);
  26. $res[$key]['channel_name'] = $channo2name[$chanNo];
  27. $res[$key]['game_name'] = $gameid2name[$gameid];
  28. }
  29. $model = [
  30. // 增加下面一行
  31. ['name' => 'channel_name' , 'desc' => "渠道名" , 'sorttype' => 'text' , 'width' => '30'] ,
  32. ['name' => 'game_name' , 'desc' => "游戏名" , 'sorttype' => 'text' , 'width' => '30'] ,
  33. ['name' => 'count' , 'desc' => '交易量' , 'sorttype' => 'number' , 'width' => '15'] ,
  34. ['name' => 'amount' , 'desc' => '交易额' , 'sorttype' => 'number' , 'width' => '15'] ,
  35. ];
  36. return $this->ajaxReturn(['status' => 200, 'data' => [
  37. 'data' => array_values($res),
  38. 'model' => $model,
  39. 'key0' => $this->genKeyword($channo2name),
  40. ]]);
  41. }

7.2.4 重新生成统计数据

  1. cd /data/www/wztj # 切换到统计目录
  2. php ApiServer/src/Consumer/ConsumerHistory.php submit_order TasksFail # 生成统计数据

7.2.5 修改前端代码

修改`App/Admin/View/Analyze/order.html`中的该任务代码为:
  1. drawEs({
  2. type:'table',
  3. id:'fail_count',
  4. name:'失败订单分析',
  5. cols:12,
  6. // 增加下面一行
  7. search:[{name:'渠道', col:3}],
  8. });
好了,刷新页面,就会发现成功了!
以上,成功修改了该统计任务!

7.3 删除统计任务

  • 下面,就把上面增加的统计任务给删除吧!
  1. # 先把该任务从实时任务集里给移除
  2. redis-cli srem tasks:submit_order TasksFail
  3. # 删除任务代码文件(若不删除,则运行restartApi.sh脚本时,又会自动添加该任务)
  4. rm ApiServer/src/Tasks/submit_order/TasksFail.php
  5. # 先确认要删除的key(每个任务集都会生成个isEnd:history:submit_order:TasksFail类似的任务,用来检测php ApiServer/src/Consumer/ConsumerHistory.php submit_order TasksFail该命令是否结束,此任务必须删除)
  6. r keys '*submit_order:TasksFail*'
  7. # 删除任务遗留数据(注:得先知道你写的任务会生成哪些统计key,然后再谨慎删除!)
  8. r del `r keys '*submit_order:TasksFail*'`
然后,把上面前端和后端的该任务对应的代码也注释或删除
以上,成功删除了该统计任务!

8. 计划任务

  • 计划任务存储在ApiServer/data/cronfile
  • 之所以每天凌晨3点重新导入并分析说玩注册、说玩注册_SDK、SDK注册这三个数据,是因为这三个在用户中心存有原始数据,同步可以保证用户中心数据和统计这边的数据一致!
  1. # 每分钟检查php-fpm是否超过所占内存
  2. * * * * * source /etc/profile;$API_ROOT_PATH/tools/auto_reload.sh
  3. # 每5分钟从订单表导入数据
  4. */5 * * * * source /etc/profile;$API_ROOT_PATH/tools/run_consumer.sh Import/ConsumerImportOrder 1
  5. */5 * * * * source /etc/profile;$API_ROOT_PATH/tools/run_consumer.sh Import/ConsumerImportOrderOther
  6. # 每周一凌晨1点同步IP地址数据
  7. 0 1 * * 1 source /etc/profile;$API_ROOT_PATH/tools/sync_geolite_date.sh
  8. # 备份统计代码生成的日志数据
  9. 0 0 * * * source /etc/profile;$API_ROOT_PATH/tools/backup_log.sh
  10. # 修正数据
  11. 0 2 * * * source /etc/profile;$API_ROOT_PATH/tools/run_consumer.sh Scheduled/check_and_fix
  12. # 重新导入并分析订单数据
  13. 0 3 * * * source /etc/profile;$API_ROOT_PATH/tools/restartImport.sh submit_order ConsumerReImportOrder nopassword
  14. # 重新导入并分析说玩注册数据
  15. 15 3 * * * source /etc/profile;$API_ROOT_PATH/tools/restartImport.sh submit_shuowan_regist ConsumerImportShuowanRegist nopassword
  16. # 重新导入并分析说玩注册_sdk数据
  17. 30 3 * * * source /etc/profile;$API_ROOT_PATH/tools/restartImport.sh submit_regist ConsumerImportRegist nopassword
  18. # 重新导入并分析SDK注册数据
  19. 45 3 * * * source /etc/profile;$API_ROOT_PATH/tools/restartImport.sh submit_shuowan_game_role ConsumerImportShuowanGameRole nopassword

9. 如何裸环境中搭建统计系统

  • 环境: CentOS release 6.5 x86_64
  • 硬盘要求:>=500G
  • 内存要求:>=16G

9.1 先下载部署脚本

9.2 脚本上传或黏贴到服务器

  • 假设上传到服务器的脚本名为deploy.sh
  • 执行如下命令:
  1. # 部署环境
  2. sh deploy.sh
  3. # 注:下面命令必须在内存>=10G时才能执行,否则死机
  4. sh /data/www/wztj/ApiServer/tools/init.sh

10. 统计项目Redis Key设计

10.1 带有api,后缀为日期的

10.2 带有api,没有日期后缀的

10.3 固定的key

10.4 查看redis里除上面的key外,还有没其他key

  1. r keys '*'|ag -v '^post'|ag -v '^analyze'|ag -v '^smids'|ag -v '^mids'|ag -v '^tasks'|ag -v '^isEnd'|ag -v '^lastSyncId'|ag -v '^beforeRunTasks'|ag -v '^id2name'|ag -v '^const'|ag -v '^convert'|ag -v '^other'|ag -v 'search:submit_online_time'

11. 常见问题处理

11.1 先理解输入输出

  • 万变不离其宗,只要明白项目的输入输出,便一切了然。先理解项目的输入输出,便可自行处理这边提到的一切问题。
  • 将输入输出整理如下:
  1. 各客户端日志 --> redislist --> mysqlredis的哈希表等 --> web后台或api查询
所有的项目里的代码,都不过是将其中某个输入,加工成某个输出而已。
下面,就举些常见的问题,来说明一下:

11.2 让SDK在线时长也统计老用户?

  • 现在项目中有做过滤,只统计今天的新增用户。那如果后续需求变化,也要统计老用户呢?要完成此需求,需要先从最原始的输入开始考虑。
  • 首先,SDK的在线时长接口有传过来老用户数据,所以这边输入不用改。
  • 然后,接口数据是传向ApiServer/src/Producter/Producter.php,查看里面的代码,发现不管新老用户,在线时长数据都有存进redis的list,所以这边的输入也不用改。
  • 接着,存进redis里的list数据被php /data/www/wztj/ApiServer/src/Consumer/ConsumerRealTime.php submit_online_time进程读取,查看此php文件, 跟踪代码的数据流向,便可以在ApiServer/src/Base/ConsumerBase.php中发现如下代码:
  1. if (!$this->redis->sIsMember("analyze:submit_shuowan_game_role:TasksUsernameGameid:{$tmpYmd}", "{$data['username']}:{$data['gameid']}")) { // 非今天新增用户
  2. unset($this->datas[$key]);
  3. }
  • 然后将此代码屏蔽!因php /data/www/wztj/ApiServer/src/Consumer/ConsumerRealTime.php submit_online_time该进程是启动时将代码导入内存中的,不会实时刷新代码,所以需要重新启动该进程,运行如下命令即可:
  1. cd /data/www/wztj && ApiServer/tools/restartConsumeRealTimeProcess.sh submit_online_time
  • 该命令会杀掉该进程后,清理日志,然后重启该进程,并让该进程成为守护进程,并日志文件记录在ApiServer/log/下。
  • 现在,客户端进来的sdk在线时长数据,都可以进行统计了。但还有个问题,那就是以前的老用户数据没进行统计。所以,接下来需要重新统计历史数据,运行如下命令即可:
  1. # 密码请查看`ApiServer/tools/check.sh`文件
  2. cd /data/www/wztj && $API_ROOT_PATH/tools/restartApi.sh submit_online_time log_sdk_online_time
  3. # 如需查看导入进度,则运行此命令来实时显示导入日志:
  4. tail -f ApiServer/log/ConsumerHistorysubmit_online_timeTasks*
  5. # 如果想知道导入结束是否结束,则运行下面命令,如输出为空,则结束
  6. st|grep -v ConsumerRealTime|grep submit_online_time
  7. # 备注:st为/etc/profile里自定义的函数,详请查看此文件的函数定义
  8. # 如果想知道导入过程中是否有错误发生,则运行:
  9. cat ApiServer/log/ConsumerHistorysubmit_online_timeTasks* | less
  10. # 然后在里面查找"php"字符串
  • 以上,成功的让sdk在线时长加入了统计老用户功能!!

11.3 数据不准确?

  1. 先通过mysql里的数据用sql语句进行查询
  2. 如结果和统计一样,那么就是源数据错了,此时再追踪源头,看redis里list存储的源数据有无错,每个api对应的redis里的源数据key格式为:【post:{api}:{date}】或【post:{api}:{date}_backup】
  3. 如结果和统计不一样,那么就是统计代码错了,此时只需到该统计对应的代码中,进行调试,便可知道哪一行错了,修正即可。然后请参考 修改统计任务 ,进行修改即可。

11.4 修改Producter.php文件,但没生效?

  • 运行下面命令重启Producter即可:
  1. kill -9 `st|ag pro|awk '{print $1}'`; ApiServer/tools/run_consumer.sh Producter/Producter

11.5 原始数据需更新?

  • 如订单表,以前的数据要更新,如更新agent之类的,这时,只能重新导入并生成统计数据。
  • 重新导入并生成该api所有任务的统计数据,必定是一件麻烦的事的,所以有写脚本来处理。
  • 所有api的重新导入并生成统计数据的脚本命令如下:
  • (注:只有客户端有备份原始数据的api才可以重新导入,故只有下面4个支持重新导入)
  1. # 重新导入并分析订单数据
  2. $API_ROOT_PATH/tools/restartImport.sh submit_order ConsumerReImportOrder
  3. # 重新导入并分析说玩注册数据
  4. $API_ROOT_PATH/tools/restartImport.sh submit_shuowan_regist ConsumerImportShuowanRegist
  5. # 重新导入并分析说玩注册_sdk数据
  6. $API_ROOT_PATH/tools/restartImport.sh submit_regist ConsumerImportRegist
  7. # 重新导入并分析SDK注册数据
  8. $API_ROOT_PATH/tools/restartImport.sh submit_shuowan_game_role ConsumerImportShuowanGameRole

11.6 其余的常见问题请查看 统计任务管理

12. 统计交接文档至此结束

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注