ThinkPHP6/8 容器化部署与微服务架构实战指南

随着云原生技术的发展,容器化部署已成为现代应用部署的标准方式。本文将详细介绍如何将ThinkPHP6/8项目进行容器化部署,以及如何构建微服务架构。

容器化概述

什么是容器化

容器化是一种轻量级的虚拟化技术,它将应用程序及其依赖项打包到一个可移植的容器中。容器化的主要优势包括:

  • 环境一致性:开发、测试、生产环境完全一致
  • 快速部署:秒级启动,快速扩缩容
  • 资源隔离:进程级别的资源隔离
  • 易于管理:统一的部署和管理方式
  • 可移植性:跨平台运行

Docker基础概念

  • 镜像(Image):只读的模板,用于创建容器
  • 容器(Container):镜像的运行实例
  • Dockerfile:构建镜像的脚本文件
  • Docker Compose:多容器应用的编排工具

ThinkPHP Docker化实践

项目结构准备

首先创建项目的Docker相关文件结构:

1
2
3
4
5
6
7
8
9
10
11
12
project/
├── docker/
│ ├── nginx/
│ │ ├── Dockerfile
│ │ └── nginx.conf
│ └── php/
│ ├── Dockerfile
│ └── php.ini
├── docker-compose.yml
├── .dockerignore
└── app/
└── (ThinkPHP项目文件)

PHP容器构建

创建docker/php/Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 使用官方PHP 8.2 FPM镜像作为基础镜像
FROM php:8.2-fpm

# 设置工作目录
WORKDIR /var/www/html

# 更新软件包列表并安装必要的系统依赖
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
apt-get update -qq && \
apt-get install -y --no-install-recommends \
libzip-dev \
zip \
libpng-dev \
libonig-dev \
libxml2-dev \
unzip \
git \
curl && \
# 安装PHP扩展
docker-php-ext-install \
pdo_mysql \
zip \
mbstring \
exif \
pcntl \
bcmath \
gd \
opcache && \
# 安装Redis扩展
pecl install redis-5.3.7 && \
docker-php-ext-enable redis && \
# 清理安装包缓存
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# 安装Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# 更换Composer源为阿里云源
RUN composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

# 设置环境变量允许在root用户下使用Composer
ENV COMPOSER_ALLOW_SUPERUSER=1

# 复制PHP配置文件
COPY php.ini /usr/local/etc/php/conf.d/custom.ini

# 复制项目文件
COPY . /var/www/html

# 安装项目依赖
RUN composer install --no-dev --optimize-autoloader

# 设置文件权限
RUN chown -R www-data:www-data /var/www/html && \
chmod -R 755 /var/www/html/runtime

# 暴露端口
EXPOSE 9000

# 启动PHP-FPM
CMD ["php-fpm"]

创建docker/php/php.ini

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
; PHP配置优化
memory_limit = 256M
post_max_size = 50M
upload_max_filesize = 50M
max_execution_time = 300
max_input_vars = 3000

; 时区设置
date.timezone = Asia/Shanghai

; OPcache配置
opcache.enable = 1
opcache.enable_cli = 1
opcache.memory_consumption = 256
opcache.interned_strings_buffer = 128
opcache.max_accelerated_files = 10000
opcache.max_wasted_percentage = 10
opcache.validate_timestamps = 0
opcache.enable_file_override = 1

; 错误报告
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log

Nginx容器构建

创建docker/nginx/Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用官方Nginx镜像
FROM nginx:1.24-alpine

# 复制Nginx配置文件
COPY nginx.conf /etc/nginx/nginx.conf

# 创建日志目录
RUN mkdir -p /var/log/nginx

# 暴露端口
EXPOSE 80 443

# 启动Nginx
CMD ["nginx", "-g", "daemon off;"]

创建docker/nginx/nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
use epoll;
multi_accept on;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

# 性能优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 50M;

# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;

# 上游PHP-FPM服务器
upstream php-fpm {
server php:9000;
}

server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html index.htm;

# 访问日志
access_log /var/log/nginx/thinkphp_access.log main;
error_log /var/log/nginx/thinkphp_error.log;

# ThinkPHP路由重写
location / {
try_files $uri $uri/ /index.php?$query_string;
}

# PHP文件处理
location ~ \.php$ {
fastcgi_pass php-fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;

# 安全设置
fastcgi_param HTTP_PROXY "";
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
}

# 静态文件缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}

# 安全设置
location ~ /\. {
deny all;
}

location ~ /(runtime|vendor)/ {
deny all;
}
}
}

Docker Compose编排

创建docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
version: '3.8'

services:
# Nginx Web服务器
nginx:
build:
context: .
dockerfile: docker/nginx/Dockerfile
container_name: thinkphp_nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./app:/var/www/html
- ./logs/nginx:/var/log/nginx
depends_on:
- php
networks:
- thinkphp_network
restart: unless-stopped

# PHP-FPM服务
php:
build:
context: ./app
dockerfile: ../docker/php/Dockerfile
container_name: thinkphp_php
volumes:
- ./app:/var/www/html
- ./logs/php:/var/log
environment:
- APP_ENV=production
- DB_HOST=mysql
- DB_PORT=3306
- DB_DATABASE=thinkphp
- DB_USERNAME=root
- DB_PASSWORD=123456
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
- mysql
- redis
networks:
- thinkphp_network
restart: unless-stopped

# MySQL数据库
mysql:
image: mysql:8.0
container_name: thinkphp_mysql
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: thinkphp
MYSQL_USER: thinkphp
MYSQL_PASSWORD: 123456
volumes:
- mysql_data:/var/lib/mysql
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
networks:
- thinkphp_network
restart: unless-stopped

# Redis缓存
redis:
image: redis:7-alpine
container_name: thinkphp_redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
- ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
networks:
- thinkphp_network
restart: unless-stopped

# 数据卷
volumes:
mysql_data:
driver: local
redis_data:
driver: local

# 网络
networks:
thinkphp_network:
driver: bridge

Docker忽略文件

创建.dockerignore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# Git相关
.git
.gitignore

# Docker相关
Dockerfile
docker-compose.yml
.dockerignore

# 开发工具
.vscode
.idea

# 日志文件
logs/
*.log

# 缓存文件
runtime/cache/
runtime/log/
runtime/temp/

# 依赖包
node_modules/
vendor/

# 环境配置
.env
.env.local

# 临时文件
*.tmp
*.temp
*.swp
*.swo

# 系统文件
.DS_Store
Thumbs.db

微服务架构设计

微服务拆分策略

将单体ThinkPHP应用拆分为多个微服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
微服务架构
├── 用户服务 (User Service)
│ ├── 用户注册/登录
│ ├── 用户信息管理
│ └── 权限验证
├── 订单服务 (Order Service)
│ ├── 订单创建
│ ├── 订单管理
│ └── 订单状态跟踪
├── 商品服务 (Product Service)
│ ├── 商品管理
│ ├── 库存管理
│ └── 价格管理
├── 支付服务 (Payment Service)
│ ├── 支付处理
│ ├── 退款处理
│ └── 支付回调
└── 通知服务 (Notification Service)
├── 邮件通知
├── 短信通知
└── 推送通知

服务间通信

HTTP API通信

创建app/service/HttpClient.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<?php
declare(strict_types=1);

namespace app\service;

use think\facade\Cache;
use think\facade\Log;

/**
* HTTP客户端服务
* 用于微服务间的HTTP通信
*/
class HttpClient
{
/**
* 服务注册中心配置
* @var array
*/
private $serviceRegistry = [
'user' => 'http://user-service:8001',
'order' => 'http://order-service:8002',
'product' => 'http://product-service:8003',
'payment' => 'http://payment-service:8004',
'notification' => 'http://notification-service:8005',
];

/**
* 发送HTTP请求
* @param string $service 服务名称
* @param string $endpoint 接口端点
* @param array $data 请求数据
* @param string $method 请求方法
* @param array $headers 请求头
* @return array 响应数据
* @throws \Exception
*/
public function request(string $service, string $endpoint, array $data = [], string $method = 'POST', array $headers = []): array
{
$url = $this->getServiceUrl($service) . $endpoint;

// 默认请求头
$defaultHeaders = [
'Content-Type: application/json',
'Accept: application/json',
'X-Request-ID: ' . $this->generateRequestId(),
'X-Service-Name: ' . config('app.name', 'unknown'),
];

$headers = array_merge($defaultHeaders, $headers);

$ch = curl_init();

curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
]);

// 设置请求方法和数据
switch (strtoupper($method)) {
case 'POST':
curl_setopt($ch, CURLOPT_POST, true);
if (!empty($data)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
break;
case 'PUT':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
if (!empty($data)) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
break;
case 'DELETE':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
break;
case 'GET':
default:
if (!empty($data)) {
$url .= '?' . http_build_query($data);
curl_setopt($ch, CURLOPT_URL, $url);
}
break;
}

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);

curl_close($ch);

// 记录请求日志
$this->logRequest($service, $endpoint, $method, $data, $httpCode, $response);

if ($error) {
throw new \Exception("HTTP请求失败: {$error}");
}

if ($httpCode >= 400) {
throw new \Exception("HTTP请求错误: HTTP {$httpCode}");
}

$result = json_decode($response, true);

if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception('响应数据格式错误: ' . json_last_error_msg());
}

return $result;
}

/**
* 获取服务URL
* @param string $service 服务名称
* @return string 服务URL
* @throws \Exception
*/
private function getServiceUrl(string $service): string
{
if (!isset($this->serviceRegistry[$service])) {
throw new \Exception("未知的服务: {$service}");
}

return $this->serviceRegistry[$service];
}

/**
* 生成请求ID
* @return string 请求ID
*/
private function generateRequestId(): string
{
return uniqid('req_', true);
}

/**
* 记录请求日志
* @param string $service 服务名称
* @param string $endpoint 接口端点
* @param string $method 请求方法
* @param array $data 请求数据
* @param int $httpCode HTTP状态码
* @param string $response 响应内容
*/
private function logRequest(string $service, string $endpoint, string $method, array $data, int $httpCode, string $response): void
{
$logData = [
'service' => $service,
'endpoint' => $endpoint,
'method' => $method,
'request_data' => $data,
'http_code' => $httpCode,
'response' => $response,
'timestamp' => date('Y-m-d H:i:s'),
];

Log::info('微服务HTTP请求', $logData);
}
}

消息队列通信

创建app/service/MessageQueue.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
<?php
declare(strict_types=1);

namespace app\service;

use think\facade\Log;
use think\queue\Job;

/**
* 消息队列服务
* 用于微服务间的异步通信
*/
class MessageQueue
{
/**
* 发布消息到队列
* @param string $queue 队列名称
* @param array $data 消息数据
* @param int $delay 延迟时间(秒)
* @return bool 是否成功
*/
public function publish(string $queue, array $data, int $delay = 0): bool
{
try {
$message = [
'id' => uniqid('msg_', true),
'timestamp' => time(),
'data' => $data,
'retry_count' => 0,
];

if ($delay > 0) {
\think\facade\Queue::later($delay, $queue, $message);
} else {
\think\facade\Queue::push($queue, $message);
}

Log::info('消息发布成功', [
'queue' => $queue,
'message_id' => $message['id'],
'delay' => $delay,
]);

return true;
} catch (\Exception $e) {
Log::error('消息发布失败', [
'queue' => $queue,
'error' => $e->getMessage(),
'data' => $data,
]);

return false;
}
}

/**
* 处理用户注册消息
* @param Job $job 队列任务
* @param array $data 消息数据
*/
public function handleUserRegistered(Job $job, array $data): void
{
try {
$userId = $data['user_id'];
$userEmail = $data['email'];

// 发送欢迎邮件
$this->sendWelcomeEmail($userId, $userEmail);

// 初始化用户积分
$this->initUserPoints($userId);

// 创建用户钱包
$this->createUserWallet($userId);

$job->delete();

Log::info('用户注册消息处理成功', ['user_id' => $userId]);
} catch (\Exception $e) {
Log::error('用户注册消息处理失败', [
'error' => $e->getMessage(),
'data' => $data,
]);

if ($job->attempts() < 3) {
$job->release(60); // 60秒后重试
} else {
$job->delete(); // 超过重试次数,删除任务
}
}
}

/**
* 发送欢迎邮件
* @param int $userId 用户ID
* @param string $email 邮箱地址
*/
private function sendWelcomeEmail(int $userId, string $email): void
{
$httpClient = new HttpClient();

$httpClient->request('notification', '/email/welcome', [
'user_id' => $userId,
'email' => $email,
'template' => 'welcome',
]);
}

/**
* 初始化用户积分
* @param int $userId 用户ID
*/
private function initUserPoints(int $userId): void
{
$httpClient = new HttpClient();

$httpClient->request('user', '/points/init', [
'user_id' => $userId,
'points' => 100, // 注册赠送100积分
]);
}

/**
* 创建用户钱包
* @param int $userId 用户ID
*/
private function createUserWallet(int $userId): void
{
$httpClient = new HttpClient();

$httpClient->request('payment', '/wallet/create', [
'user_id' => $userId,
'balance' => 0,
]);
}
}

Kubernetes部署

K8s部署配置

创建k8s/thinkphp-deployment.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
apiVersion: apps/v1
kind: Deployment
metadata:
name: thinkphp-app
labels:
app: thinkphp
spec:
replicas: 3
selector:
matchLabels:
app: thinkphp
template:
metadata:
labels:
app: thinkphp
spec:
containers:
# Nginx容器
- name: nginx
image: thinkphp/nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: app-volume
mountPath: /var/www/html
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"

# PHP-FPM容器
- name: php-fpm
image: thinkphp/php:latest
ports:
- containerPort: 9000
env:
- name: DB_HOST
value: "mysql-service"
- name: DB_DATABASE
value: "thinkphp"
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: mysql-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
- name: REDIS_HOST
value: "redis-service"
volumeMounts:
- name: app-volume
mountPath: /var/www/html
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"

# 健康检查
livenessProbe:
httpGet:
path: /health
port: 9000
initialDelaySeconds: 30
periodSeconds: 10

readinessProbe:
httpGet:
path: /ready
port: 9000
initialDelaySeconds: 5
periodSeconds: 5

volumes:
- name: app-volume
emptyDir: {}

---
apiVersion: v1
kind: Service
metadata:
name: thinkphp-service
spec:
selector:
app: thinkphp
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: thinkphp-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: thinkphp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: thinkphp-service
port:
number: 80

配置管理

创建k8s/configmap.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: v1
kind: ConfigMap
metadata:
name: thinkphp-config
data:
app.env: "production"
app.debug: "false"
cache.driver: "redis"
session.driver: "redis"
queue.driver: "redis"

---
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
data:
username: dGhpbmtwaHA= # base64编码的thinkphp
password: MTIzNDU2 # base64编码的123456

---
apiVersion: v1
kind: Secret
metadata:
name: redis-secret
type: Opaque
data:
password: cmVkaXNfcGFzcw== # base64编码的redis_pass

监控与日志

应用监控

创建app/middleware/Monitoring.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?php
declare(strict_types=1);

namespace app\middleware;

use think\facade\Log;
use think\facade\Cache;

/**
* 监控中间件
* 收集应用性能指标和健康状态
*/
class Monitoring
{
/**
* 处理请求
* @param \think\Request $request 请求对象
* @param \Closure $next 下一个中间件
* @return mixed
*/
public function handle($request, \Closure $next)
{
$startTime = microtime(true);
$startMemory = memory_get_usage();

// 记录请求开始
$requestId = uniqid('req_', true);
$request->withHeader(['X-Request-ID' => $requestId]);

try {
$response = $next($request);

// 计算性能指标
$endTime = microtime(true);
$endMemory = memory_get_usage();

$metrics = [
'request_id' => $requestId,
'method' => $request->method(),
'uri' => $request->url(),
'status_code' => $response->getCode(),
'execution_time' => round(($endTime - $startTime) * 1000, 2), // 毫秒
'memory_usage' => round(($endMemory - $startMemory) / 1024 / 1024, 2), // MB
'timestamp' => date('Y-m-d H:i:s'),
];

// 记录性能指标
$this->recordMetrics($metrics);

// 检查性能阈值
$this->checkPerformanceThresholds($metrics);

return $response;
} catch (\Exception $e) {
// 记录异常
$this->recordException($requestId, $e);
throw $e;
}
}

/**
* 记录性能指标
* @param array $metrics 性能指标
*/
private function recordMetrics(array $metrics): void
{
// 记录到日志
Log::info('性能指标', $metrics);

// 存储到缓存用于实时监控
$key = 'metrics:' . date('Y-m-d-H-i');
$currentMetrics = Cache::get($key, []);
$currentMetrics[] = $metrics;

// 只保留最近100条记录
if (count($currentMetrics) > 100) {
$currentMetrics = array_slice($currentMetrics, -100);
}

Cache::set($key, $currentMetrics, 3600);
}

/**
* 检查性能阈值
* @param array $metrics 性能指标
*/
private function checkPerformanceThresholds(array $metrics): void
{
// 响应时间阈值检查(超过2秒告警)
if ($metrics['execution_time'] > 2000) {
Log::warning('响应时间过长', $metrics);
$this->sendAlert('响应时间告警', $metrics);
}

// 内存使用阈值检查(超过50MB告警)
if ($metrics['memory_usage'] > 50) {
Log::warning('内存使用过高', $metrics);
$this->sendAlert('内存使用告警', $metrics);
}

// HTTP错误状态码检查
if ($metrics['status_code'] >= 500) {
Log::error('服务器错误', $metrics);
$this->sendAlert('服务器错误告警', $metrics);
}
}

/**
* 记录异常
* @param string $requestId 请求ID
* @param \Exception $exception 异常对象
*/
private function recordException(string $requestId, \Exception $exception): void
{
$errorData = [
'request_id' => $requestId,
'exception_class' => get_class($exception),
'message' => $exception->getMessage(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $exception->getTraceAsString(),
'timestamp' => date('Y-m-d H:i:s'),
];

Log::error('应用异常', $errorData);
$this->sendAlert('应用异常告警', $errorData);
}

/**
* 发送告警
* @param string $title 告警标题
* @param array $data 告警数据
*/
private function sendAlert(string $title, array $data): void
{
// 这里可以集成钉钉、企业微信、邮件等告警方式
// 示例:发送到监控系统
try {
$httpClient = new \app\service\HttpClient();
$httpClient->request('notification', '/alert', [
'title' => $title,
'data' => $data,
'level' => 'warning',
'service' => config('app.name'),
]);
} catch (\Exception $e) {
Log::error('告警发送失败', ['error' => $e->getMessage()]);
}
}
}

部署脚本

自动化部署脚本

创建scripts/deploy.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/bin/bash

# ThinkPHP Docker部署脚本
# 用于自动化构建和部署ThinkPHP应用

set -e

# 配置变量
APP_NAME="thinkphp-app"
DOCKER_REGISTRY="registry.example.com"
NAMESPACE="production"
VERSION=$(date +%Y%m%d%H%M%S)

echo "开始部署 $APP_NAME 版本 $VERSION"

# 1. 构建Docker镜像
echo "构建Docker镜像..."
docker build -t $DOCKER_REGISTRY/$APP_NAME:$VERSION -f docker/php/Dockerfile .
docker build -t $DOCKER_REGISTRY/$APP_NAME-nginx:$VERSION -f docker/nginx/Dockerfile .

# 2. 推送镜像到仓库
echo "推送镜像到仓库..."
docker push $DOCKER_REGISTRY/$APP_NAME:$VERSION
docker push $DOCKER_REGISTRY/$APP_NAME-nginx:$VERSION

# 3. 更新Kubernetes部署
echo "更新Kubernetes部署..."
sed -i "s|image: .*$APP_NAME:.*|image: $DOCKER_REGISTRY/$APP_NAME:$VERSION|g" k8s/thinkphp-deployment.yaml
sed -i "s|image: .*$APP_NAME-nginx:.*|image: $DOCKER_REGISTRY/$APP_NAME-nginx:$VERSION|g" k8s/thinkphp-deployment.yaml

# 4. 应用配置
echo "应用Kubernetes配置..."
kubectl apply -f k8s/configmap.yaml -n $NAMESPACE
kubectl apply -f k8s/thinkphp-deployment.yaml -n $NAMESPACE

# 5. 等待部署完成
echo "等待部署完成..."
kubectl rollout status deployment/$APP_NAME -n $NAMESPACE

# 6. 验证部署
echo "验证部署状态..."
kubectl get pods -l app=$APP_NAME -n $NAMESPACE
kubectl get services -l app=$APP_NAME -n $NAMESPACE

echo "部署完成!版本: $VERSION"

最佳实践总结

容器化最佳实践

  1. 镜像优化

    • 使用多阶段构建减小镜像体积
    • 选择合适的基础镜像
    • 清理不必要的文件和缓存
  2. 安全配置

    • 使用非root用户运行应用
    • 定期更新基础镜像
    • 扫描镜像安全漏洞
  3. 性能优化

    • 合理设置资源限制
    • 使用健康检查
    • 配置合适的重启策略

微服务架构最佳实践

  1. 服务拆分原则

    • 按业务领域拆分
    • 保持服务的单一职责
    • 避免过度拆分
  2. 通信策略

    • 同步通信用于实时性要求高的场景
    • 异步通信用于解耦和提高性能
    • 实现熔断和重试机制
  3. 数据管理

    • 每个服务独立的数据库
    • 实现分布式事务
    • 数据一致性保障

通过容器化部署和微服务架构,ThinkPHP应用可以获得更好的可扩展性、可维护性和部署效率,为现代化的云原生应用开发奠定坚实基础。

本站由 提供部署服务