### redis队列应用--数据批量处理
---
###### 优化原因
> 客户端会将玩家游戏相关的数据发送给服务端,服务端再将数据保存到数据库或者上报到BI。之前的逻辑是每次收到请求服务端都是直接操作数据库保存或者上报到Bi,存到性能问题。
###### 优化思路
> 服务端收到客户端发过来的数据上报请求后,将数据放到redis队列中,然后使用定时脚本从队列里面拉取数据并进行处理。
###### 相关代码
上報数据写入队列
```php
$key = Keys::biEventQueue();
Dao::redis()->rPush($key, json_encode($data));
```
批量拉取队列数据
```php
/**
* 批量消费数据,分批次消费,再合并结果
* @param int $total 总数量
* @param int $limit 每次拉取数量上限
*/
public function consumeReportQueue($total, $limit = 100)
{
$list = [];
$limit = min($total, $limit);
$key = Keys::biEventQueue();
$redis = Dao::redis();
while (1) {
$count = min($total, $limit);
if ($count <= 0) {
break;
}
//使用事务,保证拉取操作的原子性
$redis->watch($key);
$redis->multi();
$redis->lRange($key, 0, $count - 1);
$redis->lTrim($key, $count, -1);
$redis->lLen($key);
//返回一个数组 对应redis每个操作的返回值
$result = $redis->exec();
if (!$result) {
Log::info('consume failed', 'bi-report.log');
continue;
}
if (!$result[0]) {
break;
}
//合并数据
$list = array_merge($list, $result[0]);
if (!$result[2]) {
break;
}
$total -= $count;
}
return $list;
}
```
定时脚本消费数据
```php
$pidFile = PATH_LOG . '/bi-report';
$pid = getmypid();
$time = time();
$batch = 0;
//判断当前是否有定时脚本在执行,保证同时只能有一个脚本在执行任务
if (file_exists($pidFile)) {
cli_exit('pid file exists');
}
file_put_contents($pidFile, $pid);
while (1) {
cli_output('batch = ' . ++$batch);
$dataGroups = array();
//拉取数据
$list = Bll::biData()->consumeReportQueue(1000);
$batchCount = count($list);
Log::info(['consume', $pid, $batch, $batchCount], 'bi-report.log');
cli_output('batchCount = ' . $batchCount);
if (!$list) break;
foreach ($list as $row) {
$row = json_decode($row, true);
$dataGroups[$row['event']][] = $row;
}
//消费数据
foreach ($dataGroups as $eventName => $list) {
cli_output('eventName = ' . $eventName);
cli_output('count = ' . count($list));
$chunks = array_chunk($list, 50);
foreach ($chunks as $k => $_list) {
cli_output('chunk = ' . ($k + 1));
try {
Bll::biData()->eventReport(null, $eventName, $_list, true);
} catch (Exception $e) {
Log::info([$eventName, $_list], 'bi-report.log');
}
}
}
//退出判断
if ($batchCount < 500) {
Log::info(['end', $pid, $batch, time() - $time], 'bi-report.log');
break;
}
}
unlink($pidFile);
cli_exit('Done');
```
function exec_command(&$cmd, $async = false, $log_file = '')
{
if (IS_WIN && $async) {
$cmd .= ' > ' . ($log_file ?: 'nul');
pclose(popen("start /B " . $cmd, "r"));
} else {
$cmd .= ' > ' . ($log_file ?: '/dev/null');
if ($async) $cmd .= " &";
exec($cmd);
}
}
function exec_php_file($file, $args = null, $async = false, $log_file = '')
{
$cmd = "php {$file}";
if (!$args || !is_array($args)) {
$args = array();
}
$args['env'] = ENV;
foreach ($args as $k => $v) {
$cmd .= " {$k}={$v}";
}
exec_command($cmd, $async, $log_file);
return $cmd;
}