各位观众,大家好!今天咱们来聊聊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!"。
-
创建项目目录:
mkdir hello-serverless cd hello-serverless
-
初始化 Composer:
composer init
一路回车,使用默认配置即可。
-
创建
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 状态码、响应体和响应头。
-
创建
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 函数的入口点 (格式为文件.函数
)。
-
创建
bootstrap
文件:#!/bin/sh /opt/bootstrap index.handler
这个文件告诉 Lambda 函数如何启动你的 PHP 代码。
执行以下命令,赋予
bootstrap
文件执行权限:chmod +x bootstrap
-
打包 Lambda 函数:
zip -r deployment.zip *
这个命令会将当前目录下的所有文件打包成
deployment.zip
文件。
2.3 Lambda Layers
Lambda Layers 允许你共享代码和依赖项,避免重复上传。 我们可以创建一个 Lambda Layer 来包含 PHP 运行时。
-
下载 PHP 运行时:
从这个地址下载PHP运行时:https://github.com/stackery/php-lambda-layer 选择合适的PHP版本,比如
php-7.4-fpm.zip
。 -
解压并创建 Layer:
unzip php-7.4-fpm.zip -d php-7.4 cd php-7.4 zip -r php-7.4-layer.zip * cd ..
-
上传 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 函数
-
创建 Lambda 函数:
登录 AWS 控制台,进入 Lambda 服务,点击 "创建函数"。
- 选择 "从头开始创建"。
- 函数名称:
hello-serverless
- 运行时:选择 "使用自定义运行时" (provided.al2)。
- 架构:选择 "x86_64"。
- 点击 "创建函数"。
-
配置 Lambda 函数:
- 在 "函数代码" 选项卡中,上传
deployment.zip
文件。 - 在 "配置" -> "常规配置" 中,修改 "处理程序" 为
index.handler
。 - 在 "配置" -> "函数 URL" 中,创建函数 URL,并设置认证类型为 NONE
- 在 "函数代码" 选项卡中,上传
-
添加 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 为例。
-
安装 MySQL 客户端:
在你的
composer.json
文件中添加 MySQL 客户端依赖:{ "require": { "ext-mysqli": "*" } }
执行
composer update
安装依赖。 -
修改
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 连接信息。 -
更新 Lambda 函数:
重新打包
deployment.zip
文件并上传到 Lambda 函数。
3.2 使用框架 (Laravel/Symfony)
虽然 Serverless 函数通常是轻量级的,但你也可以使用 PHP 框架,例如 Laravel 或 Symfony。
-
创建 Laravel 项目:
composer create-project --prefer-dist laravel/laravel my-laravel-app cd my-laravel-app
-
修改
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);
-
创建
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, ]; }
-
修改
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 或更高。 -
配置路由:
在
routes/web.php
文件中定义你的路由:<?php use IlluminateSupportFacadesRoute; Route::get('/', function () { return 'Hello from Laravel Serverless!'; });
-
更新bootstrap文件
#!/bin/sh
cd /var/task
./vendor/bin/heroku-php-apache2 public/
- 更新 Lambda 函数:
重新打包 deployment.zip
文件并上传到 Lambda 函数。
3.3 使用 API Gateway
API Gateway 是 AWS 提供的一个服务,用于创建、管理和保护 API。 它可以将 HTTP 请求路由到 Lambda 函数。
-
创建 API Gateway:
登录 AWS 控制台,进入 API Gateway 服务,点击 "创建 API"。
- 选择 "HTTP API"。
- API 名称:
hello-serverless-api
- 集成类型:选择 "Lambda"。
- Lambda 函数:选择你的 Lambda 函数
hello-serverless
。 - 点击 "下一步"。
- 配置路由:
- 方法:
ANY
- 路径:
/
- 方法:
- 点击 "下一步"。
- 配置阶段:
- 阶段名称:
dev
- 自动部署更改:选中
- 阶段名称:
- 点击 "创建"。
-
测试 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) 进行异步处理。
-
发送消息到 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-region
、your-aws-access-key
、your-aws-secret-key
和your-sqs-queue-url
为你的 SQS 连接信息。 -
创建 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 的知识。
感谢大家的观看!