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. 监控日志:日志格式、状态监控、故障排查

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


参考资料:

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

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

一、微服务架构概述

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. 可观测性:链路追踪、指标监控、日志聚合

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


参考资料:

全面解析 Docker 容器化的最佳实践,涵盖镜像构建优化、Dockerfile 编写、多阶段构建、安全加固、网络存储、编排部署等核心主题,包含 3 个架构图和 2 个实战案例

阅读全文 »

Linux 性能排查指南:从入门到精通

本文系统讲解 Linux 系统性能排查的方法论、工具链和实战技巧,帮助运维和开发人员快速定位和解决性能问题。

一、性能排查方法论

1.1 USE 方法

USE(Utilization, Saturation, Errors) 是 Brendan Gregg 提出的性能分析方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
graph TB
A[性能问题] --> B[资源利用率 Utilization]
A --> C[资源饱和度 Saturation]
A --> D[资源错误 Errors]

B --> B1[CPU 使用率]
B --> B2[内存使用率]
B --> B3[磁盘使用率]
B --> B4[网络带宽]

C --> C1[CPU 队列长度]
C --> C2[内存交换]
C --> C3[磁盘 IO 队列]
C --> C4[网络丢包]

D --> D1[硬件错误]
D --> D2[系统错误]
D --> D3[应用错误]

检查清单:

资源 利用率 饱和度 错误
CPU % 使用率 运行队列长度 硬件错误
内存 % 使用率 Swap 使用 OOM 错误
磁盘 % 容量 IO 等待时间 IO 错误
网络 带宽使用 丢包/重传 连接错误

1.2 性能分析步骤

1
2
3
4
5
6
7
8
9
10
graph LR
A[发现问题] --> B[收集指标]
B --> C[定位瓶颈]
C --> D[分析原因]
D --> E[制定方案]
E --> F[验证效果]

style A fill:#ff6b6b
style C fill:#ffe66d
style F fill:#4ecdc4

标准流程:

  1. 发现问题:监控告警、用户反馈、日志异常
  2. 收集指标:使用工具收集系统指标
  3. 定位瓶颈:分析指标,找出瓶颈资源
  4. 分析原因:深入分析瓶颈原因
  5. 制定方案:设计优化方案
  6. 验证效果:实施后验证效果

1.3 性能指标阈值

指标 正常 警告 严重
CPU 使用率 < 70% 70-85% > 85%
内存使用率 < 80% 80-90% > 90%
Swap 使用率 0% < 50% > 50%
磁盘使用率 < 80% 80-90% > 90%
IO 等待 < 10% 10-30% > 30%
负载平均值 < CPU 核数 1-2 倍 > 2 倍
网络丢包 0% < 0.1% > 0.1%

二、CPU 性能排查

2.1 CPU 指标解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看 CPU 信息
lscpu
cat /proc/cpuinfo

# 查看 CPU 使用率
top -bn1 | grep "Cpu(s)"
mpstat -P ALL 1 5

# 查看负载平均值
uptime
cat /proc/loadavg

# 负载平均值解读
# 1 分钟、5 分钟、15 分钟的平均负载
# 单核 CPU:1.00 表示 100% 利用
# 4 核 CPU:4.00 表示 100% 利用

负载平均值含义:

1
2
3
4
5
6
7
8
9
负载 0.00:系统空闲
负载 0.50:系统利用 50%
负载 1.00:系统满载(单核)
负载 2.00:系统过载(单核),有进程等待
负载 4.00:系统严重过载(单核)

多核系统:负载 / CPU 核数 = 实际负载率
4 核系统负载 4.00 = 100% 利用
4 核系统负载 8.00 = 200% 利用(严重过载)

2.2 CPU 排查工具

top 命令

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
# 基本使用
top

# 批处理模式
top -bn1

# 显示特定进程
top -p 1234,5678

# 按 CPU 使用率排序
top -o %CPU

# 按内存使用率排序
top -o %MEM

# 刷新间隔 2 秒
top -d 2

# 显示线程
top -H -p 1234

# top 界面快捷键
# P: 按 CPU 排序
# M: 按内存排序
# N: 按 PID 排序
# T: 按时间排序
# k: 杀死进程
# r: 调整优先级
# q: 退出

top 输出解读:

1
2
3
4
5
6
7
8
9
top - 14:30:00 up 10 days,  2:30,  1 user,  load average: 0.50, 0.80, 0.90
Tasks: 200 total, 1 running, 199 sleeping, 0 stopped, 0 zombie
%Cpu(s): 20.0 us, 5.0 sy, 0.0 ni, 75.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 7980.0 total, 2000.0 free, 3000.0 used, 2980.0 buff/cache
MiB Swap: 2048.0 total, 2000.0 free, 48.0 used. 4500.0 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1234 root 20 0 500000 100000 10000 R 50.0 1.3 10:00.00 java
5678 mysql 20 0 800000 200000 20000 S 20.0 2.5 20:00.00 mysqld

字段说明:

  • us (user): 用户空间 CPU 使用率
  • sy (system): 内核空间 CPU 使用率
  • ni (nice): 低优先级进程 CPU 使用率
  • id (idle): 空闲 CPU 使用率
  • wa (iowait): IO 等待 CPU 使用率
  • hi (hardware interrupts): 硬件中断
  • si (software interrupts): 软件中断
  • st (steal): 虚拟机被偷取的时间

htop 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 安装 htop
yum install htop -y
apt install htop -y

# 基本使用
htop

# 显示特定进程
htop -p 1234

# 按进程名过滤
htop -c java

# htop 快捷键
# F1: 帮助
# F2: 设置
# F3: 搜索
# F4: 过滤
# F5: 树状视图
# F6: 排序
# F9: 杀死进程
# F10: 退出

vmstat 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 每秒采样一次,共 5 次
vmstat 1 5

# 显示进程、内存、IO、CPU 等信息
vmstat -a 1 5

# 显示 slab 信息
vmstat -m 1 5

# 显示磁盘 IO
vmstat -d 1 5

# 显示所有统计
vmstat -a -m -d -s -S M 1 5

vmstat 输出解读:

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
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 48000 200000 100000 2980000 0 0 10 20 100 200 20 5 75 0 0

procs:
r: 运行队列中的进程数(> CPU 核数表示过载)
b: 等待 IO 的进程数

memory:
swpd: 使用的虚拟内存(KB)
free: 空闲内存(KB)
buff: 缓冲区内存(KB)
cache: 缓存内存(KB)

swap:
si: 从磁盘换入的内存(KB/s)
so: 换出到磁盘的内存(KB/s)

io:
bi: 从块设备读取的块数/s
bo: 发送到块设备的块数/s

system:
in: 每秒中断数
cs: 每秒上下文切换数

cpu:
us: 用户时间
sy: 系统时间
id: 空闲时间
wa: IO 等待时间
st: 被偷取时间

pidstat 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 安装 pidstat
yum install sysstat -y
apt install sysstat -y

# 查看所有进程的 CPU 使用率
pidstat -u 1 5

# 查看特定进程
pidstat -p 1234 1 5

# 查看进程的线程
pidstat -t -p 1234 1 5

# 查看进程的上下文切换
pidstat -w 1 5

# 查看进程的所有统计
pidstat -u -r -d -w -h -p 1234 1 5

2.3 CPU 问题诊断

场景 1:CPU 使用率高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. 查看整体 CPU 使用率
top -bn1 | grep "Cpu(s)"

# 2. 找出占用 CPU 高的进程
top -o %CPU -bn1 | head -20

# 3. 查看进程的线程
top -H -p 1234

# 4. 查看进程的系统调用
strace -c -p 1234

# 5. 查看进程的调用栈
pstack 1234

# 6. 使用 perf 分析
perf top -p 1234
perf record -p 1234 -g -- sleep 30
perf report

场景 2:负载高但 CPU 使用率低

1
2
3
4
5
6
7
8
9
10
11
# 可能是 IO 等待导致
vmstat 1 5 # 查看 wa 列

# 查看 IO 等待高的进程
iotop -o

# 查看磁盘 IO
iostat -x 1 5

# 查看进程状态
ps aux | awk '$8 ~ /D/ {print}' # D 状态表示不可中断睡眠

场景 3:上下文切换频繁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看上下文切换次数
vmstat 1 5 # 查看 cs 列

# 查看进程的上下文切换
pidstat -w 1 5

# 查看中断
cat /proc/interrupts

# 查看软中断
cat /proc/softirqs

# 解决方案:
# 1. 减少进程数量
# 2. 优化锁竞争
# 3. 调整进程优先级
# 4. 使用 CPU 绑定

2.4 CPU 优化方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 调整进程优先级
nice -n -10 command # 启动时设置
renice -n -10 -p 1234 # 运行时调整

# CPU 绑定(减少上下文切换)
taskset -c 0,1 command # 绑定到 CPU 0,1
taskset -cp 1234 # 查看进程的 CPU 绑定

# 限制进程 CPU 使用率
cpulimit -p 1234 -l 50 # 限制为 50%

# 查看 CPU 频率
cat /proc/cpuinfo | grep "cpu MHz"
cpufreq-info

# 调整 CPU 调度策略
cpufreq-set -g performance # 性能模式
cpufreq-set -g powersave # 省电模式

三、内存性能排查

3.1 内存指标解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看内存使用情况
free -h
free -m

# 详细内存信息
cat /proc/meminfo

# 查看进程的内存使用
ps aux --sort=-%mem | head -20

# 查看内存映射
pmap -x 1234

# 查看 NUMA 信息
numactl --hardware

free 命令输出解读:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
              total        used        free      shared  buff/cache   available
Mem: 7.8G 3.0G 2.0G 100M 2.8G 4.5G
Swap: 2.0G 48M 1.9G

total: 总内存
used: 已使用内存(包括 buff/cache)
free: 完全空闲内存
shared: 共享内存
buff/cache: 缓冲区 + 缓存
available: 可用内存(估算值,包括可回收的 cache)

关键指标:
- 可用内存 = available / total
- 内存使用率 = (total - available) / total
- Swap 使用率 = used / total

3.2 内存排查工具

/proc/meminfo 详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 查看关键内存指标
cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree"

# 重要字段说明
MemTotal: 总内存
MemFree: 完全空闲内存
MemAvailable: 可用内存(包括可回收的 cache)
Buffers: 缓冲区内存
Cached: 页面缓存
SwapCached: Swap 缓存
Active: 活跃内存
Inactive: 非活跃内存
Dirty: 待写入磁盘的内存
Writeback: 正在写入磁盘的内存
AnonPages: 匿名页(进程私有内存)
Mapped: 映射文件内存
Slab: 内核 slab 分配器
SReclaimable: 可回收的 slab
SUnreclaim: 不可回收的 slab
SwapTotal: 总 Swap
SwapFree: 空闲 Swap

slabtop 命令

1
2
3
4
5
6
7
8
9
10
11
# 查看内核 slab 缓存
slabtop

# 按对象数量排序
slabtop -so

# 按内存使用排序
slabtop -sc

# 退出
q

smem 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 安装 smem
yum install smem -y
apt install smem -y

# 查看进程的内存使用
smem -r

# 按进程名查看
smem -n java

# 查看内存映射
smem -m

# 生成报告
smem -t -k

3.3 内存问题诊断

场景 1:内存使用率高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 查看整体内存使用
free -h

# 2. 找出占用内存高的进程
ps aux --sort=-%mem | head -20

# 3. 查看进程的详细内存
cat /proc/1234/status | grep -E "VmSize|VmRSS|VmData|VmStk"

# 4. 查看内存映射
pmap -x 1234 | tail -20

# 5. 检查是否有内存泄漏
watch -n 1 'ps -p 1234 -o pid,rss,vsz,comm'

# 6. 分析进程的堆内存(Java)
jmap -heap 1234
jmap -histo 1234 | head -50

场景 2:Swap 使用率高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. 查看 Swap 使用
free -h
cat /proc/swaps

# 2. 查看 Swap 使用率
vmstat 1 5 # 查看 si/so 列

# 3. 找出使用 Swap 的进程
for file in /proc/*/status ; do
awk '/VmSwap|Name/{printf $2 " " $3}END{ print ""}' $file
done | sort -k 2 -n -r | head -20

# 4. 查看 Swap 活动
sar -W 1 5

# 5. 解决方案
# - 增加物理内存
# - 减少进程内存使用
# - 调整 swappiness

场景 3:OOM Killer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 查看 OOM 日志
dmesg | grep -i "out of memory"
dmesg | grep -i "killed process"

# 2. 查看 OOM 记录
cat /var/log/messages | grep -i "oom"
journalctl -k | grep -i "oom"

# 3. 查看进程的 OOM 分数
cat /proc/1234/oom_score
cat /proc/1234/oom_score_adj

# 4. 调整 OOM 分数(保护重要进程)
echo -1000 > /proc/1234/oom_score_adj

# 5. 禁用 OOM Killer(不推荐)
echo 1 > /proc/sys/vm/panic_on_oom

3.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
# 调整 swappiness(默认 60)
cat /proc/sys/vm/swappiness
sysctl -w vm.swappiness=10 # 减少 Swap 使用
echo "vm.swappiness=10" >> /etc/sysctl.conf

# 清理页面缓存(谨慎使用)
sync
echo 1 > /proc/sys/vm/drop_caches # 清理页面缓存
echo 2 > /proc/sys/vm/drop_caches # 清理目录项和 inodes
echo 3 > /proc/sys/vm/drop_caches # 清理所有缓存

# 调整透明大页
cat /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/enabled

# 调整 overcommit
cat /proc/sys/vm/overcommit_memory
# 0: 启发式 overcommit(默认)
# 1: 总是 overcommit
# 2: 基于 overcommit_ratio 限制
echo 2 > /proc/sys/vm/overcommit_memory
echo 50 > /proc/sys/vm/overcommit_ratio # 50%

# 增加 Swap
dd if=/dev/zero of=/swapfile bs=1G count=4
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo "/swapfile none swap sw 0 0" >> /etc/fstab

四、磁盘 IO 性能排查

4.1 磁盘 IO 指标解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看磁盘使用情况
df -h
df -i # 查看 inodes

# 查看磁盘 IO 统计
iostat -x 1 5

# 查看磁盘 IO 详情
iostat -d -x -m 1 5

# 查看进程的 IO
pidstat -d 1 5

# 查看实时 IO
iotop

iostat 输出解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
20.00 0.00 5.00 10.00 0.00 65.00

Device: r/s w/s rkB/s wkB/s rrqm/s wrqm/s %util await r_await w_await
sda 50.00 30.00 2000.00 1500.00 5.00 10.00 80.00 10.00 8.00 12.00

avg-cpu:
%iowait: IO 等待时间百分比(> 30% 表示 IO 瓶颈)

Device:
r/s: 每秒读请求数
w/s: 每秒写请求数
rkB/s: 每秒读取的 KB 数
wkB/s: 每秒写入的 KB 数
rrqm/s: 每秒合并的读请求数
wrqm/s: 每秒合并的写请求数
%util: 设备利用率(> 80% 表示饱和)
await: IO 请求平均等待时间(ms)
r_await: 读请求平均等待时间
w_await: 写请求平均等待时间

4.2 磁盘 IO 排查工具

iotop 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 安装 iotop
yum install iotop -y
apt install iotop -y

# 查看实时 IO(需要 root)
iotop

# 只显示有 IO 的进程
iotop -o

# 批处理模式
iotop -b -n 10

# 查看累计 IO
iotop -a

# 查看特定进程的 IO
iotop -p 1234

# iotop 快捷键
# left/right: 排序
# a: 累计 IO
# o: 只显示有 IO 的进程
# q: 退出

pidstat IO 统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看进程的 IO
pidstat -d 1 5

# 查看特定进程
pidstat -d -p 1234 1 5

# 输出解读
Linux 5.4.0 (hostname) (03/12/2026) _x86_64_ (4 CPU)

02:30:00 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
02:30:01 PM 0 1234 100.00 50.00 0.00 10 java

kB_rd/s: 每秒读取的 KB 数
kB_wr/s: 每秒写入的 KB 数
kB_ccwr/s: 取消的写入 KB 数
iodelay: IO 延迟(块)

ioping 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 安装 ioping
yum install ioping -y
apt install ioping -y

# 测试磁盘延迟
ioping -c 10 /dev/sda

# 测试文件系统延迟
ioping -c 10 /tmp

# 测试顺序读延迟
ioping -c 10 -L /tmp

# 测试随机读延迟
ioping -c 10 -R /tmp

4.3 磁盘 IO 问题诊断

场景 1:IO 等待高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 查看 IO 等待
vmstat 1 5 # 查看 wa 列

# 2. 查看磁盘利用率
iostat -x 1 5 # 查看 %util 列

# 3. 找出 IO 高的进程
iotop -o

# 4. 查看进程的 IO
pidstat -d 1 5

# 5. 查看磁盘队列
iostat -x 1 5 # 查看 avgqu-sz 列

# 6. 查看 IO 调度算法
cat /sys/block/sda/queue/scheduler

# 7. 解决方案
# - 优化应用 IO 模式
# - 使用 SSD
# - 调整 IO 调度算法
# - 增加磁盘数量(RAID)

场景 2:磁盘空间不足

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 1. 查看磁盘使用
df -h

# 2. 查找大文件
find / -type f -size +1G -exec ls -lh {} \; 2>/dev/null

# 3. 查找大目录
du -sh /* 2>/dev/null | sort -hr | head -20

# 4. 查找最近修改的大文件
find / -type f -mtime -7 -size +100M -exec ls -lh {} \; 2>/dev/null

# 5. 查看已删除但未释放的文件
lsof | grep deleted

# 6. 清理日志
find /var/log -type f -name "*.log" -exec truncate -s 0 {} \;

# 7. 清理包管理器缓存
yum clean all # CentOS
apt clean # Ubuntu

场景 3:Inodes 耗尽

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. 查看 Inodes 使用
df -i

# 2. 查找文件数量多的目录
for i in /*; do echo $i; find $i | wc -l; done | sort -k2 -n -r | head -20

# 3. 查找小文件
find / -type f -size -1k 2>/dev/null | wc -l

# 4. 解决方案
# - 删除不必要的小文件
# - 合并小文件
# - 增加 Inodes(重新格式化)

4.4 磁盘 IO 优化方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 调整 IO 调度算法
echo deadline > /sys/block/sda/queue/scheduler # 机械盘
echo none > /sys/block/nvme0n1/queue/scheduler # SSD

# 查看当前调度算法
cat /sys/block/sda/queue/scheduler

# 调整预读大小
blockdev --getra /dev/sda # 查看
blockdev --setra 4096 /dev/sda # 设置

# 调整队列深度
echo 128 > /sys/block/sda/queue/nr_requests

# 使用 noatime 挂载(减少元数据更新)
# /etc/fstab
/dev/sda1 / ext4 defaults,noatime 0 1

# 使用 writeback 模式(性能优先)
tune2fs -o journal_data_writeback /dev/sda1

# 使用 deadline 调度器(推荐)
echo "deadline" > /sys/block/sda/queue/scheduler

五、网络性能排查

5.1 网络指标解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看网络接口
ip addr
ifconfig -a

# 查看网络统计
cat /proc/net/dev

# 查看网络连接
netstat -an
ss -an

# 查看路由表
ip route
netstat -rn

# 查看 DNS 配置
cat /etc/resolv.conf

/proc/net/dev 解读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat /proc/net/dev

Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
eth0: 1000000 10000 0 0 0 0 0 0 500000 5000 0 0 0 0 0 0

关键指标:
- bytes: 字节数
- packets: 数据包数
- errs: 错误数(应为 0)
- drop: 丢弃数(应接近 0)
- fifo: FIFO 缓冲区错误
- frame: 帧错误
- colls: 冲突数(半双工)
- carrier: 载波错误

5.2 网络排查工具

ss 命令

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
# 查看所有连接
ss -an

# 查看 TCP 连接
ss -tan

# 查看监听端口
ss -tlnp

# 查看 UDP 端口
ss -ulnp

# 查看进程信息
ss -tanp

# 查看连接统计
ss -s

# 查看内存使用
ss -tm

# 过滤端口
ss -tan | grep :80

# 过滤状态
ss -tan state established
ss -tan state time-wait

netstat 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 查看所有连接
netstat -an

# 查看 TCP 连接
netstat -ant

# 查看监听端口
netstat -tlnp

# 查看 UDP 端口
netstat -ulnp

# 查看网络统计
netstat -s

# 查看路由表
netstat -rn

# 查看接口统计
netstat -i

# 查看连接数统计
netstat -an | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

tcpdump 命令

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
# 抓包(所有接口)
tcpdump -i any

# 抓包(指定接口)
tcpdump -i eth0

# 抓包(指定端口)
tcpdump -i eth0 port 80

# 抓包(指定主机)
tcpdump -i eth0 host 192.168.1.1

# 抓包(指定协议)
tcpdump -i eth0 tcp

# 保存抓包数据
tcpdump -i eth0 -w capture.pcap

# 读取抓包文件
tcpdump -r capture.pcap

# 详细输出
tcpdump -i eth0 -v
tcpdump -i eth0 -vv
tcpdump -i eth0 -vvv

# 显示内容
tcpdump -i eth0 -X # 十六进制 + ASCII
tcpdump -i eth0 -XX # 十六进制 + ASCII(不含链路层)

ping 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 基本 ping
ping www.google.com

# 指定次数
ping -c 4 www.google.com

# 指定间隔
ping -i 0.5 www.google.com

# 指定包大小
ping -s 1000 www.google.com

# 记录路由
ping -R www.google.com

# 时间戳
ping -D www.google.com

# flood ping(需要 root)
ping -f www.google.com

traceroute 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 追踪路由
traceroute www.google.com

# 使用 ICMP
traceroute -I www.google.com

# 使用 TCP
traceroute -T www.google.com

# 指定端口
traceroute -T -p 80 www.google.com

# 不解析域名
traceroute -n www.google.com

# 最大跳数
traceroute -m 30 www.google.com

# 替代工具:mtr
mtr www.google.com
mtr -r -c 100 www.google.com # 报告模式

5.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
# 1. 检查网络接口
ip addr show
ip link show

# 2. 检查路由
ip route show
ip route get 8.8.8.8

# 3. 检查 DNS
cat /etc/resolv.conf
nslookup www.google.com
dig www.google.com

# 4. 检查防火墙
iptables -L -n
firewall-cmd --list-all

# 5. 检查端口监听
ss -tlnp | grep :80

# 6. 检查连接
telnet 192.168.1.1 80
nc -zv 192.168.1.1 80

# 7. 抓包分析
tcpdump -i eth0 port 80 -n

场景 2:网络延迟高

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 测试延迟
ping -c 100 www.google.com

# 2. 追踪路由
traceroute www.google.com
mtr www.google.com

# 3. 查看网络错误
netstat -i
cat /proc/net/dev

# 4. 查看 TCP 重传
netstat -s | grep -i retrans
ss -tm

# 5. 抓包分析
tcpdump -i eth0 -w latency.pcap
wireshark latency.pcap

# 6. 解决方案
# - 优化路由
# - 使用 CDN
# - 调整 TCP 参数

场景 3:连接数过多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1. 查看连接数统计
netstat -an | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

# 2. 查看 TIME_WAIT 连接
netstat -an | grep TIME_WAIT | wc -l

# 3. 查看进程的 connection
lsof -n | grep TCP | awk '{print $1}' | sort | uniq -c | sort -rn

# 4. 查看端口使用
ss -tan | awk '{print $5}' | cut -d: -f2 | sort | uniq -c | sort -rn

# 5. 解决方案
# - 调整 TIME_WAIT 超时
# - 启用端口复用
# - 增加文件描述符限制

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
# 调整 TCP 参数
cat >> /etc/sysctl.conf << EOF
# 启用 SYN Cookie
net.ipv4.tcp_syncookies = 1

# 减少 TIME_WAIT 超时
net.ipv4.tcp_fin_timeout = 30

# 启用端口复用
net.ipv4.tcp_tw_reuse = 1

# 增加本地端口范围
net.ipv4.ip_local_port_range = 1024 65535

# 增加连接队列
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# 调整缓冲区
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# 启用 TCP BBR 拥塞控制
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF

sysctl -p

# 增加文件描述符限制
cat >> /etc/security/limits.conf << EOF
* soft nofile 65535
* hard nofile 65535
root soft nofile 65535
root hard nofile 65535
EOF

# 查看当前限制
ulimit -n

# 启用 BBR
echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf
sysctl -p

# 验证 BBR
sysctl net.ipv4.tcp_available_congestion_control
sysctl net.ipv4.tcp_congestion_control

六、综合排查案例

案例 1:Web 服务器响应慢

问题现象: 用户反馈网站访问慢,页面加载时间长。

排查步骤:

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
# 1. 检查系统负载
uptime
# 输出:load average: 8.50, 7.20, 6.80(4 核 CPU,负载过高)

# 2. 查看 CPU 使用率
top -bn1 | grep "Cpu(s)"
# 输出:%Cpu(s): 85.0 us, 10.0 sy, 5.0 id(用户空间占用高)

# 3. 找出占用 CPU 的进程
top -o %CPU -bn1 | head -10
# 发现:java 进程占用 300% CPU

# 4. 查看 Java 进程详情
ps aux | grep java
jps -l

# 5. 查看 Java 线程
top -H -p <java_pid>
# 发现:多个线程占用高

# 6. 生成线程 dump
jstack <java_pid> > thread_dump.txt

# 7. 分析线程 dump
# 发现:大量线程处于 RUNNABLE 状态,执行复杂计算

# 8. 查看 GC 情况
jstat -gc <java_pid> 1000 10

# 9. 解决方案
# - 优化代码算法
# - 增加缓存
# - 水平扩展(增加服务器)

案例 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
# 1. 检查系统资源
vmstat 1 10
# 发现:wa(IO 等待)高达 50%

# 2. 查看磁盘 IO
iostat -x 1 10
# 发现:sda 的 %util 达到 100%,await 达到 100ms

# 3. 找出 IO 高的进程
iotop -o
# 发现:mysqld 进程 IO 高

# 4. 查看 MySQL 慢查询
mysql -e "SHOW PROCESSLIST"
mysql -e "SHOW VARIABLES LIKE 'slow_query_log%'"

# 5. 查看慢查询日志
tail -f /var/log/mysql/slow.log

# 6. 分析慢查询
mysqldumpslow /var/log/mysql/slow.log

# 7. 查看表锁
mysql -e "SHOW OPEN TABLES WHERE In_use > 0"

# 8. 解决方案
# - 优化慢查询 SQL
# - 添加索引
# - 使用 SSD
# - 读写分离

案例 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
# 1. 监控内存使用
watch -n 1 'free -h'

# 2. 查看进程内存
ps aux --sort=-%mem | head -20

# 3. 监控特定进程
watch -n 1 'ps -p <pid> -o pid,rss,vsz,comm'

# 4. 查看内存映射
pmap -x <pid>

# 5. Java 应用:查看堆内存
jmap -heap <pid>
jmap -histo:live <pid> | head -50

# 6. Java 应用:生成 heap dump
jmap -dump:format=b,file=heap.hprof <pid>

# 7. 分析 heap dump
# 使用 MAT (Memory Analyzer Tool) 分析

# 8. 查看 OOM 日志
dmesg | grep -i "out of memory"

# 9. 解决方案
# - 修复内存泄漏代码
# - 增加堆内存限制
# - 定期重启服务

七、监控工具推荐

7.1 系统监控

工具 用途 特点
top/htop 实时监控 简单直观
vmstat 系统统计 轻量级
sar 历史统计 数据持久化
dstat 综合监控 功能全面
glances Web 界面 易于使用

7.2 应用监控

工具 用途 特点
Prometheus 指标监控 云原生标准
Grafana 可视化 丰富的图表
ELK Stack 日志分析 强大的搜索
SkyWalking 链路追踪 APM 功能
Zabbix 企业监控 功能全面

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
25
26
27
28
29
30
31
32
33
34
# docker-compose.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml

grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana

node-exporter:
image: prom/node-exporter
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.ignored-mount-points="^/(sys|proc|dev|host|etc)($$|/)"'

volumes:
grafana_data:

八、总结

Linux 性能排查的关键要点:

  1. 方法论:USE 方法,从利用率、饱和度、错误三个维度分析
  2. 工具链:top/vmstat/iostat/ss 等工具的组合使用
  3. 指标解读:理解每个指标的含义和阈值
  4. 优化方案:根据瓶颈制定针对性的优化方案

记住:监控是基础,排查是技能,优化是目标


参考资料:

Git 常用命令速查:从入门到精通

本文整理了 Git 开发中最常用的命令和技巧,涵盖基础操作、分支管理、远程协作、问题排查等场景,是开发者的速查手册。

一、Git 基础配置

1.1 用户信息配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 全局配置(所有仓库)
git config --global user.name "John Doe"
git config --global user.email "john@example.com"

# 查看配置
git config --list
git config user.name
git config user.email

# 编辑配置文件
git config --global --edit

# 常用别名配置
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.lg "log --oneline --graph --all"

1.2 SSH 密钥配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 生成 SSH 密钥
ssh-keygen -t ed25519 -C "your_email@example.com"

# 查看公钥
cat ~/.ssh/id_ed25519.pub

# 添加到 SSH agent
ssh-add ~/.ssh/id_ed25519

# 测试连接 GitHub
ssh -T git@github.com

# 测试连接 GitLab
ssh -T git@gitlab.com

1.3 常用 Git 配置优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 自动修正命令
git config --global help.autocorrect 1

# 默认使用 rebase
git config --global pull.rebase true

# 显示所有分支的图形化日志
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"

# 忽略文件权限变化
git config --global core.filemode false

# 使用 VSCode 作为默认编辑器
git config --global core.editor "code --wait"

# 使用 Beyond Compare 作为 difftool
git config --global diff.tool bc3
git config --global difftool.bc3.trustexitcode true

# 使用 Beyond Compare 作为 mergetool
git config --global merge.tool bc3
git config --global mergetool.bc3.trustexitcode true

二、仓库操作

2.1 创建仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 初始化新仓库
git init
git init my-project

# 克隆远程仓库
git clone https://github.com/user/repo.git
git clone git@github.com:user/repo.git

# 克隆指定分支
git clone -b develop https://github.com/user/repo.git

# 克隆深度(只克隆最近 N 次提交)
git clone --depth 1 https://github.com/user/repo.git

# 克隆子模块
git clone --recursive https://github.com/user/repo.git

2.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
# 查看仓库状态
git status
git status -s # 简短格式

# 查看提交历史
git log
git log --oneline
git log --graph --oneline --all
git log -n 5 # 最近 5 条

# 查看某文件的修改历史
git log --follow path/to/file

# 查看某人的提交
git log --author="John"

# 查看时间范围内的提交
git log --since="2 weeks ago"
git log --until="2024-01-01"

# 查看统计信息
git log --stat
git log --shortstat
git log --numstat

# 查看某次提交的详细内容
git show commit_id
git show --stat commit_id

2.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
# 工作区 vs 暂存区
git diff

# 暂存区 vs 最新提交
git diff --cached
git diff --staged

# 工作区 vs 最新提交
git diff HEAD

# 两个提交之间的差异
git diff commit1 commit2
git diff commit1..commit2

# 两个分支之间的差异
git diff branch1 branch2

# 只比较某个文件
git diff path/to/file

# 忽略空白字符
git diff -w

# 生成补丁文件
git diff > patch.diff
git diff commit1 commit2 > patch.diff

三、文件操作

3.1 添加文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 添加指定文件
git add file.txt
git add dir/file.txt

# 添加所有文件
git add .
git add -A

# 添加所有修改的文件(不包括新文件)
git add -u

# 交互式添加
git add -p
git add -i

# 查看暂存区内容
git diff --cached

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
39
40
41
42
43
44
45
# 创建 .gitignore 文件
cat > .gitignore << EOF
# 编译产物
*.class
*.o
*.so
build/
dist/
target/

# 依赖目录
node_modules/
vendor/
__pycache__/

# 配置文件
.env
*.local
config.local.js

# IDE 文件
.idea/
.vscode/
*.swp
*.swo

# 系统文件
.DS_Store
Thumbs.db

# 日志文件
*.log
logs/

# 临时文件
tmp/
temp/
*.tmp
EOF

# 查看忽略规则
git check-ignore -v file.txt

# 测试忽略规则
git check-ignore *.log

3.3 删除文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 删除文件(工作区和暂存区)
git rm file.txt

# 删除目录
git rm -r dir/

# 只从暂存区删除(保留工作区文件)
git rm --cached file.txt

# 删除已修改但未暂存的文件
git checkout -- file.txt

# 恢复误删的文件
git checkout HEAD -- file.txt

3.4 移动/重命名文件

1
2
3
4
5
6
7
8
9
# 重命名文件
git mv old_name.txt new_name.txt

# 移动文件
git mv file.txt dir/file.txt

# 重命名后 Git 未识别
git add -A
git status # Git 会自动识别重命名

四、提交操作

4.1 基本提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 提交暂存区
git commit -m "feat: add user authentication"

# 提交并添加描述
git commit -m "feat: add user authentication" -m "Implemented JWT-based authentication"

# 提交所有修改的文件
git commit -am "fix: resolve login bug"

# 修改最后一次提交
git commit --amend
git commit --amend -m "new commit message"

# 提交时跳过预提交钩子
git commit --no-verify

4.2 提交规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Conventional Commits 格式
<type>(<scope>): <subject>

# type 类型
feat: 新功能
fix: 修复 bug
docs: 文档更新
style: 代码格式(不影响功能)
refactor: 重构
test: 测试相关
chore: 构建过程或辅助工具变动

# 示例
feat(auth): add login functionality
fix(api): resolve null pointer exception
docs(readme): update installation guide
style(format): format code with prettier
refactor(core): simplify user service
test(unit): add unit tests for auth module
chore(deps): upgrade dependencies

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
# 撤销工作区修改
git checkout -- file.txt
git restore file.txt # Git 2.23+

# 撤销暂存区修改
git reset HEAD file.txt
git restore --staged file.txt # Git 2.23+

# 撤销最后一次提交(保留修改)
git reset --soft HEAD~1
git reset --soft commit_id

# 撤销提交并丢弃修改
git reset --hard HEAD~1
git reset --hard commit_id

# 撤销远程已推送的提交(危险!)
git reset --hard HEAD~1
git push --force

# 安全撤销远程提交(推荐)
git revert commit_id
git push

4.4 查看提交历史

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 简洁的提交历史
git log --oneline

# 图形化提交历史
git log --graph --oneline --all

# 带统计信息的提交历史
git log --stat

# 搜索提交信息
git log --grep="bug fix"
git log --grep="feat" --oneline

# 查看某文件的提交历史
git log --follow -- path/to/file

# 查看某人的提交
git log --author="John" --oneline

# 查看时间范围内的提交
git log --since="2 weeks ago" --until="yesterday"

五、分支管理

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
# 查看分支
git branch
git branch -a # 所有分支(包括远程)
git branch -v # 显示最后一次提交

# 创建分支
git branch feature/login
git branch feature/login commit_id

# 切换分支
git checkout feature/login
git switch feature/login # Git 2.23+

# 创建并切换分支
git checkout -b feature/login
git switch -c feature/login # Git 2.23+

# 删除分支
git branch -d feature/login # 安全删除(已合并)
git branch -D feature/login # 强制删除(未合并)

# 重命名分支
git branch -m old-name new-name
git branch -m new-name # 当前分支

5.2 分支合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 合并分支到当前分支
git checkout main
git merge feature/login

# 快进合并(不创建合并提交)
git merge --ff-only feature/login

# 禁止快进合并(强制创建合并提交)
git merge --no-ff feature/login

# 合并时压缩提交
git merge --squash feature/login

# 查看合并冲突
git diff --name-only --diff-filter=U
git diff # 查看冲突内容

# 解决冲突后标记为已解决
git add file.txt

# 取消合并
git merge --abort

5.3 分支变基(Rebase)

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
# 变基操作
git checkout feature/login
git rebase main

# 交互式变基
git rebase -i HEAD~3
git rebase -i commit_id

# 变基交互命令
# pick: 使用提交
# reword: 使用提交,修改提交信息
# edit: 使用提交,暂停以便修改
# squash: 使用提交,压缩到上一个提交
# fixup: 使用提交,丢弃提交信息
# drop: 删除提交

# 继续变基
git rebase --continue

# 跳过当前提交
git rebase --skip

# 取消变基
git rebase --abort

# 变基到上游分支
git pull --rebase

5.4 分支管理最佳实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看已合并的分支
git branch --merged

# 查看未合并的分支
git branch --no-merged

# 查看分支的提交关系
git log --oneline --graph --all --decorate

# 清理已合并的本地分支
git branch --merged | grep -v "\*\|main\|develop" | xargs -n 1 git branch -d

# 查看远程跟踪分支
git branch -r

# 删除远程已删除的分支
git fetch -p
git remote prune origin

六、远程协作

6.1 远程仓库操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看远程仓库
git remote
git remote -v

# 添加远程仓库
git remote add origin https://github.com/user/repo.git
git remote add origin git@github.com:user/repo.git

# 修改远程仓库 URL
git remote set-url origin https://github.com/user/new-repo.git

# 删除远程仓库
git remote remove origin

# 重命名远程仓库
git remote rename origin upstream

# 获取远程仓库信息
git remote show origin

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
24
25
26
27
28
29
30
31
# 拉取远程分支
git fetch origin
git fetch origin feature/login

# 拉取并合并
git pull origin main
git pull # 默认当前分支的 upstream

# 拉取并变基
git pull --rebase origin main

# 推送分支
git push origin feature/login
git push # 推送当前分支

# 推送并设置 upstream
git push -u origin feature/login

# 强制推送(危险!)
git push --force
git push --force-with-lease # 更安全

# 推送所有分支
git push --all origin

# 推送所有标签
git push --tags origin

# 删除远程分支
git push origin --delete feature/login
git push origin :feature/login # 旧语法

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
# 查看标签
git tag
git tag -l "v1.*"

# 创建标签
git tag v1.0.0
git tag v1.0.0 commit_id

# 创建带注释的标签
git tag -a v1.0.0 -m "Release version 1.0.0"

# 查看标签信息
git show v1.0.0

# 推送标签
git push origin v1.0.0
git push --tags

# 删除本地标签
git tag -d v1.0.0

# 删除远程标签
git push origin --delete v1.0.0
git push origin :refs/tags/v1.0.0

# 检出标签
git checkout v1.0.0

6.4 Git Flow 工作流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 初始化 Git Flow
git flow init

# 开始新功能
git flow feature start login
git flow feature finish login

# 发布新版本
git flow release start 1.0.0
git flow release finish 1.0.0

# 修复生产 bug
git flow hotstart critical-bug
git flow hotfix finish critical-bug

七、高级技巧

7.1 暂存(Stash)

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
# 暂存当前修改
git stash
git stash save "WIP: login feature"

# 查看暂存列表
git stash list

# 应用暂存(不删除)
git stash apply
git stash apply stash@{1}

# 应用并删除暂存
git stash pop
git stash pop stash@{1}

# 删除暂存
git stash drop
git stash drop stash@{1}

# 清空所有暂存
git stash clear

# 从暂存创建分支
git stash branch feature/login stash@{0}

# 暂存未跟踪的文件
git stash -u
git stash --include-untracked

# 暂存所有文件(包括已删除)
git stash -a
git stash --all

7.2 樱桃拣选(Cherry-pick)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 拣选单个提交
git cherry-pick commit_id

# 拣选多个提交
git cherry-pick commit1 commit2 commit3

# 拣选提交范围
git cherry-pick commit1..commit2

# 拣选时不提交
git cherry-pick --no-commit commit_id

# 拣选时编辑提交信息
git cherry-pick --edit commit_id

# 取消樱桃拣选
git cherry-pick --abort

# 继续樱桃拣选(解决冲突后)
git cherry-pick --continue

7.3 二分查找(Bisect)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 开始二分查找
git bisect start

# 标记当前为坏版本
git bisect bad

# 标记某版本为好版本
git bisect good v1.0.0

# Git 自动切换到中间版本,测试后继续标记
git bisect good # 或 git bisect bad

# 自动二分查找
git bisect run ./test-script.sh

# 结束二分查找
git bisect reset

# 查看二分查找日志
git bisect log

7.4 子模块(Submodule)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 添加子模块
git submodule add https://github.com/user/lib.git libs/lib

# 初始化子模块
git submodule init

# 更新子模块
git submodule update
git submodule update --init --recursive

# 查看子模块状态
git submodule status

# 更新子模块到最新版本
git submodule update --remote

# 删除子模块
git submodule deinit libs/lib
git rm libs/lib
rm -rf .git/modules/libs/lib

7.5 Git Hooks

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
# Git Hooks 目录
ls .git/hooks/

# 常用 Hooks
pre-commit # 提交前检查
pre-push # 推送前检查
commit-msg # 提交信息检查
post-merge # 合并后执行

# 启用 Hook
cp .git/hooks/pre-commit.sample .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

# 示例:pre-commit 检查
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
# 检查是否有调试代码
if git diff --cached | grep -E "console\.log|debugger"; then
echo "Error: Found debug code"
exit 1
fi

# 运行代码检查
npm run lint
EOF

chmod +x .git/hooks/pre-commit

八、问题排查

8.1 查看修改历史

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 谁修改了某行代码
git blame file.txt
git blame -L 10,20 file.txt # 查看特定行

# 查看某次提交引入的修改
git show commit_id

# 查看某文件的修改历史
git log --follow -- path/to/file

# 查找引入 bug 的提交
git bisect start
git bisect bad
git bisect good v1.0.0

8.2 恢复丢失的提交

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看 reflog
git reflog
git reflog show --all

# 恢复丢失的提交
git reset --hard HEAD@{1}
git checkout -b recovery-branch commit_id

# 查看 dangling 提交
git fsck --lost-found

# 恢复 dangling 提交
git cherry-pick commit_id

8.3 解决常见问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 问题:提交信息写错
git commit --amend -m "correct message"

# 问题:漏提交了文件
git add forgotten_file.txt
git commit --amend --no-edit

# 问题:提交到了错误的分支
git reset --hard HEAD~1 # 撤销提交
git checkout correct-branch
git cherry-pick original-branch

# 问题:合并冲突
git mergetool # 使用图形化工具
git diff --name-only --diff-filter=U # 查看冲突文件

# 问题:误删了分支
git reflog
git branch recovered-branch commit_id

# 问题:Git 仓库损坏
git fsck
git gc

8.4 性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 清理不必要的文件
git gc
git gc --aggressive

# 清理远程跟踪分支
git remote prune origin

# 查看仓库大小
git count-objects -v
git du -sh .git/objects/*

# 查找大文件
git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | awk '/^blob/ {print substr($0,6)}' | sort -k2 -nr | head -20

# 从历史中删除大文件
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch path/to/large-file' --prune-empty --tag-name-filter cat -- --all

九、实用脚本

9.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
# 快速创建功能分支
function git-feat() {
git checkout -b "feat/$1"
}

# 快速创建修复分支
function git-fix() {
git checkout -b "fix/$1"
}

# 提交并推送
function git-commit-push() {
git add -A
git commit -m "$1"
git push -u origin $(git branch --show-current)
}

# 同步主分支
function git-sync-main() {
git fetch origin
git rebase origin/main
}

# 清理已合并分支
function git-cleanup() {
git branch --merged | grep -v "\*\|main\|develop" | xargs -n 1 git branch -d
}

# 查看今日提交
function git-today() {
git log --since="today" --author="$(git config user.name)"
}

9.2 Git 配置模板

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
# .gitconfig 模板
[alias]
lg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
st = status -s
co = checkout
br = branch
ci = commit
last = log -1 HEAD
unstage = reset HEAD --
who = blame -L -20,-0

[user]
name = Your Name
email = your.email@example.com

[core]
editor = code --wait
autocrlf = input
filemode = false

[pull]
rebase = true

[push]
default = simple

[merge]
tool = vscode

[difftool "vscode"]
cmd = code --wait --diff $LOCAL $REMOTE

十、总结

Git 是开发者必备的工具,掌握常用命令可以大幅提升开发效率。关键要点:

  1. 基础操作:add、commit、push、pull
  2. 分支管理:branch、checkout、merge、rebase
  3. 问题排查:log、diff、blame、reflog
  4. 高级技巧:stash、cherry-pick、bisect

建议将常用命令配置为别名,并建立适合自己的工作流。


参考资料:

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

阅读全文 »

一、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