← 返回面试目录

🐧 Linux 与部署实战指南

命令速查 · Shell脚本 · Docker/Nginx · CI/CD · 日志排查 · 线上应急

Linux命令 Shell脚本 Docker Nginx CI/CD 线上应急 日志排查

先理解:Linux 命令到底在干嘛?

Linux 服务器 = 一栋办公大楼。你用 SSH 登录服务器,就像刷卡进了大楼当管理员。 你需要知道:哪个房间在干嘛(进程管理)、哪个房间漏水了(日志排查)、 哪层楼的储藏室快塞满了(磁盘检查)、大楼的门和走廊通不通(网络排查)。 Linux 命令就是你的管理工具箱。

想象一个真实的下午…

周五下午 5 点 50 分,你正准备下班。突然群里炸了:

运营@小李:"线上下单报错了!用户投诉刷屏了!"
测试@小王:"我这边也访问不了,页面一直转圈!"
主管:@你 快看看服务器什么情况

这时候你需要的不是翻教科书,而是肌肉记忆里那几条救命的 Linux 命令。 下面这些命令不是让你背的,是让你在出事的时候能 60 秒内定位问题的。 每个命令后面都标注了:什么场景用、为什么这么用。

1. Linux 常用命令速查(手把手)

记忆口诀:文件操作像在你自己电脑上整理文件夹;进程管理像任务管理器(Ctrl+Shift+Esc);磁盘检查像看手机存储空间;网络排查像测网速+ping。

📁 文件操作

什么时候用?找配置文件、看日志文件、备份配置、清理旧日志。你 90% 的文件操作就是 cd 到目录、tail -f 看日志、grep 搜报错。

命令说明示例
ls -la列出所有文件含隐藏文件ls -la /home/app/
cd切换目录cd /var/log && cd -
cp复制文件/目录cp -r config/ config_bak/
mv移动/重命名mv app.jar app_20240101.jar
rm删除文件/目录rm -rf /tmp/old_logs/
find查找文件find / -name "app.log" 2>/dev/null
grep搜索文本内容grep -rn "ERROR" /var/log/app/
cat查看文件内容cat /etc/nginx/nginx.conf
tail -f实时查看日志尾部tail -f /var/log/app/app.log
less分页查看大文件less +F /var/log/syslog
Bash# 常用组合:查找并统计ERROR数量 grep -c "ERROR" /var/log/app/app.log # 查看最近100行日志 tail -n 100 /var/log/app/app.log # 查找大于100M的日志文件 find /var/log -name "*.log" -size +100M

⚙️ 进程管理

什么时候用?服务卡住了、CPU 飙高了、端口被占了——这就是 Linux 版的"任务管理器"。先用 top 找到最占资源的进程,再用 kill 处理。

命令说明示例
ps aux查看所有进程ps aux | grep java
top实时进程监控top -p $(pgrep -d',' java)
htop增强版top(需安装)htop --sort=PERCENT_MEM
kill优雅终止进程kill 12345
kill -9强制杀死进程kill -9 12345
lsof -i :8080查看端口占用lsof -i :8080
netstat -tlnp查看监听端口netstat -tlnp | grep 8080
Bash# 查找占用8080端口的进程并杀掉 lsof -i :8080 | grep LISTEN | awk '{print $2}' | xargs kill -9 # 查看Java进程的内存使用 ps aux | grep java | awk '{printf "PID: %s MEM: %s%% CPU: %s%%\n", $2, $4, $3}' # 查看进程树 pstree -p | grep java

💾 磁盘相关

什么时候用?磁盘满了!这是线上最常见的事故之一——日志文件疯狂增长把磁盘写满,导致数据库写入失败、服务无响应。养成习惯:每周看一次 df -h

命令说明示例
df -h查看磁盘使用情况df -h /data
du -sh *查看目录大小du -sh /var/log/*
ncdu交互式磁盘分析(需安装)ncdu /var/log
Bash# 查看磁盘使用率,超过80%报警 df -h | awk 'NR>1 && int($5)>80 {print $1, $5, "使用率过高!"}' # 找出最大的10个文件 du -ah /var/log | sort -rh | head -10 # 快速清理旧日志(保留7天) find /var/log/app -name "*.log.*" -mtime +7 -delete

🌐 网络相关

什么时候用?服务之间连不上、接口超时、DNS 解析问题。最常见场景:"为什么我的 Java 服务连不上 MySQL?"——先 ping 一下看通不通,再 telnet 看 3306 端口开没开。

命令说明示例
curlHTTP请求工具curl -X GET http://localhost:8080/health
wget下载文件wget https://example.com/app.jar
ping测试网络连通性ping -c 4 8.8.8.8
telnet测试端口连通性telnet db.example.com 3306
nslookupDNS解析查询nslookup api.example.com
ssh远程登录ssh -i key.pem user@192.168.1.100
scp远程拷贝文件scp app.jar user@server:/opt/app/
Bash# 测试接口响应时间和状态码 curl -o /dev/null -s -w "HTTP状态: %{http_code}\n耗时: %{time_total}s\n" \ http://localhost:8080/api/health # 批量检查多台服务器连通性 for host in 192.168.1.{1..10}; do ping -c 1 -W 1 $host >/dev/null 2>&1 && echo "$host 在线" || echo "$host 离线" done # SCP上传整个目录 scp -r ./dist/ user@server:/var/www/html/

🔐 权限相关

命令说明示例
chmod修改文件权限chmod 755 deploy.sh
chown修改文件所有者chown -R app:app /opt/app/
sudo以管理员执行sudo systemctl restart nginx
Bash# 给脚本加执行权限 chmod +x deploy.sh # 递归修改目录所有者 chown -R www-data:www-data /var/www/html/ # 常用权限对照 # 755 = rwxr-xr-x (所有者可读写执行,其他人可读执行) # 644 = rw-r--r-- (所有者可读写,其他人只读) # 600 = rw------- (仅所有者可读写)

📦 压缩解压

命令说明示例
tar -czf压缩目录为 .tar.gztar -czf backup.tar.gz /opt/app/
tar -xzf解压 .tar.gztar -xzf backup.tar.gz -C /opt/
unzip解压 .zipunzip -o app.zip -d /opt/app/
Bash# 打包并排除日志目录 tar -czf app-backup-$(date +%Y%m%d).tar.gz \ --exclude='*.log' --exclude='logs/' /opt/app/ # 解压到指定目录 tar -xzf app-backup-20240101.tar.gz -C /opt/restore/ # 查看压缩包内容(不解压) tar -tzf backup.tar.gz | head -20

2. Shell 脚本模板

🚀 服务管理脚本(start/stop/restart)

service.sh#!/bin/bash # 通用Java服务管理脚本 APP_NAME="my-app" APP_JAR="/opt/app/my-app.jar" APP_CONF="/opt/app/application.yml" LOG_DIR="/var/log/app" PID_FILE="/var/run/${APP_NAME}.pid" JAVA_OPTS="-Xms512m -Xmx2g -XX:+UseGCPrintGCDateStamps" # 检查服务是否运行 is_running() { if [ -f "$PID_FILE" ]; then PID=$(cat $PID_FILE) if kill -0 $PID 2>/dev/null; then return 0 fi fi return 1 } # 启动服务 start() { if is_running; then echo "[WARN] ${APP_NAME} 已在运行 (PID: $(cat $PID_FILE))" return 1 fi echo "[INFO] 启动 ${APP_NAME}..." nohup java $JAVA_OPTS \ -Dspring.config.location=$APP_CONF \ -jar $APP_JAR \ >> ${LOG_DIR}/startup.log 2>&1 & echo $! > $PID_FILE echo "[INFO] ${APP_NAME} 启动成功 (PID: $!)" } # 停止服务 stop() { if ! is_running; then echo "[WARN] ${APP_NAME} 未在运行" return 1 fi PID=$(cat $PID_FILE) echo "[INFO] 停止 ${APP_NAME} (PID: ${PID})..." kill $PID # 等待最多30秒 for i in $(seq 1 30); do if ! kill -0 $PID 2>/dev/null; then echo "[INFO] ${APP_NAME} 已停止" rm -f $PID_FILE return 0 fi sleep 1 done # 超时强制杀死 kill -9 $PID rm -f $PID_FILE echo "[WARN] ${APP_NAME} 强制停止" } # 重启 restart() { stop sleep 2 start } # 主入口 case "$1" in start) start ;; stop) stop ;; restart) restart ;; status) is_running && echo "运行中 (PID: $(cat $PID_FILE))" || echo "未运行" ;; *) echo "用法: $0 {start|stop|restart|status}" ;; esac

✂️ 日志切割脚本

log-rotate.sh#!/bin/bash # 日志切割脚本 - 配合 crontab 使用 # crontab: 0 0 * * * /opt/scripts/log-rotate.sh LOG_DIR="/var/log/app" APP_NAME="my-app" KEEP_DAYS=30 DATE=$(date +%Y%m%d) # 切割日志文件 cd $LOG_DIR || exit 1 for log_file in ${APP_NAME}.log; do if [ -f "$log_file" ]; then # 压缩并重命名 mv $log_file ${log_file}.${DATE} gzip ${log_file}.${DATE} & # 创建新的空日志文件 touch $log_file echo "[INFO] $(date) 日志切割完成: ${log_file}.${DATE}.gz" fi done # 等待压缩完成 wait # 删除过期日志 find $LOG_DIR -name "${APP_NAME}.log.*.gz" \ -mtime +$KEEP_DAYS -delete echo "[INFO] $(date) 清理 ${KEEP_DAYS} 天前的旧日志完成"

🩺 健康检查脚本

health-check.sh#!/bin/bash # 服务健康检查脚本 - 可配合 cron 每5分钟执行 # */5 * * * * /opt/scripts/health-check.sh APP_NAME="my-app" HEALTH_URL="http://localhost:8080/actuator/health" ALERT_WEBHOOK="https://hooks.example.com/alert" MAX_RETRY=3 RETRY_INTERVAL=10 send_alert() { curl -s -X POST $ALERT_WEBHOOK \ -H 'Content-Type: application/json' \ -d "{\"text\":\"[ALERT] ${APP_NAME}: $1\"}" \ >/dev/null 2>&1 } check_health() { HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ --max-time 5 $HEALTH_URL) if [ "$HTTP_CODE" = "200" ]; then return 0 fi return 1 } # 主逻辑:重试机制 FAIL_COUNT=0 for i in $(seq 1 $MAX_RETRY); do if check_health; then echo "[$(date)] ${APP_NAME} 健康检查通过" exit 0 fi FAIL_COUNT=$((FAIL_COUNT + 1)) echo "[$(date)] ${APP_NAME} 健康检查失败 (${FAIL_COUNT}/${MAX_RETRY})" sleep $RETRY_INTERVAL done # 全部重试失败,发送告警 send_alert "连续 ${MAX_RETRY} 次健康检查失败,请立即处理!" echo "[$(date)] ${APP_NAME} 告警已发送" exit 1

3. Docker Compose 实战

Docker = 统一的快递站杯。不管什么口味(Java/Python/Node),都用同样的杯子和流程打包。客人拿到的永远是标准杯子,不需要关心里面是怎么做出来的。Docker Compose = 快递站套餐组合,一杯快递站+一份小食+一杯咖啡,一键下单全部上齐。

📝 完整 docker-compose.yml(Nginx + Java + MySQL + Redis)

docker-compose.yml# 完整的微服务编排模板 version: '3.8' services: # ========== Nginx 反向代理 ========== nginx: image: nginx:1.25-alpine container_name: my-nginx ports: - "80:80" - "443:443" volumes: - ./nginx/conf.d:/etc/nginx/conf.d - ./nginx/ssl:/etc/nginx/ssl - ./html:/usr/share/nginx/html depends_on: - app networks: - app-network restart: always # ========== Java 后端应用 ========== app: build: context: . dockerfile: Dockerfile container_name: my-app ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/mydb?useSSL=false - SPRING_DATASOURCE_USERNAME=root - SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD} - SPRING_REDIS_HOST=redis - SPRING_REDIS_PORT=6379 - JAVA_OPTS=-Xms512m -Xmx1g volumes: - app-logs:/var/log/app depends_on: mysql: condition: service_healthy redis: condition: service_started networks: - app-network restart: always healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] interval: 30s timeout: 10s retries: 3 # ========== MySQL 数据库 ========== mysql: image: mysql:8.0 container_name: my-mysql ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} - MYSQL_DATABASE=mydb - TZ=Asia/Shanghai volumes: - mysql-data:/var/lib/mysql - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci networks: - app-network restart: always healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 5 # ========== Redis 缓存 ========== redis: image: redis:7-alpine container_name: my-redis ports: - "6379:6379" command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD} volumes: - redis-data:/data networks: - app-network restart: always # ========== 数据卷 ========== volumes: mysql-data: driver: local redis-data: driver: local app-logs: driver: local # ========== 网络 ========== networks: app-network: driver: bridge

🔧 Docker Compose 常用命令

命令说明
docker compose up -d后台启动所有服务
docker compose down停止并删除所有容器
docker compose logs -f app实时查看app服务日志
docker compose exec app bash进入app容器内部
docker compose ps查看所有服务状态
docker compose restart app重启单个服务
docker compose build --no-cache重新构建(不使用缓存)
docker compose pull拉取最新镜像
Bash# 一键部署 docker compose up -d --build # 查看某个服务日志(最近100行) docker compose logs --tail=100 app # 进入MySQL容器执行SQL docker compose exec mysql mysql -uroot -p$DB_PASSWORD mydb # 查看容器资源使用 docker stats --no-stream

🏗️ Dockerfile 最佳实践(多阶段构建)

Dockerfile# ========== 阶段1: 构建阶段 ========== FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /app # 先复制 pom.xml,利用 Docker 缓存加速依赖下载 COPY pom.xml . RUN mvn dependency:go-offline -B # 再复制源码并构建 COPY src ./src RUN mvn package -DskipTests -B # ========== 阶段2: 运行阶段 ========== FROM eclipse-temurin:17-jre-alpine WORKDIR /app # 安全:使用非 root 用户运行 RUN addgroup -S appgroup && adduser -S appuser -G appgroup # 只复制构建产物,不包含源码和构建工具 COPY --from=builder /app/target/*.jar app.jar # 创建日志目录 RUN mkdir -p /var/log/app && chown -R appuser:appgroup /var/log/app USER appuser # 健康检查 HEALTHCHECK --interval=30s --timeout=10s --retries=3 \ CMD wget -q --spider http://localhost:8080/actuator/health || exit 1 EXPOSE 8080 ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar app.jar"]

多阶段构建让最终镜像只包含 JRE + jar 文件,通常只有 ~200MB(而非 ~800MB)。先复制 pom.xml 再复制源码,可以充分利用 Docker 层缓存,依赖不变时秒级构建。

🌍 环境变量 & 数据卷配置

.env# 环境变量文件(不提交到 Git!) # .gitignore 中添加 .env DB_PASSWORD=your_strong_password REDIS_PASSWORD=your_redis_password SPRING_PROFILES_ACTIVE=prod
数据卷说明# 命名卷(由 Docker 管理,适合持久化数据) # mysql-data → /var/lib/docker/volumes/xxx/_data # redis-data → /var/lib/docker/volumes/xxx/_data # 绑定挂载(映射宿主机目录,适合配置文件) # ./nginx/conf.d → 容器内 /etc/nginx/conf.d # ./mysql/init.sql → 容器内初始化SQL # 备份 MySQL 数据卷 docker compose exec mysql mysqldump -uroot -p$DB_PASSWORD mydb \ | gzip > backup_$(date +%Y%m%d).sql.gz

4. Nginx 配置模板

🔄 反向代理配置(含 WebSocket)

nginx/conf.d/app.conf# 反向代理 + WebSocket 支持 upstream backend { server 127.0.0.1:8080; # 可添加多个后端实现负载均衡 # server 127.0.0.1:8081; keepalive 32; } server { listen 80; server_name api.example.com; # 通用反向代理配置 location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 超时设置 proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 120s; } # WebSocket 支持 location /ws/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 3600s; # WebSocket长连接1小时 } # 静态资源缓存 location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { proxy_pass http://backend; expires 7d; add_header Cache-Control "public, immutable"; } }

🔒 HTTPS 配置(含证书)

nginx/conf.d/ssl.conf# HTTPS 配置(Let's Encrypt / 自签证书) server { listen 80; server_name api.example.com; # HTTP 强制跳转 HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name api.example.com; # 证书路径 ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; # SSL 安全配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # HSTS(可选,强制浏览器使用HTTPS) add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; } }

⚖️ 负载均衡配置(upstream)

nginx/conf.d/lb.conf# 负载均衡配置 upstream backend_pool { # 轮询(默认):请求均匀分配 server 192.168.1.10:8080; server 192.168.1.11:8080; server 192.168.1.12:8080; # === 其他策略 === # weight 权重:server 192.168.1.10:8080 weight=3; # ip_hash 会话保持:ip_hash; # least_conn 最少连接:least_conn; # 健康检查(需要第三方模块) # check interval=3000 rise=2 fall=3 timeout=1000; keepalive 32; } server { listen 80; server_name api.example.com; location / { proxy_pass http://backend_pool; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 失败时尝试下一台 proxy_next_upstream error timeout http_502 http_503 http_504; } }

📄 静态文件服务配置

nginx/conf.d/static.conf# 前端静态文件服务 server { listen 80; server_name www.example.com; root /usr/share/nginx/html; index index.html; # SPA 路由支持(Vue/React) location / { try_files $uri $uri/ /index.html; } # 静态资源强缓存 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ { expires 30d; add_header Cache-Control "public, immutable"; access_log off; # 静态资源不记日志 } # 禁止访问隐藏文件 location ~ /\. { deny all; } # gzip 压缩 gzip on; gzip_types text/plain text/css application/json application/javascript; gzip_min_length 1024; }

🔍 常见问题排查

问题原因排查 & 解决
502 Bad Gateway 后端服务挂了或未启动 1. curl http://localhost:8080/health 检查后端
2. docker compose ps 看容器状态
3. 检查后端日志
504 Gateway Timeout 后端处理太慢,超时 1. 增大 proxy_read_timeout
2. 优化后端接口性能
3. 检查数据库慢查询
413 Request Entity Too Large 上传文件超过默认1MB限制 在 server 或 location 中添加:
client_max_body_size 50m;
proxy_pass 末尾斜杠问题 带不带 / 行为完全不同 proxy_pass http://backend; → 保留原始路径
proxy_pass http://backend/; → 替换掉匹配的路径

proxy_pass 末尾带 / 时,Nginx 会用 / 替换匹配的 location 路径。比如 location /api/ { proxy_pass http://backend/; },请求 /api/users 会转发为 /users(去掉了 /api 前缀)。不带 / 则完整保留为 /api/users。这是最常见的 Nginx 配置坑之一!

5. CI/CD 流水线

⚡ GitHub Actions 完整模板(Java项目)

.github/workflows/deploy.ymlname: Java CI/CD Pipeline on: push: branches: [ main ] pull_request: branches: [ main ] env: REGISTRY: ghcr.io IMAGE_NAME: my-org/my-app jobs: # ========== 阶段1: 构建和测试 ========== build-and-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' cache: maven - name: Build with Maven run: mvn clean package -DskipTests - name: Run Tests run: mvn test - name: Upload artifact uses: actions/upload-artifact@v4 with: name: app-jar path: target/*.jar # ========== 阶段2: 构建Docker镜像 ========== docker-build: needs: build-and-test runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 - name: Login to Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and Push Docker Image uses: docker/build-push-action@v5 with: context: . push: true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} # ========== 阶段3: 部署到服务器 ========== deploy: needs: docker-build runs-on: ubuntu-latest steps: - name: Deploy to Server uses: appleboy/ssh-action@v1 with: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} script: | cd /opt/app docker compose pull docker compose up -d --remove-orphans docker image prune -f echo "Deploy completed at $(date)"

📊 CI/CD 流程图

代码提交
git push
自动构建
mvn package
运行测试
mvn test
构建镜像
docker build
自动部署
deploy
验证上线
health check

🏆 部署策略对比

策略 原理 优点 缺点 适用场景
蓝绿部署 两套完全相同的环境,切换流量 零停机,秒级回滚 资源开销翻倍 关键业务、需要快速回滚
滚动更新 逐个替换旧版本实例 资源利用率高,无需额外环境 回滚较慢,新旧版本短暂共存 K8s默认策略、一般业务
灰度发布 先让少量流量到新版本,逐步扩大 风险可控,可观测新版本表现 需要流量管理能力 大流量应用、重大变更

蓝绿部署 = 快递站装修时开一家新店,顾客直接去新店,不喜欢就回老店。滚动更新 = 快递站连锁店一家家换新菜单。灰度发布 = 先让10%的顾客试喝新口味,没问题再全量推广。

6. 日志查看技巧

🔍 tail -f + grep 实时过滤

Bash# 实时查看并过滤 ERROR 日志 tail -f /var/log/app/app.log | grep --line-buffered "ERROR" # 同时过滤多个关键词(ERROR 或 WARN) tail -f app.log | grep --line-buffered -E "ERROR|WARN" # 高亮显示(加颜色) tail -f app.log | grep --color=auto -E "ERROR|WARN|^" # 排除某些日志(过滤掉健康检查) tail -f app.log | grep -v "health check" # 统计最近5分钟ERROR数量 tail -n 10000 app.log | grep "ERROR" | wc -l

📖 less 搜索和跳转

Bash - less 快捷键# 打开日志文件 less /var/log/app/app.log # ===== 常用快捷键 ===== # /关键词 → 向下搜索(如 /ERROR) # ?关键词 → 向上搜索 # n → 下一个匹配 # N → 上一个匹配 # G → 跳到文件末尾 # 1G → 跳到文件开头 # 100G → 跳到第100行 # F → 实时跟踪(类似 tail -f) # Ctrl+C → 退出跟踪模式 # q → 退出 less # 实用技巧:打开后直接跳到末尾并跟踪 less +F /var/log/app/app.log

📋 journalctl 查看系统日志

Bash# 查看某服务最近100行日志 journalctl -u nginx -n 100 --no-pager # 实时跟踪服务日志 journalctl -u my-app -f # 查看今天的日志 journalctl -u my-app --since today # 查看指定时间范围的日志 journalctl -u my-app --since "2024-01-01 10:00" --until "2024-01-01 12:00" # 查看内核日志(排查系统级问题) journalctl -k # 查看某进程的日志 journalctl _PID=12345

📑 多服务日志聚合查看

Bash# 同时查看多个服务日志(tmux 多窗口) tmux new-session -d -s logs tmux split-window -h -t logs tmux send-keys -t logs:0.0 'tail -f /var/log/app/app.log' C-m tmux send-keys -t logs:0.1 'tail -f /var/log/nginx/error.log' C-m tmux attach -t logs # 或者用 multitail 工具 multitail /var/log/app/app.log /var/log/nginx/access.log # 合并多个日志文件,按时间排序 awk '{print FILENAME, $0}' *.log | sort -k2 | less # Docker 多容器日志聚合 docker compose logs -f --tail=50

生产环境建议使用 ELK(Elasticsearch + Logstash + Kibana)或 Loki + Grafana 做日志聚合,支持全文搜索、可视化分析和告警。开发/测试环境用 tail/grep/less 就够了。

7. 线上紧急操作手册

线上操作原则:先恢复服务 → 再定位原因 → 最后彻底修复。任何时候都要保留现场(日志、截图),不要急于操作导致丢失关键信息。

1 🚨 服务挂了怎么办

发现告警
监控/用户反馈
先重启恢复
减少影响时间
查日志定位
找到根因
修复上线
验证恢复
应急命令# Step 1: 确认服务状态 ps aux | grep java curl http://localhost:8080/actuator/health # Step 2: 快速重启恢复(先恢复再说) cd /opt/app && ./service.sh restart # 或者 Docker 环境: docker compose restart app # Step 3: 查看启动日志,确认是否恢复 tail -f /var/log/app/startup.log # Docker 环境: docker compose logs -f app # Step 4: 定位崩溃原因(查看错误日志) grep -E "ERROR|Exception|OOM|OutOfMemory" /var/log/app/app.log | tail -50 # Step 5: 如果是OOM,查看heap dump ls -lh /opt/app/*.hprof # 下载到本地用 MAT 工具分析 scp user@server:/opt/app/java_pid*.hprof ~/

2 🗄️ 数据库挂了怎么办

应急命令# Step 1: 检查MySQL是否存活 systemctl status mysql mysql -uroot -p -e "SELECT 1" # Step 2: 查看连接数是否打满 mysql -uroot -p -e "SHOW PROCESSLIST" mysql -uroot -p -e "SHOW STATUS LIKE 'Threads_connected'" mysql -uroot -p -e "SHOW VARIABLES LIKE 'max_connections'" # Step 3: 查看慢查询 mysql -uroot -p -e "SHOW FULL PROCESSLIST" | grep -v "Sleep" # 或查看慢查询日志 tail -20 /var/log/mysql/slow.log # Step 4: Kill 掉异常慢查询(谨慎操作!) mysql -uroot -p -e "KILL QUERY 12345" # Step 5: 检查连接池配置(应用侧) grep -E "pool|connection|timeout" application.yml # Step 6: 如果无法恢复,考虑重启MySQL systemctl restart mysql # 重启后检查数据完整性 mysql -uroot -p -e "CHECK TABLE users, orders;" mydb

3 💽 磁盘满了怎么办

应急命令# Step 1: 查看磁盘使用率 df -h # Step 2: 找到占用空间最大的目录 du -sh /* | sort -rh | head -10 # 继续深入查找 du -sh /var/* | sort -rh | head -10 # Step 3: 快速清理日志(保留最近3天) find /var/log -name "*.log.*" -mtime +3 -delete # 清空超大日志文件(不删除文件) truncate -s 0 /var/log/app/app.log # Step 4: 清理Docker无用资源 docker system prune -a --volumes -f # 谨慎!会删除未使用的镜像和卷 docker image prune -f # 只清理无用镜像 # Step 5: 清理临时文件 find /tmp -type f -mtime +7 -delete rm -rf /opt/app/tmp/* # Step 6: 验证磁盘空间 df -h

日常预防:在日志切割脚本中加入磁盘使用率检查,超过 80% 自动告警。用 logrotate 工具自动管理日志轮转。

4 🧠 内存不够怎么办

应急命令# Step 1: 查看内存使用概况 free -h # Step 2: 查看内存占用TOP10进程 ps aux --sort=-%mem | head -11 # Step 3: 查看Java进程堆内存 jstat -gcutil $(pgrep -f "my-app") 1000 5 # Step 4: 临时缓解 - 重启占用内存最多的非关键服务 # 先确认哪些服务可以重启 docker compose ps # 重启指定服务 docker compose restart app # Step 5: 如果是Java OOM,调整JVM参数 # 修改 JAVA_OPTS 后重启 # -Xms512m -Xmx2g → -Xms1g -Xmx4g(根据服务器内存调整) # Step 6: 查看是否有内存泄漏(连续观察) watch -n 5 "free -h | head -2" # Step 7: 长期方案 - 垂直扩容(升级服务器内存) # 或水平扩容(增加服务器节点)

线上服务器建议设置 swap 分区作为紧急缓冲,但 swap 会导致性能下降。正确做法是限制 JVM 堆内存为物理内存的 60-70%,给操作系统和其他进程留足空间。