perf: 优化流量消费相关代码性能,解决流量纪录与已用流量有概率不一致的问题

This commit is contained in:
xboard
2024-04-27 17:06:57 +08:00
parent 4438fee3ca
commit be9ed269fa
12 changed files with 246 additions and 171 deletions
+4 -5
View File
@@ -165,7 +165,7 @@ class OrderService
{
$lastOneTimeOrder = Order::where('user_id', $user->id)
->where('period', 'onetime_price')
->where('status', 3)
->where('status', Order::STATUS_COMPLETED)
->orderBy('id', 'DESC')
->first();
if (!$lastOneTimeOrder) return;
@@ -176,7 +176,7 @@ class OrderService
$trafficUnitPrice = $paidTotalAmount / $nowUserTraffic;
$notUsedTraffic = $nowUserTraffic - (($user->u + $user->d) / 1073741824);
$result = $trafficUnitPrice * $notUsedTraffic;
$orderModel = Order::where('user_id', $user->id)->where('period', '!=', 'reset_price')->where('status', 3);
$orderModel = Order::where('user_id', $user->id)->where('period', '!=', 'reset_price')->where('status', Order::STATUS_COMPLETED);
$order->surplus_amount = $result > 0 ? $result : 0;
$order->surplus_order_ids = array_column($orderModel->get()->toArray(), 'id');
}
@@ -184,9 +184,8 @@ class OrderService
private function getSurplusValueByPeriod(User $user, Order $order)
{
$orders = Order::where('user_id', $user->id)
->where('period', '!=', 'reset_price')
->where('period', '!=', 'onetime_price')
->where('status', 3)
->whereNotIn('period', ['reset_price', 'onetime_price'])
->where('status', Order::STATUS_COMPLETED)
->get()
->toArray();
if (!$orders) return;
+96 -74
View File
@@ -7,44 +7,41 @@ use App\Models\Stat;
use App\Models\StatServer;
use App\Models\StatUser;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
class StatisticalService {
class StatisticalService
{
protected $userStats;
protected $startAt;
protected $endAt;
protected $serverStats;
protected $statServerKey;
protected $statUserKey;
protected $redis;
public function __construct()
{
ini_set('memory_limit', -1);
$this->redis = Redis::connection();
}
public function setStartAt($timestamp) {
public function setStartAt($timestamp)
{
$this->startAt = $timestamp;
$this->statServerKey = "stat_server_{$this->startAt}";
$this->statUserKey = "stat_user_{$this->startAt}";
}
public function setEndAt($timestamp) {
public function setEndAt($timestamp)
{
$this->endAt = $timestamp;
}
public function setServerStats() {
$this->serverStats = Cache::get("stat_server_{$this->startAt}");
$this->serverStats = json_decode($this->serverStats, true) ?? [];
if (!is_array($this->serverStats)) {
$this->serverStats = [];
}
}
public function setUserStats() {
$this->userStats = Cache::get("stat_user_{$this->startAt}");
$this->userStats = json_decode($this->userStats, true) ?? [];
if (!is_array($this->userStats)) {
$this->userStats = [];
}
}
/**
* 生成统计报表
*/
public function generateStatData(): array
{
$startAt = $this->startAt;
@@ -80,97 +77,120 @@ class StatisticalService {
->whereNotNull('invite_user_id')
->count();
$data['transfer_used_total'] = StatServer::where('created_at', '>=', $startAt)
->where('created_at', '<', $endAt)
->select(DB::raw('SUM(u) + SUM(d) as total'))
->value('total') ?? 0;
->where('created_at', '<', $endAt)
->select(DB::raw('SUM(u) + SUM(d) as total'))
->value('total') ?? 0;
return $data;
}
/**
* 往服务器报表缓存正追加流量使用数据
*/
public function statServer($serverId, $serverType, $u, $d)
{
$this->serverStats[$serverType] = $this->serverStats[$serverType] ?? [];
if (isset($this->serverStats[$serverType][$serverId])) {
$this->serverStats[$serverType][$serverId][0] += $u;
$this->serverStats[$serverType][$serverId][1] += $d;
} else {
$this->serverStats[$serverType][$serverId] = [$u, $d];
}
Cache::set("stat_server_{$this->startAt}", json_encode($this->serverStats));
$u_menber = "{$serverType}_{$serverId}_u"; //储存上传流量的集合成员
$d_menber = "{$serverType}_{$serverId}_d"; //储存下载流量的集合成员
$this->redis->zincrby($this->statServerKey, $u, $u_menber);
$this->redis->zincrby($this->statServerKey, $d, $d_menber);
}
/**
* 追加用户使用流量
*/
public function statUser($rate, $userId, $u, $d)
{
$this->userStats[$rate] = $this->userStats[$rate] ?? [];
if (isset($this->userStats[$rate][$userId])) {
$this->userStats[$rate][$userId][0] += $u;
$this->userStats[$rate][$userId][1] += $d;
} else {
$this->userStats[$rate][$userId] = [$u, $d];
}
Cache::set("stat_user_{$this->startAt}", json_encode($this->userStats));
$u_menber = "{$rate}_{$userId}_u"; //储存上传流量的集合成员
$d_menber = "{$rate}_{$userId}_d"; //储存下载流量的集合成员
$this->redis->zincrby($this->statUserKey, $u, $u_menber);
$this->redis->zincrby($this->statUserKey, $d, $d_menber);
}
/**
* 获取指定用户的流量使用情况
*/
public function getStatUserByUserID($userId): array
{
$stats = [];
foreach (array_keys($this->userStats) as $rate) {
if (!isset($this->userStats[$rate][$userId])) continue;
$stats[] = [
$statsUser = $this->redis->zrange($this->statUserKey, 0, -1, true);
foreach ($statsUser as $member => $value) {
list($rate, $uid, $type) = explode('_', $member);
if ($uid !== $userId)
continue;
$key = "{$rate}_{$uid}";
$stats[$key] = $stats[$key] ?? [
'record_at' => $this->startAt,
'server_rate' => $rate,
'u' => $this->userStats[$rate][$userId][0],
'd' => $this->userStats[$rate][$userId][1],
'user_id' => $userId
'server_rate' => floatval($rate),
'u' => 0,
'd' => 0,
'user_id' => intval($userId),
];
$stats[$key][$type] += $value;
}
return $stats;
return array_values($stats);
}
/**
* 获取缓存中的用户报表
*/
public function getStatUser()
{
$stats = [];
foreach ($this->userStats as $k => $v) {
foreach (array_keys($v) as $userId) {
if (isset($v[$userId])) {
$stats[] = [
'server_rate' => $k,
'u' => $v[$userId][0],
'd' => $v[$userId][1],
'user_id' => $userId
];
}
}
$statsUser = $this->redis->zrange($this->statUserKey, 0, -1, true);
foreach ($statsUser as $member => $value) {
list($rate, $uid, $type) = explode('_', $member);
$key = "{$rate}_{$uid}";
$stats[$key] = $stats[$key] ?? [
'record_at' => $this->startAt,
'server_rate' => $rate,
'u' => 0,
'd' => 0,
'user_id' => intval($uid),
];
$stats[$key][$type] += $value;
}
return $stats;
return array_values($stats);
}
/**
* 获取缓存中的服务器爆表
*/
public function getStatServer()
{
$stats = [];
foreach ($this->serverStats as $serverType => $v) {
foreach (array_keys($v) as $serverId) {
if (isset($v[$serverId])) {
$stats[] = [
'server_id' => $serverId,
'server_type' => $serverType,
'u' => $v[$serverId][0],
'd' => $v[$serverId][1],
];
}
$statsServer = $this->redis->zrange($this->statServerKey, 0, -1, true);
foreach ($statsServer as $member => $value) {
list($serverType, $serverId, $type) = explode('_', $member);
$key = "{$serverType}_{$serverId}";
if (!isset($stats[$key])) {
$stats[$key] = [
'server_id' => intval($serverId),
'server_type' => $serverType,
'u' => 0,
'd' => 0,
];
}
$stats[$key][$type] += $value;
}
return $stats;
return array_values($stats);
}
/**
* 清除用户报表缓存数据
*/
public function clearStatUser()
{
Cache::forget("stat_user_{$this->startAt}");
$this->redis->del($this->statUserKey);
}
/**
* 清除服务器报表缓存数据
*/
public function clearStatServer()
{
Cache::forget("stat_server_{$this->startAt}");
$this->redis->del($this->statServerKey);
}
public function getStatRecord($type)
@@ -236,7 +256,8 @@ class StatisticalService {
$users = User::whereIn('id', $stats->pluck('invite_user_id')->toArray())->get()->keyBy('id');
foreach ($stats as $k => $v) {
if (!isset($users[$v['invite_user_id']])) continue;
if (!isset($users[$v['invite_user_id']]))
continue;
$stats[$k]['email'] = $users[$v['invite_user_id']]['email'];
}
return $stats;
@@ -258,7 +279,8 @@ class StatisticalService {
->get();
$users = User::whereIn('id', $stats->pluck('user_id')->toArray())->get()->keyBy('id');
foreach ($stats as $k => $v) {
if (!isset($users[$v['user_id']])) continue;
if (!isset($users[$v['user_id']]))
continue;
$stats[$k]['email'] = $users[$v['user_id']]['email'];
}
return $stats;
+34 -20
View File
@@ -2,10 +2,11 @@
namespace App\Services;
use App\Jobs\TrafficFetchJob;
use App\Jobs\BatchTrafficFetchJob;
use App\Models\Order;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Support\Facades\Bus;
class UserService
{
@@ -21,10 +22,10 @@ class UserService
$day = date('d', $expiredAt);
$today = date('d');
$lastDay = date('d', strtotime('last day of +0 months'));
if ((int)$day >= (int)$today && (int)$day >= (int)$lastDay) {
if ((int) $day >= (int) $today && (int) $day >= (int) $lastDay) {
return $lastDay - $today;
}
if ((int)$day >= (int)$today) {
if ((int) $day >= (int) $today) {
return $day - $today;
}
@@ -34,7 +35,7 @@ class UserService
private function calcResetDayByYearFirstDay(): int
{
$nextYear = strtotime(date("Y-01-01", strtotime('+1 year')));
return (int)(($nextYear - time()) / 86400);
return (int) (($nextYear - time()) / 86400);
}
private function calcResetDayByYearExpiredAt(int $expiredAt): int
@@ -43,9 +44,9 @@ class UserService
$nowYear = strtotime(date("Y-{$md}"));
$nextYear = strtotime('+1 year', $nowYear);
if ($nowYear > time()) {
return (int)(($nowYear - time()) / 86400);
return (int) (($nowYear - time()) / 86400);
}
return (int)(($nextYear - time()) / 86400);
return (int) (($nextYear - time()) / 86400);
}
public function getResetDay(User $user)
@@ -53,13 +54,15 @@ class UserService
if (!isset($user->plan)) {
$user->plan = Plan::find($user->plan_id);
}
if ($user->expired_at <= time() || $user->expired_at === NULL) return null;
if ($user->expired_at <= time() || $user->expired_at === NULL)
return null;
// if reset method is not reset
if ($user->plan->reset_traffic_method === 2) return null;
if ($user->plan->reset_traffic_method === 2)
return null;
switch (true) {
case ($user->plan->reset_traffic_method === NULL): {
$resetTrafficMethod = admin_setting('reset_traffic_method', 0);
switch ((int)$resetTrafficMethod) {
switch ((int) $resetTrafficMethod) {
// month first day
case 0:
return $this->calcResetDayByMonthFirstDay();
@@ -123,9 +126,9 @@ class UserService
->orWhere('expired_at', 0);
})
->where(function ($query) {
$query->where('plan_id', NULL)
->orWhere('transfer_enable', 0);
})
$query->where('plan_id', NULL)
->orWhere('transfer_enable', 0);
})
->get();
}
@@ -139,7 +142,7 @@ class UserService
return User::all();
}
public function addBalance(int $userId, int $balance):bool
public function addBalance(int $userId, int $balance): bool
{
$user = User::lockForUpdate()->find($userId);
if (!$user) {
@@ -155,7 +158,7 @@ class UserService
return true;
}
public function isNotCompleteOrderByUserId(int $userId):bool
public function isNotCompleteOrderByUserId(int $userId): bool
{
$order = Order::whereIn('status', [0, 1])
->where('user_id', $userId)
@@ -168,13 +171,24 @@ class UserService
public function trafficFetch(array $server, string $protocol, array $data, string $nodeIp = null)
{
// 获取子节点
$childServer = ($server['parent_id'] == null && !blank($nodeIp))
? ServerService::getChildServer($server['id'], $protocol, $nodeIp)
: null;
$timestamp = strtotime(date('Y-m-d'));
collect($data)->chunk(1000)->each(function($chunk) use ($timestamp,$server,$protocol, $childServer){
TrafficFetchJob::dispatch($server, $chunk->toArray(), $protocol, $timestamp, $childServer);
$statService = new StatisticalService();
$statService->setStartAt($timestamp);
// 获取子节点
$childServer = ($server['parent_id'] == null && $nodeIp) ? ServerService::getChildServer($server['id'], $protocol, $nodeIp) : null;
foreach ($data as $uid => $v) {
$u = $v[0];
$d = $v[1];
$targetServer = $childServer ?? $server;
$statService->statUser($targetServer['rate'], $uid, $u, $d); //如果存在子节点则使用子节点的倍率
if (!blank($childServer)) { //如果存在子节点,则给子节点计算流量
$statService->statServer($childServer['id'], $protocol, $u, $d);
}
$statService->statServer($server['id'], $protocol, $u, $d);
}
collect($data)->chunk(1000)->each(function ($chunk) use ($timestamp, $server, $protocol, $childServer) {
BatchTrafficFetchJob::dispatch($server, $chunk->toArray(), $protocol, $timestamp, $childServer);
});
}
}