延迟配置加载机制

延迟加载(Lazy Load)是 Viswoole 配置系统的一项性能优化特性,允许将非关键配置推迟到实际需要时才加载,减少服务启动时间和内存占用。

工作原理

text
正常加载流程:
启动 → 加载所有 config/*.php → 启动完成
(所有配置一次性载入内存)

延迟加载流程:
启动 → 加载 config/*.php → 启动完成 → 首次访问 → 加载 config/lazy/*.php
(非必要配置推迟到首次使用时才加载)

lazy 目录

延迟配置文件放置在 config/lazy/ 目录下:

text
config/
├── app.php              # 立即加载(核心配置)
├── cache.php            # 立即加载
├── server.php           # 立即加载
├── log.php              # 立即加载
├── database.php         # 立即加载

└── lazy/                # 延迟加载目录
    ├── middleware.php    # 首次使用中间件时加载
    └── custom.php       # 自定义延迟配置

触发时机

延迟配置在 AppInitialized 事件触发后 自动加载:

php
// Config 构造函数中的注册
public function __construct(Event $event)
{
    // 1. 先立即加载主配置目录
    $this->load($this->path);  // config/

    // 2. 注册事件监听,App 初始化完成后加载 lazy 目录
    $event->on('AppInitialized', function () {
        $this->load($this->path . 'lazy' . DIRECTORY_SEPARATOR);
    }, 1);
}

时间线:

text
1. 框架引导阶段

2. 创建 Config 实例,加载 config/*.php

3. 创建容器、注册服务提供者、绑定核心组件

4. 触发 AppInitialized 事件

5. Config 监听到事件,加载 config/lazy/*.php

6. 服务就绪,可以处理请求

适用场景

适合延迟加载的配置

  • 中间件配置:只在路由解析后才需要
  • 大型业务配置:如权限矩阵、菜单树等数据量大的配置
  • 第三方 SDK 配置:仅在调用特定功能时需要
  • 条件性配置:某些环境下可能完全用不到的配置

不适合延迟加载的配置

  • 核心服务配置:cache、log、database 等
  • Swoole 服务配置:server.php 必须在启动前可用
  • 应用基础配置:app.php 中的 name、debug 等
  • 被其他立即加载配置引用的配置

使用示例

定义延迟配置

php
// config/lazy/middleware.php
// 实际的中间件懒加载配置文件,通过门面直接注册全局中间件

declare (strict_types=1);

use Viswoole\Core\Facade\Middleware;
use Viswoole\Core\Middlewares\AllowCrossDomain;

// 该中间件用于解决跨域请求问题
Middleware::register(AllowCrossDomain::class);

说明:config/lazy/middleware.php 并非返回数组形式的配置,而是直接通过 Middleware 门面注册中间件。由于该文件在 AppInitialized 事件后才加载,因此中间件注册被推迟到应用初始化完成之后,从而减少启动阶段的开销。

访问延迟配置

对于返回数组的延迟配置文件(如自定义的 permissions.php),访问方式和普通配置完全一致:

php
// 假设 config/lazy/permissions.php 返回了如下结构:
// return ['permissions' => [...], 'roles' => [...]];
$permissions = config('permissions.permissions');
$roles = config('permissions.roles');

// 首次访问时自动触发加载
// 后续访问直接从内存读取,无额外开销

注意:config/lazy/middleware.php 是特例,它通过门面直接注册中间件而非返回配置数组,因此不能用 config('middleware.xxx') 访问。

自定义延迟配置

php
// config/lazy/permissions.php
<?php

declare (strict_types=1);

/**
 * 权限配置 - 数据量大,延迟加载
 */
return [
    // 权限定义
    'permissions' => [
        'user.create' => '创建用户',
        'user.read' => '查看用户',
        'user.update' => '编辑用户',
        'user.delete' => '删除用户',
        // ... 可能数百条权限定义
    ],

    // 角色权限映射
    'roles' => [
        'admin' => ['*'],  // 全部权限
        'editor' => ['user.create', 'user.read', 'user.update'],
        'viewer' => ['user.read'],
    ],

    // 菜单配置
    'menus' => [
        // ... 大型菜单树结构
    ],
];

性能对比

延迟加载的优势

假设有以下场景:

php
// config/lazy/heavy_config.php - 包含大量数据
return [
    'large_array' => array_fill(0, 10000, 'data'),  // 约 800KB
    'complex_structure' => [...],
];
指标立即加载延迟加载
启动时间包含加载该文件的时间不包含
内存占用(未使用时)占用 ~800KB0 KB
内存占用(使用后)~800KB~800KB
首次访问开销单次加载耗时

典型收益场景

  • API 服务:可能永远不需要中间件配置(如果使用不同的路由策略)
  • 微服务:只使用部分功能模块
  • Worker 进程:Task Worker 通常不需要 HTTP 相关配置

注意事项

1. 加载时机不可控

延迟配置在 AppInitialized 后加载,如果在初始化过程中就需要某配置,则不能放入 lazy 目录。

php
// ❌ 错误:在 Provider 的 register() 方法中访问 lazy 配置
// 此时 AppInitialized 尚未触发,lazy 配置尚未加载
class AppProvider extends Provider
{
    public function register(): void
    {
        // 假设 config/lazy/permissions.php 返回了权限配置
        $permissions = config('permissions.permissions');  // 可能为 null!
    }
}

// ✅ 正确:确保在请求处理阶段访问
class SomeService
{
    public function handle(): void
    {
        $permissions = config('permissions.permissions');  // 安全,已加载
    }
}

2. 配置依赖关系

如果 A 配置引用了 B 配置,且 B 是延迟加载的,需确保两者在同一加载批次:

php
// config/app.php - 立即加载
return [
    'name' => 'App',
    // ❌ 不要在这里引用 lazy 配置
    // 'permission_list' => config('permissions.permissions'),  // 可能还未加载
];

// 解决方案:将依赖链都放入 lazy,或都保持立即加载

3. 调试提示

如果发现延迟配置未能正确加载:

bash
# 1. 检查文件是否存在于正确位置
ls config/lazy/

# 2. 检查文件语法
php -l config/lazy/middleware.php

# 3. 检查 AppInitialized 事件是否正常触发
./viswoole debug:event

最佳实践

1. 分类原则

php
// ✅ 推荐:按加载时机分类
config/
├── app.php              # 核心 - 立即
├── cache.php            # 核心 - 立即
├── server.php           # 核心 - 立即
├── database.php         # 核心 - 立即
├── log.php              # 核心 - 立即

└── lazy/
    ├── middleware.php    # 路由相关 - 延迟
    ├── permissions.php  # 业务相关 - 延迟
    └── menus.php         # UI 相关 - 延迟

2. 文件大小阈值

建议单个配置文件超过 200 行 或预估内存占用超过 50KB 时考虑放入 lazy 目录。

3. 条件性加载

对于特定环境才需要的配置:

php
// config/lazy/dev_tools.php
<?php
// 仅开发环境使用的调试工具配置
if (env('APP_ENV') !== 'local') {
    return [];  // 非开发环境返回空数组
}

return [
    'laravel' => [...],
    'telescope' => [...],
    'debugger' => [...],
];