larvel 实现注册eureka

jonathan
2019-02-16 / 0 评论

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. 创建所需文件

按如下方式创建必要的服务和提供者文件:

  1. 首先,创建EurekaService类:

    mkdir -p app/Services
    

    然后将EurekaService.php文件复制到此目录。

  2. 创建服务提供者:

    mkdir -p app/Providers
    

    然后将EurekaServiceProvider.php文件复制到此目录。

  3. 创建用于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:

  1. 启动您的Laravel应用程序:

    php artisan serve
    
  2. 检查Eureka仪表板(通常在http://your-eureka-server:8761)

  3. 在已注册应用程序列表中查找您的服务

7. 使用服务发现

现在您可以使用EurekaService来发现其他服务:

$eurekaService = app(App\Services\EurekaService::class);

// 获取所有已注册服务的信息
$allServices = $eurekaService->getAllServices();

// 获取特定服务的信息
$userService = $eurekaService->getServiceByName('USER-SERVICE');

8. 故障排除

如果遇到问题:

  1. 检查Laravel日志:storage/logs/laravel.log
  2. 验证Eureka服务器是否正在运行并可访问
  3. 检查您的Laravel应用和Eureka之间的网络连接
  4. 确保您应用的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服务注册功能,并按要求将所有代码注释和说明改为中文。以下是实现方案的主要组件和使用方法:

主要组件

  1. EurekaService 服务类

    • 处理与Eureka服务器的所有交互
    • 提供注册、心跳、注销和服务发现功能
  2. 配置文件

    • 包含Eureka连接和注册所需的所有配置选项
    • 支持通过环境变量自定义配置
  3. 服务提供者

    • 注册EurekaService到Laravel容器
    • 处理应用启动时的服务注册和关闭时的注销
  4. 健康检查控制器

    • 提供健康检查和应用信息端点
    • 符合Eureka的健康检查要求
  5. 路由配置

    • 包含健康检查和服务发现示例的路由
  6. 安装指南

    • 详细说明如何在Laravel项目中设置和配置Eureka集成

使用方法

  1. 将所有文件复制到相应的Laravel项目目录中
  2. .env文件中配置Eureka连接信息
  3. 注册服务提供者
  4. 启动应用程序,
PHP

评论

博主关闭了当前页面的评论