注: 统计都是要在当日凌晨统计前一日的数据,所以当日凌晨统计的时候 基准日期应当为前一日的日期 也就是说统计的是 前一日的留存数据 而不是今日凌晨的 今日凌晨的需明日才能统计到。
例如2014-4-11日的3日留存率 今日只是把空数据写入表 然后等日后来更新 并不是当天来统计 而是2014-4-14日再来更新今日的留存率(还在登录的人数)
涉及的统计表
| dgs_remain | CREATE TABLE `dgs_remain` ( `auto_id` int(11) NOT NULL AUTO_INCREMENT, `reg_num` int(11) NOT NULL DEFAULT '0' COMMENT '每日新注册用户', `statistics_time` int(11) NOT NULL DEFAULT '0' COMMENT '统计时间', `statistics_date` int(11) NOT NULL DEFAULT '0' COMMENT '统计日期', `second_day_num` smallint(6) DEFAULT NULL COMMENT '次日留存原数据(当前日期次日还在登录的人数)', `third_day_num` smallint(6) DEFAULT NULL COMMENT '三日留存原数据,当前日期后三日还在登录的人数', `seven_day_num` smallint(6) DEFAULT NULL COMMENT '七日留存原数据,当前日期后七日还在登录的人数', `thirty_day_num` smallint(6) DEFAULT NULL COMMENT '三十日留存原数据,当前日期后三十日还在登录的人数', `platform_id` int(11) NOT NULL COMMENT '平台ID', `dist_id` int(11) NOT NULL COMMENT '服务器ID', `fifty_day_num` smallint(6) DEFAULT NULL COMMENT '15日留存,当前日期后15日还在登录的人数', PRIMARY KEY (`auto_id`), UNIQUE KEY `UIndex_1` (`statistics_date`,`platform_id`,`dist_id`) USING BTREE ) ENGINE=MyISAM AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 |
放在游戏服务端的守护进程:
<?php /** * Created by PhpStorm. * User: buhuan * Date: 14-4-11 * Time: 下午1:46 * 给GM端推留存率的数据 */ namespace Application\Process; use Application\Core\ProcessBase; use Application\Core\Pusher; use Application\Interfaces\IProcess; class Retention extends ProcessBase implements IProcess{ private $diff = 60;//时间间隔 分钟 private $push_hour = 0;//推送的小时 0就是0点推送 /** * 开始执行命令。 */ function execute() { print '留存统计守护进程启动[pid:'.getmypid().']@'.date('Y-m-d H:i:s').'|检测间隔为'.$this->diff.'分钟,推送间隔为1天'.chr(10); $timediff = $this->diff * 60; // TODO: Implement execute() method. while(1){ $hour = intval(date('H')); if($hour == $this->push_hour){//如果是$push_hour点 则开始发送数据 $time = time(); $current_time = $time-24*60*60;//计算的基准时间 $start_datetime = date('Y-m-d',$current_time).' 00:00:00'; $end_datetime = date('Y-m-d',$current_time).' 23:59:59'; //查询当前前一天的注册人数 $sql = "SELECT COUNT(user_id) as reg_num FROM user_info_ext WHERE user_active_time > '$start_datetime' AND user_active_time < '$end_datetime'"; $d = $this->scope->db->fetch($sql); $reg_num = $d['reg_num']; //查询当前前一天登录的用户 $sql = "SELECT user_id FROM user_info_ext WHERE user_last_login_time > '$start_datetime' AND user_last_login_time < '$end_datetime' "; $d = $this->scope->db->fetchAll($sql); $login_users = $d; $reg_nums = array(); $reg_nums['day1'] = $this->nDayUsers(1,$current_time); $reg_nums['day3'] = $this->nDayUsers(3,$current_time); $reg_nums['day7'] = $this->nDayUsers(7,$current_time); $reg_nums['day15'] = $this->nDayUsers(15,$current_time); $reg_nums['day30'] = $this->nDayUsers(30,$current_time); $data = array( 'currentTime' => $time, 'reg_num' => $reg_num, 'reg_nums' => $reg_nums, 'login_users' => $login_users ); Pusher::instance($this->scope)->doPushStat(106,$data); } sleep($timediff); } } /** * @param int $n 1=1日 3=3日。。。 * @param int $current_time 基准时间 * @return array */ private function nDayDate($n,$current_time){ $diff = 24*60*60; $start_datetime = date('Y-m-d',$current_time-$n*$diff).' 00:00:00'; $end_datetime = date('Y-m-d',$current_time-$n*$diff).' 23:59:59'; $date = array(); $date[0] = $start_datetime; $date[1] = $end_datetime; return $date; } /** * 得到基准日期前第n日的注册用户集合 * @param $n * @param $current_time * @return array */ private function nDayUsers($n,$current_time){ $dates = $this->nDayDate($n,$current_time); //查询次日注册用户 $sql = "SELECT user_id FROM user_info_ext WHERE user_active_time > '{$dates[0]}' AND user_active_time < '{$dates[1]}'"; return $this->scope->db->fetchAll($sql); } }
综合服务端的接口 用来接收游戏端发送过来的数据如下面的&$data :
<?php /** * Created by PhpStorm. * User: buhuan * Date: 14-4-11 * Time: 上午11:31 * 留存 */ namespace Apps\Process\GearmanWorker; use Apps\Common\Retention; use Apps\Interfaces\IGearmanWorker; use Apps\Process\GearmanWorkerBase; class RetentionStatistics extends GearmanWorkerBase implements IGearmanWorker{ /** * 释放资源。 */ function dispose() { // TODO: Implement dispose() method. } /** * 处理任务数据。 * * @param string $data 指定任务数据对象引用。 * @param int $p 指定平台ID。 * @param int $s 指定服务器ID。 */ function execute(&$data, $p, $s) { $currentTime = intval($data['currentTime'])-24*60*60; $reg_num = $data['reg_num']; $reg_nums = $data['reg_nums']; $login_users = $data['login_users']; // TODO: Implement execute() method. Retention::instance($this->scope) ->setCurrentTime($currentTime) ->setDist_id($s) ->setPlatform_id($p) ->setRegNum($reg_num) ->setRegNums($reg_nums) ->setLoginUsers($login_users) ->createData() ->setRetentionData(1) ->setRetentionData(3) ->setRetentionData(7) ->setRetentionData(15) ->setRetentionData(30); } }
以上需要用到的留存率的统计类:
<?php namespace Apps\Common; use Application\Base\Base; use Application\Core\Console; use Application\Core\DbPdo; /** * Created by PhpStorm. * User: buhuan * Date: 14-4-11 * Time: 上午10:12 * 留存率 */ class Retention extends Base{ /** * @var 当前基准日期时间戳 */ private $current_time = 0; /** * @var array(array('user_id'=>1),array('user_id'=>2)) 当前基准日期时间戳登录的用户(包括user_id) */ private $login_users = NULL; private $platform_id = 0; private $dist_id = 0; /** * @var int 当前基准日期的注册数 */ private $reg_num = 0; /** * @var array 统计其他天数的注册用户数组 * @struct array( * 'day1' => array(array('user_id'=>1),array('user_id'=>2)) //前一日 * 'day3' => array(array('user_id'=>1),array('user_id'=>2)), //前三日 * 'day7' => array(array('user_id'=>1),array('user_id'=>2)), //前七日 * 'day15' => array(array('user_id'=>1),array('user_id'=>2)), //前15日 * 'day30' => array(array('user_id'=>1),array('user_id'=>2)) //前三十日 * ... * ) */ private $reg_nums = NULL; public static function instance($scope){ static $_instance; if($_instance == NULL){ $_instance = new Retention($scope); } return $_instance; } function __construct($scope){ parent::__construct($scope); } public function setCurrentTime($param){ $this->current_time = $param; return $this; } public function setLoginUsers($param){ $this->login_users = $param; return $this; } public function setPlatform_id($param){ $this->platform_id = $param; return $this; } public function setDist_id($param){ $this->dist_id = $param; return $this; } public function setRegNum($param){ $this->reg_num = $param; return $this; } public function setRegNums($param){ $this->reg_nums = $param; return $this; } /** * 创建当天的统计数据 */ public function createData(){ $this->checkProperties(); $current_date = strtotime(date('Y-m-d',$this->current_time)); $sql = "INSERT INTO dgs_remain (reg_num,statistics_time,statistics_date,dist_id,platform_id) VALUES ($this->reg_num,$this->current_time,$current_date,$this->dist_id,$this->platform_id)"; if($this->scope->db->execute($sql,NULL,DbPdo::SQL_TYPE_INSERT)) Console::debug('当前基准时间'.date('Y-m-d',$this->current_time).'统计数据写入成功'); return $this; } /** * @param $nDay int N日留存 1日=1 3日=3 .... */ public function setRetentionData($nDay){ $this->checkProperties(); $daytime = 24*60*60; //===================================================N日留存 $ratio_day = 0; $date = date('Y-m-d',$this->current_time-$daytime*$nDay);//N日的日期 $time = strtotime($date);//$nDay日的时间戳 //查找当前日期前$nDay日的新增注册 $sql = "SELECT reg_num FROM dgs_remain WHERE statistics_date = $time"; $d = $this->scope->db->fetch($sql); $login_users_again = 0;//$nDay日还在登录的用户数 if(isset($d['reg_num'])){//如果dgs_remain中有当前日期前$nDay日的数据的话 则进行前$nDay日的$nDay日留存更新 //查找当前日期前$nDay日注册的用户数组 if(!empty($this->login_users) && !empty($this->reg_nums)){ $active_users = $this->reg_nums['day'.$nDay]; foreach($this->login_users as $login){ foreach($active_users as $active){ if($login['user_id'] == $active['user_id']){ $login_users_again++; break 1; } } } } } switch($nDay){ case 1: $sql = "UPDATE dgs_remain SET second_day_num = $login_users_again WHERE statistics_date = $time and platform_id = $this->platform_id and dist_id = $this->dist_id"; break; case 3: $sql = "UPDATE dgs_remain SET third_day_num = $login_users_again WHERE statistics_date = $time and platform_id = $this->platform_id and dist_id = $this->dist_id"; break; case 7: $sql = "UPDATE dgs_remain SET seven_day_num = $login_users_again WHERE statistics_date = $time and platform_id = $this->platform_id and dist_id = $this->dist_id"; break; case 15: $sql = "UPDATE dgs_remain SET fifty_day_num = $login_users_again WHERE statistics_date = $time and platform_id = $this->platform_id and dist_id = $this->dist_id"; break; case 30: $sql = "UPDATE dgs_remain SET thirty_day_num = $login_users_again WHERE statistics_date = $time and platform_id = $this->platform_id and dist_id = $this->dist_id"; break; } $this->scope->db->execute($sql,NULL,DbPdo::SQL_TYPE_UPDATE); Console::debug("当前日期".date('Y-m-d',$this->current_time)." 的前 $nDay 日[".$date."]留存率更新成功"); return $this; } private function checkProperties(){ if($this->current_time == 0){ Console::debug("请设置基准时间戳"); exit; } if($this->platform_id == 0){ Console::debug("请设置平台ID"); exit; } if($this->dist_id == 0){ Console::debug("请设置服务器ID"); exit; } } }