mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-04-13 08:28:32 +00:00
refactor(redis): add type hints, PHPDoc, null-safety and health-check ping
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ConnectionTracker — connection tracker
|
||||
* ConnectionTracker — live streaming connection management.
|
||||
*
|
||||
* Tracks connection lifecycle (create, update, close), stores state in Redis
|
||||
* sorted sets (LIVE, LINE#, STREAM#, SERVER#, PROXY#) with MySQL fallback.
|
||||
* Provides server load calculation, batch connection queries by user/server/stream,
|
||||
* and closed connection activity logging.
|
||||
*
|
||||
* @package XC_VM_Domain_Stream
|
||||
* @author Divarion_D <https://github.com/Divarion-D>
|
||||
@@ -11,7 +16,17 @@
|
||||
*/
|
||||
|
||||
class ConnectionTracker {
|
||||
public static function getCapacity($rProxy = false) {
|
||||
/**
|
||||
* Calculate server/proxy load capacity.
|
||||
*
|
||||
* Counts active connections via Redis zCard or MySQL COUNT, then computes
|
||||
* load ratio using the configured strategy: band, maxclients, guar_band, or client count.
|
||||
* Result is cached to a file.
|
||||
*
|
||||
* @param bool $rProxy If true — calculate for proxy servers, otherwise for main servers.
|
||||
* @return array<int, array{online_clients: int, capacity?: float}> Map of serverID => load data.
|
||||
*/
|
||||
public static function getCapacity(bool $rProxy = false): array {
|
||||
global $rSettings, $rServers, $db;
|
||||
$rRedis = RedisManager::instance();
|
||||
$rFile = ($rProxy ? 'proxy_capacity' : 'servers_capacity');
|
||||
@@ -28,10 +43,13 @@ class ConnectionTracker {
|
||||
}
|
||||
}
|
||||
$rResults = $rMulti->exec();
|
||||
if (!is_array($rResults)) {
|
||||
$rResults = [];
|
||||
}
|
||||
$i = 0;
|
||||
foreach (array_keys($rServers) as $rServerID) {
|
||||
if ($rServers[$rServerID]['server_online']) {
|
||||
$rRows[$rServerID] = array('online_clients' => ($rResults[$i] ?: 0));
|
||||
$rRows[$rServerID] = array('online_clients' => ($rResults[$i] ?? 0));
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
@@ -82,13 +100,24 @@ class ConnectionTracker {
|
||||
}
|
||||
}
|
||||
|
||||
if (defined('CACHE_TMP_PATH') && is_dir(CACHE_TMP_PATH)) {
|
||||
if (defined('CACHE_TMP_PATH') && is_dir(CACHE_TMP_PATH) && is_writable(CACHE_TMP_PATH)) {
|
||||
file_put_contents(CACHE_TMP_PATH . $rFile, json_encode($rRows), LOCK_EX);
|
||||
}
|
||||
return $rRows;
|
||||
}
|
||||
|
||||
public static function getConnections($rServerID = null, $rUserID = null, $rStreamID = null) {
|
||||
/**
|
||||
* Get connections filtered by server, user, or stream.
|
||||
*
|
||||
* In Redis mode returns [keys[], deserialized data[]].
|
||||
* In MySQL mode performs a JOIN query with lines, streams, streams_servers tables.
|
||||
*
|
||||
* @param int|null $rServerID Server ID to filter by.
|
||||
* @param int|null $rUserID User ID to filter by.
|
||||
* @param int|null $rStreamID Stream ID to filter by.
|
||||
* @return array Connections: [keys[], data[]] for Redis or rows for MySQL.
|
||||
*/
|
||||
public static function getConnections(?int $rServerID = null, ?int $rUserID = null, ?int $rStreamID = null): array {
|
||||
global $rSettings, $db;
|
||||
$rRedis = RedisManager::instance();
|
||||
if ($rSettings['redis_handler']) {
|
||||
@@ -127,16 +156,32 @@ class ConnectionTracker {
|
||||
return $db->get_rows(true, 'user_id', false);
|
||||
}
|
||||
|
||||
public static function getMainID() {
|
||||
/**
|
||||
* Get the main server ID.
|
||||
*
|
||||
* @return int|null Server ID with is_main flag, or null if not found.
|
||||
*/
|
||||
public static function getMainID(): ?int {
|
||||
global $rServers;
|
||||
foreach ($rServers as $rServerID => $rServer) {
|
||||
if ($rServer['is_main']) {
|
||||
return $rServerID;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function addToQueue($rStreamID, $rAddPID) {
|
||||
/**
|
||||
* Add a process PID to the stream processing queue.
|
||||
*
|
||||
* Queue is stored on disk (igbinary). Dead PIDs are automatically
|
||||
* filtered out on each call.
|
||||
*
|
||||
* @param int $rStreamID Stream ID.
|
||||
* @param int $rAddPID Process PID to add.
|
||||
* @return void
|
||||
*/
|
||||
public static function addToQueue(int $rStreamID, int $rAddPID): void {
|
||||
$rActivePIDs = $rPIDs = array();
|
||||
if (file_exists(SIGNALS_TMP_PATH . 'queue_' . intval($rStreamID))) {
|
||||
$rPIDs = igbinary_unserialize(file_get_contents(SIGNALS_TMP_PATH . 'queue_' . intval($rStreamID)));
|
||||
@@ -152,7 +197,16 @@ class ConnectionTracker {
|
||||
file_put_contents(SIGNALS_TMP_PATH . 'queue_' . intval($rStreamID), igbinary_serialize($rActivePIDs), LOCK_EX);
|
||||
}
|
||||
|
||||
public static function removeFromQueue($rStreamID, $rPID) {
|
||||
/**
|
||||
* Remove a process PID from the stream processing queue.
|
||||
*
|
||||
* If the queue becomes empty, the file is deleted.
|
||||
*
|
||||
* @param int $rStreamID Stream ID.
|
||||
* @param int $rPID Process PID to remove.
|
||||
* @return void
|
||||
*/
|
||||
public static function removeFromQueue(int $rStreamID, int $rPID): void {
|
||||
$rActivePIDs = array();
|
||||
foreach ((igbinary_unserialize(file_get_contents(SIGNALS_TMP_PATH . 'queue_' . intval($rStreamID))) ?: array()) as $rActivePID) {
|
||||
if (ProcessManager::isRunning($rActivePID, 'php-fpm') && $rPID != $rActivePID) {
|
||||
@@ -166,7 +220,19 @@ class ConnectionTracker {
|
||||
}
|
||||
}
|
||||
|
||||
public static function updateConnection($rData, $rChanges = array(), $rOption = null) {
|
||||
/**
|
||||
* Update a connection in Redis with applied changes.
|
||||
*
|
||||
* With $rOption='open' — adds UUID to all sorted sets (LIVE, LINE#, STREAM#, SERVER#, etc.).
|
||||
* With $rOption='close' — removes UUID from sorted sets and marks as ENDED.
|
||||
* Data is igbinary-serialized and saved atomically via MULTI/EXEC.
|
||||
*
|
||||
* @param array $rData Current connection data.
|
||||
* @param array $rChanges Fields to update (key => value).
|
||||
* @param string|null $rOption Action: 'open', 'close', or null (data update only).
|
||||
* @return array|null Updated connection data, or null on exec failure.
|
||||
*/
|
||||
public static function updateConnection(array $rData, array $rChanges = [], ?string $rOption = null): ?array {
|
||||
$rRedis = RedisManager::instance();
|
||||
$rOrigData = $rData;
|
||||
foreach ($rChanges as $rKey => $rValue) {
|
||||
@@ -213,14 +279,36 @@ class ConnectionTracker {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function redisSignal($rPID, $rServerID, $rRTMP, $rCustomData = null) {
|
||||
/**
|
||||
* Send a signal to a server via Redis.
|
||||
*
|
||||
* Creates an entry in SIGNALS#{serverID} set and stores signal data.
|
||||
* Used for remote termination of RTMP/HLS streams on other servers.
|
||||
*
|
||||
* @param int $rPID Process PID to terminate.
|
||||
* @param int $rServerID Target server ID.
|
||||
* @param int $rRTMP 1 — RTMP, 0 — regular process.
|
||||
* @param mixed|null $rCustomData Additional signal data.
|
||||
* @return array|false MULTI/EXEC result.
|
||||
*/
|
||||
public static function redisSignal(int $rPID, int $rServerID, int $rRTMP, $rCustomData = null) {
|
||||
$rRedis = RedisManager::instance();
|
||||
$rKey = 'SIGNAL#' . md5($rServerID . '#' . $rPID . '#' . $rRTMP);
|
||||
$rData = array('pid' => $rPID, 'server_id' => $rServerID, 'rtmp' => $rRTMP, 'time' => time(), 'custom_data' => $rCustomData, 'key' => $rKey);
|
||||
return $rRedis->multi()->sAdd('SIGNALS#' . $rServerID, $rKey)->set($rKey, igbinary_serialize($rData))->exec();
|
||||
}
|
||||
|
||||
public static function getUserConnections($rUserIDs, $rCount = false, $rKeysOnly = false) {
|
||||
/**
|
||||
* Get connections for multiple users (batch).
|
||||
*
|
||||
* Uses MULTI pipeline for parallel LINE# sorted set queries.
|
||||
*
|
||||
* @param int[] $rUserIDs Array of user IDs.
|
||||
* @param bool $rCount If true — return only connection count per user.
|
||||
* @param bool $rKeysOnly If true — return only Redis keys (UUIDs) without deserialization.
|
||||
* @return array Map of userID => connections[] (or count, or keys).
|
||||
*/
|
||||
public static function getUserConnections(array $rUserIDs, bool $rCount = false, bool $rKeysOnly = false): array {
|
||||
$rRedis = RedisManager::instance();
|
||||
$rMulti = $rRedis->multi();
|
||||
foreach ($rUserIDs as $rUserID) {
|
||||
@@ -228,6 +316,9 @@ class ConnectionTracker {
|
||||
}
|
||||
$rGroups = $rMulti->exec();
|
||||
$rConnectionMap = $rRedisKeys = array();
|
||||
if (!is_array($rGroups)) {
|
||||
return ($rKeysOnly ? $rRedisKeys : $rConnectionMap);
|
||||
}
|
||||
foreach ($rGroups as $rGroupID => $rKeys) {
|
||||
if ($rCount) {
|
||||
$rConnectionMap[$rUserIDs[$rGroupID]] = count($rKeys);
|
||||
@@ -239,7 +330,7 @@ class ConnectionTracker {
|
||||
}
|
||||
$rRedisKeys = array_unique($rRedisKeys);
|
||||
if (!$rKeysOnly) {
|
||||
if (!$rCount) {
|
||||
if (!$rCount && !empty($rRedisKeys)) {
|
||||
foreach ($rRedis->mGet($rRedisKeys) as $rRow) {
|
||||
$rRow = igbinary_unserialize($rRow);
|
||||
$rConnectionMap[$rRow['user_id']][] = $rRow;
|
||||
@@ -250,7 +341,18 @@ class ConnectionTracker {
|
||||
return $rRedisKeys;
|
||||
}
|
||||
|
||||
public static function getServerConnections($rServerIDs, $rProxy = false, $rCount = false, $rKeysOnly = false) {
|
||||
/**
|
||||
* Get connections for multiple servers/proxies (batch).
|
||||
*
|
||||
* Uses MULTI pipeline for parallel SERVER#/PROXY# sorted set queries.
|
||||
*
|
||||
* @param int[] $rServerIDs Array of server IDs.
|
||||
* @param bool $rProxy If true — query PROXY# instead of SERVER#.
|
||||
* @param bool $rCount If true — return only count.
|
||||
* @param bool $rKeysOnly If true — return only UUID keys.
|
||||
* @return array Map of serverID => connections[] (or count, or keys).
|
||||
*/
|
||||
public static function getServerConnections(array $rServerIDs, bool $rProxy = false, bool $rCount = false, bool $rKeysOnly = false): array {
|
||||
$rRedis = RedisManager::instance();
|
||||
$rMulti = $rRedis->multi();
|
||||
foreach ($rServerIDs as $rServerID) {
|
||||
@@ -258,6 +360,9 @@ class ConnectionTracker {
|
||||
}
|
||||
$rGroups = $rMulti->exec();
|
||||
$rConnectionMap = $rRedisKeys = array();
|
||||
if (!is_array($rGroups)) {
|
||||
return ($rKeysOnly ? $rRedisKeys : $rConnectionMap);
|
||||
}
|
||||
foreach ($rGroups as $rGroupID => $rKeys) {
|
||||
if ($rCount) {
|
||||
$rConnectionMap[$rServerIDs[$rGroupID]] = count($rKeys);
|
||||
@@ -269,7 +374,7 @@ class ConnectionTracker {
|
||||
}
|
||||
$rRedisKeys = array_unique($rRedisKeys);
|
||||
if (!$rKeysOnly) {
|
||||
if (!$rCount) {
|
||||
if (!$rCount && !empty($rRedisKeys)) {
|
||||
foreach ($rRedis->mGet($rRedisKeys) as $rRow) {
|
||||
$rRow = igbinary_unserialize($rRow);
|
||||
$rConnectionMap[$rRow['server_id']][] = $rRow;
|
||||
@@ -280,7 +385,15 @@ class ConnectionTracker {
|
||||
return $rRedisKeys;
|
||||
}
|
||||
|
||||
public static function getFirstConnection($rUserIDs) {
|
||||
/**
|
||||
* Get the most recent connection for each user.
|
||||
*
|
||||
* Queries LINE# with LIMIT 0,1 via MULTI pipeline and deserializes results.
|
||||
*
|
||||
* @param int[] $rUserIDs Array of user IDs.
|
||||
* @return array<int, array> Map of userID => connection data.
|
||||
*/
|
||||
public static function getFirstConnection(array $rUserIDs): array {
|
||||
$rRedis = RedisManager::instance();
|
||||
$rMulti = $rRedis->multi();
|
||||
foreach ($rUserIDs as $rUserID) {
|
||||
@@ -288,11 +401,17 @@ class ConnectionTracker {
|
||||
}
|
||||
$rGroups = $rMulti->exec();
|
||||
$rConnectionMap = $rRedisKeys = array();
|
||||
if (!is_array($rGroups)) {
|
||||
return $rConnectionMap;
|
||||
}
|
||||
foreach ($rGroups as $rKeys) {
|
||||
if (0 < count($rKeys)) {
|
||||
$rRedisKeys[] = $rKeys[0];
|
||||
}
|
||||
}
|
||||
if (empty($rRedisKeys)) {
|
||||
return $rConnectionMap;
|
||||
}
|
||||
foreach ($rRedis->mGet(array_unique($rRedisKeys)) as $rRow) {
|
||||
$rRow = igbinary_unserialize($rRow);
|
||||
$rConnectionMap[$rRow['user_id']] = $rRow;
|
||||
@@ -300,7 +419,17 @@ class ConnectionTracker {
|
||||
return $rConnectionMap;
|
||||
}
|
||||
|
||||
public static function getStreamConnections($rStreamIDs, $rGroup = true, $rCount = false) {
|
||||
/**
|
||||
* Get connections for multiple streams (batch).
|
||||
*
|
||||
* Uses MULTI pipeline for parallel STREAM# sorted set queries.
|
||||
*
|
||||
* @param int[] $rStreamIDs Array of stream IDs.
|
||||
* @param bool $rGroup If true — group by stream_id, otherwise by stream_id + server_id.
|
||||
* @param bool $rCount If true — return only connection count per stream.
|
||||
* @return array Map of streamID => connections[] (or count).
|
||||
*/
|
||||
public static function getStreamConnections(array $rStreamIDs, bool $rGroup = true, bool $rCount = false): array {
|
||||
$rRedis = RedisManager::instance();
|
||||
$rMulti = $rRedis->multi();
|
||||
foreach ($rStreamIDs as $rStreamID) {
|
||||
@@ -308,6 +437,9 @@ class ConnectionTracker {
|
||||
}
|
||||
$rGroups = $rMulti->exec();
|
||||
$rConnectionMap = $rRedisKeys = array();
|
||||
if (!is_array($rGroups)) {
|
||||
return $rConnectionMap;
|
||||
}
|
||||
foreach ($rGroups as $rGroupID => $rKeys) {
|
||||
if ($rCount) {
|
||||
$rConnectionMap[$rStreamIDs[$rGroupID]] = count($rKeys);
|
||||
@@ -317,7 +449,7 @@ class ConnectionTracker {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$rCount) {
|
||||
if (!$rCount && !empty($rRedisKeys)) {
|
||||
foreach ($rRedis->mGet(array_unique($rRedisKeys)) as $rRow) {
|
||||
$rRow = igbinary_unserialize($rRow);
|
||||
if ($rGroup) {
|
||||
@@ -330,7 +462,22 @@ class ConnectionTracker {
|
||||
return $rConnectionMap;
|
||||
}
|
||||
|
||||
public static function getRedisConnections($rUserID = null, $rServerID = null, $rStreamID = null, $rOpenOnly = false, $rCountOnly = false, $rGroup = true, $rHLSOnly = false) {
|
||||
/**
|
||||
* Universal Redis connection query with multiple filters.
|
||||
*
|
||||
* Reads from LIVE/LINE#/STREAM#/SERVER# depending on provided filters.
|
||||
* Supports counting, grouping by user identity, and HLS filtering.
|
||||
*
|
||||
* @param int|null $rUserID Filter by user.
|
||||
* @param int|null $rServerID Filter by server.
|
||||
* @param int|null $rStreamID Filter by stream.
|
||||
* @param bool $rOpenOnly Only open connections (hls_end=0).
|
||||
* @param bool $rCountOnly Return [total, unique_users] instead of data.
|
||||
* @param bool $rGroup Group by user/identity.
|
||||
* @param bool $rHLSOnly Exclude HLS connections.
|
||||
* @return array Connections grouped by identity, or [count, unique].
|
||||
*/
|
||||
public static function getRedisConnections(?int $rUserID = null, ?int $rServerID = null, ?int $rStreamID = null, bool $rOpenOnly = false, bool $rCountOnly = false, bool $rGroup = true, bool $rHLSOnly = false): array {
|
||||
$rRedis = RedisManager::instance();
|
||||
$rReturn = ($rCountOnly ? array(0, 0) : array());
|
||||
$rUniqueUsers = array();
|
||||
@@ -380,7 +527,13 @@ class ConnectionTracker {
|
||||
return $rReturn;
|
||||
}
|
||||
|
||||
public static function getConnection($rUUID) {
|
||||
/**
|
||||
* Get a single connection by UUID.
|
||||
*
|
||||
* @param string $rUUID Connection UUID (Redis key).
|
||||
* @return array|null Deserialized connection data, or null if not found.
|
||||
*/
|
||||
public static function getConnection(string $rUUID): ?array {
|
||||
$rRedis = RedisManager::instance();
|
||||
if (!$rRedis) {
|
||||
return null;
|
||||
@@ -389,7 +542,17 @@ class ConnectionTracker {
|
||||
return ($raw !== false) ? igbinary_unserialize($raw) : null;
|
||||
}
|
||||
|
||||
public static function createConnection($rData) {
|
||||
/**
|
||||
* Create a new connection in Redis.
|
||||
*
|
||||
* Atomically (MULTI/EXEC) adds UUID to all sorted sets:
|
||||
* LINE#, LINE_ALL#, STREAM#, SERVER#, SERVER_LINES#, PROXY#,
|
||||
* CONNECTIONS, LIVE, and stores igbinary-serialized data.
|
||||
*
|
||||
* @param array $rData Connection data (uuid, identity, stream_id, server_id, etc.).
|
||||
* @return array|false MULTI/EXEC result.
|
||||
*/
|
||||
public static function createConnection(array $rData) {
|
||||
$rRedis = RedisManager::instance();
|
||||
$rMulti = $rRedis->multi();
|
||||
$rMulti->zAdd('LINE#' . $rData['identity'], $rData['date_start'], $rData['uuid']);
|
||||
@@ -408,7 +571,15 @@ class ConnectionTracker {
|
||||
return $rMulti->exec();
|
||||
}
|
||||
|
||||
public static function getLineConnections($rUserID, $rActive = false, $rKeys = false) {
|
||||
/**
|
||||
* Get connections for a specific user/line.
|
||||
*
|
||||
* @param int $rUserID User ID.
|
||||
* @param bool $rActive If true — only active (LINE#), otherwise all (LINE_ALL#).
|
||||
* @param bool $rKeys Unused (overwritten internally).
|
||||
* @return array UUID keys or deserialized connection data.
|
||||
*/
|
||||
public static function getLineConnections(int $rUserID, bool $rActive = false, bool $rKeys = false): array {
|
||||
$rRedis = RedisManager::instance();
|
||||
$rKeys = $rRedis->zRangeByScore((($rActive ? 'LINE#' : 'LINE_ALL#')) . $rUserID, '-inf', '+inf');
|
||||
if ($rKeys) {
|
||||
@@ -420,7 +591,14 @@ class ConnectionTracker {
|
||||
return array_map('igbinary_unserialize', $rRedis->mGet($rKeys));
|
||||
}
|
||||
|
||||
public static function getEnded() {
|
||||
/**
|
||||
* Get all ended (ENDED) connections.
|
||||
*
|
||||
* Reads members from ENDED set and deserializes data via mGet.
|
||||
*
|
||||
* @return array Array of deserialized ended connection data.
|
||||
*/
|
||||
public static function getEnded(): array {
|
||||
$rRedis = RedisManager::instance();
|
||||
$rKeys = $rRedis->sMembers('ENDED');
|
||||
if (0 >= count($rKeys)) {
|
||||
@@ -429,7 +607,14 @@ class ConnectionTracker {
|
||||
return array_map('igbinary_unserialize', $rRedis->mGet($rKeys));
|
||||
}
|
||||
|
||||
public static function getProxies($rServerID, $rOnline = true) {
|
||||
/**
|
||||
* Get proxy servers attached to the specified server.
|
||||
*
|
||||
* @param int $rServerID Parent server ID.
|
||||
* @param bool $rOnline If true — only online proxies.
|
||||
* @return array<int, array> Map of proxyID => server data.
|
||||
*/
|
||||
public static function getProxies(int $rServerID, bool $rOnline = true): array {
|
||||
global $rServers;
|
||||
$rReturn = array();
|
||||
foreach ($rServers as $rProxyID => $rServerInfo) {
|
||||
@@ -440,7 +625,19 @@ class ConnectionTracker {
|
||||
return $rReturn;
|
||||
}
|
||||
|
||||
public static function closeConnection($rActivityInfo, $rRemove = true, $rEnd = true) {
|
||||
/**
|
||||
* Close an active connection.
|
||||
*
|
||||
* Performs the full close cycle: kills the process (RTMP drop client,
|
||||
* posix_kill, or Redis signal), removes from Redis sorted sets,
|
||||
* cleans tmp files, and writes to the activity log.
|
||||
*
|
||||
* @param array|string $rActivityInfo Connection data or UUID/activity_id.
|
||||
* @param bool $rRemove Remove connection from Redis/MySQL.
|
||||
* @param bool $rEnd Mark HLS connection as ended.
|
||||
* @return bool True on successful close, false otherwise.
|
||||
*/
|
||||
public static function closeConnection($rActivityInfo, bool $rRemove = true, bool $rEnd = true): bool {
|
||||
if (!empty($rActivityInfo)) {
|
||||
global $rSettings, $rServers, $db;
|
||||
if (!$rSettings['redis_handler'] || is_object(RedisManager::instance())) {
|
||||
@@ -499,14 +696,11 @@ class ConnectionTracker {
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($rActivityInfo['server_id'] != SERVER_ID) {
|
||||
} else {
|
||||
if ($rActivityInfo['server_id'] == SERVER_ID) {
|
||||
@unlink(CONS_TMP_PATH . $rActivityInfo['uuid']);
|
||||
}
|
||||
if (!$rRemove) {
|
||||
} else {
|
||||
if ($rActivityInfo['server_id'] != SERVER_ID) {
|
||||
} else {
|
||||
if ($rRemove) {
|
||||
if ($rActivityInfo['server_id'] == SERVER_ID) {
|
||||
@unlink(CONS_TMP_PATH . $rActivityInfo['stream_id'] . '/' . $rActivityInfo['uuid']);
|
||||
}
|
||||
if ($rSettings['redis_handler']) {
|
||||
@@ -515,12 +709,10 @@ class ConnectionTracker {
|
||||
$rRedis->zRem('LINE_ALL#' . $rActivityInfo['identity'], $rActivityInfo['uuid']);
|
||||
$rRedis->zRem('STREAM#' . $rActivityInfo['stream_id'], $rActivityInfo['uuid']);
|
||||
$rRedis->zRem('SERVER#' . $rActivityInfo['server_id'], $rActivityInfo['uuid']);
|
||||
if (!$rActivityInfo['user_id']) {
|
||||
} else {
|
||||
if ($rActivityInfo['user_id']) {
|
||||
$rRedis->zRem('SERVER_LINES#' . $rActivityInfo['server_id'], $rActivityInfo['uuid']);
|
||||
}
|
||||
if (!$rActivityInfo['proxy_id']) {
|
||||
} else {
|
||||
if ($rActivityInfo['proxy_id']) {
|
||||
$rRedis->zRem('PROXY#' . $rActivityInfo['proxy_id'], $rActivityInfo['uuid']);
|
||||
}
|
||||
$rRedis->del($rActivityInfo['uuid']);
|
||||
@@ -532,7 +724,7 @@ class ConnectionTracker {
|
||||
$db->query('DELETE FROM `lines_live` WHERE `activity_id` = ?', $rActivityInfo['activity_id']);
|
||||
}
|
||||
}
|
||||
self::writeOfflineActivity($rSettings, $rActivityInfo['server_id'], $rActivityInfo['proxy_id'], $rActivityInfo['user_id'], $rActivityInfo['stream_id'], $rActivityInfo['date_start'], $rActivityInfo['user_agent'], $rActivityInfo['user_ip'], $rActivityInfo['container'], $rActivityInfo['geoip_country_code'], $rActivityInfo['isp'], $rActivityInfo['external_device'], $rActivityInfo['divergence'], $rActivityInfo['hmac_id'], $rActivityInfo['hmac_identifier']);
|
||||
self::writeOfflineActivity($rSettings, $rActivityInfo['server_id'], $rActivityInfo['proxy_id'], $rActivityInfo['user_id'], $rActivityInfo['stream_id'], $rActivityInfo['date_start'], $rActivityInfo['user_agent'], $rActivityInfo['user_ip'], $rActivityInfo['container'], $rActivityInfo['geoip_country_code'], $rActivityInfo['isp'], $rActivityInfo['external_device'] ?? '', $rActivityInfo['divergence'] ?? 0, $rActivityInfo['hmac_id'] ?? null, $rActivityInfo['hmac_identifier'] ?? '');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -540,7 +732,30 @@ class ConnectionTracker {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function writeOfflineActivity($rSettings, $rServerID, $rProxyID, $rUserID, $rStreamID, $rStart, $rUserAgent, $rIP, $rExtension, $rGeoIP, $rISP, $rExternalDevice = '', $rDivergence = 0, $rIsHMAC = null, $rIdentifier = '') {
|
||||
/**
|
||||
* Write closed connection data to the activity log file.
|
||||
*
|
||||
* Log is written as base64(json) per line to LOGS_TMP_PATH/activity.
|
||||
* Only writes if save_closed_connection setting is enabled.
|
||||
*
|
||||
* @param array $rSettings Global settings.
|
||||
* @param int $rServerID Server ID.
|
||||
* @param int $rProxyID Proxy ID (0 if no proxy).
|
||||
* @param int $rUserID User ID.
|
||||
* @param int $rStreamID Stream ID.
|
||||
* @param int $rStart Connection start Unix timestamp.
|
||||
* @param string $rUserAgent Client User-Agent.
|
||||
* @param string $rIP Client IP address.
|
||||
* @param string $rExtension Container type (rtmp, hls, etc.).
|
||||
* @param string $rGeoIP GeoIP country code.
|
||||
* @param string $rISP ISP name.
|
||||
* @param string $rExternalDevice External device identifier.
|
||||
* @param int $rDivergence Divergence value.
|
||||
* @param int|null $rIsHMAC HMAC ID.
|
||||
* @param string $rIdentifier HMAC identifier.
|
||||
* @return void
|
||||
*/
|
||||
public static function writeOfflineActivity(array $rSettings, int $rServerID, int $rProxyID, int $rUserID, int $rStreamID, int $rStart, string $rUserAgent, string $rIP, string $rExtension, string $rGeoIP, string $rISP, string $rExternalDevice = '', int $rDivergence = 0, ?int $rIsHMAC = null, string $rIdentifier = ''): void {
|
||||
if ($rSettings['save_closed_connection'] != 0) {
|
||||
if (!($rServerID && $rUserID && $rStreamID)) {
|
||||
} else {
|
||||
@@ -548,11 +763,21 @@ class ConnectionTracker {
|
||||
file_put_contents(LOGS_TMP_PATH . 'activity', base64_encode(json_encode($rActivityInfo)) . "\n", FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLiveConnections($rServerID, $rProxy = false) {
|
||||
/**
|
||||
* Count active (live) connections on a server or proxy.
|
||||
*
|
||||
* In Redis mode counts via getRedisConnections.
|
||||
* In MySQL mode performs COUNT(*) on lines_live.
|
||||
*
|
||||
* @param int $rServerID Server or proxy ID.
|
||||
* @param bool $rProxy If true — count for proxy.
|
||||
* @return int Number of active connections.
|
||||
*/
|
||||
public static function getLiveConnections(int $rServerID, bool $rProxy = false): int {
|
||||
global $db;
|
||||
|
||||
if (SettingsManager::getAll()['redis_handler']) {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* RedisManager — управление жизненным циклом Redis-подключения.
|
||||
* RedisManager — Redis connection lifecycle management.
|
||||
*
|
||||
* Singleton хранит активный экземпляр Redis.
|
||||
* Singleton that holds the active Redis instance. Provides health-check
|
||||
* via ping (debounced to 30s), auto-reconnect on failure, and low-level
|
||||
* connect/close helpers for non-singleton usage.
|
||||
*
|
||||
* @package XC_VM_Infrastructure_Redis
|
||||
* @author Divarion_D <https://github.com/Divarion-D>
|
||||
@@ -13,55 +15,99 @@
|
||||
*/
|
||||
|
||||
class RedisManager {
|
||||
/** @var Redis|null Singleton-экземпляр */
|
||||
/** @var Redis|null Singleton instance */
|
||||
private static $instance = null;
|
||||
/** @var int Last ping health-check timestamp */
|
||||
private static $lastPingCheck = 0;
|
||||
|
||||
// ──────── Singleton API ────────
|
||||
|
||||
/**
|
||||
* Возвращает активный Redis, при необходимости подключаясь.
|
||||
* @return Redis|null
|
||||
* Get the active Redis instance, connecting if necessary.
|
||||
*
|
||||
* Performs a ping health-check no more than once every 30 seconds.
|
||||
* If the connection is dead, attempts to reconnect automatically.
|
||||
*
|
||||
* @return Redis|null Active Redis instance, or null on connection failure.
|
||||
*/
|
||||
public static function instance() {
|
||||
public static function instance(): ?Redis {
|
||||
if (is_object(self::$instance)) {
|
||||
$rNow = time();
|
||||
if ($rNow - self::$lastPingCheck > 30) {
|
||||
try {
|
||||
self::$instance->ping();
|
||||
self::$lastPingCheck = $rNow;
|
||||
} catch (RedisException $e) {
|
||||
self::$instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!is_object(self::$instance)) {
|
||||
self::ensureConnected();
|
||||
self::$lastPingCheck = time();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подключается к Redis, если ещё нет соединения.
|
||||
* @return bool
|
||||
* Connect to Redis if not already connected.
|
||||
*
|
||||
* Uses ConfigReader and SettingsManager for hostname and password.
|
||||
*
|
||||
* @return bool True if connected, false otherwise.
|
||||
*/
|
||||
public static function ensureConnected() {
|
||||
public static function ensureConnected(): bool {
|
||||
self::$instance = self::connect(self::$instance, ConfigReader::getAll(), SettingsManager::getAll());
|
||||
return is_object(self::$instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Закрывает singleton-подключение.
|
||||
* @return bool
|
||||
* Close the singleton connection.
|
||||
*
|
||||
* @return bool Always returns true.
|
||||
*/
|
||||
public static function closeInstance() {
|
||||
public static function closeInstance(): bool {
|
||||
self::$instance = self::close(self::$instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, подключён ли singleton.
|
||||
* @return bool
|
||||
* Check whether the singleton is connected.
|
||||
*
|
||||
* @return bool True if connected.
|
||||
*/
|
||||
public static function isConnected() {
|
||||
public static function isConnected(): bool {
|
||||
return is_object(self::$instance);
|
||||
}
|
||||
|
||||
// ──────── Low-level API (без singleton) ────────
|
||||
|
||||
public static function setSignal($rKey, $rData) {
|
||||
|
||||
/**
|
||||
* Write a signal to the filesystem cache.
|
||||
*
|
||||
* Stores a JSON-encoded [key, data] pair in SIGNALS_TMP_PATH.
|
||||
*
|
||||
* @param string $rKey Signal key.
|
||||
* @param mixed $rData Signal payload.
|
||||
* @return void
|
||||
*/
|
||||
public static function setSignal(string $rKey, $rData): void {
|
||||
file_put_contents(SIGNALS_TMP_PATH . 'cache_' . md5($rKey), json_encode(array($rKey, $rData)));
|
||||
}
|
||||
|
||||
public static function connect($rRedis, $rConfig, $rSettings) {
|
||||
/**
|
||||
* Connect to Redis (low-level, non-singleton).
|
||||
*
|
||||
* If $rRedis is already a live connection, returns it as-is.
|
||||
* Otherwise creates a new connection using hostname from $rConfig
|
||||
* and password from $rSettings.
|
||||
*
|
||||
* @param Redis|null $rRedis Existing Redis instance or null.
|
||||
* @param array $rConfig Config array (must contain 'hostname').
|
||||
* @param array $rSettings Settings array (must contain 'redis_password').
|
||||
* @return Redis|null Connected Redis instance, or null on failure.
|
||||
*/
|
||||
public static function connect(?Redis $rRedis, array $rConfig, array $rSettings): ?Redis {
|
||||
if (is_object($rRedis)) {
|
||||
try {
|
||||
$rRedis->ping();
|
||||
@@ -87,7 +133,13 @@ class RedisManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static function close($rRedis) {
|
||||
/**
|
||||
* Close a Redis connection.
|
||||
*
|
||||
* @param Redis|null $rRedis Redis instance to close.
|
||||
* @return null Always returns null (for assignment: $redis = close($redis)).
|
||||
*/
|
||||
public static function close(?Redis $rRedis): ?Redis {
|
||||
if (is_object($rRedis)) {
|
||||
$rRedis->close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user