个人博客低成本CC防护：抵御85%+常见CC攻击
低成本CC防护：我的实战配置与效果喜报：被大规模CC了！平常的一天，突然受到了来自衣食父母华为云的服务器告警：虚拟机网络连接数突破15400个，CPU占用超过 85% 。就知道肯定是被CC了。果然网站被打是催更技术类博主最好的方式（雾不过我之前是设置过缓存的，有点好奇怎么绕过缓存，事后我抓取了对应时间的Nginx日志access.log：43.152.29.37 - - [30/Oct/2025:23:27:16 +0800] "GET /3bb6bfea9/90a2c9c9fc?limit=ef616a8b1b145df&token=176fe0f01f&utm_source=a5d4f247077b HTTP/1.0" 404 29335 "http://www.nekopara.uk/3bb6bfea9/90a2c9c9fc?limit=ef616a8b1b145df&token=176fe0f01f&utm_source=a5d4f247077b" "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0" "91.209.77.93, 43.152.29.37" 43.152.24.20 - - [30/Oct/2025:23:27:16 +0800] "GET /profile/config/delete?search=9844b7e151&version=504397e6490c&utm_campaign=bd5783ef290&page=4b9e8cc670d HTTP/1.0" 404 29335 "http://www.nekopara.uk/profile/config/delete?search=9844b7e151&version=504397e6490c&utm_campaign=bd5783ef290&page=4b9e8cc670d" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" "138.2.109.39, 43.152.24.20" 43.152.143.43 - - [30/Oct/2025:23:27:16 +0800] "GET /health/admin?utm_medium=eb37025 HTTP/1.0" 404 29335 "http://www.nekopara.uk/health/admin?utm_medium=eb37025" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36" "163.61.245.58, 43.152.143.43" 43.174.55.12 - - [30/Oct/2025:23:27:16 +0800] "GET /5de3c/0727afb/info?type=f06d22885b8 HTTP/1.0" 404 29335 "http://www.nekopara.uk/5de3c/0727afb/info?type=f06d22885b8" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15" "103.184.54.67, 43.174.55.12" 43.152.14.36 - - [30/Oct/2025:23:27:16 +0800] "GET /edit/a97b/cba81414/content.htm?offset=09cd68b44dd60&key=030f41284 HTTP/1.0" 404 29335 "http://www.nekopara.uk/edit/a97b/cba81414/content.htm?offset=09cd68b44dd60&key=030f41284" "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36" "49.145.113.40, 43.152.14.36" 43.152.14.27 - - [30/Oct/2025:23:27:16 +0800] "GET /status/356037c/list/app.json?id=8ccefcb24&key=257c67462c3ef80&token=45b973f&lang=373143c9654e06f HTTP/1.0" 404 29335 "http://www.nekopara.uk/status/356037c/list/app.json?id=8ccefcb24&key=257c67462c3ef80&token=45b973f&lang=373143c9654e06f" "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0" "138.2.109.39, 43.152.14.27" 43.152.24.20 - - [30/Oct/2025:23:27:16 +0800] "GET /upload/view/detail?utm_source=0a511c HTTP/1.0" 404 29335 "http://www.nekopara.uk/upload/view/detail?utm_source=0a511c" "Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Mobile Safari/537.36" "119.93.135.193, 43.152.24.20" 101.33.20.48 - - [30/Oct/2025:23:27:16 +0800] "GET /05ad3/list/view/data.txt?timestamp=7cf23ee09d6dc HTTP/1.0" 404 29335 "http://www.nekopara.uk/05ad3/list/view/data.txt?timestamp=7cf23ee09d6dc" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15" "187.49.87.255, 101.33.20.48" 43.175.30.25 - - [30/Oct/2025:23:27:16 +0800] "GET /profile/4e95d5?limit=b15210f1d9b08&token=63e194b&type=bfea84c37a29f8&nonce=b222a4b1b38a HTTP/1.0" 404 29335 "http://www.nekopara.uk/profile/4e95d5?limit=b15210f1d9b08&token=63e194b&type=bfea84c37a29f8&nonce=b222a4b1b38a" "Mozilla/5.0 (Linux; Android 15; Pixel 9 Pro XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Mobile Safari/537.36" "206.84.201.101, 43.175.30.25" 43.152.29.37 - - [30/Oct/2025:23:27:16 +0800] "GET /8ac4af716/info.php?ref=b09658abd0&page=3d48aaa2614300&page=5b84ad8c3&token=717bad8cac HTTP/1.0" 404 16 "http://www.nekopara.uk/8ac4af716/info.php?ref=b09658abd0&page=3d48aaa2614300&page=5b84ad8c3&token=717bad8cac" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36" "41.139.226.11, 43.152.29.37" 43.152.26.48 - - [30/Oct/2025:23:27:16 +0800] "GET /info?offset=34297ebb3908dec&timestamp=91d8330f0f6330&sort=8d7f7fe923&category=9f7158e6486f01f HTTP/1.0" 404 29335 "http://www.nekopara.uk/info?offset=34297ebb3908dec&timestamp=91d8330f0f6330&sort=8d7f7fe923&category=9f7158e6486f01f" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0" "2.139.62.85, 43.152.26.48" 43.152.143.41 - - [30/Oct/2025:23:27:16 +0800] "GET /6cc1/download?category=248162dd8&token=fc785d9170235a&id=e6c8ef9&limit=b852d3ab3d28 HTTP/1.0" 404 29335 "http://www.nekopara.uk/6cc1/download?category=248162dd8&token=fc785d9170235a&id=e6c8ef9&limit=b852d3ab3d28" "Mozilla/5.0 (iPhone; CPU iPhone OS 18_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1 Mobile/15E148 Safari/604.1" "203.76.222.119, 43.152.143.41" 好家伙，随机请求URL，每一次都不一样，难怪能MISS，CPU直接被干爆了！而且，我还惊奇的发现，记录下来的都是EdgeOne的IP，这也暴露了之前设置，传递来源IP没做好。既然发现了弱点和问题，那我们就来解决它！修复来源IP传递异常首先，这个问题出现在同VPC的跳板服务器上，之前只是添加了请求头，但是没有信任上一级也就是来源于CDN的IP的X-Forwarded-For请求头。并且还重复添加了请求头导致最终源服务器上呈现的是两个不一样的X-Forwarded-For，第一个是真实的IP,第二个是CDN的IP。修复方法很简单，不在跳板服务器添加请求头，并且信任CDN的X-Forwarded-For请求头就好了，直接传递过去。下面是配置文件：server { listen 443 ssl; server_name www.nekopara.uk; set_real_ip_from 0.0.0.0/0; real_ip_header X-Forwarded-For; ssl_certificate /data/certs/nekopara_uk_cf.pem; ssl_certificate_key /data/certs/nekopara_uk_cf.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; location / { client_max_body_size 32m; proxy_pass https://www.nekopara.uk:443; proxy_set_header X-Forwarded-Proto $scheme; }注意！我这里设置信任整个IP段的前提是，我已经在安全组设置了访问白名单，仅CDN的IP可以访问443和80端口，所以这样子设置是安全的。如果你没有设置访问白名单，这样子会有较大的安全风险！你应该在这部分逐个加入CDN的IP进行过滤，并且考虑屏蔽其他IP的请求。能够正常传递来源IP是后面进行设置的基础，如果没有正常传递来源IP，源站的Nginx配置规则会屏蔽掉CDN的访问导致访问全部被拒绝！一定要注意！为源站设置请求限流因为大量这种随机请求的资源消耗是相当可观的，我们需要进行严格的限制。第一次尝试的解决方案我第一次考虑的做法是考虑在Nginx的配置文件中添加limit_req_zone $binary_remote_addr zone=dynamic_limit:10m rate=1r/s;和limit_req zone=dynamic_limit burst=2 nodelay;进行限制，普通用户正常访问不以极高的手速快速刷新页面的话基本上不会被阻止。同时，使用limit_req_zone $binary_remote_addr zone=static_limit:10m rate=50r/s;和limit_req zone=static_limit burst=50 nodelay;对于静态的文件进行独立限流，因为访问网站会产生多个静态资源文件请求，限流规则应该设置得更为宽松，避免误伤正常用户。还有就是，如果设置了限流，默认Nginx会返回503错误代码并显示：503 Service Temporarily Unavailable。我认为不够友好，于是定义将错误返回为429，也就是Too Many Requests，更好的传达实际情况，并且自定义了429界面。请注意，返回的429界面需要尽可能的小和简单，传达清楚意思即可。如果太过于华丽，文件肯定会比较大，会占用较大的服务器上行带宽。以下是详细配置文件：主配置文件nginx.conf:# /etc/nginx/nginx.conf user nginx; # Set number of worker processes automatically based on number of CPU cores. worker_processes auto; # Enables the use of JIT for regular expressions to speed-up their processing. pcre_jit on; # Configures default error logger. error_log /var/log/nginx/error.log warn; # Includes files with directives to load dynamic modules. include /etc/nginx/modules/*.conf; # Include files with config snippets into the root context. include /etc/nginx/conf.d/*.conf; events { # The maximum number of simultaneous connections that can be opened by # a worker process. worker_connections 1024; } http { # 动态页面限流配置（严格） limit_req_zone $binary_remote_addr zone=dynamic_limit:10m rate=1r/s; # 静态资源限流配置（宽松） limit_req_zone $binary_remote_addr zone=static_limit:10m rate=50r/s; # 定义 FastCGI 缓存路径 fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=my_fastcgi_cache:16m inactive=60m; # 定义缓存键，通常包含请求方法、主机名和 URI fastcgi_cache_key "$scheme$request_method$host$request_uri"; # Includes mapping of file name extensions to MIME types of responses # and defines the default type. include /etc/nginx/mime.types; default_type application/octet-stream; # Name servers used to resolve names of upstream servers into addresses. # It's also needed when using tcpsocket and udpsocket in Lua modules. #resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001]; # Don't tell nginx version to the clients. Default is 'on'. server_tokens off; # Specifies the maximum accepted body size of a client request, as # indicated by the request header Content-Length. If the stated content # length is greater than this size, then the client receives the HTTP # error code 413. Set to 0 to disable. Default is '1m'. client_max_body_size 1m; # Sendfile copies data between one FD and other from within the kernel, # which is more efficient than read() + write(). Default is off. sendfile on; # Causes nginx to attempt to send its HTTP response head in one packet, # instead of using partial frames. Default is 'off'. tcp_nopush on; # Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2. # TIP: If you're not obligated to support ancient clients, remove TLSv1.1. ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; # Path of the file with Diffie-Hellman parameters for EDH ciphers. # TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048` #ssl_dhparam /etc/ssl/nginx/dh2048.pem; # Specifies that our cipher suits should be preferred over client ciphers. # Default is 'off'. ssl_prefer_server_ciphers on; # Enables a shared SSL cache with size that can hold around 8000 sessions. # Default is 'none'. ssl_session_cache shared:SSL:2m; # Specifies a time during which a client may reuse the session parameters. # Default is '5m'. ssl_session_timeout 1h; # Disable TLS session tickets (they are insecure). Default is 'on'. ssl_session_tickets off; # Enable gzipping of responses. #gzip on; # Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'. gzip_vary on; # Helper variable for proxying websockets. map $http_upgrade $connection_upgrade { default upgrade; '' close; } # Specifies the main log format. log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; # Sets the path, format, and configuration for a buffered log write. access_log /var/log/nginx/access.log main; # Includes virtual hosts configs. include /etc/nginx/http.d/*.conf; }网站配置文件nekopara.conf:server { listen 443 ssl; server_name nekopara.uk; ssl_certificate /data/certs/nekopara_uk.pem; ssl_certificate_key /data/certs/nekopara_uk.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384'; location / { return 302 https://www.nekopara.uk$request_uri; } } # Typecho HTTPS服务器块 server { listen 443 ssl; #proxy_protocol; server_name www.nekopara.uk; #set_real_ip_from 127.0.0.1; set_real_ip_from 192.168.1.255; real_ip_header X-Forwarded-For; #real_ip_recursive on; #set $real_host $http_x_forwarded_host; #if ($http_x_forwarded_host = '') { # set $real_host $host; #} # 添加这一行，确保生成的重定向不带后端端口 #port_in_redirect off; # 设置最大上传文件大小为 32M client_max_body_size 32M; # SSL证书和密钥路径 ssl_certificate /data/certs/nekopara_uk.pem; ssl_certificate_key /data/certs/nekopara_uk.key; # SSL设置（可选但推荐用于安全） ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # 网站根目录 root /data/typecho; index index.html index.php; # 1. 静态资源限流（宽松，50r/s） location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|eot|svg|mp4|webp|zip|rar|7z|psb)$ { limit_req zone=static_limit burst=50 nodelay; try_files $uri $uri/ =404; add_header Cache-Control "public"; } # PHP处理，并启用FastCGI缓存 location ~ \.php$ { limit_req zone=dynamic_limit burst=2 nodelay; fastcgi_ignore_headers Cache-Control Expires Set-Cookie; try_files $uri =404; fastcgi_pass unix:/var/run/php-fpm82/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; # 启用 FastCGI 缓存 fastcgi_cache my_fastcgi_cache; fastcgi_cache_valid 200 301 302 404 10s; # 设置缓存的有效时间 fastcgi_cache_methods GET HEAD; # 只缓存 GET 和 HEAD 请求 # 不缓存 POST 请求和其他非幂等性请求 set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } # 如果存在特定会话 cookie，则不缓存 if ($http_cookie ~* "PHPSESSID|__typecho_uid|__typecho_authCode") { set $skip_cache 1; } fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; # 添加头部信息显示缓存状态 add_header X-FastCGI-Cache $upstream_cache_status; } # 3. 默认路由（用于Typecho伪静态） location / { try_files $uri $uri/ /index.php?$args; autoindex off; } #自定义503为429 Too Many Requests error_page 503 =429 /429.html; location = /429.html { internal; root /etc/nginx/error_pages; #添加响应头告诉客户端或者爬虫请10秒后重试 add_header Retry-After 10 always; } # Deny access to hidden files location ~ /\.ht { deny all; } }自定义429界面429.html： 喵~别急！429 Too Many Requests 喵呜~刷新太快啦！ 429 Too Many Requests 小喵需要休息10秒~ ฅ^•ﻌ•^ฅ 但是在后续自己模拟单点慢速攻击的时候发现了一个问题，如果是持续不断的请求，即使大部分是429拒绝的相应，仍有夹杂着404的查询相应。虽然单个IP影响不大，但是如果攻击规模较大，仍然会拖垮服务器，并且也会比返回429错误消耗更多的带宽，需要更精准的限制和打击。这是测试用的Python程序：import requests import time import argparse import csv import os import string import random from datetime import datetime def generate_random_path(length=15): """生成指定长度的随机字符串（包含大小写字母和数字）""" characters = string.ascii_letters + string.digits return ''.join(random.choices(characters, k=length)) def main(): parser = argparse.ArgumentParser(description='随机URL请求测试脚本（用于验证服务器处理能力）') parser.add_argument('domain', help='目标域名（例如: example.com）') parser.add_argument('--interval', type=int, default=1000, help='请求间隔（毫秒，默认1000毫秒=1秒）') parser.add_argument('--count', type=int, default=100, help='请求总次数（默认100次）') parser.add_argument('--output', default='random_request_test.csv', help='输出CSV文件名（默认: random_request_test.csv）') parser.add_argument('--timeout', type=int, default=5, help='请求超时时间（秒，默认5秒）') parser.add_argument('--user-agent', default='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', help='自定义User-Agent') args = parser.parse_args() # 验证域名格式 if not args.domain.startswith('http'): args.domain = f"https://{args.domain}" # 创建请求头 headers = { 'User-Agent': args.user_agent, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Connection': 'keep-alive' } # 准备结果列表 results = [] start_time = time.time() print(f"开始随机URL请求测试 (总请求: {args.count}, 间隔: {args.interval}ms, 超时: {args.timeout}s)") print(f"目标域名: {args.domain}") print(f"输出文件: {args.output}\n") # 创建CSV文件 with open(args.output, 'w', newline='', encoding='utf-8') as csvfile: fieldnames = ['timestamp', 'url', 'status', 'response_time_sec', 'content_length_bytes'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() # 执行请求 for i in range(1, args.count + 1): # 生成随机路径 random_path = generate_random_path() url = f"{args.domain}/{random_path}" # 记录请求开始时间 request_start = time.time() try: # 发送请求 response = requests.get( url, headers=headers, timeout=args.timeout ) response_time = time.time() - request_start status = response.status_code content_length = len(response.content) # 记录结果 result = { 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'url': url, 'status': status, 'response_time_sec': response_time, 'content_length_bytes': content_length } results.append(result) # 输出进度 print(f"[{i}/{args.count}] {url} | 状态: {status} | 响应时间: {response_time:.3f}s | 大小: {content_length}B") # 写入CSV writer.writerow(result) except Exception as e: error_msg = str(e) result = { 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'url': url, 'status': 'ERROR', 'response_time_sec': 0, 'content_length_bytes': 0 } results.append(result) print(f"[{i}/{args.count}] {url} | 错误: {error_msg}") # 间隔处理（最后一个请求不等待） if i < args.count: time.sleep(args.interval / 1000.0) # 生成报告 total_time = time.time() - start_time print(f"\n测试完成! 总耗时: {total_time:.2f}秒") print(f"结果已保存至: {args.output}") if __name__ == "__main__": main()这是测试的输出：[vanilla@NekoWorks ~]$ python3 random_request_test.py www.nekopara.uk --interval 50 --count 500 开始随机URL请求测试 (总请求: 500, 间隔: 50ms, 超时: 5s) 目标域名: https://www.nekopara.uk 输出文件: random_request_test.csv [1/500] https://www.nekopara.uk/6pKJpS6oAgTGGg9 | 状态: 404 | 响应时间: 0.430s | 大小: 29335B [2/500] https://www.nekopara.uk/h9utooL2wE8Iu9R | 状态: 404 | 响应时间: 0.373s | 大小: 29335B [3/500] https://www.nekopara.uk/VQr7eBnBMePGezI | 状态: 404 | 响应时间: 0.389s | 大小: 29335B [4/500] https://www.nekopara.uk/r87rqw8ftyrOV4E | 状态: 404 | 响应时间: 0.364s | 大小: 29335B [5/500] https://www.nekopara.uk/V8BBeJqFidADG8a | 状态: 429 | 响应时间: 0.274s | 大小: 266B [6/500] https://www.nekopara.uk/2FModsb5p1imNZY | 状态: 404 | 响应时间: 0.360s | 大小: 29335B [7/500] https://www.nekopara.uk/IGr3v06kf9xbw2L | 状态: 429 | 响应时间: 0.217s | 大小: 266B [8/500] https://www.nekopara.uk/vMUNbNh12WseZ79 | 状态: 429 | 响应时间: 0.265s | 大小: 266B [9/500] https://www.nekopara.uk/1qkNpklBD60HZlS | 状态: 429 | 响应时间: 0.218s | 大小: 266B [10/500] https://www.nekopara.uk/cg5pKmaTfI0pJF0 | 状态: 404 | 响应时间: 0.307s | 大小: 29335B [11/500] https://www.nekopara.uk/xPgKNHa3UTBVzBV | 状态: 429 | 响应时间: 0.217s | 大小: 266B [12/500] https://www.nekopara.uk/HvwRcAMHNb24egX | 状态: 429 | 响应时间: 0.293s | 大小: 266B [13/500] https://www.nekopara.uk/A7f0eWXeO35zUg3 | 状态: 404 | 响应时间: 0.287s | 大小: 29335B [14/500] https://www.nekopara.uk/SdadEt4mQFNUI2P | 状态: 429 | 响应时间: 0.278s | 大小: 266B [15/500] https://www.nekopara.uk/3ZJncTYxzQovjIA | 状态: 429 | 响应时间: 0.227s | 大小: 266B [16/500] https://www.nekopara.uk/gljFhsR4cYYk3aP | 状态: 404 | 响应时间: 0.301s | 大小: 29335B [17/500] https://www.nekopara.uk/CaFhCaIwy98bSRj | 状态: 429 | 响应时间: 0.216s | 大小: 266B [18/500] https://www.nekopara.uk/os3ZwlQY9d9gikA | 状态: 429 | 响应时间: 0.228s | 大小: 266B [19/500] https://www.nekopara.uk/Ai1OU2q1ucI5dRV | 状态: 404 | 响应时间: 0.366s | 大小: 29335B [20/500] https://www.nekopara.uk/HSzYv7RTC1bJBsD | 状态: 429 | 响应时间: 0.214s | 大小: 266B [21/500] https://www.nekopara.uk/QiX7850u9CB29wj | 状态: 429 | 响应时间: 0.218s | 大小: 266B [22/500] https://www.nekopara.uk/mczKcopcmVCSgO0 | 状态: 429 | 响应时间: 0.223s | 大小: 266B [23/500] https://www.nekopara.uk/zbodIK0sYxhY4Yy | 状态: 404 | 响应时间: 0.300s | 大小: 29335B [24/500] https://www.nekopara.uk/B5KkwqUZLWra8Ec | 状态: 429 | 响应时间: 0.235s | 大小: 266B [25/500] https://www.nekopara.uk/uy2IuH3f8gD9hfP | 状态: 429 | 响应时间: 0.210s | 大小: 266B [26/500] https://www.nekopara.uk/lpZdcdLIUEifinU | 状态: 404 | 响应时间: 0.297s | 大小: 29335B可以很明显的发现，每隔1秒就会有一个绕过防护的404请求返回，持续消耗服务器资源。现在只用了Nginx自带的限流功能，能做到的也就这么多了。虽然对于单个恶意IP，成功请求一次404消耗的资源极少，服务器压根就没动静；但是，如果是成百上千个恶意IP进行高强度请求，累计负载将变得相当可观！于是，我想要自己写防御规则，去更加智能的进行限制和防御。进阶：使用自定义规则进行防护这部分我们需要安装Nginx的Lua拓展，用Lua来实现防御的逻辑。apk add nginx-mod-http-lua说点题外话，在尝试进阶方案的时候，我终于深深感受到了国内外大语言模型的技术能力差距。我请教了通义千问，换了几种表述，并且把现有网站配置文件，已经安装的相关软件包都告诉他了，但是它生成的解决方案代码全部报错。试了五次没有一次成功。后面直接找ChatGPT，一次就秒了！看来国内大语言模型还是有待提高啊。实现思路AI只能为你写代码，但是具体的程序逻辑这个最好还是要根据自己博客运营的现实情况自己想防护策略，确保精准打击恶意访问，但是不影响正常访客。我设想的封禁策略是这样的：对于动态响应的请求，这部分一般是博客的页面返回的html，设置限流为单个IP每秒一个请求，对于个人博客来说这已经足以满足正常的访客需求了。并且允许进行两个突发请求，也就是在第一次的一秒内，允许三个请求，来应对第一次页面加载的情况。对于静态文件请求，这部分可以宽松一点，毕竟我实际上看完整加载一次网站静态资源数量会有大约50个，并且静态资源对服务器性能的消耗极少，主要是带宽的消耗。而对于带宽的话在之前的博客已经通过防火墙限制了单个IP能够发起的并发连接数，再加上文件小，基本上不太拉得起来带宽。所以考虑是适当放宽，额外允许50个突发请求，也就是第一秒内允许100个突发请求，足以满足一般个人博客对静态资源的需求。对于随机图片的API，因为后端PHP逻辑简单，代码量少，即使被较高并发轮询也不会消耗太多资源。且一般情况下访问主页，最极端的情况下，也就是文章封面全部是随机封面，且访客不停地往下拉，每秒10个查询请求已经足够，再加上突发再给20个请求，足以应付首页往下拉可以看到20个封面的情况了。剩下的页面需要点加载更多，这就已经需要数秒的时间，此时策略的令牌桶已经补充了10个令牌，足以满足下一步浏览产生的请求。特别的，为了防止前面提到的即使限制了请求速率，持续不断的恶意请求仍然会每秒产生一个404页面消耗资源，我还做出了进一步的限制策略：从访问开始，就会以10秒为单位作为时间窗口观察请求的数量和结果。如果IP请求在这10秒内请求的结果中429错误占到5次及以上，则进行为期10秒的临时封禁，期间任何请求均返回429错误。在封禁的10秒内，如果请求数量仍达到3次及以上，则延长10秒封禁时间，并在新的封禁时间内继续计数，如果请求数量仍达到3次及以上，继续延长封禁时间，以此类推。当最新的10秒观察窗口期内，请求数量降低到2个及以下，则解除封禁，可以继续正常访问。这样的策略，给了正常访客一定的折腾空间，只要封禁前访问请求次数不超限制，基本上就是正常浏览。但是对于恶意请求，一旦触发了封禁机制，就会被一直封禁。因为解除封禁的规则比判定封禁更严格。实现判定封禁的配置文件基于这个逻辑，我向ChatGPT详细描述了我的需求和实现思路，并且附上了现有的Nginx配置文件，以及告诉他目前已经安装的相关软件包和正在使用的发行版版本，询问是否需要补充安装软件包。这样详细的提示词给到ChatGPT，它直接轻松秒杀：修改后的nginx.conf：# /etc/nginx/nginx.conf user nginx; # Set number of worker processes automatically based on number of CPU cores. worker_processes auto; # Enables the use of JIT for regular expressions to speed-up their processing. pcre_jit on; # Configures default error logger. error_log /var/log/nginx/error.log warn; # Includes files with directives to load dynamic modules. include /etc/nginx/modules/*.conf; # Include files with config snippets into the root context. include /etc/nginx/conf.d/*.conf; events { # The maximum number of simultaneous connections that can be opened by # a worker process. worker_connections 1024; } http { lua_shared_dict abuse 10m; lua_shared_dict tb 10m; # 静态资源限流配置（宽松） limit_req_zone $binary_remote_addr zone=static_limit:10m rate=50r/s; # 定义 FastCGI 缓存路径 fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=my_fastcgi_cache:16m inactive=60m; # 定义缓存键，通常包含请求方法、主机名和 URI fastcgi_cache_key "$scheme$request_method$host$request_uri"; # Includes mapping of file name extensions to MIME types of responses # and defines the default type. include /etc/nginx/mime.types; default_type application/octet-stream; # Name servers used to resolve names of upstream servers into addresses. # It's also needed when using tcpsocket and udpsocket in Lua modules. #resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001]; # Don't tell nginx version to the clients. Default is 'on'. server_tokens off; # Specifies the maximum accepted body size of a client request, as # indicated by the request header Content-Length. If the stated content # length is greater than this size, then the client receives the HTTP # error code 413. Set to 0 to disable. Default is '1m'. client_max_body_size 1m; # Sendfile copies data between one FD and other from within the kernel, # which is more efficient than read() + write(). Default is off. sendfile on; # Causes nginx to attempt to send its HTTP response head in one packet, # instead of using partial frames. Default is 'off'. tcp_nopush on; # Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2. # TIP: If you're not obligated to support ancient clients, remove TLSv1.1. ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; # Path of the file with Diffie-Hellman parameters for EDH ciphers. # TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048` #ssl_dhparam /etc/ssl/nginx/dh2048.pem; # Specifies that our cipher suits should be preferred over client ciphers. # Default is 'off'. ssl_prefer_server_ciphers on; # Enables a shared SSL cache with size that can hold around 8000 sessions. # Default is 'none'. ssl_session_cache shared:SSL:2m; # Specifies a time during which a client may reuse the session parameters. # Default is '5m'. ssl_session_timeout 1h; # Disable TLS session tickets (they are insecure). Default is 'on'. ssl_session_tickets off; # Enable gzipping of responses. #gzip on; # Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'. gzip_vary on; # Helper variable for proxying websockets. map $http_upgrade $connection_upgrade { default upgrade; '' close; } # Specifies the main log format. log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; # Sets the path, format, and configuration for a buffered log write. access_log /var/log/nginx/access.log main; # Includes virtual hosts configs. include /etc/nginx/http.d/*.conf; }这里新增了两个共享内存块abuse和tb用于存储相关的请求信息。修改后的nekopara.conf:server { listen 443 ssl; server_name nekopara.uk; ssl_certificate /data/certs/nekopara_uk.pem; ssl_certificate_key /data/certs/nekopara_uk.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384'; location / { return 302 https://www.nekopara.uk$request_uri; } } # Typecho HTTPS服务器块 server { listen 443 ssl; #proxy_protocol; server_name www.nekopara.uk; set_real_ip_from 192.168.1.255; real_ip_header X-Forwarded-For; # 设置最大上传文件大小为 32M client_max_body_size 32M; # SSL证书和密钥路径 ssl_certificate /data/certs/nekopara_uk.pem; ssl_certificate_key /data/certs/nekopara_uk.key; # SSL设置（可选但推荐用于安全） ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # 网站根目录 root /data/typecho; index index.html index.php; # 1. 静态资源限流（宽松，50r/s） location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|eot|svg|mp4|webp|zip|rar|7z|psb)$ { limit_req zone=static_limit burst=50 nodelay; try_files $uri $uri/ =404; add_header Cache-Control "public"; } # PHP处理，并启用FastCGI缓存 location ~ \.php$ { fastcgi_ignore_headers Cache-Control Expires Set-Cookie; try_files $uri =404; fastcgi_pass unix:/var/run/php-fpm82/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; # 启用 FastCGI 缓存 fastcgi_cache my_fastcgi_cache; fastcgi_cache_valid 200 301 302 404 10s; # 设置缓存的有效时间 fastcgi_cache_methods GET HEAD; # 只缓存 GET 和 HEAD 请求 # 不缓存 POST 请求和其他非幂等性请求 set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } # 如果存在特定会话 cookie，则不缓存 if ($http_cookie ~* "PHPSESSID|__typecho_uid|__typecho_authCode") { set $skip_cache 1; } fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; # 添加头部信息显示缓存状态 add_header X-FastCGI-Cache $upstream_cache_status; # 动态页面封禁逻辑规则 access_by_lua_block { local abuse = ngx.shared.abuse -- 计数/封禁存储 local tb = ngx.shared.tb -- token-bucket 存储 local ip = ngx.var.remote_addr or "unknown" local uri = ngx.var.uri or "/" -- 参数（可按需调整） local RATE = 1 -- 平均速率：1 r/s local BURST = 2 -- burst 值（与 nginx 的 burst 对应） local CAPACITY = BURST + 1 -- token 桶容量 (burst + 1) local TRIGGER_WINDOW = 10 -- 触发封禁的观察窗口（秒） local TRIGGER_COUNT = 5 -- 在观察窗口内达到此次数则封禁 local BAN_TTL = 10 -- 初始封禁时长（秒） local BAN_EXT_HIT_WINDOW = 10 -- 封禁期间统计窗口（秒） local BAN_EXT_HIT_COUNT = 3 -- 封禁期间若在最近 BAN_EXT_HIT_WINDOW 秒内请求次数 >= 3 则延长 -- 宽松策略（仅用于特定豁免路径） local PIC_RATE = 10 -- 更高速率，按需调整 local PIC_BURST = 20 -- 更大突发，按需调整 local PIC_CAPACITY = PIC_BURST + 1 -- 哪些 URI 被豁免为“宽松模式”（只对精确入口豁免） local function is_pic_exempt(u) if not u then return false end if u == "/pic_api" or u == "/pic_api/" or u == "/pic_api/index.php" then return true end return false end local pic_exempt = is_pic_exempt(uri) -- helper keys local ban_key = "ban:" .. ip local lim_key = "lim:" .. ip -- 5s 触发计数 key local banhit_key = "banhit:" .. ip -- 封禁期内计数 key -- 如果不是 pic_exempt，先检查封禁（pic_exempt 跳过 ban 检查） if not pic_exempt then local is_banned = abuse:get(ban_key) if is_banned then -- 记录封禁期内的请求计数以便延长封禁 local ok, err = abuse:incr(banhit_key, 1, 0, BAN_EXT_HIT_WINDOW) if not ok then abuse:set(banhit_key, 1, BAN_EXT_HIT_WINDOW) ok = 1 end if ok >= BAN_EXT_HIT_COUNT then abuse:set(ban_key, 1, BAN_TTL) abuse:set(banhit_key, 0, BAN_EXT_HIT_WINDOW) end return ngx.exit(503) end end -- token-bucket 通用实现（根据是否豁免使用不同参数） local now = ngx.now() local tokens_key = "tokens:" .. ip local last_key = "last:" .. ip local tokens = tb:get(tokens_key) local last = tb:get(last_key) if tokens == nil then if pic_exempt then tokens = PIC_CAPACITY else tokens = CAPACITY end end if last == nil then last = now end local delta = now - tonumber(last) if delta < 0 then delta = 0 end local rate = (pic_exempt and PIC_RATE) or RATE local capacity = (pic_exempt and PIC_CAPACITY) or CAPACITY local new_tokens = tonumber(tokens) + delta * rate if new_tokens > capacity then new_tokens = capacity end if new_tokens >= 1 then -- 允许请求，消耗 1 token new_tokens = new_tokens - 1 tb:set(tokens_key, new_tokens, 60) tb:set(last_key, now, 60) return else -- 被限流：如果是 pic_exempt，则我们不走封禁逻辑，只返回 503（表现为 429 页面） if pic_exempt then -- 可选：可以不计入 abuse 触发计数，避免误封 return ngx.exit(503) end -- 严格路径：记录触发计数并在达到阈值时封禁 local cnt, err = abuse:incr(lim_key, 1, 0, TRIGGER_WINDOW) if not cnt then abuse:set(lim_key, 1, TRIGGER_WINDOW) cnt = 1 end if cnt >= TRIGGER_COUNT then abuse:set(ban_key, 1, BAN_TTL) abuse:delete(lim_key) end return ngx.exit(503) end } } # 3. 默认路由（用于Typecho伪静态） location / { try_files $uri $uri/ /index.php?$args; autoindex off; } #自定义503为429 Too Many Requests error_page 503 =429 /429.html; location = /429.html { internal; root /etc/nginx/error_pages; #添加响应头告诉爬虫重试时间间隔 add_header Retry-After 10 always; } # Deny access to hidden files location ~ /\.ht { deny all; } }主要是新增了access_by_lua_block这个Lua脚本块来实现封禁逻辑，注释已经很清楚了，需要的进行按需修改即可。（虽然我不懂Lua，问LLM就完了）测试效果同样的，我们来尝试使用之前的测试脚本进行测试，查看返回的结果：[vanilla@NekoWorks ~]$ python3 random_request_test.py www.nekopara.uk --interval 50 --count 500 开始随机URL请求测试 (总请求: 500, 间隔: 50ms, 超时: 5s) 目标域名: https://www.nekopara.uk 输出文件: random_request_test.csv [1/500] https://www.nekopara.uk/tsq5L3DXmTP3pgt | 状态: 404 | 响应时间: 0.430s | 大小: 29758B [2/500] https://www.nekopara.uk/0imQYTBUKXMaWEe | 状态: 404 | 响应时间: 0.431s | 大小: 29758B [3/500] https://www.nekopara.uk/mb2uMeZihKa6Qx3 | 状态: 404 | 响应时间: 0.369s | 大小: 29758B [4/500] https://www.nekopara.uk/ik6TaHfby3dVnO6 | 状态: 404 | 响应时间: 0.446s | 大小: 29758B [5/500] https://www.nekopara.uk/kOaNyipoAoJp7oz | 状态: 429 | 响应时间: 0.222s | 大小: 266B [6/500] https://www.nekopara.uk/SQvv9uNbBOukoCM | 状态: 404 | 响应时间: 0.302s | 大小: 29758B [7/500] https://www.nekopara.uk/3N0aRXMhpnbn1gP | 状态: 429 | 响应时间: 0.310s | 大小: 266B [8/500] https://www.nekopara.uk/d0G1xWPmmVNqbHw | 状态: 429 | 响应时间: 0.224s | 大小: 266B [9/500] https://www.nekopara.uk/o2IpKDOdVMCD9r2 | 状态: 404 | 响应时间: 0.435s | 大小: 29758B [10/500] https://www.nekopara.uk/EdpSSsWhxNtcQ0E | 状态: 429 | 响应时间: 0.291s | 大小: 266B [11/500] https://www.nekopara.uk/V1GaQNNrt9dz4YP | 状态: 429 | 响应时间: 0.311s | 大小: 266B [12/500] https://www.nekopara.uk/qQiIxZrIBEAtbeB | 状态: 429 | 响应时间: 0.219s | 大小: 266B [13/500] https://www.nekopara.uk/c4jCFSFD5bgFwrw | 状态: 429 | 响应时间: 0.241s | 大小: 266B [14/500] https://www.nekopara.uk/ukHBaBKx9Dn1uIY | 状态: 429 | 响应时间: 0.315s | 大小: 266B [15/500] https://www.nekopara.uk/KcvHSqSHoamkTb9 | 状态: 429 | 响应时间: 0.277s | 大小: 266B [16/500] https://www.nekopara.uk/VQPfGcKjPSSx1mn | 状态: 429 | 响应时间: 0.279s | 大小: 266B [17/500] https://www.nekopara.uk/WCXISBiDEuuEze5 | 状态: 429 | 响应时间: 0.321s | 大小: 266B [18/500] https://www.nekopara.uk/LxsC9KgFCGojRVQ | 状态: 429 | 响应时间: 0.228s | 大小: 266B [19/500] https://www.nekopara.uk/aXwLD0nIDKrEw6z | 状态: 429 | 响应时间: 0.220s | 大小: 266B [20/500] https://www.nekopara.uk/mn5SgwFI1fB3pPL | 状态: 429 | 响应时间: 0.217s | 大小: 266B [21/500] https://www.nekopara.uk/cDtZOUEUDBeIam6 | 状态: 429 | 响应时间: 0.218s | 大小: 266B [22/500] https://www.nekopara.uk/xdikUSlbaqhkQBV | 状态: 429 | 响应时间: 0.227s | 大小: 266B [23/500] https://www.nekopara.uk/fYmwQCdwuUEvb6Q | 状态: 429 | 响应时间: 0.216s | 大小: 266B [24/500] https://www.nekopara.uk/bI0npEBugVcHenG | 状态: 429 | 响应时间: 0.217s | 大小: 266B [25/500] https://www.nekopara.uk/ecUcKf1We2GjNZE | 状态: 429 | 响应时间: 0.218s | 大小: 266B可以看到，一旦触发封禁，后面快速请求的结果都是429，非常的成功！总结与反思经过一番测试和调试，我可以很自信地说：我的博客已经能够有效抵御常见的中小规模 CC 攻击。对于一个纯粹的个人博客而言，这样的防护能力，我认为已经相当不错了。但你要问——这套方案能不能完全无视所有 CC 攻击？ 我必须很遗憾且负责任地告诉你：不行！目前的防御策略，对单 IP 高频、多 IP 高频这类“显眼包”式攻击有不错的拦截效果。但如果是海量 IP 发起的低频、慢速、行为模拟正常的 CC 攻击，那我的服务器大概率会当场暴毙。 毕竟，这类请求和真实用户的访问行为太像了，总不能为了防攻击，把正常访客也一并拒之门外吧 😅所以，如果你真的想用 CC 打垮我的网站，那我也不妨“贴心”地给你“指明方向”，顺便帮你算笔账：🎯 攻击指南（兼成本核算）IP 资源：你需要一个至少 1000+ 独立 IP 的僵尸网络。市面上靠谱的 DDoS 僵尸网络租赁服务，日租成本约 50–100 美元。攻击频率：每个 IP 每秒发起一次请求，或更低频但持续不断，还得随机访问不同 URL，避免触发我的频率封禁规则——别让我在日志里看出破绽。持续时间：想让我网站瘫痪一整天？那你得连续攻击 24 小时不停歇。这意味着：僵尸网络租赁：$100技术维护与调度（别让 IP 被批量封，算是DLC，可选项）：$200+总体成本保守估计：$100–500 美元/天而我这边呢？ 服务器是白嫖的华为云资源，带宽和计算资源基本零成本。你砸几百美元攻击我一天，我关掉网站喝杯咖啡，等你钱烧完，我秒恢复上线。🤔 所以，请问：你真的愿意花大几百美元，只为让一个不盈利、没广告、日均访问量不到百人的个人博客下线几天？这笔钱，够买几十杯星巴克，够报一门编程课，够买台树莓派折腾半年。 相比之下，把它砸在一个“打不赢也赚不到”的小博客上，真的值得吗？因此，对于那些脚本小子、网络喷子、或者闲着无聊想搞点事情的小学生来说——这套防护机制已经足够让你“出师未捷身先死”。而如果你真愿意花大价钱，动用专业资源对我持续攻击…… 那我只能说：承蒙厚爱，荣幸之至 😊
在Manjaro Linux上安装Abaqus 2022本来没什么素材更新文章的，这下因为阴差阳错各种巧合，来素材了（ 刚立flag没几天说是其实在目前阶段我并不需要用这个软件，究其原因，是我朋友的电脑内存不够，他的研究生导师的课题组要用Abaqus这个软件进行有限元分析模拟，不出所料炸内存了。他想起来我有这么一个128G内存的“电脑”（服务器），于是想让我帮忙算一下。好巧不巧，这逼软件有Linux版，那就试试吧！然后试试就逝世，这逼养的古董屎山软件的安装方式，以及各种奇葩的要求和报错属实难绷，挂出来让大家品屎罢！踩坑报错把我搞傻了，国内各种资料网站参考了一遍没能解决，压力ChatGPT解决了，GPT yyds! 记录一下吧，给后面有需要的人指路。理论上这篇教程同样适用于在Arch Linux上安装，应该也可以作为在其他发行版进行安装时的参考。软件获取自己可以找一下，相对而言还算是比较好找的。如果找不到，我这里有：Abaqus_2022_Linux.7z.001 Abaqus_2022_Linux.7z.002 Abaqus_2022_Linux.7z.003 Abaqus_2022_Linux.7z.004 请下载全部压缩分卷后解压，解压密码是：www.nekopara.uk如果你下载我打包的版本，那安装前准备的修改Linux.sh脚本部分可以跳过，已经帮改好了。安装前准备首先，进入安装目录，赋予全部文件可执行权限已经修改权限，不然你会得到报错：Error: This media has been unzipped or copied in such a way that the permissions of some files have been lost. Please unzip or copy the media again.chmod -R 777 DS.SIMULIA.Suite.2022.Linux64/*接着，要修改安装目录里面的每一个Linux.sh文件，强行指定检测的发行版为CentOS或者其他位于支持列表的发行版。这些文件存在于：DS.SIMULIA.Suite.2022.Linux64/1/inst/common/init/Linux.sh （这个好像是主安装程序的，但是实际上主安装程序没屌用，就是套娃然后依此启动3 4 5文件夹里面的安装程序，套娃安装，也是唐完了。）DS.SIMULIA.Suite.2022.Linux64/3/NETVIBES_Exalead_CloudView/Linux64/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/3/Search_Doc/Linux64/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/3/SIMULIA_FLEXnet_LicenseServer/Linux64/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/4/SIMULIA_EstablishedProducts/Linux64/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/5/SIMULIA_Documentation/AllOS/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/5/SIMULIA_EstablishedProducts_CAA_API/Linux64/1/inst/common/init/Linux.shDS.SIMULIA.Suite.2022.Linux64/5/SIMULIA_Isight/Linux64/1/inst/common/init/Linux.sh找到这一行：DSY_OS_Release=`lsb_release --short --id |sed 's/ //g'`修改为：DSY_OS_Release="CentOS"有一说一，这安装包的目录结构太抽象了，真的是唐完了。修改完成后，就可以开始安装了。安装软件我实测下来，是可以不使用root权限执行安装的，只需要安装在一个普通权限可以写入的文件夹里面即可。权限最小化，不轻易给出root权限！而且，直接不经包管理器修改/usr目录是不太好的。如你享受Windows上面那种套娃安装的操蛋感，那请执行:DS.SIMULIA.Suite.2022.Linux64/1/StartGUI.sh。他会一步一步引导你安装，记得勾选许可证服务器安装，用于验证：然后他就先给你吃一口屎：招笑完了，安装还要我一个一个去手动选择文件夹？《请插入 5 的包络体 3，或者指定其目录路径。》什么抽象翻译？接下来让我们手动一个一个安装，就不跟这个主安装程序一起发癫了。安装FLEXnet License Server进入目录：DS.SIMULIA.Suite.2022.Linux64/3/SIMULIA_FLEXnet_LicenseServer/Linux64/1/执行：./StartGUI.sh这里选择一个别的目录，例如我安装在别的硬盘就填别的目录/run/media/chocola/RAID0-2T/SIMULIA/，记得是替换前面这截：/usr/SIMULIA/，而不是这截粘贴上去，不然目录结构会乱。安装目标目录只要有写入权限即可：记得勾上这个：下一步安装就行了。安装Established Products进入目录：DS.SIMULIA.Suite.2022.Linux64/4/SIMULIA_EstablishedProducts/Linux64/1/，操作方法同上，记得更改目录。在安装界面，根据你的需要进行选择，我是直接全选了：选择跳过许可证设置：选择设置启动命令的地方，类似与快捷方式，用于启用程序，本身并不大，我是一起放在安装目录了：然后让你选择外部插件目录，同样的方式修改：下一步，求解器接口根据自己需求配置，我这里默认，下一步。选择工作目录位置，原本默认目录是/usr/temp很明显不太对，让我们放到安装目录下的temp文件夹：下一步，那个配置什么Tosca Fluid接口，我是直接下一步跳过了，然后开始安装。复制许可证在安装包内找到ABAQUSLM__lmgrd__SSQ.lic这个文件，将其复制到软件的这个目录：SIMULIA/License/2022/linux_a64/code/bin/下，我给出的是相对路径，实际路径你自己根据安装的位置匹配。复制完成后，在这个目录下，运行命令启动许可证管理器：./lmgrd -c ABAQUSLM__lmgrd__SSQ.lic然后你会惊喜的发现报错：zsh: 没有那个文件或目录: ./lmgrd然后你发现lmgrd这个逼文件确实是在目录里面，这是怎么回事呢，接下来开始排雷。软件启动排雷安装需要的软件包我们需要安装两个软件包来解决报错和问题，这两个软件包是：patchelf和libjpeg6-turbo：pacman -S patchelf libjpeg6-turbopatchelf用于修补二进制文件，解决无法启动的问题。对应的表现为启动时报错：./lmgrd zsh: 没有那个文件或目录: ./lmgrdlibjpeg6-turbo用于解决Abaqus2022 CAE启动报错：./abq2022 cae /run/media/chocola/RAID0-2T/SIMULIA/EstProducts/2022/linux_a64/code/bin/ABQcaeK: error while loading shared libraries: libjpeg.so.62: cannot open shared object file: No such file or directory Abaqus Error: Abaqus/CAE Kernel exited with an error.修复lmgrd的没有那个文件或目录执行以下命令修补`lmgrd：patchelf --set-interpreter /usr/lib/ld-linux-x86-64.so.2 ./lmgrd然后启动，得到下一个报错：./lmgrd -c ABAQUSLM__lmgrd__SSQ.lic 20:39:47 (lmgrd) ----------------------------------------------- 20:39:47 (lmgrd) Please Note: 20:39:47 (lmgrd) 20:39:47 (lmgrd) This log is intended for debug purposes only. 20:39:47 (lmgrd) In order to capture accurate license 20:39:47 (lmgrd) usage data into an organized repository, 20:39:47 (lmgrd) please enable report logging. Use Flexera's 20:39:47 (lmgrd) software license administration solution, 20:39:47 (lmgrd) FlexNet Manager, to readily gain visibility 20:39:47 (lmgrd) into license usage data and to create 20:39:47 (lmgrd) insightful reports on critical information like 20:39:47 (lmgrd) license availability and usage. FlexNet Manager 20:39:47 (lmgrd) can be fully automated to run these reports on 20:39:47 (lmgrd) schedule and can be used to track license 20:39:47 (lmgrd) servers and usage across a heterogeneous 20:39:47 (lmgrd) network of servers including Windows NT, Linux 20:39:47 (lmgrd) and UNIX. 20:39:47 (lmgrd) 20:39:47 (lmgrd) ----------------------------------------------- 20:39:47 (lmgrd) 20:39:47 (lmgrd) 20:39:47 (lmgrd) Server's System Date and Time: Tue Oct 21 2025 20:39:47 CST 20:39:47 (lmgrd) SLOG: Summary LOG statistics is enabled. 20:39:47 (lmgrd) Can't make directory /usr/tmp/.flexlm, errno: 2(No such file or directory) 20:39:47 (lmgrd) FlexNet Licensing (v11.16.5.1 build 257031 x64_lsb) started on miku-vm (linux) (10/21/2025) 20:39:47 (lmgrd) Copyright (c) 1988-2019 Flexera. All Rights Reserved. 20:39:47 (lmgrd) World Wide Web: http://www.flexerasoftware.com 20:39:47 (lmgrd) License file(s): ABAQUSLM__lmgrd__SSQ.lic 20:39:47 (lmgrd) lmgrd tcp-port 27800 20:39:47 (lmgrd) (@lmgrd-SLOG@) =============================================== 20:39:47 (lmgrd) (@lmgrd-SLOG@) === LMGRD === 20:39:47 (lmgrd) (@lmgrd-SLOG@) Start-Date: Tue Oct 21 2025 20:39:47 CST 20:39:47 (lmgrd) (@lmgrd-SLOG@) PID: 5586 20:39:47 (lmgrd) (@lmgrd-SLOG@) LMGRD Version: v11.16.5.1 build 257031 x64_lsb ( build 257031 (ipv6)) 20:39:47 (lmgrd) (@lmgrd-SLOG@) 20:39:47 (lmgrd) (@lmgrd-SLOG@) === Network Info === 20:39:47 (lmgrd) (@lmgrd-SLOG@) Listening port: 27800 20:39:47 (lmgrd) (@lmgrd-SLOG@) 20:39:47 (lmgrd) (@lmgrd-SLOG@) === Startup Info === 20:39:47 (lmgrd) (@lmgrd-SLOG@) Server Configuration: Single Server 20:39:47 (lmgrd) (@lmgrd-SLOG@) Command-line options used at LS startup: -c ABAQUSLM__lmgrd__SSQ.lic 20:39:47 (lmgrd) (@lmgrd-SLOG@) License file(s) used: ABAQUSLM__lmgrd__SSQ.lic 20:39:47 (lmgrd) (@lmgrd-SLOG@) =============================================== 20:39:47 (lmgrd) Starting vendor daemons ... 20:39:47 (lmgrd) Started ABAQUSLM (internet tcp_port 44829 pid 5591) 20:39:47 (lmgrd) license daemon: execute process failed: (./ABAQUSLM) -T miku-vm 11.16 7 -c :ABAQUSLM__lmgrd__SSQ.lic: 20:39:47 (lmgrd) license daemon: system error code: No such file or directory 20:39:47 (lmgrd) ABAQUSLM exited with status 45 (Child cannot exec requested server) 20:39:47 (lmgrd) Please correct problem and restart daemons解决办法是，用同样的手法，修补ABAQUSLM：patchelf --set-interpreter /usr/lib/ld-linux-x86-64.so.2 ./ABAQUSLM启动后，你又会得到一个船新的报错：./lmgrd -c ABAQUSLM__lmgrd__SSQ.lic 20:42:57 (lmgrd) ----------------------------------------------- 20:42:57 (lmgrd) Please Note: 20:42:57 (lmgrd) 20:42:57 (lmgrd) This log is intended for debug purposes only. 20:42:57 (lmgrd) In order to capture accurate license 20:42:57 (lmgrd) usage data into an organized repository, 20:42:57 (lmgrd) please enable report logging. Use Flexera's 20:42:57 (lmgrd) software license administration solution, 20:42:57 (lmgrd) FlexNet Manager, to readily gain visibility 20:42:57 (lmgrd) into license usage data and to create 20:42:57 (lmgrd) insightful reports on critical information like 20:42:57 (lmgrd) license availability and usage. FlexNet Manager 20:42:57 (lmgrd) can be fully automated to run these reports on 20:42:57 (lmgrd) schedule and can be used to track license 20:42:57 (lmgrd) servers and usage across a heterogeneous 20:42:57 (lmgrd) network of servers including Windows NT, Linux 20:42:57 (lmgrd) and UNIX. 20:42:57 (lmgrd) 20:42:57 (lmgrd) ----------------------------------------------- 20:42:57 (lmgrd) 20:42:57 (lmgrd) 20:42:57 (lmgrd) Server's System Date and Time: Tue Oct 21 2025 20:42:57 CST 20:42:57 (lmgrd) SLOG: Summary LOG statistics is enabled. 20:42:57 (lmgrd) Can't make directory /usr/tmp/.flexlm, errno: 2(No such file or directory) 根据报错，这逼养的程序想要一个不符合规范的临时目录：/usr/tmp/，正常的临时目录应该是/tmp给他就是了：mkdir -p /usr/tmp chmod 1777 /usr/tmp再次启动，这次应该就正常了？然而并卵：20:49:59 (lmgrd) ----------------------------------------------- 20:49:59 (lmgrd) Please Note: 20:49:59 (lmgrd) 20:49:59 (lmgrd) This log is intended for debug purposes only. 20:49:59 (lmgrd) In order to capture accurate license 20:49:59 (lmgrd) usage data into an organized repository, 20:49:59 (lmgrd) please enable report logging. Use Flexera's 20:49:59 (lmgrd) software license administration solution, 20:49:59 (lmgrd) FlexNet Manager, to readily gain visibility 20:49:59 (lmgrd) into license usage data and to create 20:49:59 (lmgrd) insightful reports on critical information like 20:49:59 (lmgrd) license availability and usage. FlexNet Manager 20:49:59 (lmgrd) can be fully automated to run these reports on 20:49:59 (lmgrd) schedule and can be used to track license 20:49:59 (lmgrd) servers and usage across a heterogeneous 20:49:59 (lmgrd) network of servers including Windows NT, Linux 20:49:59 (lmgrd) and UNIX. 20:49:59 (lmgrd) 20:49:59 (lmgrd) ----------------------------------------------- 20:49:59 (lmgrd) 20:49:59 (lmgrd) 20:49:59 (lmgrd) Server's System Date and Time: Tue Oct 21 2025 20:49:59 CST 20:49:59 (lmgrd) SLOG: Summary LOG statistics is enabled. 20:50:14 (lmgrd) The TCP port number in the license, 27800, is already in use. 20:50:14 (lmgrd) Possible causes: 20:50:14 (lmgrd) 1) The license server manager (lmgrd) is already running for this license. 20:50:14 (lmgrd) 2) The OS has not "cleared" this port since lmgrd died. 20:50:14 (lmgrd) 3) Another process is using this port number (unlikely). 20:50:14 (lmgrd) Solutions: 20:50:14 (lmgrd) 1) Make sure lmgrd and all vendor daemons for this 20:50:14 (lmgrd) license are not running. 20:50:14 (lmgrd) 2) You may have to wait for the OS to clear this port. 20:50:14 (lmgrd) Retrying for about 5 more minutes 20:50:32 (lmgrd) Still trying... 我们要来释放被占用的端口，这时观察进程列表，发现残留了多个lmgrd，通通干掉，然后再尝试启动：./lmgrd -c ABAQUSLM__lmgrd__SSQ.lic命令行出现下面的内容，就说明成功启动了：20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) =============================================== 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) === Vendor Daemon === 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Vendor daemon: ABAQUSLM 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Start-Date: Mon Oct 20 2025 20:31:06 CST 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) PID: 3409793 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) VD Version: v11.16.5.1 build 257031 x64_lsb ( build 257031 (ipv6)) 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) === Startup/Restart Info === 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Options file used: None 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Is vendor daemon a CVD: No 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Is FlexNet Licensing Service installed and compatible: No 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) FlexNet Licensing Service Version: -NA- 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Is TS accessed: No 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) TS access time: -NA- 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Number of VD restarts since LS startup: 0 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) === Network Info === 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Listening port: 54131 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Daemon select timeout (in seconds): 1 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) === Host Info === 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Host used in license file: chocola-sa5212m4 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) HostID node-locked in license file: ID=20170101 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) HostID of the License Server: "6c92bf6bb2d1 6c92bf6bb2d0" 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) Running on Hypervisor: Not determined - treat as Physical 20:31:06 (ABAQUSLM) (@ABAQUSLM-SLOG@) ===============================================软件许可证设置如果不进行这步，直接打开Be Like：./abq2022 cae Failed to startup licensing (err01): 1A000058 ERROR 1A000058: License servers configuration file not found 13:14:57.UTC DSLS server status: 0 license server declared in file /var/DassaultSystemes/Licenses/DSLicSrv.txt File not found. Abaqus Error: Abaqus/CAE Kernel exited with an error.配置方法并不难，找到安装目录的这个位置：SIMULIA/EstProducts/2022/linux_a64/SMA/site/的custom_v6.env文件。注释掉importEnv('licensing.env')，然后加上这两行：license_server_type=FLEXNET abaquslm_license_file="27800@localhost"我给出一个示例方便理解。示例原文件内容：# Installation of Established Products 2022 # Tue Oct 21 21:09:02 2025 plugin_central_dir="/run/media/chocola/RAID0-2T/SIMULIA/CAE/plugins/2022" # retrieve licensing configuration from EstablishedProductsConfig.ini importEnv('licensing.env')示例修改后的内容:# Installation of Established Products 2022 # Mon Oct 20 18:27:00 2025 plugin_central_dir="/run/media/chocola/RAID0-2T/SIMULIA/CAE/plugins/2022" # retrieve licensing configuration from EstablishedProductsConfig.ini license_server_type=FLEXNET abaquslm_license_file="27800@localhost" #importEnv('licensing.env')这时，你应该就可以进入SIMULIA/Commands/目录，启动Abaqus了：./abq2022 cae如果到这里你报错libjpeg.so.62缺失，请返回这个步骤的开头安装libjpeg6-turbo软件包。不出意外的话，你会看到这样一个界面：这就说明你的安装成功了，吗？然而并没有，如果你使用这个软件进行模拟分析，添加自己的分析程序，你会发现无法进行！原因很简单，没有fortran环境！部署Abaqus需要的Fortran环境当发生报错的时候，我整个人是蒙逼的，没有任何技术提示，只是说终止了。然后我才意识到在启动的位置有log文件，也就是在SIMULIA/Commands/目录里面，因为在这里启动的，算是工作目录，打开一看：Analysis initiated from SIMULIA established products Abaqus JOB C3D20_Uel_2 Abaqus 2022 Abaqus License Manager checked out the following licenses: Abaqus/Standard checked out 5 tokens from Flexnet server localhost. <9994 out of 9999 licenses remain available>. Begin Compiling Abaqus/Standard User Subroutines 2025年10月20日 星期一 20时50分43秒 sh: 行 1: ifort: 未找到命令 Abaqus Error: Problem during compilation - /run/media/chocola/Rin/abq_job/C3D20.f Abaqus/Analysis exited with errors问题一目了然：ifort: 未找到命令。安装Intel oneAPI HPC Toolkit上网查阅资料得知，ifort是牙膏厂的Intel oneAPI HPC Toolkit的其中一个工具，所以我们需要去牙膏厂官网下载并安装这个工具，下载入口Bing搜一下就有了。下载回来的安装包是一个2.2GB的.sh文件，属实逆天。下载回来后，执行安装，不需要root权限：chmod +x intel-oneapi-base-toolkit-2025.0.1.46_offline.sh ./intel-oneapi-base-toolkit-2025.0.1.46_offline.sh安装后会占用13.3GB空间，要找一个空间较大的地方安装。安装完成后，进入安装目录，找到setvars.sh文件，然后在目录执行：source ./setvars.sh执行完成后，查看环境设置是否正常:ifx -help如果有输出内容，就没问题了。但是这时候会有一个问题，Abaqus的命令是ifort，而新版的oneAPI HPC Toolkit已经变成ifx了，怎么办呢？如果你经常用Linux，那你已经有答案了——软链接进入安装目录下的oneapi/compiler/2025.0/bin/目录，找到ifx文件，然后：ln -s ./ifx ifort这下就不会ifort: 未找到命令了。带上Fortran环境启动Abaqus在oneAPI的目录使用source ./setvars.sh临时导入环境到终端后，切换到Abaqus的安装目录启动Abaqus即可：source ./setvars.sh cd /run/media/chocola/RAID0-2T/SIMULIA/Commands ./abq2022 cae 不出意外的话，这次就真能运行了：
绕过黑洞！利用VPC内网互通低成本实现DDoS攻击下网站高可用
VPC内网互通：让被黑洞的服务器仍能访问事情起因很简单，前段时间我的华为云服务器又被闲的蛋疼的人刷了DDoS礼物，也是成功地触发了EIP（弹性公网IP）黑洞封堵。喜报如下：尊敬的(Rin):感谢您使用华为云产品，非常抱歉打扰您。在北京时间2025/09/23 10:26，华为云监测到您的弹性公网IP(EIP)实例 发生EIP封堵事件。 详情：节点：亚太-新加坡实例名称：119.8.185.128实例ID：b002926d-60de-4951-a9e9-b337e3a550d8告警描述：{"reason_zh": 尊敬的Rin：华为云DDoS防护服务在2025-09-23 10:26:56检测到您的EIP 119.8.185.128 异常流量已超过DDoS防护峰值触发封堵，封堵时带宽为 79 Gbps, 您服务器的访问将受到限制，24小时后系统将自动为您解封。您可登陆官网购买DDoS高防服务提升防护能力，保障服务器稳定运行。, "reason_en": Dear Rin: HUAWEI CLOUD Anti-DDoS Service detects that your EIP 119.8.185.128 abnormal traffic has exceeded the DDoS protection peak at 2025-09-23 10:26:56 and triggers blocking. Bandwidth when blocking is 79 Gbps, Access to your server will be restricted, and the system will automatically unblock it for you after 24 hours. You can log in to the official website of HUAWEI CLOUD to purchase Advanced Anti-DDoS service to enhance protection capabilities and ensure stable server operation.}原因分析：尊敬的Rin：华为云DDoS防护服务在2025-09-23 10:26:56检测到您的EIP 119.8.185.128 异常流量已超过DDoS防护峰值触发封堵，封堵时带宽为 79 Gbps您服务器的访问将受到限制，24小时后系统将自动为您解封。您可登陆官网购买DDoS高防服务提升防护能力，保障服务器稳定运行。如有疑问，您可以登录华为云官网提交工单咨询，会有相应工程师为您核实查看。IP我就懒得打码了，反正就是这个网站域名平时解析的IP，有什么好打码的，ping一下就能看到了。我在之前的文章，就有说过一套抗DDoS的方案，并且已经付诸实践了（详见文章：《当网站遭遇DDoS：如何优雅地应对云服务器被封堵》）。很显然，并没有太大影响，在网站主服务器被打黑洞的三分钟后，脚本就自动切换服务器到备用服务器并且套上了Cloudflare的代理，无需我干预，唯一的不足就是在被DDoS期间网站访问变慢了。但是，当时我就有说过，那套方案是有缺陷的，也就是因为网站数据同步是单向的，在被攻击期间交互产生的数据，后面会被覆盖掉。好巧不巧，华为云又送钱了，我寻思着我之前开的华为云服务器已经续费十年多了，不如开个新的云服务器，搞VPC内网互通，彻底解决上述的痛点问题。这里说个题外话，我发现新开的华为云 Flexus L 实例相比于之前已经大幅缩水，网络回国优化没了，而且CPU性能只有原来的三分之一了，已经变得跟阿里云的轻量服务器一个水平了，而且价格还更高。除非你和我一样是白嫖的，否则真金白银买我是不推荐，因为差不多一样的东西，阿里云那边要大概便宜个10%-20%，买华为云真不划算了。现在华为云的 Flexus L 实例算是把华为手机的高价低配学过来了，难绷。但是老实例性能和网络那还是相当不错的。资源准备为了实现这个方案，你需要准备两台云服务器，并且两台服务器必须在同一个地域，以便进行VPC内网互通。虽然同一个地域就足够，但将两台服务器放在同一个可用区会获得更好的内网延迟（通常<1ms），而跨可用区的内网延迟可能会稍高（但通常也在1-5ms范围内）。这就是最重要的前提要求，日常访问的可以是两台服务器的任意一台，只需要它的性能可以满足业务要求。对于另一台服务器，那是作为网站流量跳板服务器，最低配即可，方便节约成本，因为只需要在上面运行一个代理流量程序，在主实例EIP被封堵时代理接管流量入口。因为本方案抗DDoS采用的是Cloudflare的CDN，所以跳板实例的网络可以差一点，只需要保证到Cloudflare的回源服务器线路通畅即可，国内访问绕路都不是事。平时流量也不会从这边走，只是作为保障用途应急使用。网络配置一般情况下，云服务器在同一个地域会默认分配到同一个VPC实现内网互通，如果没有互通，修改两个云服务器的VPC为同一个VPC即可，或者使用VPC对等连接（VPC Peering），详情可以查阅各类云的参考文档，或者直接去发工单骚扰客服。当其中一个服务器ping另一个服务器的内网IP地址有反应，那就说明内网互通成功了，可以进行下一步工作。就像这样：主服务器:NyankoHost [~]# ping 192.168.1.255 PING 192.168.1.255 (192.168.1.255): 56 data bytes 64 bytes from 192.168.1.255: seq=0 ttl=64 time=1.152 ms 64 bytes from 192.168.1.255: seq=1 ttl=64 time=0.950 ms 64 bytes from 192.168.1.255: seq=2 ttl=64 time=0.932 ms 64 bytes from 192.168.1.255: seq=3 ttl=64 time=0.879 ms 64 bytes from 192.168.1.255: seq=4 ttl=64 time=0.899 ms 64 bytes from 192.168.1.255: seq=5 ttl=64 time=0.891 ms 64 bytes from 192.168.1.255: seq=6 ttl=64 time=0.885 ms 64 bytes from 192.168.1.255: seq=7 ttl=64 time=0.878 ms ^C --- 192.168.1.255 ping statistics --- 8 packets transmitted, 8 packets received, 0% packet loss round-trip min/avg/max = 0.878/0.933/1.152 ms代理服务器：MewHost:~# ping 192.168.13.229 PING 192.168.13.229 (192.168.13.229): 56 data bytes 64 bytes from 192.168.13.229: seq=0 ttl=64 time=1.051 ms 64 bytes from 192.168.13.229: seq=1 ttl=64 time=1.032 ms 64 bytes from 192.168.13.229: seq=2 ttl=64 time=1.006 ms 64 bytes from 192.168.13.229: seq=3 ttl=64 time=0.978 ms 64 bytes from 192.168.13.229: seq=4 ttl=64 time=0.988 ms 64 bytes from 192.168.13.229: seq=5 ttl=64 time=0.978 ms 64 bytes from 192.168.13.229: seq=6 ttl=64 time=0.980 ms 64 bytes from 192.168.13.229: seq=7 ttl=64 time=0.999 ms ^C --- 192.168.13.229 ping statistics --- 8 packets transmitted, 8 packets received, 0% packet loss round-trip min/avg/max = 0.978/1.001/1.051 ms设置代理服务器代理服务器Nginx配置文件设置建议使用一个轻量化的Linux发行版来节省资源，例如Alpine，本教程就是基于Alpine Linux 3.20部署的，然后安装上Nginx。编辑Nginx的默认站点配置文件/etc/nginx/http.d/default.conf，修改如下：server { listen 80 default_server; #listen [::]:443 ssl default_server; server_name _; # 这是一个通配符，表示任何未匹配的主机名 #return 444; # 关闭连接，不发送任何响应 root /var/www/; # 静态文件的位置 index index.html; # 默认主页 location / { try_files $uri $uri/ =404; # 尝试提供请求的文件或目录，如果不存在则返回404 } # 处理错误页面 error_page 404 /404.html; location = /404.html { internal; } } server { listen 443 ssl default_server; #listen [::]:443 ssl default_server; server_name _; # 这是一个通配符，表示任何未匹配的主机名 ssl_certificate /etc/nginx/ssl/self_cert.pem; ssl_certificate_key /etc/nginx/ssl/self_cert.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384'; root /var/www/; # 静态文件的位置 index index.html; # 默认主页 location / { try_files $uri $uri/ =404; # 尝试提供请求的文件或目录，如果不存在则返回404 } # 处理错误页面 error_page 404 /404.html; location = /404.html { internal; } }这里的配置文件是匹配默认站点，对于https则采用自签名证书匹配后返回默认页面，防止被证书扫描暴露备用服务器IP导致一起被打。至于怎么安装Nginx，怎么启动，怎么生成自签名证书，这些我之前的文章说过很多次了，不会的可以翻一下我的博客文章，例如《实战：帮群友迁移Typecho博客并解决数据库问题》，这里不再赘述。接下来配置代理的配置文件/etc/nginx/http.d/proxy_nekopara_uk.conf：server { listen 443 ssl; server_name www.nekopara.uk; ssl_certificate /data/certs/nekopara_uk_cf.pem; ssl_certificate_key /data/certs/nekopara_uk_cf.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; location / { client_max_body_size 32m; proxy_pass https://www.nekopara.uk:443; # 必要的代理头，保留原始 Host（按需） proxy_set_header Host $host; # 或改成 $proxy_host / example.com proxy_set_header X-Real-IP $remote_addr; # 把客户端 IP 放入 X-Real-IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }采用的证书建议是使用Cloudflare生成的源服务器证书，因为可以设置15年有效期，有效防止证书忘记更新的情况。这里别忘了，要把代理服务器的hosts文件改一下，因为如果在内网直接配置IP回源的话，会被自己匹配到默认站点而不是网站，你也看到了我配置文件回源的配置是proxy_pass https://www.nekopara.uk:443;，所以你需要修改/etc/hosts文件，加上以下内容：192.168.13.229 nekopara.uk www.nekopara.uk其中，192.168.13.229是主服务器的IP地址，后面则是匹配的域名，这样子反代后的流量走到主服务器就不会匹配默认站点了。代理服务器安全组设置（可选）这部分属于安全加固的内容，为可选项，建议配置。为了最大程度保护用于代理的备用服务器不被扫描暴露，我们需要配置安全组白名单，在80和443端口只放行来自Cloudflare IP的流量。Cloudflare的IP地址段可以在IP 地址范围 | Cloudflare查询到，包括以下IP段：173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22在云服务器的安全组里面配置一个 Cloudflare IP 地址组，添加到80和443端口放行规则里面，只放行该地址组的IP，以及必须的端口，如22，最小化攻击面。设置IP地址组，添加Cloudflare IP：创建新的安全组，并设置80和443放行的源地址为 Cloudflare IP 地址组：切换备用服务器的安全组为新创建的安全组：这样，就保证了备用服务器不会被轻易扫描出来，除了Cloudflare的IP，在别的IP地址访问下，这个服务器看起来压根就没运行网站。其实如果可以，连22端口的规则都可以删了，反正可以内网直接连接，也省得22端口每天都在被高强度爆操。主服务器配置修改你主服务器网站的Nginx配置文件，在服务器块的前面部分加上下面的内容：set_real_ip_from 192.168.1.255; real_ip_header X-Forwarded-For;这是信任内网的服务器提供的远端IP地址，避免在网站的访问日志里面看到访问的IP全是代理服务器的内网IP。完整的配置示例文件如下：# Typecho HTTPS服务器块 server { listen 443 ssl; server_name www.nekopara.uk; #信任并设置代理服务器提供的远端IP set_real_ip_from 192.168.1.255; real_ip_header X-Forwarded-For; # 设置最大上传文件大小为 32M client_max_body_size 32M; # SSL证书和密钥路径 ssl_certificate /data/certs/nekopara_uk.pem; ssl_certificate_key /data/certs/nekopara_uk.key; # SSL设置（可选但推荐用于安全） ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # 网站根目录 root /data/typecho; index index.html index.php; # Serve static files directly location / { try_files $uri $uri/ /index.php?$args; autoindex off; } # PHP处理，并启用FastCGI缓存 location ~ \.php$ { fastcgi_ignore_headers Cache-Control Expires Set-Cookie; try_files $uri =404; fastcgi_pass unix:/var/run/php-fpm82/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; # 启用 FastCGI 缓存 fastcgi_cache my_fastcgi_cache; fastcgi_cache_valid 200 301 302 404 10s; # 设置缓存的有效时间 fastcgi_cache_methods GET HEAD; # 只缓存 GET 和 HEAD 请求 # 不缓存 POST 请求和其他非幂等性请求 set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } # 如果存在特定会话 cookie，则不缓存 if ($http_cookie ~* "PHPSESSID|__typecho_uid|__typecho_authCode") { set $skip_cache 1; } fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; # 添加头部信息显示缓存状态 add_header X-FastCGI-Cache $upstream_cache_status; } # Deny access to hidden files location ~ /\.ht { deny all; } }验证代理情况下真实IP传递效果我采用面向LLM编程写了下面的测试代码，用于展示客户端访问IP的情况，保存为ip.php放到网站目录访问即可看到效果：<?php // 获取客户端IP地址 $ip = $_SERVER['REMOTE_ADDR']; $ip2 = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '未通过代理/CDN'; // 显示IP地址（带简单HTML格式） echo " 您的IP地址信息 "; // 显示REMOTE_ADDR（直接连接IP） echo " "; echo "REMOTE_ADDR: " . htmlspecialchars($ip) . " "; echo "通常是直接连接服务器的IP（可能是代理或真实IP）"; echo " "; // 显示HTTP_X_FORWARDED_FOR（原始客户端IP） echo " "; echo "HTTP_X_FORWARDED_FOR: " . htmlspecialchars($ip2) . " "; echo "通常表示原始客户端IP（仅当使用代理/CDN时存在）"; echo " "; // 添加说明 echo " "; echo "说明： "; echo "• 如果您未使用代理或CDN，HTTP_X_FORWARDED_FOR 可能为空或显示'未通过代理/CDN' "; echo "• 如果您使用了CDN（如Cloudflare）、代理或负载均衡，HTTP_X_FORWARDED_FOR 通常包含您真实的IP地址 "; echo "• REMOTE_ADDR 通常是与服务器直接建立连接的设备IP"; echo " "; ?>通过主服务器IP直接访问会在REMOTE_ADDR显示客户端真实的IP地址，HTTP_X_FORWARDED_FOR显示未通过代理/CDN；通过代理服务器访问则REMOTE_ADDR显示客户端真实的IP地址，HTTP_X_FORWARDED_FOR也显示客户端真实的IP地址。这才是配置正确的情况。这里有一个小技巧，如果需要测试代理的可访问性，不需要配置DNS，这样反而会暴露备用服务器的IP地址，可以编辑自己电脑的hosts文件，指定主机名对应的IP为代理服务器IP进行测试即可。而配置错误的情况下，REMOTE_ADDR或HTTP_X_FORWARDED_FOR会显示VPC内另一个服务器的内网IP地址，这就需要重新配置，毕竟会影响网站的访问记录。直接访问的情况：通过代理访问的情况：配置错误的情况：注意⚠️：测试完成后请删除ip.php文件，避免服务器信息泄漏。配置自动切换脚本我们需要在备用服务器上面运行一个监测脚本监测主服务器状态，当主服务器被黑洞时可以更改Cloudflare DNS配置让流量通过Cloudflare代理后回源，避免访问中断且保护备用服务器。这部分就和之前那篇文章是一样的了，基本上没有什么变化：#!/bin/bash # 🐱 Cloudflare API Token CF_API_TOKEN="neko????????????????????????????????para" # 🐱 域名，例如 nekopara.uk CF_ZONE_NAME="nekopara.uk" # 🐱 记录名称列表，例如 "nekopara.uk www.nekopara.uk" CF_RECORD_NAMES=(nekopara.uk www.nekopara.uk) # 🐱 目标 IP TARGET_IP="119.8.185.128" # 🐱 目标端口 TARGET_PORT="80" # 🐱 备用 IP BACKUP_IP="172.16.1.233" # 🐱 获取 Zone ID get_zone_id() { curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${CF_ZONE_NAME}" \ -H "Authorization: Bearer ${CF_API_TOKEN}" \ -H "Content-Type: application/json" | jq -r '.result[0].id' } # 🐱 获取 DNS 记录 ID get_record_id() { local zone_id="$1" local record_name="$2" curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records?type=A&name=${record_name}" \ -H "Authorization: Bearer ${CF_API_TOKEN}" \ -H "Content-Type: application/json" | jq -r '.result[0].id' } # 🐱 更新 DNS 记录 update_dns_record() { local zone_id="$1" local record_id="$2" local record_name="$3" local ip="$4" local proxied="$5" curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}" \ -H "Authorization: Bearer ${CF_API_TOKEN}" \ -H "Content-Type: application/json" \ --data "{\"type\":\"A\",\"name\":\"${record_name}\",\"content\":\"${ip}\",\"ttl\":1,\"proxied\":${proxied}}" | jq } # 🐱 检测目标 IP 的 HTTP 状态码 check_target() { curl -s --connect-timeout 10 "http://${TARGET_IP}:${TARGET_PORT}" -o /dev/null return $? } # 🐱 初始化 zone_id=$(get_zone_id) declare -A record_ids for record_name in "${CF_RECORD_NAMES[@]}"; do record_ids["$record_name"]=$(get_record_id "$zone_id" "$record_name") done current_ip="$TARGET_IP" proxied=false failure_count=0 success_count=0 # 🐱 监控循环 while true; do if check_target; then echo "🐾 目标 IP ${TARGET_IP} 响应正常" failure_count=0 success_count=$((success_count + 1)) if [ "$current_ip" != "$TARGET_IP" ] && [ "$success_count" -ge 3 ]; then echo "🐾 切换回原始 IP ${TARGET_IP}，关闭代理" for record_name in "${CF_RECORD_NAMES[@]}"; do update_dns_record "$zone_id" "${record_ids[$record_name]}" "$record_name" "$TARGET_IP" false done current_ip="$TARGET_IP" proxied=false success_count=0 fi else echo "🐾 目标 IP ${TARGET_IP} 无响应" failure_count=$((failure_count + 1)) success_count=0 if [ "$current_ip" != "$BACKUP_IP" ] && [ "$failure_count" -ge 3 ]; then echo "🐾 切换到备用 IP ${BACKUP_IP}，开启代理" for record_name in "${CF_RECORD_NAMES[@]}"; do update_dns_record "$zone_id" "${record_ids[$record_name]}" "$record_name" "$BACKUP_IP" true done current_ip="$BACKUP_IP" proxied=true failure_count=0 fi fi sleep 60 done详细的配置方法，请参考《当网站遭遇DDoS：如何优雅地应对云服务器被封堵》，这里不再赘述。小尾巴最后还有一点别忘了，那就是将VPC内网IP加入iptables访问白名单，如果你按照我之前的文章设置过单IP访问连接数限制的话。在规则列表前面添加白名单：iptables -I INPUT 1 -p tcp -s 192.168.0.0/16 --dport 80 -j ACCEPT iptables -I INPUT 2 -p tcp -s 192.168.0.0/16 --dport 443 -j ACCEPT如果不这么做，当流量变大时，VPC内网的代理服务器的请求就会全部被拦截，导致访问失败，跟网站挂掉没什么两样了。（我 防 我 自 己）总结这样以来，我们就成功构建了一个在平时能够快速访问，而在被DDoS时又不会彻底挂掉的网站，用较低的成本平衡了访问速度和抗DDoS能力。在平时直接走服务器的IP提升访问速度，而在被DDoS时，通过Cloudflare代理保护源服务器。其工作的逻辑和过程大概如下：当被DDoS攻击时：主服务器EIP被打黑洞无法访问 > 备用服务器监测到主服务器无法访问 > 切换为Cloudflare代理流量，并更改DNS解析记录到备用服务器的EIP > 网站继续可访问，但是速度变慢当DDoS停止，主服务器EIP解封：备用服务器监测到主服务器正常访问 > 关闭Cloudflare代理流量，并更改DNS解析记录到主服务器的EIP > 网站恢复访问速度后记后续我发现这个方案有一个需要完善的地方，那就是——对于内网的VPC流量，没有必要走HTTPS，这会造成额外的性能损耗，最合适的方式是使用HTTP进行传输。这时，对应代理服务器的反代配置中的proxy_pass https://www.nekopara.uk:443;则需改成proxy_pass http://www.nekopara.uk:8086;，我建议是通过非标准端口进行HTTP回源，避免冲突。另外，记得不要在安全组放行这个端口给外部访问。而对于主服务器的Nginx配置文件，则增加多一个服务器块，配置可以直接复制，删掉ssl相关的内容，更改端口，设置传递参数，让PHP后端认为端口是443：# Typecho HTTP 服务器块 用于内网传输 server { listen 8086; server_name www.nekopara.uk; #信任并设置代理服务器提供的远端IP set_real_ip_from 192.168.1.255; real_ip_header X-Forwarded-For; # 设置最大上传文件大小为 32M client_max_body_size 32M; # 网站根目录 root /data/typecho; index index.html index.php; # Serve static files directly location / { try_files $uri $uri/ /index.php?$args; autoindex off; } # PHP处理，并启用FastCGI缓存 location ~ \.php$ { fastcgi_ignore_headers Cache-Control Expires Set-Cookie; try_files $uri =404; fastcgi_pass unix:/var/run/php-fpm82/php8.2-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; # 【关键修改】欺骗 PHP，让它以为自己在跑 HTTPS 443 # 必须写在 include 之后，以覆盖默认值 fastcgi_param HTTP_HOST $server_name; fastcgi_param HTTPS $http_x_forwarded_proto; fastcgi_param SERVER_PORT 443; fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto; # 启用 FastCGI 缓存 fastcgi_cache my_fastcgi_cache; fastcgi_cache_valid 200 301 302 404 10s; # 设置缓存的有效时间 fastcgi_cache_methods GET HEAD; # 只缓存 GET 和 HEAD 请求 # 不缓存 POST 请求和其他非幂等性请求 set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } # 如果存在特定会话 cookie，则不缓存 if ($http_cookie ~* "PHPSESSID|__typecho_uid|__typecho_authCode") { set $skip_cache 1; } fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; # 添加头部信息显示缓存状态 add_header X-FastCGI-Cache $upstream_cache_status; } # Deny access to hidden files location ~ /\.ht { deny all; } }关键是要设置fastcgi_param HTTP_HOST $server_name; 这个参数，设置完成后，VPC内网互通的网站流量就跑在HTTP上了，可以减轻服务器的TLS加密负担。
矿渣再就业：双卡 P104-100 运行大语言模型可行性报告
尝试使用双卡 P104-100 运行大语言模型Introduction（bushi 鉴定为搞学术搞的在我之前的文章，我说了我买了四张P104-100用于跑Blender Sheepit渲染积分，现在我想试试看开发出新的玩法——用P104-100通过Ollama运行大语言模型。因为大语言模型的话，显存大小很重要，很大程度上是显存即正义，一些比较大的模型，我的RTX2080Ti 11G显存还是力不从心。虽然说P104-100才8GB的显存，但是招架不住它便宜啊，79包邮的价格要什么自行车，买两张不就是16G了吗？虽然说不能这么算，但是Ollama支持把大语言模型拆分到不同的显卡进行运行，买都买了，不如试试看？目前的硬件情况目前的四张P104并不是安装在一起的，而是分别放在两个不同的机器上面。又因为我没有支持安装四卡的主板，所以想测试四卡并行跑比较大的模型还是算了。还有一个比较蛋疼的问题是，因为我算是为了利用闲置硬件买的卡，所以是一块H61+E3-1220v2+两条8G内存。这样子做主要是为了满足Sheepit的需求，因为它不支持多GPU同时渲染，所以只能每个GPU开一个实例，分开渲染，占用双倍资源，所以显卡并没有全部安装在一起。也正因为是这样，第二张卡是通过PCIe X1的延长线转接出来的，啊对，就是挖矿用的那种延长线，因为Cycles渲染只需要一次性加载数据，所以即使是PCIe 1.0 X1也影响不大，无非就是加载慢一点。但是，如果是多卡并行跑LLM就是另一个故事了，会涉及频繁的数据交换，这样子即使是显卡原来就砍废的PCIe 1.0 X4都会造成严重瓶颈，更何况转接板的PCIe 1.0 X1。虽然理论上跑起来的效果会非常难看，但是，实践是检验真理的唯一标准，不试试看怎么知道是什么情况呢？于是我打算尝试一下。部署Ollama这算是论文的 Approach 吗？（bushi 鉴定为搞学术搞的因为目前Pascal架构的N卡驱动还是支持的，但是今年10月就正式抛弃，所以，现在还是可以使用最新的Ollama，也算是赶上末班车了。如果后面Ollama也抛弃这些老架构，那就真的寄了，但是跑当下的模型，未来也是可以跑的，只不过有可能新的模型就跑不了了。Pascal架构的显卡最后支持应该是CUDA 12.9了。所以，直接打开Github下载最近版本的Ollama就行了，因为Ollama更新频繁，文章写作测试时用的是前不久的0.11.10版本，进行测试。如果你部署出现什么问题，建议切换到这篇文章使用的相同版本，也就是0.11.10。毕竟在0.11.11的What's Changed里面，增加了CUDA 13的支持，不知道会不会影响Pascal架构的最后CUDA 12.9版本，因为文章的重点不在这里，如果有需要请自行验证，也欢迎你在评论区说一下验证的结果。部署的方式也很简单，在Github的Releases找到对应版本的Ollama下载就行了，有点大，慢慢下就行。下完后解压即可使用：mkdir ollama mv ollama-linux-amd64.tgz ollama/ cd ollama/ tar -xzvf ollama-linux-amd64.tgz rm ollama-linux-amd64.tgz因为Ollama默认会把模型下载在用户文件夹的.ollama隐藏文件夹下，会占用大量空间。为了避免这种情况，我选择创建一个数据文件夹，并设置Ollama将模型文件放在那里。mkdir data然后指定创建的data文件夹为临时HOME目录后，启动Ollama。下面是启动脚本run.sh，请放在刚刚创建的ollama文件夹里面。export HOME=/home/rin/ollama/data/ ./bin/ollama serve/home/rin/ollama/data/请替换为你需要存放模型的实际路径。然后直接启动Ollama:./run.sh进入ollama/bin/目录，执行以下命令运行LLM：./ollama run qwen3:8b --verbose这里的实例用的qwen3:8b，可以根据实际需需要替换为目标模型。--verbose是设置显示 Ollama 每秒生成的 Token 数量。测试平台如图所示：以下测试均在Ollama 0.11.10进行。注意！数据为我本人实测结果，不同环境和提示词结果可能会有差异！单卡P104-100跑常见模型速度测试模型均使用最新模型进行测试，测试的提示词是：请帮我写一段2000字的小故事，讲述两只猫娘 Chocola 和 Vanilla 的日常生活。通义千问模型速度回答时 PCIe 最高交换速率qwen3:0.6b64.24 tokens/sRx:105MB/s Tx:85MB/sqwen3:1.7b59.84 tokens/sRx:85MB/s Tx:65MB/sqwen3:4b30.29 tokens/sRx:70MB/s Tx:50MB/sqwen3:8b22.05 tokens/sRx:50MB/s Tx:40MB/s测试通义千问的时候，在模型推理回答期间，显卡的PCIe I/O是上去的，在测试qwen3:0.6b的时候，大概会双向干到差不多100MB/s的双向传输速度，CPU其中一个核心跑满。而且我发现一个有趣的现象，也就是每秒生成Token数越多，带宽占用越高；每秒生成Token数越少，带宽占用越低。特别是qwen3:4b模型，一开始推理速度极快，到后面速度就降下来了，PCIe交换速率也随之下降。虽然PCIe 1.0 x1理论速度250MB/s，但是我发现在模型加载的时候，大概只能跑到200MB/s左右的速度。不过即使是这个速度，也满足了模型推理期间的数据交换需求。所以用这种矿渣卡跑LLM完全是可行的。另外，我还发现了一个致命的问题，当在提示词中要求的字数太多时，例如要求写10000字的文章，通义千问就会变成复读机，一直在重复其中一段话，永远不停止，只能Ctrl+C强行停止。这个情况在我这边可以稳定复现，经过测试，qwen3:1.7b qwen3:4b这几个模型都有出现过这样的情况，不知道是硬件问题导致的还是模型缺陷，但是可以肯定的是，换另一张P104-100跑也是同样的结果。DeepSeek模型速度回答时 PCIe 最高交换速率deepseek-r1:1.5b51.62 tokens/sRx:60MB/s Tx:45MB/sdeepseek-r1:7b27.77 tokens/sRx:60MB/s Tx:45MB/sdeepseek-r1:8b22.68 tokens/sRx:60MB/s Tx:45MB/s对比通义千问，deepseek-r1的三个不同的模型在单显卡的PCIe I/O速度上是类似的，并没有出现明显的随着每秒生成Token数越多，带宽占用越高，整体趋于稳定。我也顺带测了一下DeepSeek在回答要求写10000字的文章这种问答时，会不会出现复读机的情况。进行了几轮测试下来，确实没有遇到或者说触发，相对而言通义千问更容易触发。Gemma模型速度回答时 PCIe 最高交换速率gemma3:270m118.22 tokens/sRx:90MB/s Tx:180MB/sgemma3:1b51.38 tokens/sRx:55MB/s Tx:80MB/sgemma3:4b38.83 tokens/sRx:75MB/s Tx:75MB/sGemma再次出现了类似通义千问的复读机现象，尤其是gemma3:270m，是几乎稳定触发。gemma3:1b也有触发的现象，但是概率相比于通义千问要低。另外，gemma3:270m因为模型比较小，回答速度突破了110 tokens/s，其返回占用带宽相比于前面两个模型要高得多，达到了180MB/s。不过gemma3:4b的PCIe I/O的速度占用有点反常，Rx反而变高了。Llama由于Llama能塞进8G显存的模型只有一个，所以只测试了llama3.1:8b。模型速度回答时 PCIe 最高交换速率llama3.1:8b26.34 tokens/sRx:60MB/s Tx:45MB/s双卡P104-100跑常见模型速度测试前面的测试只是前菜，现在才是重点——双卡P104-100运行LLM测试。测试时采用和单卡测试时一样的提示词，但是选择模型大小在8G以上16G以下的模型，能让两张卡同时开跑。由于这个大小的模型，大部分只有系列里面的其中一个合适，所以只是测试了这个模型系列里面对应大小的一个模型。ChatGPT虽然但是，加载gpt-oss:20b模型的过程是很慢的，差不多加载了5分钟。模型速度GPU0 PCIe 1.0 x4 最大带宽占用GPU1 PCIe 1.0 x1 最大带宽占用gpt-oss:20b28.14 tokens/sRx:65MB/s Tx:35MB/sRx:55MB/s Tx:50MB/s但是实际测试下来，gpt-oss:20b的输出速度算是能用的水平了，两块79包邮的矿渣有这个表现已经不错了。并且，实际上哪怕是PCIe 1.0 x1也没有造成太严重的瓶颈，甚至影响不大。通义千问模型速度GPU0 PCIe 1.0 x4 最大带宽占用GPU1 PCIe 1.0 x1 最大带宽占用qwen3:14b14.32 tokens/sRx:50MB/s Tx:25MB/sRx:35MB/s Tx:40MB/sDeepSeek模型速度GPU0 PCIe 1.0 x4 最大带宽占用GPU1 PCIe 1.0 x1 最大带宽占用deepseek-r1:14b14.44 tokens/sRx:60MB/s Tx:25MB/sRx:55MB/s Tx:40MB/sGemma模型速度GPU0 PCIe 1.0 x4 最大带宽占用GPU1 PCIe 1.0 x1 最大带宽占用gemma3:12b16.53 tokens/sRx:60MB/s Tx:60MB/sRx:45MB/s Tx:40MB/sConclusion（确信）经过以上的实验，我们可以得出以下结论：在多卡或者单卡并行跑大语言模型时，每秒生成Token数越高，运行期间PCIe I/O带宽的占用也就越高。在Token生产速度低于100 tokens/s时，带宽占用普遍在100MB/s以下，因此即使是使用了PCIe x1矿卡转接板，矿渣显卡运行在PCIe 1.0 x1的通信速度下，对性能也没有较大的负面影响。因为PCIe 1.0 x1实际上最大速度可达200MB/s，高于实验测试得到的IO交换的速度。对于采用P104-100多卡运行LLM的方案，我认为是有很高性价比的，79一张包邮的价格，买两张组合起来即可以大约25-28 tokens/s的速度运行ChatGPT-oss 20B的模型，已经达到了相当可用的水平，性价比非常的高。Discussion（确信）虽然这个实验较为完整的评估了两张P104-100的大语言模型运行效果，但是由于我本人硬件条件有限，没有能测试四张P104-100的大语言模型运行效果，这个有待他人进行相关实验进行补充。另外，我还发现了一个有意思的现象，在双卡P104-100运行大语言模型的时候，gpt-oss:20b的输出token速度要远远快于其他14b或者12b的模型，甚至达到了两倍的速度差别。这与常见的模型参数越大，模型运行速度越慢的常见情况有所出入。
P104 矿渣转生超高性价比 CUDA 渲染卡P104-100 ，传奇矿渣卡，历经三次矿潮，本身也是专业矿卡，没有显示输出，PCIe被砍成1.1 x4，对于打游戏的人来说，这玩意表现不如P106-100。而比他同时代更高一档的卡P102-100，因为有10G的大显存，成为了玩AI炼丹人的入门玩具，价格在300元上下。而P104-100，拥有8G显存，相当于GTX1070，价格却只要79包邮，相当便宜！于是，我就一口气买了四张回来玩玩，看看纯种大矿卡到底是什么样的，看看能不能秽土转生为 Blender 渲染卡，为我赚取 SheepIt 渲染积分。关于 SheepIt 是什么，这是一个免费的社区渲染农场，通过用户贡献算力帮你渲染 Blender 项目，作为代价，你需要帮别人渲染项目来赚取积分，渲染项目会消耗积分。而我，并不打算直接用 P104 进行渲染，因为8G显存渲染相对复杂的场景是不够用的，我的正奶机 RTX2080Ti 都甩这玩意几条街。我的思路是用这张卡赚取足够多的 SheepIt 渲染积分，来给我的未来3D创作做准备，因为渲染农场的速度和效率是单台电脑没法比的。这样子就相当于用现在的空闲时间，换取未来的渲染时间。P104-100 你下辈子还是矿卡，但是不挖比特币，以太坊，而是给我挖 SheepIt Blender 渲染农场积分！从矿场中来，到矿场中去！召唤矿渣矿渣到手后，我先上机跑了一下 SheepIt，可以渲染，相对计算强度为32%，和榜单上的GTX1070Ti差不多。这要归功于GDDR5X显存和超频到了1860MHz的核心频率，温度低的时候甚至冲到了1920MHz。虽然每张卡的Boost频率都不太一样，但是基本盘都是在1860MHz左右，有一张卡低一点，1835MHz，但是也比GTX1070的1683MHz要高不少了。这玩意可以看作是超频版的GTX1070。我也测试了一下Ollama，跑Qwen2.5 7B的模型轻轻松松，速度很快。跑Ollama的时候功耗比跑Blender高，会干到170W左右，这个玩意功耗墙180W，SheepIt渲染的话，观察下来最高也就120W左右，没跑满功耗，但是nvtop显示已经占用100%了。该测的都测了，显卡被矿老板锻炼得肌肉发达，就是有点发烧，跑测试都干75度去了，性能没问题。现在就该让我乙女解剖了！让我看看！然后，我就拆开了——让我看看！这叫什么？崭新出土！已经不能用伊拉克成色来形容了，这叫出土文物！硅脂干得跟水泥一样，要用指甲刮下来！导热垫也已经硬邦邦的了。之前这货的工作强度可想而知。观察了一下塑料壳上面的生产印记，好家伙，2018年4月的卡，那会我还没中考呢！这个年份对得起这样的积灰。看一下散热器的积灰细节：看完了这玩意的内部情况，该让我给这位经历了三波矿潮风雨的老同志接风洗尘了。清理矿渣清理的时候，整个桌子都布满尘土，特别是清理风扇的时候，一扫就是一把土出来！真的绝了！不清理干净直接这个状态让他继续给我干活，估计没多久就要嗝屁了。清理完成后，瞬间舒适：散热器特写，好像并没有太严重的氧化，清理干净后整体成色还可以：清理后我只重新涂了硅脂，没有换导热垫，换导热垫成本就上去了，79包邮的矿渣卡不值得这么做，擦擦将就用吧！你这矿渣卡从矿老板的哥布林窝里面出来，能遇到我这样的狗修金撒玛，对你这么好，就偷着乐吧！我给你洗干净了，你就该给我去赚 SheepIt 渲染积分了。装机本来我是想要一台机器插4张卡，但是，我发现就只插两张，E3-1220v2都不太顶得住，因为渲染完成后还需要用CPU进行降噪分析，并且渲染过程中会一直占满一个CPU核心，所以4C4T的E3-1220v2带两张P104已经比较极限了，基本上占用率都在50%+。所以我转换了思路，搞两台机器，一台机器装两张卡。也幸亏当时在家组装焊接副电源的时候，保险起见，500W的服务器电源只焊接了两条双8PIN供电线，让我可以分开部署。现在有两条8G内存，插上去就是16G，每个客户端分7.5G，给系统留1G就行，反正是无头模式启动，先看看效果怎么样。装好后Be Like:如果之前关注过我的应该就发现了，这是我宿舍云的电脑。我宿舍云的功能全部都迁移到家里云了，用来干这个也算是物尽其用吧。两张P104-100，一张直接插主板的PCIe X16，另一张通过挖矿的PCIe X1转PCIe X16转接线接上。一开始我还担心X1的转接线性能损耗问题导致性能下降，事实证明我是多虑了，几乎没有明显的性能下降，只是渲染启动的时候，数据加载会慢一点，仅此而已！其实这个电脑的电源才300W，用来带两张P104是包炸的。因为我家里面正好有服务器电源，这玩意是短接触点触发，然后稳定输出12V，于是我就想用这玩意单独给显卡供电。焊上从烂电源剪下来的PCIe 8PIN供电线，然后把触发触点焊上延长线，接到继电器上，继电器的触发电源连接主电源的12V，这样，当主电源启动时，这个服务器电源也会一并触发启动，输出12V，做到与主电源同步上电。30块钱的500W铂金电源，想想都香！装系统系统我选择了AlmaLinux 8.10，保险起见先安装的带MATE桌面的版本，后续再开启SSH，安装xrdp和tigervnc用于远程桌面，以及cockpit面板进行远程管理。按照引导提示一步一步安装系统就可以了，但是这个系统的安装程序有点问题，如果你不预先提前分区硬盘，他会把根目录放在硬盘最靠后的位置，导致性能受到影响。我是先手动进行了分区操作，在live桌面环境安装kde-partitionmanager，然后再用root权限启动即可进行分区操作。我的分区长这样，可以作为参考：[root@P104-Cluster rin]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 462.1G 0 disk ├─sda1 8:1 0 2M 0 part ├─sda2 8:2 0 50M 0 part /boot/efi ├─sda3 8:3 0 600M 0 part /boot ├─sda4 8:4 0 32G 0 part / ├─sda5 8:5 0 2G 0 part [SWAP] └─sda6 8:6 0 427.5G 0 part /home建议通过BIOS模式启动安装镜像，然后设置BIOSboot分区，以便系统支持BIOS和UEFI双启动模式。装显卡驱动关于这部分，我之前写过文章，这里就不再赘述，可以在我的网站直接查找。我这里就给出一串命令梭哈，梭哈完成重启驱动就装上了：yum install epel-release dnf config-manager --add-repo http://developer.download.nvidia.com/compute/cuda/repos/rhel8/$(uname -i)/cuda-rhel8.repo dnf install kernel-headers-$(uname -r) kernel-devel-$(uname -r) tar bzip2 make automake gcc gcc-c++ pciutils elfutils-libelf-devel libglvnd-opengl libglvnd-glx libglvnd-devel acpid pkgconfig dkms dnf module install nvidia-driver:575-dkms安装Java环境桌面系统因为安装了LibreOffcie的原因自带了 Java 1.8.0，然而 SheepIt 客户端需要更高版本的 Java ，所以我们需要进行安装：yum install java-21-openjdk但是，安装完成后，命令行查看 Java 版本，发现怎么还是1.8.0？这时候就要切换系统环境的 Java 版本了，用这个命令切换：update-alternatives --config java运行后选择 java 21 即可。设置Sheepit客户端由于最近 SheepIt 更改了客户端，现在是打开客户端启动器，然后进行客户端下载，再启动客户端。因为有时候下载客户端的速度奇慢无比，等待过程着实操蛋。于是我决定把下载的客户端找出来，复制到工作目录备用。启动的时候，已经有提示了：[rin@P104-Cluster sheepit]$ java -jar ./client-launcher.jar -login Chocola -password qm4AQY6Y8zyz7Zj51UWedSTvao8zupmEV8GCcugD -memory 7.5G -compute-method GPU --headless -hostname P104-Cluster01 -ui text -gpu OPTIX_0 -cache-dir /home/rin/sheepit/GPU1 -shared-zip /home/rin/sheepit/shared-zip Server::downloadClient(/tmp/b15aba88aec93a7c66c89a5ba7bf6bd7.jar) Status: Starting client download... Status: Download complete! everything is fine will launch /tmp/b15aba88aec93a7c66c89a5ba7bf6bd7.jar复制出来，以免每次启动重复下载：mv /tmp/b15aba88aec93a7c66c89a5ba7bf6bd7.jar ./sheepit-client.jar下次启动直接：java -jar ./sheepit-client.jar -login Chocola -password qm4AQY6Y8zyz7Zj51UWedSTvao8zupmEV8GCcugD -memory 7.5G -compute-method GPU --headless -hostname P104-Cluster01 -ui text -gpu OPTIX_0 -cache-dir /home/rin/sheepit/GPU1 -shared-zip /home/rin/sheepit/shared-zip这里说明一下参数的含义：参数作用说明-login, -password登录 SheepIt 帐号，进行身份认证-memory控制客户端的最大可用内存（示例为 7.5GB）-compute-method选择计算方式（GPU / CPU）--headless无图形界面模式，适合服务器或脚本运行-hostname为客户端指定易于识别的名称-ui text文本模式界面，适用于终端环境-gpu指定使用哪块 GPU（如 OPTIX_0）-cache-dir设置缓存存储位置，建议为独立目录-shared-zip指定共享资源存储目录，让多个客户端可以共用资源把这段命令保存为.sh脚本文件，下次启动直接在目录一键执行就可以。根据需要自行修改，当然，不改也能用，如果你一直用上面这段实例原封不动的跑，我会很感谢你的 XD有一点要注意的，因为 SheepIt 的服务器在国外，所以下载速度可能会很慢，建议搭配魔法上网环境使用。在启动参数加上-proxy http://login:password@host:port代理参数即可，懂的都懂。SheepIt，启动！矿之圣女 P104 桑，给你洗干净换好硅脂就该给我干活了喵！温度也不那么高了，大概60多度这样子，算是比较正常的满载温度了。积分赚取速度表现这是 P104 单卡跑4个小时赚取的积分：76053分，折合每个小时赚取约19013分，也就是一天应该可以赚取大约456318分。这是 RTX2080Ti 跑了五天多，大约136.4小时的积分：6142811分，折合每个小时赚取约45035分，也就是一天应该可以赚大约1080847分。这两样来看，P104 赚取积分的速度大约是 RTX2080Ti 的42%。尽管现在 RTX2080Ti 的性价比非常高，目前应该是1300左右就能购入了，但是，1300却可以买16张 P104-100 ！而当42%X16，那将是672%，也就是可以买到6.7倍的性能。所以 P104-100 这玩意的性价比是非常炸裂的。缺点总结这张卡看似美好，但是，这肯定是有代价的，那就是巨大的耗电量。除非你在电费相对廉价的地区，不然不是很推荐组多台这种机器赚取渲染积分，我觉得搞个双卡就算是一个比较甜点的选择了。我上四卡主要是我这边不用我交电费） 除此之外，卡越多，对平台的要求也就越高，对应的成本也会上去。到手后一张张拆开整备也是很花时间的，我四张卡清理整备完都快花了我一天时间，当然，我清理的是相对比较细致的，如果只是凑合用估计也不用这么久。还有一个问题，就是，因为是Pascal架构，已经比较老了，有一些新特性不支持，导致不是全部的项目都能渲染。也就是，这玩意挂 SheepIt 渲染的时候，更有可能遇到空窗期，也就是没有任务可做，处于空闲状态。可以对比一下 P104-100 的可渲染项目和 RTX2080Ti 的可渲染项目的区别：P104-100:ProjectStatuslager-berechnung 1-3350Renderableflat-screen_water_5_K7Renderableflat-screen_water_5_yu-gi-ohRenderableflat-screen_water_5_nintendoDS_cycles 1-1500Renderable3 170-480Renderable0099-10086001-1-SRenderable0061-10086001-1-SRenderable0057-10086001-1-SRenderable0012-10086001-1-SRenderableplane 1-150 CxCFRenderableDOVEL-2Renderable3.2Needs a GPU with higher cuda compute capabilityPotential Abstract Animation 1-600 7yItRequires CPUearth_v2 - CopieNot enough vramwar 2Requires CPUfarmhouse(copy) 2 1-200 aOnFNot enough vramfarmhouse(copy) 2 1-200 sarANot enough vramr1t1 3200 cam 2No big archive download on this computerS0E50_EUV_INTRO_new_rev7Not enough vramV3 - Vote 1_SHEEPITNeeds a GPU with higher cuda compute capabilityfin3No big archive download on this computerBallRequires CPUdance_07No big archive download on this computer2 0-450 eTeYNeeds a GPU with higher cuda compute capabilityZ.10Needs a GPU with higher cuda compute capabilitysheepit250714-N22-MARINER anim sc - 07 429-574Not enough vramr1t1 3600No big archive download on this computerbrandingNo big archive download on this computergodz in city FPV 1050-1400No big archive download on this computerPart 1Needs a GPU with higher cuda compute capability32.warp energy tunnel 1-2000Needs a GPU with higher cuda compute capabilityASCENSOR 3Requires CPUgrassland123 1-220No big archive download on this computerrampage rally render 1001-1144 46cXNeeds a GPU with higher cuda compute capabilityBinocles_cotxeNot enough free memory, requiring: 10.8 GB, available: 7.3 GBplane 1-150Project rate-limited due to lack of points.Prj 3 Perfume S4No big archive download on this computerscena 3 troth 1-400 33CrRequires CPUAnim_Rainbow_testNeeds a GPU with higher cuda compute capabilityschool vid 1-600No eevee or Grease Pencil project on headless sessionanimationSheepIT02Needs a GPU with higher cuda compute capabilityWebsiteAnimation_CorrectModels3_Part1_v5Not enough free memory, requiring: 17.0 GB, available: 7.3 GBWebsiteAnimation_CorrectModels3_Part2_config_v2 444-924No big archive download on this computerRoji 2192-4044Needs a GPU with higher cuda compute capabilityPrj 3 Perfume S6Project is not fully synced on all mirrors.RTX2080Ti：ProjectStatus2 0-450 eTeYRenderablegodz in city FPV 1050-1400Renderable32.warp energy tunnel 1-2000RenderableBinocles_cotxeRenderableDOVEL-2RenderableAnim_Rainbow_testRenderableanimationSheepIT02Renderableflat-screen_water_5_K7Renderablelager-berechnung 1-3350Renderablegrassland123 1-220Renderableschool vid 1-600Renderable3 170-480Renderable0099-10086001-1-SRenderable0061-10086001-1-SRenderable0057-10086001-1-SRenderable0012-10086001-1-SRenderableWebsiteAnimation_CorrectModels3_Part2_config_v2 444-924RenderableRoji 2192-4044Renderable3.2Renderableplane 1-150RenderableWebsiteAnimation_CorrectModels3_Part1_v5RenderablePotential Abstract Animation 1-600 7yItRequires CPUearth_v2 - CopieNot enough vramwar 2Requires CPUfarmhouse(copy) 2 1-200 aOnFNot enough vramfarmhouse(copy) 2 1-200 sarANot enough vramr1t1 3200 cam 2Requires CPUS0E50_EUV_INTRO_new_rev7Not enough vramV3 - Vote 1_SHEEPITProject rate-limited due to lack of points.fin3Requires CPUBallProject rate-limited due to lack of points.dance_07Requires CPUZ.10Project rate-limited due to lack of points.sheepit250714-N22-MARINER anim sc - 07 429-574Not enough vramr1t1 3600Requires CPUflat-screen_water_5_yu-gi-ohProject rate-limited due to lack of points.brandingRequires CPUflat-screen_water_5_nintendoDS_cycles 1-1500Project rate-limited due to lack of points.Part 1Project rate-limited due to lack of points.ASCENSOR 3Requires CPUrampage rally render 1001-1144 46cXProject rate-limited due to lack of points.scena 3 troth 1-400 33CrRequires CPUplane 1-150 CxCFProject rate-limited due to lack of points.Prj 3 Perfume S6Project is not fully synced on all mirrors.可以明显看到 RTX2080Ti 可以渲染的项目更多。如果只是自己玩玩，搞两张卡就行，花不了多少钱的，还能玩玩矿渣圣遗物，79包邮的价格，炸了也不心疼。同时，也可以给失足矿渣卡一个改过自新的机会，让它金盆洗手，重新做卡。后记：Sheepit冲榜四张P104-100配合我的主力卡RTX2080Ti，在连续跑了一个月后，顺利冲榜 top 1% renderers ，排名20，截图留念。
解决 Manjaro 的 Ark 打开 zip 中文乱码这似乎是一个软件包的BUG，一直都没有修复，需要安装AUR的p7zip-natspec软件包代替7zip软件包才能解决。这个问题从我第一次使用Manjaro就有了，到现在都要这样子手动解决。出问题的样子Be Like:今天又让我碰到了，顺手解决了，写个小文章记录一下。下面是解决步骤：从 AUR 安装 p7zip-natspec确保安装了yay，以及确保电脑处于可访问国际互联网的环境，然后执行以下命令安装：yay -S p7zip-natspec到后面会提示p7zip-natspec与7zip冲突，要删除7zip才可以安装：正在加载软件包... 正在解析依赖关系... 正在查找软件包冲突... :: p7zip-natspec-17.05-2 与 7zip-24.09-3 有冲突 (p7zip)。删除 7zip 吗？ [y/N] y 软件包 (2) 7zip-24.09-3 [删除] p7zip-natspec-17.05-2 全部安装大小： 10.69 MiB 净更新大小： 4.50 MiB :: 进行安装吗？ [Y/n] (1/1) 正在检查密钥环里的密钥 [############################################] 100% (1/1) 正在检查软件包完整性 [############################################] 100% (1/1) 正在加载软件包文件 [############################################] 100% (1/1) 正在检查文件冲突 [############################################] 100% (2/2) 正在检查可用存储空间 [############################################] 100% :: 正在处理软件包的变化... (1/1) 正在删除 7zip [############################################] 100% (1/1) 正在安装 p7zip-natspec [############################################] 100% :: 正在运行事务后钩子函数... (1/1) Arming ConditionNeedsUpdate... 禁用 Ark 的 Libzip 插件依次点开 Ark 的菜单>设置>配置 Ark取消勾选 Libzip 插件：应用操作后，重新打开zip文件，中文不再乱码：
在Linux中自定义N卡的风扇转速曲线我个人对硬件的温度比较重视，认为是越低越好，显卡这玩意超过70度我觉得就已经是高温了。由于我已经主力使用Linux系统很久了，一直都是在需要给显卡上强度的时候，直接拉满显卡风扇。但是后面发现不是每时每刻都需要跑满的，没有负载的时候风扇还在呼呼吹确实是有点浪费了，于是就想搓一个bash脚本，用于显卡风扇控制。脚本本体没错，又是面向LLM编程！#!/bin/bash # 初始化上一次的风扇转速 last_speed=-1 while true; do # 获取 GPU 温度（单位：摄氏度） temp=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits -i 0) # 判断温度范围并计算风扇转速 if (( $(echo "$temp <= 40" | bc -l) )); then current_speed=40 elif (( $(echo "$temp >= 70" | bc -l) )); then current_speed=100 else # 线性插值：温度在 40~70°C 之间，速度从 40%~100% current_speed=$(echo "scale=0; 40 + 2 * ($temp - 40)" | bc) fi # 如果转速发生变化，才执行设置并输出调试信息 if [ "$current_speed" -ne "$last_speed" ]; then echo "GPU Temp: $temp°C → Fan Speed: $current_speed%" nvidia-settings -a "[gpu:0]/GPUFanControlState=1" -a "[fan:0]/GPUTargetFanSpeed=$current_speed" > /dev/null 2>&1 last_speed=$current_speed fi # 每秒检测一次 sleep 1 done脚本说明脚本逻辑：40度以下，转速40%，40度到70度，线性递增，到70度及以上时为100%。至于为什么不是停转？因为我的是涡轮卡，没法停（ ，需要的话请自行修改。使用方法：把代码保存为set_GPU_fan.sh，赋予执行权限后用root执行。建议配合tmux挂在后台。注意事项：这个脚本只针对单GPU，单风扇的情况，如果你是多GPU或者nvidia-settings厘米出现了多个风扇控制，就需要进行脚本的完善和修改。我这个脚本只在有桌面环境的情况下测试过没问题，对于无GUI的情况，我是没有测试过的，据说是要运行X Server才可以运行nvidia-settings。希望这个脚本可以更好的帮助你控制显卡温度。
当网站遭遇DDoS：如何优雅地应对云服务器被封堵
网站被DDoS后制定解决方案很平常的一天，我在网站主题交流群里，发现有群友网站被打掉线了。刚刚给他发“贺电”，没过多久，我也收到了华为云发来的“贺电”：尊敬的(Rin):感谢您使用华为云产品，非常抱歉打扰您。在北京时间2025/05/22 20:33，华为云监测到您的弹性公网IP(EIP)实例 发生EIP封堵事件。 详情：节点：亚太-新加坡实例名称：119.8.?.?实例ID：88????e7-1??7-4??a-8??d-0??????????5告警描述：{"reason_zh": 尊敬的Rin：华为云DDoS防护服务在2025-05-22 20:33:05检测到您的EIP 119.8.?.? 异常流量已超过DDoS防护峰值触发封堵，封堵时带宽为 67 Gbps, 您服务器的访问将受到限制，24小时后系统将自动为您解封。您可登陆官网购买DDoS高防服务提升防护能力，保障服务器稳定运行。, "reason_en": Dear Rin: HUAWEI CLOUD Anti-DDoS Service detects that your EIP 119.8.?.? abnormal traffic has exceeded the DDoS protection peak at 2025-05-22 20:33:05 and triggers blocking. Bandwidth when blocking is 67 Gbps, Access to your server will be restricted, and the system will automatically unblock it for you after 24 hours. You can log in to the official website of HUAWEI CLOUD to purchase Advanced Anti-DDoS service to enhance protection capabilities and ensure stable server operation.}原因分析：尊敬的Rin：华为云DDoS防护服务在2025-05-22 20:33:05检测到您的EIP 119.8.?.? 异常流量已超过DDoS防护峰值触发封堵，封堵时带宽为 67 Gbps您服务器的访问将受到限制，24小时后系统将自动为您解封。您可登陆官网购买DDoS高防服务提升防护能力，保障服务器稳定运行。如有疑问，您可以登录华为云官网提交工单咨询，会有相应工程师为您核实查看。而且连着几个群友的网站都被攻击，甚至有人还被短信轰炸了，初步断定就是群里面有人搞鬼，但是没有证据抓现行，只能自己做好预案和防护了。由于DDoS攻击基本上都会被云服务器提供商黑洞IP，如果只有单台云服务器，这个真的无解。但是，我有不止一台机器，这可就有解决办法了。我的大体解决思路是这样的：配置服务器访问频率限制，防止高频恶意请求（CC攻击）耗尽服务器资源。设置主服务器定期备份内容到备用服务器。备用服务器部署检测，一分钟一次，判定主服务器状态，如果超过三次无响应，判定为被打挂了。判定打挂后，通过Cloudflare的API，设置域名的A记录IP地址为备用服务器IP地址，并开启Cloudflare的代理防护。当主服务器恢复后，自动将DNS记录切换回主服务器，然后将备份的内容还原到主服务器。接下来，让我们开始任务吧。这里说一下环境条件：目标操作系统为 Alpine Linux 3.20 ，LNMP环境都为系统默认设置安装的，备份的目标为Typecho博客。配置服务器访问频率限制虽然我一开始网站就已经做了缓存配置，是能抗高并发的，不至于像某些人的网站itdog网站测速一测就死。但是，如果是高频的请求，还是会耗尽带宽的资源，导致网络阻塞，访问变慢。所以，是时候配合iptables和内核设置来阻挡恶意请求了。首先是拉黑IP，平时注意留意服务器的网络负载，如果遇到有过高负载历史的时候，善于使用Nginx的日志进行IP追溯，拉黑恶意IP。拉黑命令如下：iptables -A INPUT -s 45.140.143.77 -j DROP iptables -A INPUT -s 106.15.224.186 -j DROP其次，就是设置单个IP的最大连接数，对超出的部分数据包进行丢弃处理（如果是拒绝处理，还是会回传数据包导致上行被占）。这里设置为20：iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 20 --connlimit-mask 32 -j DROP iptables -A INPUT -p tcp --dport 443 -m connlimit --connlimit-above 20 --connlimit-mask 32 -j DROP一并设置内核参数保证连接处理数量足够：sysctl -w net.netfilter.nf_conntrack_max=10240由于直接丢弃数据包会造成半连接状态，仍然会消耗资源，所以要进行一些规则防止资源耗尽。#防御 SYN Flood # 启用 SYN Cookies（防御 SYN Flood） sysctl -w net.ipv4.tcp_syncookies=1 # 限制 SYN 请求速率（每秒最多 20 个） iptables -A INPUT -p tcp --syn --dport 443 -m connlimit --connlimit-above 20 -j DROP iptables -A INPUT -p tcp --syn --dport 80 -m connlimit --connlimit-above 20 -j DROP # 缩短 SYN 重试次数（加速无效连接释放） sysctl -w net.ipv4.tcp_syn_retries=2 sysctl -w net.ipv4.tcp_synack_retries=2至此，这个设置已经可以防御常见的CC攻击手段。不过需要注意的是，以上命令只是临时生效，重启后就会失效，如果需要永久生效，则需要写入配置文件。不过考虑到云服务器很少重启，问题也不大。设置定期备份我打算使用mysqldump进行数据库导入和导出操作，然后使用rsync进行内容同步，最后使用chmod和chown设置网站目录权限。配置ssh密钥登陆首先，如果要自动化，则需要配置ssh密钥登陆。在客户端生成SSH密钥对：ssh-keygen -t rsa -b 4096 -C "your_email@nekopara.uk"执行后一路回车即可。然后，将公钥复制到服务器：ssh-copy-id root@server.nekopara.uk提示输入密码，输入目标服务器密码即可完成操作。这里your_email@nekopara.uk请替换为你实际邮箱，server.nekopara.uk替换为实际服务器的IP或者域名，推荐是IP。设置自动复制规则在主服务器上，将以下内容保存为backup_blog.sh放到root文件夹：/usr/bin/mysqldump -u typecho_user -p'p@sswd' typecho > "/data/database/typecho_backup.sql" /usr/bin/rsync -avz --no-perms --delete -e ssh /data/ root@backupserver.nekopara.uk:/data/ /usr/bin/ssh root@backupserver.nekopara.uk /root/set_website.sh然后赋予执行权限：chmod +x /root/backup_blog.sh相应的，在备份服务器上，也要在root文件夹创建一个set_website.sh文件进行文件权限的设置和数据库导入：/bin/chown -R nginx:nginx /data/typecho/ /bin/chmod -R 755 /data/typecho/ /usr/bin/mysql -u typecho_user -p'p@sswd' typecho < /data/database/typecho_backup.sql设置完成后，自己执行一次backup_blog.sh脚本看看有没有备份成功。要注意，这里的backupserver.nekopara.uk请替换为实际备份服务器的IP或者域名，推荐是IP。typecho_user替换为实际博客数据库的用户名，p@sswd替换为实际数据库的密码，typecho替换为实际数据库。测试成功后，配置crontab自动任务，一个小时执行一次：0 * * * * "/root"/backup_blog.sh >> /var/log/backup.log 2>&1设置状态检测和切换策略首先，这个前提条件是你要去Cloudflare获取区域API令牌：打开目标站点>点击“获取您的 API 令牌”>点击“创建令牌”>使用“编辑区域 DNS”模板>然后选择特定区域，创建令牌记录下这个令牌，后面会用到，而且以后不再可以查看了，请妥善保存。然后，在root文件夹创建一个dns_auto_update.sh，内容如下：#!/bin/bash # 🐱 Cloudflare API Token CF_API_TOKEN="neko????????????????????????????????para" # 🐱 域名，例如 nekopara.uk CF_ZONE_NAME="nekopara.uk" # 🐱 记录名称列表，例如 "nekopara.uk www.nekopara.uk" CF_RECORD_NAMES=(nekopara.uk www.nekopara.uk) # 🐱 目标 IP TARGET_IP="119.8.?.?" # 🐱 目标端口 TARGET_PORT="80" # 🐱 备用 IP BACKUP_IP="172.16.1.233" # 🐱 获取 Zone ID get_zone_id() { curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${CF_ZONE_NAME}" \ -H "Authorization: Bearer ${CF_API_TOKEN}" \ -H "Content-Type: application/json" | jq -r '.result[0].id' } # 🐱 获取 DNS 记录 ID get_record_id() { local zone_id="$1" local record_name="$2" curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records?type=A&name=${record_name}" \ -H "Authorization: Bearer ${CF_API_TOKEN}" \ -H "Content-Type: application/json" | jq -r '.result[0].id' } # 🐱 更新 DNS 记录 update_dns_record() { local zone_id="$1" local record_id="$2" local record_name="$3" local ip="$4" local proxied="$5" curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${record_id}" \ -H "Authorization: Bearer ${CF_API_TOKEN}" \ -H "Content-Type: application/json" \ --data "{\"type\":\"A\",\"name\":\"${record_name}\",\"content\":\"${ip}\",\"ttl\":1,\"proxied\":${proxied}}" | jq } # 🐱 检测目标 IP 的 HTTP 状态码 check_target() { curl -s --connect-timeout 10 "http://${TARGET_IP}:${TARGET_PORT}" -o /dev/null return $? } # 🐱 初始化 zone_id=$(get_zone_id) declare -A record_ids for record_name in "${CF_RECORD_NAMES[@]}"; do record_ids["$record_name"]=$(get_record_id "$zone_id" "$record_name") done current_ip="$TARGET_IP" proxied=false failure_count=0 success_count=0 # 🐱 监控循环 while true; do if check_target; then echo "🐾 目标 IP ${TARGET_IP} 响应正常" failure_count=0 success_count=$((success_count + 1)) if [ "$current_ip" != "$TARGET_IP" ] && [ "$success_count" -ge 3 ]; then echo "🐾 切换回原始 IP ${TARGET_IP}，关闭代理" for record_name in "${CF_RECORD_NAMES[@]}"; do update_dns_record "$zone_id" "${record_ids[$record_name]}" "$record_name" "$TARGET_IP" false done current_ip="$TARGET_IP" proxied=false success_count=0 fi else echo "🐾 目标 IP ${TARGET_IP} 无响应" failure_count=$((failure_count + 1)) success_count=0 if [ "$current_ip" != "$BACKUP_IP" ] && [ "$failure_count" -ge 3 ]; then echo "🐾 切换到备用 IP ${BACKUP_IP}，开启代理" for record_name in "${CF_RECORD_NAMES[@]}"; do update_dns_record "$zone_id" "${record_ids[$record_name]}" "$record_name" "$BACKUP_IP" true done current_ip="$BACKUP_IP" proxied=true failure_count=0 fi fi sleep 60 done运行这个脚本需要确保jq和curl软件包已经安装。这个shell脚本前面是配置部分，请根据注释的提示替换为实际内容。脚本策略是，每60秒（一分钟）检测一次目标站点的http服务，如果目标站点连续三次异常，则切换到备用服务器并开启Cloudflare的代理防护。当目标服务器恢复正常后，则切换回去。确认脚本无误后，在备用服务器上面创建一个tmux终端挂着就行了：tmux new -t dns_auto ./dns_auto_update.sh总结一下其实漏了一步——将备份服务器的内容还原回目标服务器。这部分就跟第一部分是同理的，不加crontab计划任务就行了。也可以结合切换脚本自行实现。由于我网站流量比较小，对内容的及时性没有这么高的要求，在被打期间我也不会写文章。最多就是在被打期间如果有人评论，那这个评论会在主服务器恢复后的数据同步中被清除，无非就是丢一两条评论，而且大部分时间也没人评论，所以无所谓了。现在想想如果真的搞了，万一恢复的时间赶在同步时间的点子上，冲突搞炸数据库就不好玩了。其实就算是主服务器备份到备胎服务器，也不一定需要计划任务，每次更新文章后连上去执行一下脚本就行，顺手的事情。切换期间如果真有评论想要保留的，手动迁移过去也不费什么事，毕竟被打也算是小概率事件，玩网站一年多了才被打这一次。或许有人会好奇，既然被打就是切Cloudflare，为什么不一直套Cloudflare，这样不就不怕打了吗？其实我不怎么做是有原因的，因为我还是希望网站速度能快一些，而切了Cloudflare，那速度自然就快不起来了。而且，一直套Cloudflare降低网站国内访问速度和质量，和在关键时刻因为DDoS临时切换Cloudflare降低速度和质量，哪个更好还是看得出来的。当然，怎么玩是建立在你有两个服务器到的基础上，如果只有一个，打寄了就寄了，一点办法都没有。其实之前就已经做好被打的预案了，但是实际发生了被DDoS的时候，还是有点手忙脚乱，这下能让我的网站更加完善了。最主要还是想保持网站不掉线罢了。
二次开发 Typecho 后台服务器状态查看插件
发现Typecho后台服务器状态插件在翻Typecho插件的时候，我偶然发现了两双筷子写的Typecho后台服务器状态查看插件，于是就尝试了一下下。这个插件在桌面端网页没问题，感觉还挺不错的。但是一旦变成移动端，后台的布局就会出现问题，逼死强迫症。而且，CPU利用率有时候还会变成0.9999999999%这种离谱的数据，我寻思着就不能取两位小数好看一点吗？于是，我决定，自己修改代码，把服务器信息移动到页面底部的位置，并且修改显示数据的逻辑，让信息看着更加舒适一点。（其实也不完全是我写，是面向LLM编程 X）修改代码的过程首先，需要把注册方法的地方改为页面底部Typecho_Plugin::factory('admin/footer.php')->begin = array('LinuxInfo_Plugin', 'render');然后就是修改一下插入的样式：echo <<<STR $info_str STR;因为我觉的系统信息用鼠标悬浮预览不明显，于是修改为直接独立一行把信息全部展示：return <<<SYS_INFO 服务器信息: 持续运行：{$uptime}，Web服务器：{$_SERVER['SERVER_SOFTWARE']}，PHP 版本：{$php['version']}，SAPI 接口：{$php['sapi']}，PHP内存限制：{$php['mem_max']}，上传文件限制：{$php['up_max']} SYS_INFO;以及让LLM帮忙完善了CPU信息获取的逻辑：private static function cpu_info(){ function GetCoreInformation() { $data = file('/proc/stat'); $cores = []; foreach( $data as $line ) { if( preg_match('/^cpu[0-9]/', $line) ) { $info = explode(' ', $line); $cores[] = [ 'user' => $info[1], 'nice' => $info[2], 'sys' => $info[3], 'idle' => $info[4], 'iowait' => $info[5], 'irq' => $info[6], 'softirq' => $info[7] ]; } } return $cores; } function GetCpuPercentages($stat1, $stat2) { if(count($stat1) !== count($stat2)){ return; } $cpus = []; for( $i = 0, $l = count($stat1); $i < $l; $i++) { $dif = []; $dif['user'] = $stat2[$i]['user'] - $stat1[$i]['user']; $dif['nice'] = $stat2[$i]['nice'] - $stat1[$i]['nice']; $dif['sys'] = $stat2[$i]['sys'] - $stat1[$i]['sys']; $dif['idle'] = $stat2[$i]['idle'] - $stat1[$i]['idle']; $dif['iowait'] = $stat2[$i]['iowait'] - $stat1[$i]['iowait']; $dif['irq'] = $stat2[$i]['irq'] - $stat1[$i]['irq']; $dif['softirq'] = $stat2[$i]['softirq'] - $stat1[$i]['softirq']; $total = array_sum($dif); $cpu = []; foreach($dif as $x=>$y) $cpu[$x] = round($y / $total * 100, 2); $cpus['CPU' . $i] = $cpu; } return $cpus; } $stat1 = GetCoreInformation(); sleep(1); $stat2 = GetCoreInformation(); $data = GetCpuPercentages($stat1, $stat2); $overall_cpu_usage = 0; $core_details = []; foreach ($data as $core => $usage) { $core_usage = 100 - $usage['idle']; $overall_cpu_usage += $core_usage; $core_details[] = "{$core}: " . number_format($core_usage, 2) . "%"; } $overall_cpu_usage = round($overall_cpu_usage / count($data), 2); $core_details_str = implode(", ", $core_details); $sys_load = sys_getloadavg(); $sys_load_1min = number_format($sys_load[0], 3); $sys_load_5min = number_format($sys_load[1], 3); $sys_load_15min = number_format($sys_load[2], 3); return <<<CPU_INFO CPU: {$overall_cpu_usage} % CPU_INFO; }原来的逻辑只是判断单个CPU的占有率，当CPU数量为多个的时候就会不准确，这里我优化成了整体利用率，并且悬停时会显示每个CPU的占用率信息。改造效果虽然不是彻底重构，但是我对改造后的插件还是比较满意的。手机的网页端也舒服了。修改后的插件分享修改后的插件已经放到了GitHub：Typecho-LinuxInfo反正源码也不多，我这里也可以直接放出来：Plugin.php:<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; /** * 查看系统信息，仅限 Linux 系统，基于两双筷子的插件进行二次开发，使用帮助 * * @package LinuxInfo * @author GTX690战术核显卡导弹 * @version 1.1.0 * @link https://www.nekopara.uk */ class LinuxInfo_Plugin implements Typecho_Plugin_Interface { // 插件名称 protected static $plugin_name = 'LinuxInfo'; /** * 激活插件方法,如果激活失败,直接抛出异常 * * @access public * @return void * @throws Typecho_Plugin_Exception */ public static function activate() { // 如果当前系统不是 linux 则抛出错误 if(PHP_OS != 'Linux') { throw new Typecho_Plugin_Exception(_t("此插件仅支持 Linux 系统")); } // 在后端页面底部 注册方法 Typecho_Plugin::factory('admin/footer.php')->begin = array('LinuxInfo_Plugin', 'render'); } /** * 禁用插件方法,如果禁用失败,直接抛出异常 * * @static * @access public * @return void * @throws Typecho_Plugin_Exception */ public static function deactivate(){} /** * 获取插件配置面板 * * @access public * @param Typecho_Widget_Helper_Form $form 配置面板 * @return void */ public static function config(Typecho_Widget_Helper_Form $form) { /** 分类名称 */ $show_item = array( 'cpu_info' => _t('CPU使用情况') . '（ CPU 使用率、系统负载）', 'mem_info' => _t('内存使用情况') . '（内存使用率、SWAP交换分区）', 'disk_info' => _t('磁盘使用情况') . '（磁盘使用率、磁盘已用空间、磁盘总空间）', 'sys_info' => _t('服务器信息') . ' （系统运行时间、服务器软件、PHP 版本、SAPI 接口、PHP 内存限制、上传文件限制）', ); // 设置默认选中 $show_option_value = ['cpu_info','mem_info','disk_info']; $ShowInfo = new Typecho_Widget_Helper_Form_Element_Checkbox('show_items',$show_item, $show_option_value, _t('选择显示系统信息')); $form->addInput($ShowInfo->multiMode()); $separator = new Typecho_Widget_Helper_Form_Element_Text('separator', NULL, ' ★ ', _t('分隔符'), '设置数据项分割字符'); $form->addInput($separator); } /** * 个人用户的配置面板 * * @access public * @param Typecho_Widget_Helper_Form $form * @return void */ public static function personalConfig(Typecho_Widget_Helper_Form $form){} /** * 插件实现方法 * * @access public * @return void */ public static function render() { $show_items = Typecho_Widget::widget('Widget_Options')->plugin(self::$plugin_name)->show_items; $separator = Typecho_Widget::widget('Widget_Options')->plugin(self::$plugin_name)->separator; // 获取 linux 系统消息 $info_str = ''; $show_items_max = count($show_items) - 1; foreach ($show_items as $show_item_k => $show_item) { $method = $show_item; if (method_exists(__CLASS__, $method)) { $info_str .= self::$method(); } if ($show_items_max != $show_item_k) { $info_str .= $separator; } } echo <<<STR $info_str STR; } // 获取CPU信息 private static function cpu_info(){ function GetCoreInformation() { $data = file('/proc/stat'); $cores = []; foreach( $data as $line ) { if( preg_match('/^cpu[0-9]/', $line) ) { $info = explode(' ', $line); $cores[] = [ 'user' => $info[1], 'nice' => $info[2], 'sys' => $info[3], 'idle' => $info[4], 'iowait' => $info[5], 'irq' => $info[6], 'softirq' => $info[7] ]; } } return $cores; } function GetCpuPercentages($stat1, $stat2) { if(count($stat1) !== count($stat2)){ return; } $cpus = []; for( $i = 0, $l = count($stat1); $i < $l; $i++) { $dif = []; $dif['user'] = $stat2[$i]['user'] - $stat1[$i]['user']; $dif['nice'] = $stat2[$i]['nice'] - $stat1[$i]['nice']; $dif['sys'] = $stat2[$i]['sys'] - $stat1[$i]['sys']; $dif['idle'] = $stat2[$i]['idle'] - $stat1[$i]['idle']; $dif['iowait'] = $stat2[$i]['iowait'] - $stat1[$i]['iowait']; $dif['irq'] = $stat2[$i]['irq'] - $stat1[$i]['irq']; $dif['softirq'] = $stat2[$i]['softirq'] - $stat1[$i]['softirq']; $total = array_sum($dif); $cpu = []; foreach($dif as $x=>$y) $cpu[$x] = round($y / $total * 100, 2); $cpus['CPU' . $i] = $cpu; } return $cpus; } $stat1 = GetCoreInformation(); sleep(1); $stat2 = GetCoreInformation(); $data = GetCpuPercentages($stat1, $stat2); $overall_cpu_usage = 0; $core_details = []; foreach ($data as $core => $usage) { $core_usage = 100 - $usage['idle']; $overall_cpu_usage += $core_usage; $core_details[] = "{$core}: " . number_format($core_usage, 2) . "%"; } $overall_cpu_usage = round($overall_cpu_usage / count($data), 2); $core_details_str = implode(", ", $core_details); $sys_load = sys_getloadavg(); $sys_load_1min = number_format($sys_load[0], 3); $sys_load_5min = number_format($sys_load[1], 3); $sys_load_15min = number_format($sys_load[2], 3); return <<<CPU_INFO CPU: {$overall_cpu_usage} % CPU_INFO; } // 获取内存信息 private static function mem_info(){ if (false === ($str = @file("/proc/meminfo"))) return false; $str = implode("", $str); preg_match_all("/MemTotal\s{0,}\:+\s{0,}([\d\.]+).+?MemFree\s{0,}\:+\s{0,}([\d\.]+).+?Cached\s{0,}\:+\s{0,}([\d\.]+).+?SwapTotal\s{0,}\:+\s{0,}([\d\.]+).+?SwapFree\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $buf); preg_match_all("/Buffers\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $buffers); $mem_info['memTotal'] = round($buf[1][0]/1024, 2); $mem_info['memFree'] = round($buf[2][0]/1024, 2); $mem_info['memBuffers'] = round($buffers[1][0]/1024, 2); $mem_info['memCached'] = round($buf[3][0]/1024, 2); $mem_info['memUsed'] = $mem_info['memTotal']-$mem_info['memFree']; $mem_info['memPercent'] = (floatval($mem_info['memTotal'])!=0)?round($mem_info['memUsed']/$mem_info['memTotal']*100,2):0; $mem_info['memRealUsed'] = $mem_info['memTotal'] - $mem_info['memFree'] - $mem_info['memCached'] - $mem_info['memBuffers']; //真实内存使用 $mem_info['memRealFree'] = $mem_info['memTotal'] - $mem_info['memRealUsed']; //真实空闲 $mem_info['memRealPercent'] = (floatval($mem_info['memTotal'])!=0)?round($mem_info['memRealUsed']/$mem_info['memTotal']*100,2):0; //真实内存使用率 $mem_info['memCachedPercent'] = (floatval($mem_info['memCached'])!=0)?round($mem_info['memCached']/$mem_info['memTotal']*100,2):0; //Cached内存使用率 $mem_info['swapTotal'] = round($buf[4][0]/1024, 2); $mem_info['swapFree'] = round($buf[5][0]/1024, 2); $mem_info['swapUsed'] = round($mem_info['swapTotal']-$mem_info['swapFree'], 2); $mem_info['swapPercent'] = (floatval($mem_info['swapTotal'])!=0)?round($mem_info['swapUsed']/$mem_info['swapTotal']*100,2):0; $mem_per = round($mem_info['memRealUsed'] / $mem_info['memTotal'] * 100, 2); return <<<MEM_INFO 内存: {$mem_per} % MEM_INFO; } // 获取磁盘信息 private static function disk_info(){ $dt = round(@disk_total_space(".")/(1024*1024*1024), 2); //总 $df = round(@disk_free_space(".")/(1024*1024*1024), 2); //可用 $du = $dt-$df; //已用 $disk_use = round($du / $dt * 100, 2); return <<<DISK_INFO 硬盘: {$disk_use} % DISK_INFO; } // 获取 系统信息 private static function sys_info() { if (false === ($str = @file("/proc/uptime"))) return false; $str = explode(" ", implode("", $str)); $str = trim($str[0]); $min = $str / 60; $hours = $min / 60; $days = floor($hours / 24); $hours = floor($hours - ($days * 24)); $min = floor($min - ($days * 60 * 24) - ($hours * 60)); if ($days !== 0) $uptime = $days."天"; if ($hours !== 0) $uptime .= $hours."小时"; $uptime .= $min."分钟"; $php['version'] = PHP_VERSION; $php['sapi'] = php_sapi_name(); $php['mem_max'] = get_cfg_var('memory_limit'); $php['up_max'] = get_cfg_var('upload_max_filesize'); return <<<SYS_INFO 服务器信息: 持续运行：{$uptime}，Web服务器：{$_SERVER['SERVER_SOFTWARE']}，PHP 版本：{$php['version']}，SAPI 接口：{$php['sapi']}，PHP内存限制：{$php['mem_max']}，上传文件限制：{$php['up_max']} SYS_INFO; } }优先使用Github上面的吧，万一那天我修复一些问题，可能只会修改Github上面的仓库的内容而忘记修改这篇文章。当然，我希望以后不会出现问题。使用方法将GitHub上面的Plugin.php或者这个页面的Plugin.php内容保存下载，放到Typecho的插件目录usr/plugins/下的LinuxInfo文件夹（需要自己创建），例如：typecho/usr/plugins/LinuxInfo/Plugin.php，进入Typecho后台启用插件即可。默认不显示系统详细信息，需要自己勾选显示才会展示。不过，需要注意的是，这个插件仅可用于Linux系统，Windows系统不适用。谁用Windows做服务器啊？！！不会吧不会吧？最后还是要感谢一下两双筷子写出来这个有意思的插件。