getMessage()); } return $newNodeId; } /** * 节点注册与心跳更新 * 这是一个原子操作,如果节点不存在则创建,如果存在则更新。 */ public static function heartbeat() { $nodeId = static::getNodeId(); if (empty($nodeId)) { Log::error('无法获取当前节点ID,心跳更新失败'); return; } try { $host = SystemHost::where('node_id', $nodeId)->find(); $data = static::collectHostInfo(); if (empty($host)) { $host = new SystemHost(); // 首次注册 $data['node_id'] = $nodeId; Log::info("节点 [{$nodeId}] 已成功注册并上线。"); } $host->save($data); // 主节点自动选举:若无在线主节点,当前节点自动成为主节点 $master = SystemHost::where('is_master', 1)->where('status', 1)->find(); if (empty($master)) { // 先清除所有旧的主节点标记(含已离线的),避免出现多个 is_master=1 SystemHost::where('is_master', 1)->update(['is_master' => 0]); $host->is_master = 1; $host->save(); Log::info("节点 [{$nodeId}] 自动当选为主节点。"); } // 过期节点检测:超过90秒无心跳的节点标记为离线 $staleTime = date('Y-m-d H:i:s', time() - 90); $staleCount = SystemHost::where('status', 1) ->where('last_heartbeat_at', '<', $staleTime) ->update(['status' => 0]); if ($staleCount > 0) { Log::info("已将 {$staleCount} 个过期节点标记为离线。"); } } catch (\Exception $e) { Log::error("节点 [{$nodeId}] 心跳更新失败: " . $e->getMessage()); throw $e; } } /** * 收集主机的性能指标和静态信息 (不依赖任何外部系统命令). * @return array */ public static function collectHostInfo(): array { // 获取CPU平均负载 (如果函数存在且未被禁用) $cpuLoad = null; if (function_exists('sys_getloadavg')) { $load = sys_getloadavg(); $cpuLoad = is_array($load) ? implode(',', array_map(fn ($l) => round($l, 2), $load)) : null; } return [ // 动态指标 'status' => 1, 'last_heartbeat_at' => date('Y-m-d H:i:s'), 'ip_address' => gethostbyname(gethostname()), 'cpu_load' => $cpuLoad, 'memory_usage' => memory_get_usage(), // false: 获取脚本自身内存占用 'disk_free' => disk_free_space(App::getRootPath()), 'disk_total' => disk_total_space(App::getRootPath()), // 静态信息 'os_info' => php_uname(), 'php_version' => PHP_VERSION, ]; } /** * 获取当前主节点的node_id. * * @return string|null 返回主节点ID,无主节点时返回null */ public static function getMasterNode(): ?string { $master = SystemHost::where('is_master', 1)->where('status', 1)->find(); return $master ? $master->node_id : null; } /** * 手动切换主节点. * * @param string $nodeId 目标节点的node_id * @return bool 切换成功返回true,节点不存在返回false */ public static function setMasterNode(string $nodeId): bool { // 先清除所有主节点标记 SystemHost::where('is_master', 1)->update(['is_master' => 0]); // 设置新主节点 $host = SystemHost::where('node_id', $nodeId)->find(); if ($host) { $host->is_master = 1; $host->save(); return true; } return false; } }