diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 4983997..9b492c1 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -65,6 +65,8 @@ export default defineConfig({ { text: '开发', items: [ + {text: '日志记录最佳实践', link: '/开发/日志记录最佳实践'}, + {text: 'Nginx配置文件说明', link: '/开发/Nginx配置文件说明'}, {text: '搭建K8S集群环境', link: '/开发/搭建K8S集群环境'}, {text: '抖音评论区设计', link: '/开发/抖音评论区设计'}, {text: 'MYDB操作手册', link: '/开发/MYDB操作手册'}, diff --git "a/docs/\345\274\200\345\217\221/Nginx\351\205\215\347\275\256\346\226\207\344\273\266\350\257\264\346\230\216.md" "b/docs/\345\274\200\345\217\221/Nginx\351\205\215\347\275\256\346\226\207\344\273\266\350\257\264\346\230\216.md" new file mode 100644 index 0000000..46a78d0 --- /dev/null +++ "b/docs/\345\274\200\345\217\221/Nginx\351\205\215\347\275\256\346\226\207\344\273\266\350\257\264\346\230\216.md" @@ -0,0 +1,202 @@ +# Nginx配置文件说明 + +```nginx +user root; # 设置运行Nginx的用户为root +worker_processes auto; # 自动设置工作进程的数量,通常等于CPU核心数 +error_log /usr/local/nginx/logs/error.log; # 错误日志路径 +pid /usr/local/nginx/logs/nginx.pid; # 存储Nginx主进程ID的文件路径 +include /usr/share/nginx/modules/*.conf; # 包含额外的模块配置文件 +events { + worker_connections 1024; # 每个工作进程可以同时处理的最大连接数 +} +http { + + # 定义访问日志格式,$remote_addr等是变量,代表客户端地址等信息 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + access_log /usr/local/nginx/logs/access.log main; # 访问日志路径及使用的日志格式(main),可以关掉 + sendfile on; # 启用高效文件传输模式,用于提高大文件传输效率 + tcp_nopush on; # 禁止TCP推送,减少网络延迟 + tcp_nodelay on; # 禁用Nagle算法,减少数据包延迟 + keepalive_timeout 65; # 连接保持超时时间,单位秒 + types_hash_max_size 2048; # 设置类型哈希表的最大大小,优化MIME类型查找 + client_max_body_size 20M; # 设置客户端请求体的最大值,超过此限制的请求将被拒绝,默认为1m + + # 轮询 + upstream server_group { + server 101.31.7.24:8080; # node1 + server 101.31.7.25:8081; # node2 + server 101.31.7.26:8082; # node3 + } + + # 加权轮询 + upstream server_group { + server 101.31.7.24:8080 weight=3; # node1 + server 101.31.7.25:8081 weight=5; # node2 + server 101.31.7.26:8082 weight=10; # node3 + } + + # IP哈希分配 + upstream server_group { + ip_hash; + server 101.31.7.24:8080; # node1 + server 101.31.7.25:8081; # node2 + server 101.31.7.26:8082; # node3 + } + + # 最少连接 + upstream server_group { + least_conn; + server 101.31.7.24:8080; # node1 + server 101.31.7.25:8081; # node2 + server 101.31.7.26:8082; # node3 + } + + server { + + gzip on; # 开启Gzip压缩,减少传输内容体积 + gzip_buffers 4 16k; # 设置Gzip缓冲区大小 + gzip_comp_level 6; # Gzip压缩级别,范围从1到9,数值越大压缩效果越好但越占用CPU资源 + gzip_min_length 100k; # 设置允许压缩的最小字节数,小于该值的响应不会被压缩 + gzip_http_version 1.1; # 设置支持的HTTP协议版本,默认是1.1 + gzip_vary on; # 添加Vary: Accept-Encoding HTTP头,告知代理服务器识别是否启用Gzip压缩 + gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; # 设置需要压缩的文件类型 + + listen 8888; # 监听端口 + server_name 101.31.7.23; # 服务器名称,支持多个域名或IP,空格分隔 + + location /api { + # proxy_pass 端口后面加路径,该路径就会替换location中的路径,有/也会替换 + # 没加路径就只替换访问路径的ip和端口 + proxy_pass http://101.31.7.23:8180/; + } + location / { + root /home/zcloud/applications/iomp-web; # 前端部署目录 + index index.html index.htm; + } + location /images/ { + alias /home/zcloud/file/; + autoindex off; + } + } + } +} +``` + +## 配置项的含义 + +1. **user root;** + + - 设置 Nginx 进程的用户为 `root`。 +2. **worker_processes auto;** + - 自动设置工作进程的数量,通常设置为 CPU 核心数。 +3. **error_log /usr/local/nginx/logs/error.log;** + - 设置错误日志的路径。 +4. **pid /usr/local/nginx/logs/nginx.pid;** + - 设置 Nginx 进程的 PID 文件路径。 +5. **include /usr/share/nginx/modules/*.conf;** + - 包含 Nginx 模块的配置文件。 +6. **events { ... }** + - 配置事件处理模块。 + - `worker_connections 1024;` 设置每个工作进程的最大连接数。 +7. **http { ... }** + - 配置 HTTP 服务器的行为。 + - `log_format main ...;` 定义访问日志的格式。 + - `access_log /usr/local/nginx/logs/access.log main;` 设置访问日志的路径和格式。 +8. **sendfile on;** + - 启用 sendfile 功能,提高文件传输效率。 +9. **tcp_nopush on;** + - 禁止 TCP 推送,减少网络延迟。 +10. **tcp_nodelay on;** + - 禁用 Nagle 算法,减少数据包延迟。 +11. **keepalive_timeout 65;** + - 设置保持连接的超时时间,单位为秒。 +12. **types_hash_max_size 2048;** + - 设置 MIME 类型哈希表的最大大小。 +13. **client_max_body_size 20M;** + - 设置客户端请求体的最大大小。 +14. **server { ... }** + - 定义一个服务器块。 + - `listen 8888;` 监听 8888 端口。 + - `server_name 101.31.7.23;` 设置服务器名称。 +15. **gzip on;** + - 开启 Gzip 压缩。 +16. **gzip_buffers 4 16k;** + - 设置 Gzip 压缩所需的缓冲区大小。 +17. **gzip_comp_level 6;** + - 设置 Gzip 压缩级别。 +18. **gzip_min_length 100k;** + - 设置允许压缩的最小字节数。 +19. **gzip_http_version 1.1;** + - 设置支持的 HTTP 版本。 +20. **gzip_types ...;** + - 设置需要压缩的文件类型。 +21. **gzip_vary on;** + - 添加 `Vary: Accept-Encoding` HTTP 头。 +22. **location /api { ... }** +- 定义一个位置块,用于处理 `/api` 路径下的请求。 + +- `proxy_pass http://101.31.7.23:8180/;` 将请求代理到指定的后端服务器。 +23. **location / { ... }** + + - 定义一个位置块,用于处理根路径下的请求。 + + - `root /home/zcloud/applications/iomp-web;` 设置前端部署目录。 + + - `index index.html index.htm;` 设置默认索引文件。 +24. **location /images/ { ... }** + - 定义一个位置块,用于处理 `/images/` 路径下的请求。 + - `alias /home/zcloud/file/;` 设置别名目录。 + - `autoindex off;` 关闭自动索引功能。 + +- **alias**:将请求的 URI 替换为指定路径。例如,`/images/logo.png` 会被替换为 `/home/zcloud/file/logo.png`。 +- **root**:将请求的 URI 直接附加到指定路径后面。例如,`/images/logo.png` 会被附加到 `/home/zcloud/applications/iomp-web/images/logo.png`。 + +## 常用命令 + +### 1. 重新加载配置文件 +```sh +./nginx -s reload +``` +- **用途**:在不中断服务的情况下,重新加载 Nginx 的配置文件。 + +### 2. 检查配置文件是否正确 +```sh +./nginx -t +``` +- **用途**:检查 Nginx 配置文件的语法是否正确,确保配置无误。 + +### 3. 启动 Nginx +```sh +./nginx +``` +- **用途**:启动 Nginx 服务器。 + +### 4. 快速停止 Nginx +```sh +./nginx -s stop +``` +- **用途**:快速停止 Nginx 服务器,立即关闭所有连接。 + +### 5. 完整有序地停止 Nginx +```sh +./nginx -s quit +``` +- **用途**:完整有序地停止 Nginx 服务器,等待所有请求结束后再关闭服务器。 + +### 6. 查找 Nginx 配置文件 +```sh +find / -name nginx.conf +``` +- **用途**:在系统中查找名为 `nginx.conf` 的配置文件。 + +### 总结 + +- **重新加载配置文件**:`./nginx -s reload` +- **检查配置文件**:`./nginx -t` +- **启动 Nginx**:`./nginx` +- **快速停止 Nginx**:`./nginx -s stop` +- **有序停止 Nginx**:`./nginx -s quit` +- **查找配置文件**:`find / -name nginx.conf` + diff --git "a/docs/\345\274\200\345\217\221/index.md" "b/docs/\345\274\200\345\217\221/index.md" index 0f0ca4e..e27d2c5 100644 --- "a/docs/\345\274\200\345\217\221/index.md" +++ "b/docs/\345\274\200\345\217\221/index.md" @@ -4,13 +4,15 @@ ## 目录 -1. [搭建K8S集群环境](./搭建K8S集群环境.md) -2. [抖音评论区设计](./抖音评论区设计.md) -3. [MYDB操作手册](./MYDB操作手册.md) -4. [批量导出zip压缩包和Excel表格](./批量导出zip压缩包和Excel表格.md) -5. [阿里云OSS和内容安全Java实现参考代码](./阿里云OSS和内容安全Java实现参考代码.md) -6. [SSO单点登录的实现原理](./SSO单点登录的实现原理.md) -7. [ThreadLocal原理和使用](./ThreadLocal原理和使用.md) -8. [对Java中多态的理解](./对Java中多态的理解.md) -9. [JDBC和JAVA类型对比](./JDBC和JAVA类型对比.md) -10. [C++的STL库常见函数](./C++的STL库常见函数.md) \ No newline at end of file +1. [日志记录最佳实践](./日志记录最佳实践.md) +2. [Nginx配置文件说明](./Nginx配置文件说明.md) +3. [搭建K8S集群环境](./搭建K8S集群环境.md) +4. [抖音评论区设计](./抖音评论区设计.md) +5. [MYDB操作手册](./MYDB操作手册.md) +6. [批量导出zip压缩包和Excel表格](./批量导出zip压缩包和Excel表格.md) +7. [阿里云OSS和内容安全Java实现参考代码](./阿里云OSS和内容安全Java实现参考代码.md) +8. [SSO单点登录的实现原理](./SSO单点登录的实现原理.md) +9. [ThreadLocal原理和使用](./ThreadLocal原理和使用.md) +10. [对Java中多态的理解](./对Java中多态的理解.md) +11. [JDBC和JAVA类型对比](./JDBC和JAVA类型对比.md) +12. [C++的STL库常见函数](./C++的STL库常见函数.md) \ No newline at end of file diff --git "a/docs/\345\274\200\345\217\221/\346\227\245\345\277\227\350\256\260\345\275\225\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/docs/\345\274\200\345\217\221/\346\227\245\345\277\227\350\256\260\345\275\225\346\234\200\344\275\263\345\256\236\350\267\265.md" new file mode 100644 index 0000000..256b60b --- /dev/null +++ "b/docs/\345\274\200\345\217\221/\346\227\245\345\277\227\350\256\260\345\275\225\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -0,0 +1,276 @@ +# 日志记录最佳实践 + +image-20250109163531835 + +## 基本原则 + +- ❌ 避免记录重复、无意义的日志,确保每一条日志都有助于调试 +- 🔄 避免在循环中使用日志,以免产生大量冗余数据 +- 📘 只记录必要的参数,而不是完整的对象结构 + +## 风险防范 + +- 🌐 将英文作为日志语言,以预防编码问题 +- 🔨 不滥用Error级别的日志,减轻紧急响应负担 + +- 🚫 不要在日志记录过程中阻断业务流程 + + ###### 错误方式 + + 不要用可能会发生空指针异常的对象或方法 + + ```java + public void createShop(Shop shop){ + log.info("create and print log: {}", shop.getName().toLowerCase()); + } + ``` + +## 性能保障 + +- 💡 使用专用日志库,而非标准输出,来提高性能并便于管理 + + ###### 错误方式 + + ```java + public void love() { + System.out.println("i love java..."); + // 业务逻辑 + ... + } + ``` + + ###### 原因:println 内部实现是带锁的 + + ```java + public void println(boolean x) { + synchronized (this) { + print(x); + newLine(); + } + } + ``` + + ##### 正确方式 + + ```java + public void love() { + log.info("i love java..."); + // 业务逻辑 + ... + } + ``` + +- 🔒 统一日志框架减少依赖耦合,提升维护便利性 + + 禁用`Log4j`或`LogBack`的API输出日志,而是用`Slf4j`,防止代码和日志强耦合 + + ```java + // 直接使用 + @Slf4j + public class Test {} + + // 或者直接引入 + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + private static final Logger log = LoggerFactory.getLogger(xxx.class); + ``` + +- ⚙️ 根据日志级别控制输出,防止不必要的资源耗费 + + ##### 错误方式 + + ```java + public void hello(String name) { + log.trace("trace hello" + name); + log.debug("debug hello" + name); + log.info("info hello" + name); + // 业务逻辑 + ... + } + ``` + + ##### 正确方式 + + 强烈建议使用占位符来输出内容,防止执行字符串拼接操作,浪费系统资源 + + ```java + public void hello() { + if (log.isTraceEnabled()) { + log.trace("trace hello {}", name); + } + if (log.isDebugEnabled()) { + log.debug("debug hello {}", name); + } + if (log.isInfoEnabled()) { + log.info("info hello {}", name); + } + // 业务逻辑 + ... + } + ``` + +- 👾 错误地使用`e.printStackTrace()`,可能导致严重的资源耗尽 + + ###### 错误方式 + + ```java + public void hello() { + try { + // 业务逻辑 + ... + } catch (Exception e) { + e.printStackTrace(); + } + } + ``` + + ###### 原因:`e.printStackTrace()`会将产生的字符串存放到字符串常量池,降低可用内存空间 + + ```java + public void printStackTrace() { + printStackTrace(System.err); + } + ``` + + ##### 正确方式 + + ```java + public void hello() { + try { + // 业务逻辑 + ... + } catch (Exception e) { + log.error("execute failed", e); + } + } + ``` + +- 🐛 在日志中禁用复杂对象序列化以防潜在风险 + + ###### 错误方式 + + 禁止使用JSON序列化工具,这些工具是通过调用对象的get方法将对象进行序列化的,如果对象的get方法被重写则可能会面临抛出异常的风险 + + ```java + public void hello(Object data) { + log.info("print log, data={}", JSON.toJSONString(data)); + // 业务逻辑 + ... + } + ``` + + ##### 正确方式 + + 推荐自定义toString 方法 + + ```java + public void hello() { + log.info("hello and print log, data={}", data); + } + ``` + +## 开发简化 + +- 🕵️‍♂️ 提供详细异常信息有助于问题追踪 + + ###### 错误方式 + + ```java + public void hello() { + try { + // 业务逻辑 + ... + } catch (Exception e) { + // 没有打印异常 e,无法定位出现什么类型的异常 + log.error("execute failed"); + // 没有记录详细的堆栈异常信息,只记录错误基本描述信息,不利于排查问题 + log.error("execute failed", e.getMessage()); + } + } + ``` + + ##### 正确方式 + + ```java + public void hello() { + try { + // 业务逻辑 + ... + } catch (Exception e) { + log.error("execute failed", e); + } + } + ``` + +- 💼 记录关键函数的输入/输出,增强排错能力 + + ```java + public String doSth(String id, String type) { + log.info("start: {}, {}", id, type); // 入口处记录初始值 + String res = process(id, type); + log.info("end: {}, {}, {}", id, type, res); // 出口处记录结果 + } + ``` + +- 🤝 利用条件语句前后的日志,简化程序跟踪 + + ```java + public void doSth() { + ... + if (user.isVip()) { + log.info("vip member, Id: {}, start vip", userId()); + // 会员逻辑 + } else { + log.info("该用户是非会员, Id: {}, 开始处理非会员逻辑", userId()); + // 非会员逻辑 + } + ... + } + ``` + +- 📍 添加Trace ID用于关联相关事件流,串联一次性的日志链路 + + ```java + import javax.servlet.ServletResponse; + import javax.servlet.http.HttpServletRequest; + import java.io.IOException; + import java.util.UUID; + + @Component + public class MDCCFilter implements Filter { + + private static final String TRACE_ID = "traceId"; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, javax.servlet.ServletException { + try { + // 为每个请求生成唯一的 Trace ID + String traceId = UUID.randomUUID().toString(); + MDC.put(TRACE_ID, traceId); + + // 如果需要,可以从请求中提取更多上下文信息,例如用户 ID + if (request instanceof HttpServletRequest) { + HttpServletRequest httpRequest = (HttpServletRequest) request; + String userId = httpRequest.getHeader("X-User-Id"); // 假设用户 ID 在请求头中 + + if (userId != null) { + MDC.put("userId", userId); + } + } + + // 执行后续的 Filter 链 + chain.doFilter(request, response); + } finally { + // 清理 MDC,避免线程数据污染 + MDC.clear(); + } + } + } + + // 日志格式 + %d{yyyy-MM-dd HH:mm:ss} [%thread] [%X{traceId}] + %logger{36} - %msg%n + ... + ```