PHP Serverless 架构 (Lambda/Azure Functions):无服务器函数开发与部署

各位观众,大家好!今天咱们来聊聊PHP Serverless,这玩意儿听起来高大上,其实简单来说,就是让你的PHP代码在云端“隐身”运行,不用管服务器,省心省力。咱们今天就一步一步来,从概念到实战,让你也能玩转PHP Serverless。

开场白:为什么要Serverless?

想象一下,你开个小卖部,卖点零食。传统模式是你得租个店面,装修,进货,还得天天守着。Serverless就像啥?就像你把货放到一个共享的货架上,有人来买,你才付货架的钱,没人买就不用付钱。省钱!省心!

Serverless的主要优点:

  • 降低成本: 按需付费,不用为闲置资源买单。
  • 自动伸缩: 流量高峰时自动扩容,低谷时自动缩容。
  • 简化运维: 无需管理服务器,专注于代码逻辑。
  • 快速部署: 代码即部署,快速迭代。

第一部分:Serverless 基础概念

Serverless 不是真的“没有服务器”,而是你不需要关心服务器。 你只需要关注你的代码,云服务商会帮你处理服务器的配置、管理和维护。

1.1 函数即服务 (FaaS)

FaaS (Function as a Service) 是 Serverless 的核心组成部分。它允许你将应用程序分解为独立的、可执行的函数。 每个函数都响应特定的事件触发器。

1.2 事件驱动

Serverless 函数由事件触发。事件可以是 HTTP 请求、消息队列中的消息、定时任务等等。

1.3 状态管理

Serverless 函数通常是无状态的,这意味着它们不保留任何数据。如果需要存储数据,你需要使用外部服务,例如数据库或对象存储。

1.4 常用平台

  • AWS Lambda: 亚马逊的 Serverless 服务。
  • Azure Functions: 微软的 Serverless 服务。
  • Google Cloud Functions: 谷歌的 Serverless 服务。

咱们今天主要以AWS Lambda为例,因为AWS Lambda比较成熟,社区也比较活跃。

第二部分:PHP Lambda 函数开发

2.1 开发环境准备

首先,你需要一个AWS账号,安装AWS CLI工具,并配置好权限。这些都可以在AWS官方文档中找到详细的步骤。

其次,安装PHP和Composer。PHP版本推荐7.4及以上。

2.2 第一个 PHP Lambda 函数

咱们来创建一个简单的 Lambda 函数,返回 "Hello, Serverless!"。

  1. 创建项目目录:

    mkdir hello-serverless
    cd hello-serverless
  2. 初始化 Composer:

    composer init

    一路回车,使用默认配置即可。

  3. 创建 index.php 文件:

    <?php
    
    function handler($event, $context) {
        return [
            'statusCode' => 200,
            'body' => json_encode([
                'message' => 'Hello, Serverless!',
            ]),
            'headers' => [
                'Content-Type' => 'application/json',
            ],
        ];
    }

    这个 handler 函数是 Lambda 函数的入口点。它接收两个参数:$event$context$event 包含了触发函数的事件信息,$context 包含了函数的运行时上下文信息。

    函数的返回值是一个数组,包含了 HTTP 状态码、响应体和响应头。

  4. 创建 aws-lambda-tools-defaults.json 文件:

    {
      "region": "your-aws-region",
      "configuration": "Release",
      "function-runtime": "provided.al2",
      "function-memory-size": 128,
      "function-timeout": 30,
      "function-handler": "index.handler"
    }
    • region: 你的 AWS 区域 (例如:us-east-1)。
    • configuration: 构建配置 (通常为 Release)。
    • function-runtime: Lambda 函数的运行时环境 (provided.al2 表示使用自定义运行时)。
    • function-memory-size: Lambda 函数的内存大小 (单位:MB)。
    • function-timeout: Lambda 函数的超时时间 (单位:秒)。
    • function-handler: Lambda 函数的入口点 (格式为 文件.函数)。
  5. 创建 bootstrap 文件:

    #!/bin/sh
    /opt/bootstrap index.handler

    这个文件告诉 Lambda 函数如何启动你的 PHP 代码。

    执行以下命令,赋予 bootstrap 文件执行权限:

    chmod +x bootstrap
  6. 打包 Lambda 函数:

    zip -r deployment.zip *

    这个命令会将当前目录下的所有文件打包成 deployment.zip 文件。

2.3 Lambda Layers

Lambda Layers 允许你共享代码和依赖项,避免重复上传。 我们可以创建一个 Lambda Layer 来包含 PHP 运行时。

  1. 下载 PHP 运行时:

    从这个地址下载PHP运行时:https://github.com/stackery/php-lambda-layer 选择合适的PHP版本,比如 php-7.4-fpm.zip

  2. 解压并创建 Layer:

    unzip php-7.4-fpm.zip -d php-7.4
    cd php-7.4
    zip -r php-7.4-layer.zip *
    cd ..
  3. 上传 Layer 到 AWS Lambda:

    使用 AWS CLI 上传 Layer:

    aws lambda publish-layer-version --layer-name php-74-runtime --zip-file fileb://php-7.4/php-7.4-layer.zip --compatible-runtimes provided.al2

    记下返回的 LayerVersionArn,后续配置 Lambda 函数时需要用到。

2.4 部署 Lambda 函数

  1. 创建 Lambda 函数:

    登录 AWS 控制台,进入 Lambda 服务,点击 "创建函数"。

    • 选择 "从头开始创建"。
    • 函数名称:hello-serverless
    • 运行时:选择 "使用自定义运行时" (provided.al2)。
    • 架构:选择 "x86_64"。
    • 点击 "创建函数"。
  2. 配置 Lambda 函数:

    • 在 "函数代码" 选项卡中,上传 deployment.zip 文件。
    • 在 "配置" -> "常规配置" 中,修改 "处理程序" 为 index.handler
    • 在 "配置" -> "函数 URL" 中,创建函数 URL,并设置认证类型为 NONE
  3. 添加 Layer:

    在 "函数代码" 选项卡中,点击 "Layers",然后点击 "添加 Layer"。

    • 选择 "指定 ARN"。
    • 输入你之前获取的 LayerVersionArn
    • 点击 "添加"。

2.5 测试 Lambda 函数

在AWS Lambda控制台中点击“测试”按钮,创建一个简单的测试事件,然后点击“测试”。

你应该会看到如下的输出:

{
  "statusCode": 200,
  "body": "{"message":"Hello, Serverless!"}",
  "headers": {
    "Content-Type": "application/json"
  }
}

或者,直接访问你创建的函数 URL,你应该会看到相同的输出。

第三部分:高级应用

3.1 数据库连接

Serverless 应用通常需要访问数据库。 你可以使用 AWS RDS、Aurora 或者 DynamoDB。 这里以 MySQL 为例。

  1. 安装 MySQL 客户端:

    在你的 composer.json 文件中添加 MySQL 客户端依赖:

    {
      "require": {
        "ext-mysqli": "*"
      }
    }

    执行 composer update 安装依赖。

  2. 修改 index.php 文件:

    <?php
    
    function handler($event, $context) {
        $host = 'your-db-host';
        $username = 'your-db-username';
        $password = 'your-db-password';
        $database = 'your-db-name';
    
        $conn = mysqli_connect($host, $username, $password, $database);
    
        if (!$conn) {
            return [
                'statusCode' => 500,
                'body' => json_encode([
                    'message' => 'Failed to connect to MySQL: ' . mysqli_connect_error(),
                ]),
                'headers' => [
                    'Content-Type' => 'application/json',
                ],
            ];
        }
    
        $query = "SELECT * FROM users";
        $result = mysqli_query($conn, $query);
    
        $users = [];
        while ($row = mysqli_fetch_assoc($result)) {
            $users[] = $row;
        }
    
        mysqli_close($conn);
    
        return [
            'statusCode' => 200,
            'body' => json_encode([
                'users' => $users,
            ]),
            'headers' => [
                'Content-Type' => 'application/json',
            ],
        ];
    }

    替换 $host$username$password$database 为你的 MySQL 连接信息。

  3. 更新 Lambda 函数:

    重新打包 deployment.zip 文件并上传到 Lambda 函数。

3.2 使用框架 (Laravel/Symfony)

虽然 Serverless 函数通常是轻量级的,但你也可以使用 PHP 框架,例如 Laravel 或 Symfony。

  1. 创建 Laravel 项目:

    composer create-project --prefer-dist laravel/laravel my-laravel-app
    cd my-laravel-app
  2. 修改 server.php 文件:

    <?php
    
    use IlluminateContractsHttpKernel;
    use IlluminateHttpRequest;
    
    define('LARAVEL_START', microtime(true));
    
    /*
    |--------------------------------------------------------------------------
    | Check if the application is under maintenance
    |--------------------------------------------------------------------------
    |
    | If the application is in maintenance / demo mode via the "down"
    | command we will load this file so that any pre-rendered content
    | can be shown instead of starting the framework, which could be
    | changed at any moment.
    |
    */
    
    if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) {
        require __DIR__.'/../storage/framework/maintenance.php';
    }
    
    /*
    |--------------------------------------------------------------------------
    | Register the Composer Autoloader
    |--------------------------------------------------------------------------
    |
    | Composer provides a convenient, automatically generated class loader for
    | our application. We just need to utilize it! We'll simply require it
    | into the script here so that we don't have to worry about manual
    | loading any of our classes later on. It feels nice to relax.
    |
    */
    
    require __DIR__.'/../vendor/autoload.php';
    
    /*
    |--------------------------------------------------------------------------
    | Turn On The Lights
    |--------------------------------------------------------------------------
    |
    | We need to illuminate PHP development, so let us turn on the lights.
    | This bootstraps the framework and gets it ready for use, then it
    | will load up this application so that we can run it and send
    | the responses back to the browser and delight our users.
    |
    */
    
    $app = require_once __DIR__.'/../bootstrap/app.php';
    
    /*
    |--------------------------------------------------------------------------
    | Run The Application
    |--------------------------------------------------------------------------
    |
    | Here we will make the application "handle" the incoming request.
    | After the response is sent, we will call the terminate method
    | on the application, which will be useful if you need any
    | terminal operations to run after the response is sent.
    |
    */
    
    $kernel = $app->make(Kernel::class);
    
    $response = $kernel->handle(
        $request = Request::capture()
    )->send();
    
    $kernel->terminate($request, $response);
  3. 创建 index.php 文件:

    <?php
    
    require __DIR__ . '/vendor/autoload.php';
    
    use IlluminateContractsHttpKernel;
    use IlluminateHttpRequest;
    use IlluminateHttpResponse;
    
    function handler($event, $context) {
        // Define the base path.
        define('LARAVEL_BASE_PATH', '/tmp');
    
        // Ensure the /tmp directory exists.
        if (!is_dir(LARAVEL_BASE_PATH)) {
            mkdir(LARAVEL_BASE_PATH, 0777, true);
        }
    
        $uri = $_SERVER['REQUEST_URI'] = $event['path'];
        $_SERVER['REQUEST_METHOD'] = $event['httpMethod'];
        $_SERVER['REQUEST_QUERY_STRING'] = isset($event['queryStringParameters']) ? http_build_query($event['queryStringParameters']) : '';
    
        $headers = $event['headers'];
        foreach ($headers as $key => $value) {
            $normalizedKey = str_replace('-', '_', strtoupper($key));
            $_SERVER['HTTP_' . $normalizedKey] = $value;
        }
    
        ob_start();
        /** @var IlluminateFoundationApplication $app */
        $app = require __DIR__ . '/bootstrap/app.php';
    
        /** @var IlluminateContractsHttpKernel $kernel */
        $kernel = $app->make(Kernel::class);
    
        $request = Request::capture();
    
        $response = $kernel->handle(
            $request
        );
    
        $kernel->terminate($request, $response);
    
        $content = ob_get_clean();
    
        $responseBody = $response->getContent();
    
        return [
            'statusCode' => $response->getStatusCode(),
            'headers' => $response->headers->all(),
            'body' => $responseBody,
        ];
    
    }
  4. 修改 aws-lambda-tools-defaults.json 文件:

    {
      "region": "your-aws-region",
      "configuration": "Release",
      "function-runtime": "provided.al2",
      "function-memory-size": 512,
      "function-timeout": 30,
      "function-handler": "index.handler"
    }

    注意,Laravel 框架需要更多的内存,所以需要将 function-memory-size 设置为 512MB 或更高。

  5. 配置路由:

    routes/web.php 文件中定义你的路由:

    <?php
    
    use IlluminateSupportFacadesRoute;
    
    Route::get('/', function () {
        return 'Hello from Laravel Serverless!';
    });
  6. 更新bootstrap文件

#!/bin/sh
cd /var/task
./vendor/bin/heroku-php-apache2 public/
  1. 更新 Lambda 函数:

重新打包 deployment.zip 文件并上传到 Lambda 函数。

3.3 使用 API Gateway

API Gateway 是 AWS 提供的一个服务,用于创建、管理和保护 API。 它可以将 HTTP 请求路由到 Lambda 函数。

  1. 创建 API Gateway:

    登录 AWS 控制台,进入 API Gateway 服务,点击 "创建 API"。

    • 选择 "HTTP API"。
    • API 名称:hello-serverless-api
    • 集成类型:选择 "Lambda"。
    • Lambda 函数:选择你的 Lambda 函数 hello-serverless
    • 点击 "下一步"。
    • 配置路由:
      • 方法:ANY
      • 路径:/
    • 点击 "下一步"。
    • 配置阶段:
      • 阶段名称:dev
      • 自动部署更改:选中
    • 点击 "创建"。
  2. 测试 API:

    API Gateway 创建完成后,你会得到一个 API Endpoint URL。 你可以使用 curl 或 Postman 测试 API。

    curl https://your-api-endpoint-url

3.4 异步处理

对于耗时的任务,可以使用 SQS (Simple Queue Service) 或 SNS (Simple Notification Service) 进行异步处理。

  1. 发送消息到 SQS:

    <?php
    
    use AwsSqsSqsClient;
    
    function handler($event, $context) {
        $sqs = new SqsClient([
            'region' => 'your-aws-region',
            'version' => 'latest',
            'credentials' => [
                'key' => 'your-aws-access-key',
                'secret' => 'your-aws-secret-key',
            ],
        ]);
    
        $queueUrl = 'your-sqs-queue-url';
    
        $result = $sqs->sendMessage([
            'QueueUrl' => $queueUrl,
            'MessageBody' => json_encode([
                'message' => 'Hello, SQS!',
            ]),
        ]);
    
        return [
            'statusCode' => 200,
            'body' => json_encode([
                'messageId' => $result['MessageId'],
            ]),
            'headers' => [
                'Content-Type' => 'application/json',
            ],
        ];
    }

    替换 your-aws-regionyour-aws-access-keyyour-aws-secret-keyyour-sqs-queue-url 为你的 SQS 连接信息。

  2. 创建 SQS 触发的 Lambda 函数:

    创建一个新的 Lambda 函数,运行时选择 "使用自定义运行时" (provided.al2),触发器选择 "SQS",并配置 SQS 队列。

    <?php
    
    function handler($event, $context) {
        foreach ($event['Records'] as $record) {
            $message = json_decode($record['body'], true);
            // 处理消息
            error_log('Received message: ' . $message['message']);
        }
    
        return [
            'statusCode' => 200,
            'body' => json_encode([
                'message' => 'Message processed!',
            ]),
            'headers' => [
                'Content-Type' => 'application/json',
            ],
        ];
    }

第四部分:最佳实践

  • 保持函数轻量级: 避免在 Lambda 函数中执行耗时的操作。
  • 使用 Lambda Layers: 共享代码和依赖项,减少部署包的大小。
  • 配置适当的内存和超时时间: 根据函数的需求配置内存和超时时间。
  • 监控 Lambda 函数: 使用 CloudWatch 监控 Lambda 函数的性能和错误。
  • 使用环境变量: 将配置信息存储在环境变量中,避免硬编码。
  • 错误处理: 确保 Lambda 函数能够正确处理错误。
  • 代码复用: 尽量复用代码,减少代码冗余。

结语

Serverless 架构为 PHP 开发者提供了一种新的方式来构建和部署应用程序。 它简化了运维,降低了成本,并提高了开发效率。 希望今天的分享能够帮助你入门 PHP Serverless 开发。 以后有机会再跟大家分享更多关于Serverless 的知识。

感谢大家的观看!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注