larvel 实现注册eureka
项目需要,php部分功能作为微服务注册到eureka
如下实现
<?php
namespace App\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
class EurekaService
{
protected $client;
protected $config;
protected $instanceId;
protected $appName;
protected $ipAddress;
protected $port;
protected $vipAddress;
protected $heartbeatInterval;
protected $eurekaUrl;
protected $renewalTimer;
/**
* EurekaService 构造函数
*/
public function __construct()
{
$this->client = new Client();
$this->loadConfig();
$this->setupInstance();
}
/**
* 从Laravel配置文件加载配置
*/
protected function loadConfig()
{
$this->config = config('eureka');
$this->eurekaUrl = $this->config['eureka_url'];
$this->appName = $this->config['app_name'];
$this->ipAddress = $this->config['ip_address'] ?? gethostbyname(gethostname());
$this->port = $this->config['port'] ?? 80;
$this->vipAddress = $this->config['vip_address'] ?? $this->appName;
$this->heartbeatInterval = $this->config['heartbeat_interval'] ?? 30;
$this->instanceId = $this->appName . ':' . $this->ipAddress . ':' . $this->port;
}
/**
* 设置实例详细信息
*/
protected function setupInstance()
{
// 默认实例数据
$this->instanceData = [
'instance' => [
'instanceId' => $this->instanceId,
'hostName' => $this->ipAddress,
'app' => strtoupper($this->appName),
'ipAddr' => $this->ipAddress,
'status' => 'UP',
'port' => [
'$' => $this->port,
'@enabled' => 'true',
],
'securePort' => [
'$' => 443,
'@enabled' => 'false',
],
'homePageUrl' => "http://{$this->ipAddress}:{$this->port}/",
'statusPageUrl' => "http://{$this->ipAddress}:{$this->port}/info",
'healthCheckUrl' => "http://{$this->ipAddress}:{$this->port}/health",
'dataCenterInfo' => [
'@class' => 'com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo',
'name' => 'MyOwn',
],
'leaseInfo' => [
'renewalIntervalInSecs' => 30,
'durationInSecs' => 90,
'registrationTimestamp' => 0,
'lastRenewalTimestamp' => 0,
'evictionTimestamp' => 0,
'serviceUpTimestamp' => 0,
],
'metadata' => [
'@class' => 'java.util.Collections$EmptyMap',
'management.port' => (string)$this->port,
],
'vipAddress' => $this->vipAddress,
'secureVipAddress' => $this->vipAddress,
'isCoordinatingDiscoveryServer' => 'false',
'lastUpdatedTimestamp' => (string)(time() * 1000),
'lastDirtyTimestamp' => (string)(time() * 1000),
'actionType' => 'ADDED',
]
];
}
/**
* 向Eureka服务器注册应用
*
* @return bool
*/
public function register()
{
try {
$response = $this->client->request(
'POST',
"{$this->eurekaUrl}/apps/{$this->appName}",
[
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json'
],
'json' => $this->instanceData
]
);
if ($response->getStatusCode() === 204) {
Log::info("成功将服务 {$this->appName} 注册到Eureka");
$this->startHeartbeat();
return true;
}
Log::error("向Eureka注册失败。状态码: " . $response->getStatusCode());
return false;
} catch (GuzzleException $e) {
Log::error("Eureka注册错误: " . $e->getMessage());
return false;
}
}
/**
* 启动心跳进程以保持注册活跃
*/
protected function startHeartbeat()
{
// 存储缓存值以跟踪心跳是否活跃
Cache::put('eureka_heartbeat_active', true, now()->addDay());
// 在生产环境中,您可能希望使用更可靠的方法
// 如计划任务或队列工作器来处理心跳
$this->scheduleNextHeartbeat();
}
/**
* 安排下一次心跳
*/
protected function scheduleNextHeartbeat()
{
// 这是一个简化的演示方法
// 在实际应用中,您应该使用Laravel的任务调度器或队列工作器
dispatch(function () {
if (Cache::get('eureka_heartbeat_active', false)) {
$this->sendHeartbeat();
$this->scheduleNextHeartbeat();
}
})->delay(now()->addSeconds($this->heartbeatInterval));
}
/**
* 向Eureka发送心跳
*
* @return bool
*/
public function sendHeartbeat()
{
try {
$response = $this->client->request(
'PUT',
"{$this->eurekaUrl}/apps/{$this->appName}/{$this->instanceId}",
[
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json'
]
]
);
if ($response->getStatusCode() === 200) {
Log::debug("已向Eureka发送 {$this->appName} 的心跳");
return true;
}
Log::warning("向Eureka发送心跳失败。状态码: " . $response->getStatusCode());
return false;
} catch (GuzzleException $e) {
Log::error("Eureka心跳错误: " . $e->getMessage());
return false;
}
}
/**
* 从Eureka服务器注销
*
* @return bool
*/
public function deregister()
{
try {
// 首先停止心跳
Cache::forget('eureka_heartbeat_active');
$response = $this->client->request(
'DELETE',
"{$this->eurekaUrl}/apps/{$this->appName}/{$this->instanceId}"
);
if ($response->getStatusCode() === 200) {
Log::info("成功从Eureka注销服务 {$this->appName}");
return true;
}
Log::error("从Eureka注销失败。状态码: " . $response->getStatusCode());
return false;
} catch (GuzzleException $e) {
Log::error("Eureka注销错误: " . $e->getMessage());
return false;
}
}
/**
* 从Eureka获取所有服务
*
* @return array|null
*/
public function getAllServices()
{
try {
$response = $this->client->request(
'GET',
"{$this->eurekaUrl}/apps",
[
'headers' => [
'Accept' => 'application/json'
]
]
);
if ($response->getStatusCode() === 200) {
return json_decode($response->getBody()->getContents(), true);
}
Log::error("从Eureka获取服务失败。状态码: " . $response->getStatusCode());
return null;
} catch (GuzzleException $e) {
Log::error("从Eureka获取服务时出错: " . $e->getMessage());
return null;
}
}
/**
* 通过应用名称获取特定服务
*
* @param string $appName
* @return array|null
*/
public function getServiceByName($appName)
{
try {
$response = $this->client->request(
'GET',
"{$this->eurekaUrl}/apps/{$appName}",
[
'headers' => [
'Accept' => 'application/json'
]
]
);
if ($response->getStatusCode() === 200) {
return json_decode($response->getBody()->getContents(), true);
}
Log::error("从Eureka获取服务 {$appName} 失败。状态码: " . $response->getStatusCode());
return null;
} catch (GuzzleException $e) {
Log::error("从Eureka获取服务 {$appName} 时出错: " . $e->getMessage());
return null;
}
}
}
<?php
return [
/*
|--------------------------------------------------------------------------
| Eureka 连接设置
|--------------------------------------------------------------------------
|
| 配置连接到Eureka服务发现服务器的设置。
|
*/
// Eureka服务器URL
'eureka_url' => env('EUREKA_URL', 'http://localhost:8761/eureka'),
// 您的应用名称(用于服务注册)
'app_name' => env('EUREKA_APP_NAME', 'laravel-service'),
// 实例IP地址(如果未指定,默认为服务器IP)
'ip_address' => env('EUREKA_IP_ADDRESS', null),
// 服务运行的端口
'port' => env('EUREKA_PORT', 80),
// VIP地址(用于负载均衡的虚拟IP,如果未指定,默认为app_name)
'vip_address' => env('EUREKA_VIP_ADDRESS', null),
// 心跳间隔(秒)
'heartbeat_interval' => env('EUREKA_HEARTBEAT_INTERVAL', 30),
// 关于此实例的元数据(可选)
'metadata' => [
'management.port' => env('EUREKA_MANAGEMENT_PORT', 80),
'environment' => env('APP_ENV', 'production'),
],
];
<?php
namespace App\Providers;
use App\Services\EurekaService;
use Illuminate\Support\ServiceProvider;
class EurekaServiceProvider extends ServiceProvider
{
/**
* 注册服务
*
* @return void
*/
public function register()
{
$this->mergeConfigFrom(
__DIR__ . '/../../config/eureka.php', 'eureka'
);
$this->app->singleton(EurekaService::class, function ($app) {
return new EurekaService();
});
}
/**
* 引导服务
*
* @return void
*/
public function boot()
{
$this->publishes([
__DIR__ . '/../../config/eureka.php' => config_path('eureka.php'),
], 'config');
// 仅在启用Eureka且不在控制台运行时注册
if (config('eureka.enabled', true) && !$this->app->runningInConsole()) {
$eureka = $this->app->make(EurekaService::class);
$eureka->register();
// 注册关闭函数,在应用程序关闭时注销
register_shutdown_function(function () use ($eureka) {
$eureka->deregister();
});
}
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class HealthController extends Controller
{
/**
* 返回应用程序健康状态
*
* @return \Illuminate\Http\JsonResponse
*/
public function health()
{
$status = 'UP';
$components = [];
// 检查数据库连接
try {
DB::connection()->getPdo();
$components['database'] = [
'status' => 'UP',
'type' => 'db',
'details' => [
'database' => config('database.default'),
]
];
} catch (\Exception $e) {
$status = 'DOWN';
$components['database'] = [
'status' => 'DOWN',
'type' => 'db',
'details' => [
'error' => $e->getMessage(),
]
];
}
// 检查磁盘空间
$diskFree = disk_free_space('/');
$diskTotal = disk_total_space('/');
$diskThreshold = 0.1; // 10%空闲空间阈值
$diskStatus = ($diskFree / $diskTotal > $diskThreshold) ? 'UP' : 'DOWN';
if ($diskStatus === 'DOWN') {
$status = 'DOWN';
}
$components['diskSpace'] = [
'status' => $diskStatus,
'details' => [
'total' => $diskTotal,
'free' => $diskFree,
'threshold' => $diskThreshold,
]
];
return response()->json([
'status' => $status,
'components' => $components,
]);
}
/**
* 返回应用程序信息
*
* @return \Illuminate\Http\JsonResponse
*/
public function info()
{
return response()->json([
'app' => [
'name' => config('app.name'),
'version' => config('app.version', '1.0.0'),
'environment' => config('app.env'),
'laravel' => app()->version(),
],
]);
}
}
Laravel Eureka集成指南
本指南将帮助您将Laravel应用程序与Netflix的Eureka服务发现系统集成。此集成允许您的Laravel应用向Eureka注册,维持心跳,并发现其他服务。
1. 设置配置
首先,为Eureka创建一个新的配置文件:
php artisan vendor:publish --provider="App\Providers\EurekaServiceProvider" --tag="config"
如果发布命令不起作用(因为我们尚未注册提供者),请手动创建配置文件:
mkdir -p config
然后将eureka.php
配置文件放在Laravel项目的config
目录中。
2. 配置环境变量
将这些变量添加到您的.env
文件中:
EUREKA_URL=http://your-eureka-server:8761/eureka
EUREKA_APP_NAME=your-service-name
EUREKA_PORT=80
EUREKA_IP_ADDRESS=your-service-ip
3. 创建所需文件
按如下方式创建必要的服务和提供者文件:
-
首先,创建EurekaService类:
mkdir -p app/Services
然后将
EurekaService.php
文件复制到此目录。 -
创建服务提供者:
mkdir -p app/Providers
然后将
EurekaServiceProvider.php
文件复制到此目录。 -
创建用于Eureka健康检查的控制器:
mkdir -p app/Http/Controllers
然后将
HealthController.php
文件复制到此目录。
4. 注册服务提供者
将Eureka服务提供者添加到config/app.php
文件的providers数组中:
'providers' => [
// 其他提供者...
App\Providers\EurekaServiceProvider::class,
],
5. 添加健康检查路由
将这些路由添加到您的routes/api.php
文件中:
Route::get('/health', [App\Http\Controllers\HealthController::class, 'health']);
Route::get('/info', [App\Http\Controllers\HealthController::class, 'info']);
6. 测试集成
您可以通过以下方式测试您的Laravel应用程序是否成功注册到Eureka:
-
启动您的Laravel应用程序:
php artisan serve
-
检查Eureka仪表板(通常在http://your-eureka-server:8761)
-
在已注册应用程序列表中查找您的服务
7. 使用服务发现
现在您可以使用EurekaService来发现其他服务:
$eurekaService = app(App\Services\EurekaService::class);
// 获取所有已注册服务的信息
$allServices = $eurekaService->getAllServices();
// 获取特定服务的信息
$userService = $eurekaService->getServiceByName('USER-SERVICE');
8. 故障排除
如果遇到问题:
- 检查Laravel日志:
storage/logs/laravel.log
- 验证Eureka服务器是否正在运行并可访问
- 检查您的Laravel应用和Eureka之间的网络连接
- 确保您应用的IP地址设置正确
9. 额外配置
对于生产用途,请考虑:
-
设置计划任务以实现更可靠的心跳:
// 在 App\Console\Kernel.php 中 protected function schedule(Schedule $schedule) { $schedule->call(function () { app(App\Services\EurekaService::class)->sendHeartbeat(); })->everyThirtySeconds(); }
-
在选择服务实例时添加负载均衡逻辑
-
为服务调用实现断路器
<?php
// routes/api.php
use App\Http\Controllers\HealthController;
use Illuminate\Support\Facades\Route;
// Eureka健康检查端点
Route::get('/health', [HealthController::class, 'health']);
Route::get('/info', [HealthController::class, 'info']);
// 示例:创建使用其他Eureka服务的路由
Route::get('/service-example', function() {
$eurekaService = app(\App\Services\EurekaService::class);
// 示例:获取服务信息
$serviceInfo = $eurekaService->getServiceByName('EXAMPLE-SERVICE');
if (!$serviceInfo) {
return response()->json(['error' => '未找到服务'], 404);
}
// 提取实例详细信息(简化示例)
$instance = $serviceInfo['application']['instance'][0] ?? null;
if (!$instance) {
return response()->json(['error' => '没有可用实例'], 503);
}
$serviceUrl = "http://{$instance['ipAddr']}:{$instance['port']['$']}/api/endpoint";
// 在这里,您可以使用Guzzle或其他HTTP客户端调用服务
// $client = new \GuzzleHttp\Client();
// $response = $client->get($serviceUrl);
return response()->json([
'message' => '服务已发现',
'serviceUrl' => $serviceUrl
]);
});
我已经为Laravel应用程序实现了Eureka服务注册功能,并按要求将所有代码注释和说明改为中文。以下是实现方案的主要组件和使用方法:
主要组件
-
EurekaService 服务类
- 处理与Eureka服务器的所有交互
- 提供注册、心跳、注销和服务发现功能
-
配置文件
- 包含Eureka连接和注册所需的所有配置选项
- 支持通过环境变量自定义配置
-
服务提供者
- 注册EurekaService到Laravel容器
- 处理应用启动时的服务注册和关闭时的注销
-
健康检查控制器
- 提供健康检查和应用信息端点
- 符合Eureka的健康检查要求
-
路由配置
- 包含健康检查和服务发现示例的路由
-
安装指南
- 详细说明如何在Laravel项目中设置和配置Eureka集成
使用方法
- 将所有文件复制到相应的Laravel项目目录中
- 在
.env
文件中配置Eureka连接信息 - 注册服务提供者
- 启动应用程序,
评论