0%

全面解析 CI/CD 流水线设计,涵盖 Jenkins、GitLab CI、GitHub Actions 等平台,包含完整配置示例、最佳实践和实战案例

阅读全文 »

精选 20 个实用 Python 自动化脚本,涵盖文件处理、数据分析、网络请求、系统运维、Web scraping 等场景,所有代码可直接运行

阅读全文 »

Nginx 配置最佳实践:从入门到精通

本文全面讲解 Nginx 的配置语法、常用场景、性能优化和安全加固,帮助运维和开发人员构建高性能、高可用的 Web 服务。

一、Nginx 基础架构

1.1 Nginx 工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
graph TB
A[客户端请求] --> B[Master 进程]
B --> C[Worker 进程 1]
B --> D[Worker 进程 2]
B --> E[Worker 进程 N]
C --> F[处理请求]
D --> F
E --> F
F --> G[返回响应]

style B fill:#ff6b6b
style C fill:#4ecdc4
style D fill:#4ecdc4
style E fill:#4ecdc4

核心概念:

  • Master 进程:管理 Worker 进程,处理配置加载、日志打开等
  • Worker 进程:处理实际请求,多进程模型
  • 事件驱动:非阻塞 I/O,高并发性能
  • 连接复用:Keepalive 减少连接开销

1.2 安装 Nginx

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
# Ubuntu/Debian
apt update
apt install nginx -y

# CentOS/RHEL
yum install epel-release -y
yum install nginx -y

# 从源码编译
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -zxvf nginx-1.24.0.tar.gz
cd nginx-1.24.0
./configure --prefix=/usr/local/nginx \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_gzip_static_module
make && make install

# 验证安装
nginx -v
nginx -V # 查看详细配置

# 管理 Nginx
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx # 平滑重载
systemctl status nginx

# 测试配置
nginx -t
nginx -T # 显示完整配置

1.3 目录结构

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
# Nginx 目录结构
/etc/nginx/
├── nginx.conf # 主配置文件
├── mime.types # MIME 类型配置
├── conf.d/ # 额外配置文件
│ ├── default.conf
│ └── example.com.conf
├── sites-available/ # 可用站点配置(Debian)
├── sites-enabled/ # 启用站点配置(Debian)
├── modules-available/ # 可用模块
├── modules-enabled/ # 启用模块
├── ssl/ # SSL 证书
├── logs/ # 日志目录
│ ├── access.log
│ └── error.log
└── conf/ # 其他配置
├── fastcgi.conf
├── koi-utf
└── win-utf

# 网站目录
/var/www/
└── html/
└── index.html

# 日志目录(自定义)
/var/log/nginx/
├── access.log
└── error.log

二、配置文件详解

2.1 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
# 主配置文件结构
user nginx; # 运行用户
worker_processes auto; # Worker 进程数
worker_rlimit_nofile 65535; # 文件描述符限制

error_log /var/log/nginx/error.log warn; # 错误日志
pid /var/run/nginx.pid; # PID 文件

events {
worker_connections 10240; # 每个 Worker 的最大连接数
use epoll; # 使用 epoll 模型(Linux)
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;

# Gzip 压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json
application/javascript application/xml;

# 包含其他配置
include /etc/nginx/conf.d/*.conf;
}

2.2 核心指令详解

worker_processes

1
2
3
4
5
6
7
8
# 设置 Worker 进程数
worker_processes auto; # 自动(推荐,等于 CPU 核数)
worker_processes 4; # 固定数量
worker_processes 1; # 单进程(调试用)

# 绑定 CPU(提高性能)
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;

worker_connections

1
2
3
4
5
6
7
events {
# 每个 Worker 的最大连接数
worker_connections 10240;

# 最大客户端连接数 = worker_processes × worker_connections
# 例如:4 × 10240 = 40960
}

keepalive_timeout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
http {
# Keepalive 超时时间
keepalive_timeout 65;

# Keepalive 请求数上限
keepalive_requests 100;

# 与上游服务器的 Keepalive
upstream backend {
server 127.0.0.1:8080;
keepalive 32; # 保持 32 个空闲连接
}

server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
}

三、常用场景配置

3.1 静态文件服务器

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
server {
listen 80;
server_name example.com www.example.com;

root /var/www/example.com;
index index.html index.htm;

# 访问日志
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;

# 静态文件
location / {
try_files $uri $uri/ =404;
}

# 图片缓存
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}

# CSS/JS 缓存
location ~* \.(css|js)$ {
expires 7d;
add_header Cache-Control "public";
access_log off;
}

# 字体文件
location ~* \.(woff|woff2|ttf|eot|otf)$ {
expires 30d;
add_header Cache-Control "public";
access_log off;
}

# 禁止访问隐藏文件
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}

# 禁止访问备份文件
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
}

3.2 反向代理

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
server {
listen 80;
server_name api.example.com;

location / {
# 代理到后端服务
proxy_pass http://127.0.0.1:8080;

# 传递客户端信息
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

# 缓冲区
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;

# 临时文件
proxy_temp_file_write_size 64k;
}

# WebSocket 支持
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400;
}
}

3.3 负载均衡

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
# 上游服务器组
upstream backend {
# 轮询(默认)
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;

# 权重
# server 192.168.1.10:8080 weight=3;
# server 192.168.1.11:8080 weight=2;
# server 192.168.1.12:8080 weight=1;

# IP Hash(会话保持)
# ip_hash;

# 最少连接
# least_conn;

# 备用服务器
# server 192.168.1.13:8080 backup;

# 故障服务器
# server 192.168.1.14:8080 down;

# 健康检查参数
# server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;

# Keepalive 连接
keepalive 32;
}

server {
listen 80;
server_name app.example.com;

location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;

# 超时
proxy_connect_timeout 10s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;

# 重试
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
proxy_next_upstream_tries 3;
}
}

3.4 HTTPS 配置

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
server {
listen 443 ssl http2;
server_name example.com www.example.com;

# SSL 证书
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;

# SSL 优化
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

# 现代 SSL 配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;

root /var/www/example.com;
index index.html;

location / {
try_files $uri $uri/ =404;
}
}

# HTTP 重定向到 HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}

3.5 动静分离

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
upstream backend {
server 127.0.0.1:8080;
keepalive 32;
}

server {
listen 80;
server_name example.com;
root /var/www/example.com;

# 静态文件
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
try_files $uri =404;
}

# 动态请求
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Connection "";
}

# API 接口
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

四、性能优化

4.1 基础优化

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
# nginx.conf
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;

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

http {
# 基础优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;

# Keepalive
keepalive_timeout 65;
keepalive_requests 100;

# 缓冲区
client_body_buffer_size 10M;
client_max_body_size 10M;

# 超时
client_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;

# Gzip 压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/x-javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
font/eot
font/otf
font/ttf
font/woff
font/woff2
text/css
text/javascript
text/plain
text/xml;
}

4.2 缓存优化

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
# 代理缓存配置
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:100m
max_size=10g inactive=60m use_temp_path=off;

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://backend;

# 启用缓存
proxy_cache my_cache;

# 缓存键
proxy_cache_key $scheme$request_method$host$request_uri;

# 缓存有效期
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;

# 缓存条件
proxy_cache_methods GET HEAD;

# 缓存头
add_header X-Cache-Status $upstream_cache_status;

# 绕过缓存
proxy_cache_bypass $http_pragma $http_authorization;
proxy_no_cache $http_pragma $http_authorization;
}

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

4.3 连接优化

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
http {
# 打开文件缓存
open_file_cache max=100000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

# 重置超时连接
reset_timedout_connection on;

# 服务器名称哈希
server_names_hash_bucket_size 128;
server_names_hash_max_size 4096;

# 类型哈希
types_hash_max_size 2048;
types_hash_bucket_size 64;

# 限制请求
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
listen 80;
server_name example.com;

# 限制请求频率
location / {
limit_req zone=one burst=20 nodelay;
limit_conn addr 10;

proxy_pass http://backend;
}

# API 限流
location /api/ {
limit_req zone=one burst=5 nodelay;
limit_conn addr 5;

proxy_pass http://backend;
}
}
}

4.4 FastCGI 优化(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
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.php index.html;

location ~ \.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;

# FastCGI 缓存
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2
keys_zone=php_cache:100m max_size=1g
inactive=60m;

fastcgi_cache php_cache;
fastcgi_cache_key $request_method$host$fastcgi_script_name$query_string;
fastcgi_cache_valid 200 30m;
fastcgi_cache_valid 404 1m;
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;

# FastCGI 参数
fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;

# 超时
fastcgi_connect_timeout 60s;
fastcgi_send_timeout 180s;
fastcgi_read_timeout 180s;
}
}

五、安全加固

5.1 基础安全配置

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
server {
listen 80;
server_name example.com;

# 隐藏 Nginx 版本
server_tokens off;

# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

# 禁止访问敏感文件
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}

location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}

location ~* \.(git|svn|htaccess|htpasswd|ini|log|sh|sql|conf|bak|swp)$ {
deny all;
access_log off;
log_not_found off;
}

# 禁止访问备份文件
location ~* \.(bak|backup|old|orig|save|tmp|temp)$ {
deny all;
access_log off;
log_not_found off;
}

# 限制请求方法
if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|OPTIONS)$) {
return 405;
}

# 限制请求体大小
client_max_body_size 10M;

# 限制请求头大小
large_client_header_buffers 4 16k;
}

5.2 DDoS 防护

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
http {
# 限制连接数
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn_zone $server_name zone=server:10m;

# 限制请求频率
limit_req_zone $binary_remote_addr zone=req:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=5r/s;

# 限制请求体
client_body_buffer_size 10M;
client_max_body_size 10M;

# 超时设置
client_body_timeout 10s;
client_header_timeout 10s;
keepalive_timeout 15s;
send_timeout 10s;

server {
listen 80;
server_name example.com;

# 应用限制
location / {
limit_conn addr 10;
limit_conn server 1000;
limit_req zone=req burst=20 nodelay;

proxy_pass http://backend;
}

# API 严格限制
location /api/ {
limit_conn addr 5;
limit_req zone=api burst=5 nodelay;

proxy_pass http://backend;
}

# 登录接口更严格
location /login {
limit_req zone=api burst=3 nodelay;

proxy_pass http://backend;
}

# 静态文件宽松限制
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
limit_conn addr 20;
limit_req zone=req burst=50;

expires 30d;
access_log off;
}
}
}

# 自定义错误页面
http {
limit_req_status 429;
limit_conn_status 429;

server {
error_page 429 /429.html;
location = /429.html {
root /var/www/error_pages;
internal;
}
}
}

5.3 SSL/TLS 安全

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
server {
listen 443 ssl http2;
server_name example.com;

# 证书
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;

# 协议
ssl_protocols TLSv1.2 TLSv1.3;

# 加密套件
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

# 会话
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

# OCSP
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

# 安全头
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}

5.4 访问控制

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
server {
listen 80;
server_name example.com;

# IP 白名单
location /admin/ {
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;

proxy_pass http://backend;
}

# IP 黑名单
# 在 http 块中
# geo $block_ip {
# default 0;
# 192.168.1.100 1;
# 10.0.0.50 1;
# }

# server {
# if ($block_ip) {
# return 403;
# }
# }

# 基本认证
location /private/ {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;

proxy_pass http://backend;
}

# 基于 User-Agent 的限制
if ($http_user_agent ~* (curl|wget|scrapy|python-requests)) {
return 403;
}

# 基于 Referer 的限制
location ~* \.(jpg|jpeg|png|gif)$ {
valid_referers none blocked server_names *.example.com;
if ($invalid_referer) {
return 403;
}
}
}

# 生成 .htpasswd 文件
# htpasswd -c /etc/nginx/.htpasswd username

六、监控与日志

6.1 日志配置

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
http {
# 自定义日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';

log_format json escape=json '{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"request":"$request",'
'"status": $status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent"'
'}';

# 访问日志
access_log /var/log/nginx/access.log main;
# access_log /var/log/nginx/access.log json;

# 错误日志
error_log /var/log/nginx/error.log warn;

# 按域名分离日志
server {
listen 80;
server_name example.com;

access_log /var/log/nginx/example.com.access.log main;
error_log /var/log/nginx/example.com.error.log warn;
}

# 条件日志
map $status $loggable {
~^[23] 0;
default 1;
}
access_log /var/log/nginx/errors.log main if=$loggable;
}

6.2 日志分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 查看访问日志
tail -f /var/log/nginx/access.log

# 统计访问最多的 IP
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# 统计访问最多的 URL
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20

# 统计状态码
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

# 统计 404 错误
grep " 404 " /var/log/nginx/access.log

# 统计慢请求(>1s)
awk '($10 > 1) {print $7, $10}' /var/log/nginx/access.log

# 使用 GoAccess 分析
goaccess /var/log/nginx/access.log -o report.html --log-format=COMBINED

# 使用 awk 分析
awk '{print $1}' access.log | sort | uniq -c | sort -nr | head -10

6.3 状态监控

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
# 启用状态模块
http {
server {
listen 80;
server_name localhost;

# Nginx 状态
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
allow 192.168.1.0/24;
deny all;
}

# 健康检查
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}

# 查看状态
curl http://localhost/nginx_status

# 输出示例:
# Active connections: 100
# server accepts handled requests
# 1000000 1000000 5000000
# Reading: 10 Writing: 50 Waiting: 40

6.4 Prometheus 监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用 nginx-prometheus-exporter
# 安装 exporter
docker run -p 9113:9113 nginx/nginx-prometheus-exporter:latest \
-nginx.scrape-uri="http://localhost/nginx_status"

# Prometheus 配置
# prometheus.yml
scrape_configs:
- job_name: 'nginx'
static_configs:
- targets: ['localhost:9113']

# Grafana Dashboard
# 导入 Nginx Dashboard (ID: 12708)

七、故障排查

7.1 常见问题

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
# 测试配置
nginx -t
nginx -T

# 重载配置
nginx -s reload

# 查看进程
ps aux | grep nginx

# 查看端口
netstat -tlnp | grep nginx
ss -tlnp | grep nginx

# 查看日志
tail -f /var/log/nginx/error.log
tail -f /var/log/nginx/access.log

# 查看打开的文件
lsof -p $(cat /var/run/nginx.pid)

# 查看系统资源
top -p $(cat /var/run/nginx.pid)

# 测试连接
curl -I http://localhost
curl -v http://localhost

7.2 错误码排查

错误码 含义 常见原因 解决方案
400 错误请求 请求格式错误 检查客户端请求
403 禁止访问 权限不足 检查文件权限、IP 限制
404 未找到 文件不存在 检查路径、root 配置
405 方法不允许 HTTP 方法错误 检查允许的请求方法
413 请求体过大 超过限制 调整 client_max_body_size
414 URI 过长 URL 太长 调整 large_client_header_buffers
499 客户端关闭 客户端超时 优化后端响应时间
500 服务器错误 配置错误 检查 error.log
502 错误网关 后端服务异常 检查后端服务状态
503 服务不可用 后端过载 检查后端负载
504 网关超时 后端响应超时 增加 proxy_read_timeout

7.3 调试技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 增加日志详细程度
error_log /var/log/nginx/error.log debug;

# 调试特定模块
error_log /var/log/nginx/error.log debug_http;
error_log /var/log/nginx/error.log debug_upstream;

# 返回调试信息
location /debug {
return 200 "
remote_addr: $remote_addr
remote_user: $remote_user
request: $request
uri: $uri
args: $args
request_method: $request_method
content_type: $content_type
content_length: $content_length
host: $host
http_host: $http_host
server_protocol: $server_protocol
";
add_header Content-Type text/plain;
}

八、总结

Nginx 配置的关键要点:

  1. 基础配置:worker_processes、worker_connections、keepalive
  2. 常用场景:静态文件、反向代理、负载均衡、HTTPS
  3. 性能优化:缓存、压缩、连接优化
  4. 安全加固:隐藏版本、安全头、访问控制、DDoS 防护
  5. 监控日志:日志格式、状态监控、故障排查

记住:配置要简洁,优化要适度,安全要优先


参考资料:

如何进行架构设计,可以一起讨论,如何基于需求进行业务建模,如何进行业务流程设计,如何进行应用架构设计,如何进行技术架构设计,如何进行部署架构设计,如何应对高并发高流量高可用挑战!

阅读全文 »

一、Jenkins Rest API简单介绍
在githup上,jenkins-rest项目地址:链接地址,接口定义和接口测试代码都可以详细看到。 但在使用过程中遇到了问题让我纠结了很久。 看下使用REST API前置条件:
需要运行着的jenkins环境
需要一个管理员权限的账号
CSRF功能需要关闭掉,不然会影响POST接口的请求(我在这个上面纠结了很久,我使用的版本是Jenkins2.263.4,高于2.0版本的jenkins页面上已经不提供直接关闭CSRF的勾选按钮功能,网上搜索到的很多都是通过勾选按钮关闭CSRF功能,而新版本的JENKINS已经不支持这种方式—起初,我是不想关闭CSRF,但是解决不了API POST请求接口返回403forbiden问题,然后网上看到了一种,将获取到的crumb设置到header中,通过通过httpclient方式URL请求Jenkins API,这种方式我没能成功,最终放弃了,代码编写起来也很麻烦,跟API调用方法传入参数的方式比较起来的话) 4、插件的要求我感觉我没用到。

二、使用步骤
1.关闭CSRF保护
编辑jenkins配置文件

vim /etc/sysconfig/jenkins
AI写代码
java
运行
1
在默认配置文件JENKINS_JAVA_OPTIONS里面加入取消相关保护的参数配置,即可关闭CSRF,配置内容如下:

-Dhudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION=true
AI写代码
java
运行
1
修改后如下:

重启jenkins即可生效

systemctl restart jenkins.service
AI写代码
java
运行
1
登录jenkins,Dashboard->Configure Global Security,跨站请求伪造保护会有提示,即可只修改已经生效了

2.引入jenkins-rest.jar包
我新建了个maven项目用来测试API,pom.xml引入了jenkins-rest包,由githup可知,最新的版本0.0.28,使用20-28版本会出现一个问题,启动api测试代码会报一个类方法未定义的错误,这个是因为com.google.guava包在jenkins-rest包里默认的版本不对,或者使用低版本的jenkins-rest包,但我对比了下看低版本提供的API方法还是少好几个的

log4j:WARN No such property [datePattern] in org.apache.log4j.RollingFileAppender.
Exception in thread “main” java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;Ljava/lang/Object;Ljava/lang/Object;)V
AI写代码
java
运行
1
2
pom.xml配置如下:

        com.cdancy
        jenkins-rest
        0.0.28
    

    
        com.google.guava
        guava
        28.1-jre

AI写代码
xml

1
2
3
4
5
6
7
8
9
10
11
3.创建客户端
githup上有示例代码,按照自己jenkins访问地址和账户密码修改下即可。

JenkinsClient client = JenkinsClient.builder()
.endPoint(“http://127.0.0.1:8080“) // Optional. Defaults to http://127.0.0.1:8080
.credentials(“admin:password”) // Optional.
.build();

SystemInfo systemInfo = client.api().systemApi().systemInfo();
assertTrue(systemInfo.jenkinsVersion().equals(“1.642.4”));
AI写代码
java
运行
1
2
3
4
5
6
7
由代码可以看出一共提供了6个API类,分别是

API类 说明
pluginManagerApi 插件API:提供插件操作接口,一共就俩方法,查看和安装插件
crumbIssuerApi 系统哈希值信息(用于防御CSRF攻击)
jobsAPi 任务(项目管理),提供任务的创建、修改、查询、删除等操作,主要使用接口
queueApi 任务队列相关
statisticsApi jenkins统计相关
systemApi jenkins系统信息,如版本

4.jobsApi-任务API
JobsApi jobApi=client.api().jobsApi();
AI写代码
java
运行
1
4.1 jobList-任务一览
/**
* 任务一览
* param 文件夹路径“”,取的是最外层的任务,若要查看任务文件夹中的任务,入参则为文件名名称
/
JobList jobList=jobApi.jobList(“”);
for(int i=0;i<jobList.jobs().size();i++){
log.info(jobList.jobs().get(i));
}
AI写代码
java
运行
1
2
3
4
5
6
7
8
4.2 jobInfo-具体任务详细信息
/
*
* 根据任务名称获取任务信息
* param 文件夹路径null,最外层任务 任务名称“test”
/
JobInfo jobInfo=jobApi.jobInfo(null,”test”);
log.info(“test任务详细信息”+jobInfo);
AI写代码
java
运行
1
2
3
4
5
6
4.3 buildInfo-任务构建的详细信息
/
*
* 根据任务名称和任务构建id,获取构建信息
* param 文件夹路径null 任务名称“test” 构建ID 3
/
jobApi.buildInfo(null,”test”,3);
log.info(“test任务第三次构建详细信息:”+jobApi);
AI写代码
java
运行
1
2
3
4
5
6
4.4 create-新建任务
File file=new File(“C:\Users\dwzq\Desktop\config.xml”);
StringBuilder xmlStr=new StringBuilder();
BufferedReader read=null;
read=new BufferedReader(new FileReader(file));
String tempStr;
while((tempStr=read.readLine())!=null){
xmlStr.append(tempStr);
}
read.close();
/
*
* 创建job任务
* param 文件夹路径null 新建任务名称“test2” 任务详细配置文件内容:xmlStr
*/
RequestStatus status=jobApi.create(null,”test2”,xmlStr.toString());
log.info(status);
AI写代码
java
运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
4.5 config-查看任务配置文件内容(两个入参)
/**
* 获取任务配置文件内容
* param 文件夹路径null 任务名称“test2”
/
String testConfig=jobApi.config(null,”test2”);
log.info(“testConfig:”+testConfig);
AI写代码
java
运行
1
2
3
4
5
6
4.6 config-更新任务配置文件内容(三个入参)
/
*
* 更新任务的配置文件
* param 文件夹路径null 任务名称“test2” 新的任务配置文件内容xmlStr.toString
/
boolean updateConfig=jobApi.config(null,”test2”,xmlStr.toString());
log.info(“updateConfig result:”+updateConfig);
AI写代码
java
运行
1
2
3
4
5
6
4.7 description-获取任务描述内容(两个入参)
/
*
获取任务描述
* param 文件夹路径null 任务名称“test2”
/
String description= jobApi.description(null,”test2”);
log.info(“description:”+description);
AI写代码
java
运行
1
2
3
4
5
6
4.8 description-获取任务描述内容(三个入参)
/

* 更新任务的描述
* param 文件夹路径null 任务名称“tes2” 新的任务描述
/
Boolean updateDescription=jobApi.description(null,”test2”,”this is update test job description”);
log.info(“updateDescription result:”+updateConfig);
AI写代码
java
运行
1
2
3
4
5
6
4.9 delete-删除任务
/
*
* 删除任务
* param 文件夹路径null 任务名称
/
RequestStatus deleteResult=jobApi.delete(null,”test2”);
log.info(“deleteResult:”+deleteResult);
AI写代码
java
运行
1
2
3
4
5
6
4.10 enable-启用任务
/
*
* 启用任务
* param 文件夹路径null 任务名称“tes2”
/
boolean enableResult=jobApi.enable(null,”test2”);
log.info(“enableResult:”+enableResult);
AI写代码
java
运行
1
2
3
4
5
6
4.11 disable-禁用任务
/
*
* 禁用任务
* param 文件夹路径null 任务名称“tes2”
*/
boolean disableResult=jobApi.disable(null,”test2”);
log.info(“disableResult:”+disableResult);
AI写代码
java
运行

4.12 build-构建任务
/**
* 任务构建
* param 文件夹路径null , 任务名“test”
/
IntegerResponse buildJob=jobApi.build(null,”test”);
log.info(“buildJob”+buildJob);
AI写代码
java
运行
1
2
3
4
5
6
4.13 buildWithParameters-带参数构建任务
/
*
* 带参数构建任务
* param 文件夹路径null , 任务名“test” 参数
*/
Map<String, List> param=new HashMap();
List list=new ArrayList();
list.add(“staging”);
param.put(“DEPLOY_ENV”,list);
param.put(“myparam”,list);
IntegerResponse buldParam=jobApi.buildWithParameters(null,”test”,param);
log.info(“buildParam:”+buldParam);
AI写代码
java
运行

1
2
3
4
5
6
7
8
9
10
11
4.14 lastBuildNumber-上次任务的构建序号
/**
上次构建的序号
* param 文件夹路径null , 任务名“pipeline-hello-word” 参数
/
Integer lastBuildNumber=jobApi.lastBuildNumber(null,”pipeline-hello-word”);
log.info(“lastBuildNumber:”+lastBuildNumber);
AI写代码
java
运行
1
2
3
4
5
6
4.15 lastBuildTimestamp-上次任务的构建时间戳
/

* 上次任务构建时间
* param 文件夹路径null , 任务名“test”
/
String lastBuildTimetamp=jobApi.lastBuildTimestamp(null,”test”);
log.info(“lastBuildTimestamp:”+lastBuildTimetamp);
AI写代码
java
运行
1
2
3
4
5
6
4.16 progressiveText-任务构建对应的控制台输出内容(3个入参)
/
*
* 获取任务构建对应的控制台输出
* param 文件夹路径null 任务名称“test” 0:控制台输出文本开始的位置
/
ProgressiveText progressiveText=jobApi.progressiveText(null,”test”,0);
log.info(“progressText:”+progressiveText);
AI写代码
java
运行
1
2
3
4
5
6
4.17 progressiveText-任务构建对应的控制台输出内容(3个入参)
/
*
* 获取任务某个构建次数控制台对应的输出
* param 文件夹路径null 任务名称“test” 构建index:16 0:控制台输出文本开始的位置
/
ProgressiveText progressiveText1=jobApi.progressiveText(null,”test”,16,0);
log.info(“progressText num=16:”+progressiveText1);
AI写代码
java
运行
1
2
3
4
5
6
4.18 rename-任务重命名
/
*
* 任务名称重命名
* param 文件夹路径null 任务名称“test” 任务新名词“test2New”
/
boolean renameResult=jobApi.rename(null,”test2”,”test2New”);
log.info(“renameResult:”+renameResult);
AI写代码
java
运行
1
2
3
4
5
6
4.19 workflow-任务某次构建的步骤信息
/
*
* 获取任务构建index对应的构建步骤内容
* param 文件夹路径null 任务名称”pipeline-hello-word” 构建index:17
*/
Workflow workFlow=jobApi.workflow(null,”pipeline-hello-word”,17);
log.info(“workFlow:”+workFlow);
AI写代码
java
运行
1
2
3
4
5
6
4.20 pipelineNode-获取pipelineNode信息
这个没和页面具体功能联系起来,不知道具体获取的什么信息

 /**
  * 获取pipelineNode信息
  * param 文件夹路径null  任务名称"pipeline-hello-word"    构建index:17  nodeId:1
  */
 PipelineNode pipelineNode=jobApi.pipelineNode(null,"pipeline-hello-word",17,1);
 log.info("pipeLineNodeInfo:"+pipelineNode);

AI写代码
java
运行
1
2
3
4
5
6
任务涉及到的接口是这20个,接口涉及到的数据结构的定义可以在githup domain查看

5.systemApi-系统环境API
SystemApi systemApi=client.api().systemApi();
AI写代码
java
运行
1
5.1 systemInfo-查看jenkins系统环境信息
/**
* 查看jenkins系统环境信息
/
SystemInfo systemInfo=systemApi.systemInfo();
log.info(“systemInfo:”+systemInfo);
AI写代码
java
运行
1
2
3
4
5
5.2 quietDown-准备关闭jenkins,对应Prepare for Shutdown功能
/
*
* 准备关闭jenkins,对应Prepare for Shutdown功能
/
RequestStatus quietDown=systemApi.quietDown();
log.info(“quietDown:”+quietDown);
AI写代码
java
运行
1
2
3
4
5
5.3 cancelQuietDown-取消准备关闭jenkins
/
*
* 取消准备关闭jenkins
/
RequestStatus cancleQuietDown= systemApi.cancelQuietDown();
log.info(“cancelQuietDown:”+cancleQuietDown);
AI写代码
java
运行
1
2
3
4
5
6.pluginManagerApi-插件API
PluginManagerApi pluginManagerApi=client.api().pluginManagerApi();
AI写代码
java
运行
1
6.1 plugins-查看所有插件信息
/
*
查看插件所有信息
param 不是很理解这两个参数的具体含义,第一个参数调整返回的插件信息不一样
/
Plugins plugins=pluginManagerApi.plugins(3,null);
for(int i=0;i<plugins.plugins().size();i++) {
log.info(plugins.plugins().get(i).longName()+”:” + plugins.plugins().get(i));
}
AI写代码
java
运行
1
2
3
4
5
6
7
8
6.2 installNecessaryPlugins-插件安装
/

* 安装插件
* param 插件ID@版本号
/
RequestStatus requestStatus=pluginManagerApi.installNecessaryPlugins(“artifactory@3.10.6“);
log.info(“artifactory:”+requestStatus);
AI写代码
java
运行
1
2
3
4
5
6
7.statisticsApi-统计信息API
/

* 我的系统返回值为空,所以不清楚具体统计的什么
/
StatisticsApi statisticsApi= client.api().statisticsApi();
OverallLoad overallLoad=statisticsApi.overallLoad();
log.info(“overallLoad:”+overallLoad);
AI写代码
java
运行
1
2
3
4
5
6
8.queueApi-任务队列API
/
*
* 三个接口,因为没数据,所以对应页面功能不是很理解
*/
QueueApi queueApi=client.api().queueApi();
List queueList=queueApi.queue();
RequestStatus cancelStatus=queueApi.cancel(1);
QueueItem queueItem=queueApi.queueItem(1);
AI写代码
java

系统配置MinIO对象存储集群

系统默认使用本地存储,含有第三方文件存储功能的版本可以配置接入使用MinIO对象存储,下面演示MinIO集群的配置方法,演示主机操作系统为CentOS 8.2,对象存储MinIO版本为2020-08-08T04:50:06Z。本次演示使用4台服务器。

实现规划

主机名称 IP地址 安装软件 开放端口 绑定域名
V1 ***.100.100.152(公网) 172.26.188.173(内网) MinIO Nginx 80 (公网开放) 9000 (内网开放) img1.diyhi.com
V2 ***.100.241.36(公网) 172.26.188.174(内网) MinIO Nginx 80 (公网开放) 9000 (内网开放) img2.diyhi.com
V3 ***.100.159.63(公网) 172.26.188.175(内网) MinIO Nginx 80 (公网开放) 9000 (内网开放) img3.diyhi.com
V4 ***.92.127.117(公网) 172.26.188.176(内网) MinIO Nginx 80 (公网开放) 9000 (内网开放) img4.diyhi.com
S1 ***.100.153.26(公网) 172.26.188.178(内网) JDK 1.8 Tomcat 8.5 MySQL 5.7 Nginx 80 bbs.diyhi.com

美女测试

img

主机配置:V1

安装配置MinIO

1、创建并进入安装目录

输入命令 mkdir /usr/local/minio
输入命令 cd /usr/local/minio

2、下载对应当前系统版本的安装包

输入命令 wget https://dl.min.io/server/minio/release/linux-amd64/minio

3、创建minio启动用户并授权

输入命令 getent group minio || groupadd -r minio
输入命令 getent passwd minio || useradd -r -d /opt -s /bin/nologin -g minio minio

4、minio文件赋予750权限

输入命令 chmod 750 /usr/local/minio/minio

5、创建存放数据目录

输入命令 mkdir /usr/local/minio/data

6、编辑minio配置文件

输入命令 vim /usr/local/minio/minio.conf

将下面的配置内容复制到minio.conf文件中。请按实际情况更改下面的参数,集群下所有节点的账号密码必须一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 指定数据存储目录(注意这个目录要存在)
MINIO_VOLUMES="http://172.26.188.173/usr/local/minio/data http://172.26.188.174/usr/local/minio/data http://172.26.188.175/usr/local/minio/data http://172.26.188.176/usr/local/minio/data"
#
#指定监听端口(也可以不监听具体ip,只写 :9000即可)
MINIO_OPTS="--address :9000"
#
#Access key,相当于账号
MINIO_ACCESS_KEY="test"
#
#Secret key,相当于密码
MINIO_SECRET_KEY="diEvRU6eQez123456Dkkpo4srS"
#
#区域值,这是完全自己写的,比如你愿意的话写“abcd”也行,但标准格式是“国家-区域-编号”,
#如“中国-华北-1号”就可写成“cn-north-1”,又比如“美国-西部-2号”可写成“us-west-1”
# #MINIO_REGION="cn-south-1"
#
#域名
# #MINIO_DOMAIN=s3.diyhi.com

7、更改文件、目录属主属组

输入命令 chown -R minio:minio /usr/local/minio

8、设置命令启动服务

输入命令 vim /usr/lib/systemd/system/minio.service

将下面的配置内容复制到minio.service文件中。

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
[Unit]
Description=MinIO
Documentation=https://docs.min.io
Wants=network-online.target
After=network-online.target
AssertFileIsExecutable=/usr/local/minio/minio

[Service]

User=minio
Group=minio

EnvironmentFile=/usr/local/minio/minio.conf
ExecStartPre=/bin/bash -c "if [ -z \"${MINIO_VOLUMES}\" ]; then echo \"Variable MINIO_VOLUMES not set in /etc/default/minio\"; exit 1; fi"

ExecStart=/usr/local/minio/minio server $MINIO_OPTS $MINIO_VOLUMES

# Let systemd restart this service always
Restart=always

# Specifies the maximum file descriptor number that can be opened by this process
LimitNOFILE=65536

# Disable timeout logic and wait until process is stopped
TimeoutStopSec=infinity
SendSIGKILL=no

[Install]
WantedBy=multi-user.target

保存配置文件后执行systemctl daemon-reload命令刷新

输入命令 systemctl daemon-reload

使用配置好的系统服务管理MinIO
systemctl enable minio 配置开机启动
systemctl start minio 启动
systemctl stop minio 停止
systemctl restart minio 重启
systemctl disable minio 删除开机启动
systemctl daemon-reload 刷新

安装配置Nginx

1.下载并安装nginx

输入命令 yum install nginx

2.启动nginx

输入命令 systemctl start nginx

使用配置好的系统服务管理Nginx
systemctl enable nginx 配置开机启动
systemctl start nginx 启动nginx
systemctl stop nginx 停止nginx
systemctl restart nginx 重启nginx
systemctl disable nginx 删除开机启动
systemctl daemon-reload 刷新

3.配置nginx

默认的配置文件在 /etc/nginx 路径下,使用该配置已经可以正确地运行nginx;如需要自定义,修改其下的 nginx.conf 等文件即可

输入命令 vim /etc/nginx/conf.d/default.conf

配置Nginx参数 请按实际情况更改下面的参数。

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
163
164
165
166
167
168
169
170
171
172
173
174
175
# proxy_cache_path 缓存文件路径 需要手动建文件夹 mkdir /etc/nginx/cache
# levels 设置缓存文件目录层次;levels=1:2 表示两级目录
# keys_zone 设置缓存名字和共享内存大小
# inactive 在指定时间内没人访问则被删除 30m/分钟
# inactive 未被访问文件在缓存中保留时间,如果设为30m,则30分钟未被访问则不论状态是否为expired,缓存控制程序会删掉文件。inactive默认是10分钟。需要注意的是,inactive和expired配置项的含义是不同的,expired只是缓存过期,但不会被删除,inactive是删除指定时间内未被访问的缓存文件 30m/分钟 7d/7天
# max_size 最大缓存空间,如果缓存空间满,默认覆盖掉缓存时间最长的资源。
proxy_cache_path /etc/nginx/cache levels=1:2 keys_zone=img_cache:10m inactive=1d max_size=50G;

server {
listen 80;
server_name _;
return 404;
}


#配置请求转发,将网页访问80端口转发到MinIO的9000端口
server{
listen 80;
server_name img1.diyhi.com;
location /{
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:9000;
client_max_body_size 100M; #允许上传文件大小,默认是1M

}



location ~ /(file/topic|file/help)/.*\.(m3u8|ts|mp4|avi|mkv|wmv|wav|rm|rmvb|mp3|flac|ape|zip|rar|7z|txt|docx|doc|pptx|ppt|xlsx|xls)$ {
#只在非DELETE请求时处理
if ($request_method ~ ^(GET|POST|HEAD|OPTIONS|PUT|TRACE|CONNECT)$ ) {
#返回302,让下一步执行内部跳转
return 302;
error_page 302 = @process_secure_link;
}
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:9000;

}

location @process_secure_link {
secure_link $arg_md5,$arg_expires;
#表示 MD5(密钥 + 不包含主机名和请求参数的剩余部分文件路径 + 过期时间) 1234567890123456是密钥,必须是16位字符并且和管理后台的'基本设置'中'文件防盗链密钥'一致
secure_link_md5 1234567890123456$uri$arg_expires;


#当匹配成功的时候 secure_link 是非空非0的

# 没有匹配到返回 ""
if ($secure_link = "") {
return 403;
}

# 没有匹配到返回0
if ($secure_link = "0") {
return 410;
}
#文件名重命名
if ($arg_filename != "") {
add_header Content-Disposition 'attachment; filename="$arg_filename"';
}


proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
#proxy_redirect off;
#MinIO
proxy_pass http://127.0.0.1:9000;
}




location ~ /(file/topic)/.*\.(jpg|jpeg|gif|png|bmp)$ {
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:9000;
client_max_body_size 100M; #允许上传文件大小,默认是1M

#只在GET请求时处理图片
if ($request_method = GET) {
#返回302,让下一步执行内部跳转
return 302;
error_page 302 = @process_image_filter;
}
}


#Nginx 不支持在 if {} 这个 block 里面用 image_filter 函数,image_filter 的第一个参数 resize/crop 也不能用变量的方式传输

# ~ 为区分大小写匹配 ~* 为不区分大小写匹配
#location ~ .*.(gif|jpg|jpeg|png|bmp)$ {
#location ~ /(file/topic)/.*\.(jpg|jpeg|gif|png|bmp)$ {
location @process_image_filter {
proxy_pass http://127.0.0.1:9000;
#标记条件 nginx的配置中不支持if条件的逻辑与&amp;&amp; 逻辑或|| 运算 ,而且不支持if的嵌套语法
set $flag 0;
#将参数赋值给宽
set $width $arg_width;

#是否为原图
set $originalImage 0;

if ($width != ''){
set $flag "${flag}1";
}
#预设宽度为240或400,当输入其它宽度时默认改为240
if ($arg_width !~ ^(240|400)$){
set $flag "${flag}1";
}
#如果URL带宽度参数并且不属于预先指定尺寸,则默认显示宽240
if ($flag = "011"){
set $width '240';
}


#当请求的是原图时(即不带参数),则设置宽高维度为”-”
if ( $width = '' ) {
set $width '-';
}

#如果访问原图
if ( $width = '-' ) {
set $originalImage 1;
}

#原图不缓存
proxy_no_cache $originalImage;

#默认使用的key就是URL
proxy_cache_key $host$uri$width;


# 将缩略图缓存在服务,避免每次请求都重新生成
proxy_cache img_cache;
# 有效的文件,在服务器缓存 1 天
proxy_cache_valid 200 1d;
#默认值:proxy_cache_lock off;
#可配置段:http, server, location
#作用:默认不开启,开启的话则每次只能有一个请求更新相同的缓存,其他请求要么等待缓存&gt;有数据要么限时等待锁释放。通常在多个客户端请求缓存未命中时,只有第一个请求可以发向原服务器,其他请求要等待第一个响应返回或者超时后,使用缓存响应客户端。该参数可以合并回源请求,减轻峰值流量下的压力。
proxy_cache_lock on;
#默认值:proxy_cache_lock_timeout 5s;
#可配置段:http, server, location
#作用:等待缓存锁超时之后将直接请求后端,结果不会被缓存。
proxy_cache_lock_timeout 5s;
#当缓存过期后,如果开启了 proxy_cache_revalidate,则会发出一次 if-modified-since 或 if-none-match 条件请求,如果后端返回 304,则此时$upstream_cache_status 为 REVALIDATED,我们将得到两个好处,节省带宽和减少写磁盘的次数。
proxy_cache_revalidate on;

#标记缓存是否命中,在访问文件头Response Headers中查看
# MISS 未命中,请求被传送到后端
# HIT 缓存命中
# EXPIRED 缓存已经过期请求被传送到后端
# UPDATING 正在更新缓存,将使用旧的应答
# STALE 后端将得到过期的应答
add_header Nginx-Cache "$upstream_cache_status";


#按比例减少图像到指定大小,公减少一个可以另一个用"-"来表示,出错415,参数值可包含变量,可以与rotate一起使用,则两个一起生效。
image_filter resize $width -;
#设置读取图像缓冲的最大大小,超过则415错误。
image_filter_buffer 100M;
#设置变换的JPEG图像的期望质量。可接受的值是从1到100的范围内。较小的值通常意味着既降低图像质量,减少传输数据,推荐的最大值为95。参数值可以包含变量。
image_filter_jpeg_quality 90;
#定义是否应该透明转换的GIF图像或PNG图像与调色板中指定的颜色时,可以保留。透明度的损失将导致更好的图像质量。在PNG的Alpha通道总是保留透明度。
image_filter_transparency on;

}

}

配置完成保存。 调用nginx -t测试配置文件是否正确

7.日志

访问日志默认路径 /var/log/nginx/access.log
错误日志默认路径 /var/log/nginx/error.log

防火墙开放端口配置

仅允许内网IP访问指定端口

#(1)允许172.26.188.0 IP段访问9000端口
输入命令 firewall-cmd –permanent –add-rich-rule=’rule family=”ipv4” source address=”172.26.188.0/24” port protocol=”tcp” port=”9000” accept’
#(2)允许公网访问80端口
输入命令 firewall-cmd –permanent –zone=public –add-port=80/tcp
#(3)执行完命令要刷新才能生效
输入命令 firewall-cmd –reload
#(4)列出所有的开放端口
输入命令 firewall-cmd –list-all
列出所有的开放端口

移除策略

firewall-cmd –permanent –remove-rich-rule=’rule family=”ipv4” source address=”IP地址” port protocol=”tcp” port=”端口号” accept’
#例如移除9000端口策略
firewall-cmd –permanent –remove-rich-rule=’rule family=”ipv4” source address=”172.26.188.0/24” port protocol=”tcp” port=”9000” accept’
输入命令 firewall-cmd –reload 执行完命令要刷新才能生效

主机配置:V2

… …
… …
省略的教程和上面的主机V1配置相同
… …
… …

配置Nginx参数 请按实际情况更改下面的参数。

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
163
164
165
166
167
168
169
170
171
172
173
174
# proxy_cache_path 缓存文件路径 需要手动建文件夹 mkdir /etc/nginx/cache
# levels 设置缓存文件目录层次;levels=1:2 表示两级目录
# keys_zone 设置缓存名字和共享内存大小
# inactive 在指定时间内没人访问则被删除 30m/分钟
# inactive 未被访问文件在缓存中保留时间,如果设为30m,则30分钟未被访问则不论状态是否为expired,缓存控制程序会删掉文件。inactive默认是10分钟。需要注意的是,inactive和expired配置项的含义是不同的,expired只是缓存过期,但不会被删除,inactive是删除指定时间内未被访问的缓存文件 30m/分钟 7d/7天
# max_size 最大缓存空间,如果缓存空间满,默认覆盖掉缓存时间最长的资源。
proxy_cache_path /etc/nginx/cache levels=1:2 keys_zone=img_cache:10m inactive=1d max_size=50G;

server {
listen 80;
server_name _;
return 404;
}


#配置请求转发,将网页访问80端口转发到MinIO的9000端口
server{
listen 80;
server_name img2.diyhi.com;
location /{
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:9000;
client_max_body_size 100M; #允许上传文件大小,默认是1M

}

location ~ /(file/topic|file/help)/.*\.(m3u8|ts|mp4|avi|mkv|wmv|wav|rm|rmvb|mp3|flac|ape|zip|rar|7z|txt|docx|doc|pptx|ppt|xlsx|xls)$ {
#只在非DELETE请求时处理
if ($request_method ~ ^(GET|POST|HEAD|OPTIONS|PUT|TRACE|CONNECT)$ ) {
#返回302,让下一步执行内部跳转
return 302;
error_page 302 = @process_secure_link;
}
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:9000;

}

location @process_secure_link {
secure_link $arg_md5,$arg_expires;
#表示 MD5(密钥 + 不包含主机名和请求参数的剩余部分文件路径 + 过期时间) 1234567890123456是密钥,必须是16位字符并且和管理后台的'基本设置'中'文件防盗链密钥'一致
secure_link_md5 1234567890123456$uri$arg_expires;


#当匹配成功的时候 secure_link 是非空非0的

# 没有匹配到返回 ""
if ($secure_link = "") {
return 403;
}

# 没有匹配到返回0
if ($secure_link = "0") {
return 410;
}
#文件名重命名
if ($arg_filename != "") {
add_header Content-Disposition 'attachment; filename="$arg_filename"';
}



proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
#proxy_redirect off;
#MinIO
proxy_pass http://127.0.0.1:9000;
}




location ~ /(file/topic)/.*\.(jpg|jpeg|gif|png|bmp)$ {
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:9000;
client_max_body_size 100M; #允许上传文件大小,默认是1M

#只在GET请求时处理图片
if ($request_method = GET) {
#返回302,让下一步执行内部跳转
return 302;
error_page 302 = @process_image_filter;
}
}


#Nginx 不支持在 if {} 这个 block 里面用 image_filter 函数,image_filter 的第一个参数 resize/crop 也不能用变量的方式传输

# ~ 为区分大小写匹配 ~* 为不区分大小写匹配
#location ~ .*.(gif|jpg|jpeg|png|bmp)$ {
#location ~ /(file/topic)/.*\.(jpg|jpeg|gif|png|bmp)$ {
location @process_image_filter {
proxy_pass http://127.0.0.1:9000;
#标记条件 nginx的配置中不支持if条件的逻辑与&amp;&amp; 逻辑或|| 运算 ,而且不支持if的嵌套语法
set $flag 0;
#将参数赋值给宽
set $width $arg_width;

#是否为原图
set $originalImage 0;

if ($width != ''){
set $flag "${flag}1";
}
#预设宽度为240或400,当输入其它宽度时默认改为240
if ($arg_width !~ ^(240|400)$){
set $flag "${flag}1";
}
#如果URL带宽度参数并且不属于预先指定尺寸,则默认显示宽240
if ($flag = "011"){
set $width '240';
}


#当请求的是原图时(即不带参数),则设置宽高维度为”-”
if ( $width = '' ) {
set $width '-';
}

#如果访问原图
if ( $width = '-' ) {
set $originalImage 1;
}

#原图不缓存
proxy_no_cache $originalImage;

#默认使用的key就是URL
proxy_cache_key $host$uri$width;


# 将缩略图缓存在服务,避免每次请求都重新生成
proxy_cache img_cache;
# 有效的文件,在服务器缓存 1 天
proxy_cache_valid 200 1d;
#默认值:proxy_cache_lock off;
#可配置段:http, server, location
#作用:默认不开启,开启的话则每次只能有一个请求更新相同的缓存,其他请求要么等待缓存&gt;有数据要么限时等待锁释放。通常在多个客户端请求缓存未命中时,只有第一个请求可以发向原服务器,其他请求要等待第一个响应返回或者超时后,使用缓存响应客户端。该参数可以合并回源请求,减轻峰值流量下的压力。
proxy_cache_lock on;
#默认值:proxy_cache_lock_timeout 5s;
#可配置段:http, server, location
#作用:等待缓存锁超时之后将直接请求后端,结果不会被缓存。
proxy_cache_lock_timeout 5s;
#当缓存过期后,如果开启了 proxy_cache_revalidate,则会发出一次 if-modified-since 或 if-none-match 条件请求,如果后端返回 304,则此时$upstream_cache_status 为 REVALIDATED,我们将得到两个好处,节省带宽和减少写磁盘的次数。
proxy_cache_revalidate on;

#标记缓存是否命中,在访问文件头Response Headers中查看
# MISS 未命中,请求被传送到后端
# HIT 缓存命中
# EXPIRED 缓存已经过期请求被传送到后端
# UPDATING 正在更新缓存,将使用旧的应答
# STALE 后端将得到过期的应答
add_header Nginx-Cache "$upstream_cache_status";


#按比例减少图像到指定大小,公减少一个可以另一个用"-"来表示,出错415,参数值可包含变量,可以与rotate一起使用,则两个一起生效。
image_filter resize $width -;
#设置读取图像缓冲的最大大小,超过则415错误。
image_filter_buffer 100M;
#设置变换的JPEG图像的期望质量。可接受的值是从1到100的范围内。较小的值通常意味着既降低图像质量,减少传输数据,推荐的最大值为95。参数值可以包含变量。
image_filter_jpeg_quality 90;
#定义是否应该透明转换的GIF图像或PNG图像与调色板中指定的颜色时,可以保留。透明度的损失将导致更好的图像质量。在PNG的Alpha通道总是保留透明度。
image_filter_transparency on;

}

}

… …
… …
… …

主机配置:V3

… …
… …
省略的教程和上面的主机V1配置相同
… …
… …

配置Nginx参数 请按实际情况更改下面的参数。

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
163
164
165
166
167
168
169
170
171
172
173
174
175
# proxy_cache_path 缓存文件路径 需要手动建文件夹 mkdir /etc/nginx/cache
# levels 设置缓存文件目录层次;levels=1:2 表示两级目录
# keys_zone 设置缓存名字和共享内存大小
# inactive 在指定时间内没人访问则被删除 30m/分钟
# inactive 未被访问文件在缓存中保留时间,如果设为30m,则30分钟未被访问则不论状态是否为expired,缓存控制程序会删掉文件。inactive默认是10分钟。需要注意的是,inactive和expired配置项的含义是不同的,expired只是缓存过期,但不会被删除,inactive是删除指定时间内未被访问的缓存文件 30m/分钟 7d/7天
# max_size 最大缓存空间,如果缓存空间满,默认覆盖掉缓存时间最长的资源。
proxy_cache_path /etc/nginx/cache levels=1:2 keys_zone=img_cache:10m inactive=1d max_size=50G;

server {
listen 80;
server_name _;
return 404;
}


#配置请求转发,将网页访问80端口转发到MinIO的9000端口
server{
listen 80;
server_name img3.diyhi.com;
location /{
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:9000;
client_max_body_size 100M; #允许上传文件大小,默认是1M

}



location ~ /(file/topic|file/help)/.*\.(m3u8|ts|mp4|avi|mkv|wmv|wav|rm|rmvb|mp3|flac|ape|zip|rar|7z|txt|docx|doc|pptx|ppt|xlsx|xls)$ {
#只在非DELETE请求时处理
if ($request_method ~ ^(GET|POST|HEAD|OPTIONS|PUT|TRACE|CONNECT)$ ) {
#返回302,让下一步执行内部跳转
return 302;
error_page 302 = @process_secure_link;
}
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:9000;

}

location @process_secure_link {
secure_link $arg_md5,$arg_expires;
#表示 MD5(密钥 + 不包含主机名和请求参数的剩余部分文件路径 + 过期时间) 1234567890123456是密钥,必须是16位字符并且和管理后台的'基本设置'中'文件防盗链密钥'一致
secure_link_md5 1234567890123456$uri$arg_expires;


#当匹配成功的时候 secure_link 是非空非0的

# 没有匹配到返回 ""
if ($secure_link = "") {
return 403;
}

# 没有匹配到返回0
if ($secure_link = "0") {
return 410;
}
#文件名重命名
if ($arg_filename != "") {
add_header Content-Disposition 'attachment; filename="$arg_filename"';
}


proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
#proxy_redirect off;
#MinIO
proxy_pass http://127.0.0.1:9000;
}




location ~ /(file/topic)/.*\.(jpg|jpeg|gif|png|bmp)$ {
proxy_set_header Host $host;
proxy_set_header X-Real-Ip $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:9000;
client_max_body_size 100M; #允许上传文件大小,默认是1M

#只在GET请求时处理图片
if ($request_method = GET) {
#返回302,让下一步执行内部跳转
return 302;
error_page 302 = @process_image_filter;
}
}


#Nginx 不支持在 if {} 这个 block 里面用 image_filter 函数,image_filter 的第一个参数 resize/crop 也不能用变量的方式传输

# ~ 为区分大小写匹配 ~* 为不区分大小写匹配
#location ~ .*.(gif|jpg|jpeg|png|bmp)$ {
#location ~ /(file/topic)/.*\.(jpg|jpeg|gif|png|bmp)$ {
location @process_image_filter {
proxy_pass http://127.0.0.1:9000;
#标记条件 nginx的配置中不支持if条件的逻辑与&amp;&amp; 逻辑或|| 运算 ,而且不支持if的嵌套语法
set $flag 0;
#将参数赋值给宽
set $width $arg_width;

#是否为原图
set $originalImage 0;

if ($width != ''){
set $flag "${flag}1";
}
#预设宽度为240或400,当输入其它宽度时默认改为240
if ($arg_width !~ ^(240|400)$){
set $flag "${flag}1";
}
#如果URL带宽度参数并且不属于预先指定尺寸,则默认显示宽240
if ($flag = "011"){
set $width '240';
}


#当请求的是原图时(即不带参数),则设置宽高维度为”-”
if ( $width = '' ) {
set $width '-';
}

#如果访问原图
if ( $width = '-' ) {
set $originalImage 1;
}

#原图不缓存
proxy_no_cache $originalImage;

#默认使用的key就是URL
proxy_cache_key $host$uri$width;


# 将缩略图缓存在服务,避免每次请求都重新生成
proxy_cache img_cache;
# 有效的文件,在服务器缓存 1 天
proxy_cache_valid 200 1d;
#默认值:proxy_cache_lock off;
#可配置段:http, server, location
#作用:默认不开启,开启的话则每次只能有一个请求更新相同的缓存,其他请求要么等待缓存&gt;有数据要么限时等待锁释放。通常在多个客户端请求缓存未命中时,只有第一个请求可以发向原服务器,其他请求要等待第一个响应返回或者超时后,使用缓存响应客户端。该参数可以合并回源请求,减轻峰值流量下的压力。
proxy_cache_lock on;
#默认值:proxy_cache_lock_timeout 5s;
#可配置段:http, server, location
#作用:等待缓存锁超时之后将直接请求后端,结果不会被缓存。
proxy_cache_lock_timeout 5s;
#当缓存过期后,如果开启了 proxy_cache_revalidate,则会发出一次 if-modified-since 或 if-none-match 条件请求,如果后端返回 304,则此时$upstream_cache_status 为 REVALIDATED,我们将得到两个好处,节省带宽和减少写磁盘的次数。
proxy_cache_revalidate on;

#标记缓存是否命中,在访问文件头Response Headers中查看
# MISS 未命中,请求被传送到后端
# HIT 缓存命中
# EXPIRED 缓存已经过期请求被传送到后端
# UPDATING 正在更新缓存,将使用旧的应答
# STALE 后端将得到过期的应答
add_header Nginx-Cache "$upstream_cache_status";


#按比例减少图像到指定大小,公减少一个可以另一个用"-"来表示,出错415,参数值可包含变量,可以与rotate一起使用,则两个一起生效。
image_filter resize $width -;
#设置读取图像缓冲的最大大小,超过则415错误。
image_filter_buffer 100M;
#设置变换的JPEG图像的期望质量。可接受的值是从1到100的范围内。较小的值通常意味着既降低图像质量,减少传输数据,推荐的最大值为95。参数值可以包含变量。
image_filter_jpeg_quality 90;
#定义是否应该透明转换的GIF图像或PNG图像与调色板中指定的颜色时,可以保留。透明度的损失将导致更好的图像质量。在PNG的Alpha通道总是保留透明度。
image_filter_transparency on;

}

}

… …
… …
… …

主机配置:V4

微服务架构设计模式:从理论到实践

本文系统讲解微服务架构的核心设计模式,包括服务拆分、通信、数据管理、容错等关键领域,帮助架构师构建可扩展、高可用的分布式系统。

一、微服务架构概述

1.1 什么是微服务?

微服务架构是一种将单一应用程序开发为一组小型服务的方法,每个服务:

  • 运行在独立的进程中
  • 通过轻量级机制(通常是 HTTP)通信
  • 围绕业务能力构建
  • 可独立部署和扩展
  • 使用不同的技术栈

1.2 单体 vs 微服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
graph TB
subgraph 单体架构
A[Web 层] --> B[业务逻辑层]
B --> C[数据访问层]
C --> D[(数据库)]
end

subgraph 微服务架构
E[API Gateway] --> F[用户服务]
E --> G[订单服务]
E --> H[商品服务]
E --> I[支付服务]
F --> J[(用户 DB)]
G --> K[(订单 DB)]
H --> L[(商品 DB)]
I --> M[(支付 DB)]
end
维度 单体架构 微服务架构
开发效率 高(本地调试) 中(需要服务协调)
部署复杂度 低(一次部署) 高(多次部署)
可扩展性 低(整体扩展) 高(按需扩展)
技术多样性 低(统一技术栈) 高(各服务独立)
故障隔离 低(单点故障) 高(服务隔离)
数据一致性 高(本地事务) 低(分布式事务)

1.3 何时使用微服务?

适合场景:

  • 团队规模大(>10 人)
  • 业务复杂度高
  • 需要快速迭代
  • 不同模块有不同扩展需求
  • 需要技术多样性

不适合场景:

  • 初创项目(快速验证)
  • 团队规模小
  • 业务逻辑简单
  • 对一致性要求极高

二、服务拆分模式

2.1 基于业务能力拆分

按业务领域划分服务边界:

1
2
3
4
5
6
7
graph LR
subgraph 电商系统
A[用户服务] --> B[认证/注册/个人信息]
C[商品服务] --> D[商品管理/库存/分类]
E[订单服务] --> F[下单/支付/物流]
G[营销服务] --> H[优惠券/活动/推荐]
end

拆分原则:

  • 高内聚:相关功能放在一起
  • 低耦合:服务间依赖最小化
  • 单一职责:每个服务做好一件事
  • 自治性:服务可独立运行

2.2 基于子域拆分(DDD)

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
// 领域驱动设计示例

// 核心域 - 订单
public class Order {
private OrderId id;
private CustomerId customerId;
private List<OrderItem> items;
private Money totalAmount;
private OrderStatus status;

public void place() {
// 业务逻辑
this.status = OrderStatus.PLACED;
DomainEvents.publish(new OrderPlacedEvent(this));
}
}

// 支撑域 - 库存
public class Inventory {
private ProductId productId;
private int quantity;

public void reserve(int quantity) {
if (this.quantity < quantity) {
throw new InsufficientInventoryException();
}
this.quantity -= quantity;
}
}

// 通用域 - 通知
public class NotificationService {
public void sendEmail(String to, String subject, String body) {
// 发送邮件
}

public void sendSms(String phone, String message) {
// 发送短信
}
}

2.3 拆分陷阱与避免

陷阱 1:分布式单体

1
2
3
4
5
6
7
8
9
10
11
graph TB
A[服务 A] --> B[服务 B]
B --> C[服务 C]
C --> A
A --> D[服务 D]
D --> B

style A fill:#ff6b6b
style B fill:#ff6b6b
style C fill:#ff6b6b
style D fill:#ff6b6b

问题:服务间紧密耦合,一个服务故障导致级联故障。

解决方案:

  • 引入异步通信
  • 添加熔断器
  • 设计容错机制

陷阱 2:过度拆分

1
2
3
4
5
6
7
8
❌ 错误示例:
- UserService(用户基础信息)
- UserProfileService(用户 profile)
- UserPreferenceService(用户偏好)
- UserAuthService(用户认证)

✅ 正确示例:
- UserService(所有用户相关功能)

三、服务通信模式

3.1 同步通信(REST/gRPC)

REST API 设计

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
// RESTful API 示例
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {

@Autowired
private OrderService orderService;

// 创建订单
@PostMapping
public ResponseEntity<OrderDTO> createOrder(@RequestBody CreateOrderRequest request) {
Order order = orderService.create(request);
OrderDTO dto = orderMapper.toDTO(order);
return ResponseEntity.created(URI.create("/orders/" + order.getId()))
.body(dto);
}

// 查询订单
@GetMapping("/{orderId}")
public ResponseEntity<OrderDTO> getOrder(@PathVariable Long orderId) {
Order order = orderService.getById(orderId);
return ResponseEntity.ok(orderMapper.toDTO(order));
}

// 取消订单
@PostMapping("/{orderId}/cancel")
public ResponseEntity<Void> cancelOrder(@PathVariable Long orderId) {
orderService.cancel(orderId);
return ResponseEntity.noContent().build();
}
}

gRPC 高性能通信

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
// order.proto
syntax = "proto3";

package order;

service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (OrderResponse);
rpc GetOrder(GetOrderRequest) returns (OrderResponse);
rpc CancelOrder(CancelOrderRequest) returns (CancelOrderResponse);
}

message CreateOrderRequest {
int64 user_id = 1;
repeated OrderItem items = 2;
string address = 3;
}

message OrderItem {
int64 product_id = 1;
int32 quantity = 2;
}

message OrderResponse {
int64 order_id = 1;
string status = 2;
double total_amount = 3;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// gRPC 服务实现
@GrpcService
public class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase {

@Override
public void createOrder(CreateOrderRequest request,
StreamObserver<OrderResponse> responseObserver) {
Order order = orderService.create(request);

OrderResponse response = OrderResponse.newBuilder()
.setOrderId(order.getId())
.setStatus(order.getStatus().name())
.setTotalAmount(order.getTotalAmount().doubleValue())
.build();

responseObserver.onNext(response);
responseObserver.onCompleted();
}
}

REST vs gRPC 对比:

特性 REST gRPC
协议 HTTP/1.1 HTTP/2
数据格式 JSON Protobuf
性能 高(2-10 倍)
浏览器支持 需要代理
生态成熟度
适用场景 对外 API 内部服务通信

3.2 异步通信(消息队列)

事件驱动架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sequenceDiagram
participant O as 订单服务
participant MQ as 消息队列
participant I as 库存服务
participant P as 支付服务
participant N as 通知服务

O->>MQ: 发布 OrderCreated 事件
MQ->>I: 消费事件
MQ->>P: 消费事件
MQ->>N: 消费事件
I->>I: 扣减库存
P->>P: 创建支付
N->>N: 发送通知
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
// 事件发布
@Component
public class OrderEventPublisher {

@Autowired
private RabbitTemplate rabbitTemplate;

public void publishOrderCreated(Order order) {
OrderCreatedEvent event = new OrderCreatedEvent();
event.setOrderId(order.getId());
event.setUserId(order.getUserId());
event.setAmount(order.getTotalAmount());
event.setTimestamp(System.currentTimeMillis());

rabbitTemplate.convertAndSend(
"order.events",
"order.created",
event
);
}
}

// 事件消费
@Component
public class InventoryEventListener {

@RabbitListener(queues = "inventory.queue")
public void handleOrderCreated(OrderCreatedEvent event) {
try {
inventoryService.reserveStock(event.getOrderId(), event.getItems());
} catch (Exception e) {
// 失败处理:重试或进入死信队列
throw new AmqpRejectAndDontRequeueException(e);
}
}
}

3.3 API Gateway 模式

1
2
3
4
5
6
7
8
9
graph TB
A[客户端] --> B[API Gateway]
B --> C[认证/鉴权]
B --> D[限流]
B --> E[路由]
C --> F[用户服务]
D --> G[订单服务]
E --> H[商品服务]
B --> I[支付服务]

Spring Cloud Gateway 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- Authentication
- RateLimiter=100

- id: order-service
uri: lb://order-service
predicates:
- Path=/api/orders/**
filters:
- Authentication
- CircuitBreaker

- id: product-service
uri: lb://product-service
predicates:
- Path=/api/products/**

四、数据管理模式

4.1 数据库按服务拆分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
graph TB
subgraph 用户服务
A[User Service] --> B[(User DB)]
end

subgraph 订单服务
C[Order Service] --> D[(Order DB)]
end

subgraph 商品服务
E[Product Service] --> F[(Product DB)]
end

subgraph 支付服务
G[Payment Service] --> H[(Payment DB)]
end

原则:

  • 每个服务独享数据库
  • 禁止跨库 JOIN
  • 通过 API 或事件访问其他服务数据

4.2 CQRS(命令查询职责分离)

1
2
3
4
5
6
7
graph TB
A[命令端] --> B[Write Model]
B --> C[(写数据库)]
C --> D[事件处理器]
D --> E[Read Model]
E --> F[(读数据库)]
G[查询端] --> F
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
// 命令端
@Service
public class OrderCommandService {

@Autowired
private OrderRepository orderRepository;

@Transactional
public OrderId createOrder(CreateOrderCommand command) {
Order order = new Order(command.getUserId(), command.getItems());
orderRepository.save(order);

// 发布领域事件
eventPublisher.publish(new OrderCreatedEvent(order));

return order.getId();
}
}

// 查询端
@Service
public class OrderQueryService {

@Autowired
private OrderReadRepository orderReadRepository;

public OrderDTO getOrder(Long orderId) {
OrderReadModel model = orderReadRepository.findById(orderId);
return orderMapper.toDTO(model);
}

public List<OrderDTO> getUserOrders(Long userId) {
List<OrderReadModel> models = orderReadRepository.findByUserId(userId);
return models.stream()
.map(orderMapper::toDTO)
.collect(Collectors.toList());
}
}

4.3 事件溯源(Event Sourcing)

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
// 事件存储
@Entity
public class DomainEvent {
@Id
private Long id;

private String aggregateType;
private String aggregateId;
private String eventType;
private String eventData; // JSON
private Long timestamp;
private Integer version;
}

// 聚合根
public class Order {
private OrderId id;
private List<OrderEvent> events = new ArrayList<>();

public void apply(OrderEvent event) {
events.add(event);
// 根据事件更新状态
if (event instanceof OrderCreatedEvent) {
// 更新订单状态
}
}

public List<OrderEvent> getUncommittedEvents() {
return events;
}

public void clearUncommittedEvents() {
events.clear();
}
}

// 事件存储库
@Repository
public class EventStoreRepository {

public void save(List<DomainEvent> events) {
eventRepository.saveAll(events);
}

public List<DomainEvent> getEvents(String aggregateId) {
return eventRepository.findByAggregateIdOrderByVersion(aggregateId);
}
}

4.4 Saga 分布式事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sequenceDiagram
participant O as 订单服务
participant I as 库存服务
participant P as 支付服务
participant S as Saga 协调器

O->>S: 开始 Saga
S->>I: 预留库存
I-->>S: 成功
S->>P: 创建支付
P-->>S: 失败
S->>I: 补偿:取消预留
I-->>S: 补偿成功
S->>O: Saga 失败
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
// Saga 实现
@Service
public class OrderSaga {

@Autowired
private InventoryService inventoryService;

@Autowired
private PaymentService paymentService;

@Autowired
private OrderService orderService;

@Transactional
public Order createOrder(CreateOrderRequest request) {
try {
// 1. 创建订单(待支付状态)
Order order = orderService.createPending(request);

// 2. 预留库存
inventoryService.reserveStock(order.getId(), order.getItems());

// 3. 创建支付
Payment payment = paymentService.createPayment(order);

// 4. 确认订单
orderService.confirm(order.getId(), payment.getId());

return order;

} catch (Exception e) {
// 补偿操作
compensate(request, e);
throw e;
}
}

private void compensate(CreateOrderRequest request, Exception cause) {
// 补偿逻辑
if (cause instanceof PaymentException) {
inventoryService.cancelReservation(request.getOrderId());
}
orderService.cancel(request.getOrderId());
}
}

五、容错与弹性模式

5.1 熔断器(Circuit Breaker)

1
2
3
4
5
6
7
8
9
graph LR
A[CLOSED] -->|失败率>阈值 | B[OPEN]
B -->|休眠时间到 | C[HALF_OPEN]
C -->|成功 | A
C -->|失败 | B

style A fill:#4ecdc4
style B fill:#ff6b6b
style C fill:#ffe66d
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
// Resilience4j 配置
@Configuration
public class ResilienceConfig {

@Bean
public CircuitBreakerConfig circuitBreakerConfig() {
return CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 失败率阈值 50%
.waitDurationInOpenState(Duration.ofSeconds(30)) // 打开状态等待时间
.slidingWindowSize(10) // 滑动窗口大小
.minimumNumberOfCalls(5) // 最小调用次数
.build();
}

@Bean
public RetryConfig retryConfig() {
return RetryConfig.custom()
.maxAttempts(3) // 最大重试次数
.waitDuration(Duration.ofSeconds(1)) // 重试间隔
.retryExceptions(SocketTimeoutException.class) // 重试异常类型
.build();
}
}

// 使用熔断器
@Service
public class OrderService {

@Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;

@CircuitBreaker(name = "paymentService", fallbackMethod = "createOrderFallback")
public Order createOrder(CreateOrderRequest request) {
// 调用支付服务
Payment payment = paymentClient.create(request);
return orderRepository.save(new Order(payment));
}

// 降级方法
public Order createOrderFallback(CreateOrderRequest request, Exception e) {
log.warn("支付服务不可用,使用降级逻辑", e);
// 返回缓存数据或默认值
return Order.createPending(request);
}
}

5.2 超时与重试

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
// 超时配置
@Configuration
public class HttpClientConfig {

@Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000); // 连接超时 5 秒
factory.setReadTimeout(10000); // 读取超时 10 秒
return new RestTemplate(factory);
}
}

// 重试机制
@Service
public class PaymentService {

@Retryable(
value = {ConnectTimeoutException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2)
)
public Payment createPayment(Order order) {
return paymentClient.create(order);
}

@Recover
public Payment recover(ConnectTimeoutException e, Order order) {
log.error("支付服务重试失败", e);
return Payment.createPending(order);
}
}

5.3 限流(Rate Limiting)

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
// 令牌桶限流
@Service
public class RateLimiterService {

private final RateLimiter rateLimiter = RateLimiter.create(
RateLimiterConfig.custom()
.limitRefreshPeriod(Duration.ofSeconds(1))
.limitForPeriod(100) // 每秒 100 个请求
.timeoutDuration(Duration.ofMillis(500))
.build()
);

public void execute(Runnable task) {
Boolean permission = rateLimiter.acquirePermission();
if (permission) {
task.run();
} else {
throw new RateLimitExceededException("请求过于频繁");
}
}
}

// 网关层限流
@Configuration
public class GatewayConfig {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("order-service", r -> r
.path("/api/orders/**")
.filters(f -> f
.requestRateLimiter(config -> {
config.setRateLimiter(redisRateLimiter());
})
)
.uri("lb://order-service")
)
.build();
}

@Bean
public RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(100, 200); // 每秒 100 个请求,令牌桶容量 200
}
}

5.4 舱壁模式(Bulkhead)

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
// 线程池隔离
@Configuration
public class BulkheadConfig {

@Bean
public ThreadPoolBulkheadRegistry bulkheadRegistry() {
ThreadPoolBulkheadConfig config = ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(50)
.coreThreadPoolSize(10)
.queueCapacity(100)
.build();

return ThreadPoolBulkheadRegistry.of(config);
}
}

@Service
public class OrderService {

@Bulkhead(name = "paymentBulkhead", type = Bulkhead.Type.THREADPOOL)
public Order createOrder(CreateOrderRequest request) {
// 使用独立线程池执行
return paymentClient.create(request);
}
}

六、实战案例

案例 1:电商平台微服务架构

系统架构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
graph TB
A[客户端] --> B[API Gateway]
B --> C[认证服务]
B --> D[用户服务]
B --> E[商品服务]
B --> F[订单服务]
B --> G[支付服务]
B --> H[物流服务]

C --> I[(用户 DB)]
D --> I
E --> J[(商品 DB)]
F --> K[(订单 DB)]
G --> L[(支付 DB)]
H --> M[(物流 DB)]

subgraph 消息队列
N[RabbitMQ]
end

F --> N
G --> N
H --> N

服务依赖关系:

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
// 订单服务 - 依赖其他服务
@Service
public class OrderServiceImpl implements OrderService {

@Autowired
private ProductService productService;

@Autowired
private InventoryService inventoryService;

@Autowired
private PaymentService paymentService;

@Autowired
private UserService userService;

@Override
@Transactional
public Order create(CreateOrderRequest request) {
// 1. 验证用户
User user = userService.getById(request.getUserId());

// 2. 验证商品并计算价格
List<Product> products = productService.getByIds(request.getProductIds());
Money total = calculateTotal(products, request.getQuantities());

// 3. 创建订单
Order order = new Order(user, products, total);
orderRepository.save(order);

// 4. 扣减库存(异步)
inventoryService.reserveAsync(order.getId(), request.getItems());

return order;
}
}

案例 2:服务迁移策略

从单体到微服务的演进:

1
2
3
4
5
6
7
8
graph LR
A[单体应用] --> B[模块化单体]
B --> C[垂直拆分]
C --> D[微服务]

A -->|阶段 1| B
B -->|阶段 2| C
C -->|阶段 3| D

迁移步骤:

  1. 识别边界:分析代码库,识别业务边界
  2. 提取模块:将模块拆分为独立项目
  3. 数据拆分:按服务拆分数据库
  4. 服务化:实现服务间通信
  5. 独立部署:建立 CI/CD 流水线
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 绞杀者模式 - 逐步替换
@Configuration
public class StranglerConfig {

@Bean
public RouterFunction<ServerResponse> routingFunction(
OldService oldService,
NewService newService) {

return RouterFunctions.route()
// 新功能走新服务
.path("/api/v2", request ->
newService.handle(request))
// 旧功能走旧服务
.path("/api/v1", request ->
oldService.handle(request))
.build();
}
}

七、监控与可观测性

7.1 分布式链路追踪

1
2
3
4
5
6
7
8
9
10
11
# SkyWalking 配置
agent:
service_name: order-service
namespace: production

collector:
backend_service: 192.168.1.100:11800

logging:
level: debug
output: logs/skywalking-agent.log

7.2 指标监控

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
// Prometheus 指标
@Component
public class OrderMetrics {

private final Counter orderCounter;
private final Timer orderTimer;
private final Gauge pendingOrdersGauge;

public OrderMetrics(MeterRegistry registry) {
orderCounter = Counter.builder("orders.total")
.description("Total orders created")
.register(registry);

orderTimer = Timer.builder("orders.duration")
.description("Order creation duration")
.register(registry);

pendingOrdersGauge = Gauge.builder("orders.pending")
.description("Pending orders count")
.register(registry, this, OrderMetrics::getPendingCount);
}

public void recordOrderCreation(long durationMs) {
orderCounter.increment();
orderTimer.record(durationMs, TimeUnit.MILLISECONDS);
}
}

7.3 日志聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ELK Stack 配置
filebeat:
inputs:
- type: log
paths:
- /var/log/order-service/*.log
fields:
service: order-service
environment: production

output:
elasticsearch:
hosts: ["http://elasticsearch:9200"]
index: "order-service-%{+yyyy.MM.dd}"

八、总结

微服务架构设计的关键要点:

  1. 合理拆分:基于业务能力或 DDD 子域,避免过度拆分
  2. 通信选择:同步(REST/gRPC)+ 异步(消息队列)
  3. 数据管理:数据库按服务拆分,使用 CQRS/事件溯源
  4. 容错设计:熔断、重试、限流、舱壁
  5. 可观测性:链路追踪、指标监控、日志聚合

微服务不是银弹,需要权衡复杂度和收益。记住:合适的架构才是最好的架构


参考资料: