Linux查看进程占用CPU,top和ps命令

前言

服务器突然卡了?CPU跑满了?想知道哪个进程占CPU最多?本文讲top和ps命令,帮你快速定位问题进程。

一、top命令 — 实时监控

启动top

top

top界面说明

第一行:

  • 系统时间、运行时间、登录用户数、负载(1/5/15分钟)

第二行:

  • 任务总数、运行中、睡眠中、停止、僵尸进程

第三行(CPU):

  • us:用户态占用
  • sy:内核态占用
  • id:空闲

第四行(内存):

  • total:总内存
  • used:已用
  • free:空闲

进程列表:

  • PID:进程ID
  • USER:运行用户
  • %CPU:CPU占用
  • %MEM:内存占用
  • COMMAND:进程名

top常用快捷键

快捷键 作用
P 按CPU排序
M 按内存排序
k 杀掉进程
q 退出
1 显示每个CPU核心

只看某个进程

top -p 进程ID

二、ps命令 — 查看进程快照

查看所有进程

ps aux

按CPU排序

ps aux --sort=-%cpu | head -10

解释:

  • aux:显示所有进程
  • --sort=-%cpu:按CPU降序
  • head -10:只看前10个

按内存排序

ps aux --sort=-%mem | head -10

查找特定进程

ps aux | grep nginx

三、查找占用CPU最高的进程

方法1:用top

top

按P,按CPU排序,第一个就是占用最高的。

方法2:用ps

ps aux --sort=-%cpu | head -5

输出:

USER  PID  %CPU  %MEM  COMMAND
root  1234  50.0  10.0  php-fpm

四、查找占用内存最高的进程

ps aux --sort=-%mem | head -5

五、杀掉进程

根据PID杀

kill 进程ID

强制杀

kill -9 进程ID

根据进程名杀

pkill nginx

杀掉所有php-fpm

pkill php-fpm

六、常见场景

场景1:CPU跑满了

排查步骤:

  1. 用top看哪个进程CPU高
  2. 如果是php-fpm,看是不是有慢请求
  3. 如果是mysql,看是不是有慢SQL
  4. 杀掉异常进程

场景2:内存占满了

排查步骤:

  1. 用top按M排序
  2. 看哪个进程内存高
  3. 杀掉或重启

场景3:某个进程一直跑

ps aux | grep 进程名

找到PID后杀掉。

七、常见坑

坑1:top和ps看到的CPU不一样

原因: top是实时刷新的,ps是快照。

解决: 用top看实时变化。

坑2:杀了进程又起来

原因: 有守护进程自动拉起。

解决: 找到父进程一起杀,或者停掉服务。

systemctl stop php-fpm

坑3:僵尸进程

现象: 进程状态是Z(Zombie)。

原因: 子进程退出了,父进程没回收。

解决: 重启父进程。

八、常用命令汇总

# 实时监控
top

# 按CPU排序
ps aux --sort=-%cpu | head -10

# 按内存排序
ps aux --sort=-%mem | head -10

# 查找进程
ps aux | grep 进程名

# 杀进程
kill PID
kill -9 PID
pkill 进程名

九、负载怎么看

什么是负载

负载就是正在等待CPU运行的进程数。

怎么判断高不高

  • 负载 < CPU核心数:正常
  • 负载 > CPU核心数:有点忙
  • 负载 > 2倍CPU核心数:很卡

查看CPU核心数

nproc

总结

记住:

  1. 用top实时看CPU和内存
  2. 按P按CPU排序,按M按内存排序
  3. 用ps aux --sort=-%cpu找CPU最高的
  4. 用kill -9 PID杀进程
  5. 负载超过CPU核心数就有问题

最常用:

top
ps aux --sort=-%cpu | head -10
kill -9 进程ID

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:58 

PHP常用数组函数,10个必须掌握

前言

PHP数组函数太多了,记不住?本文整理了10个最常用的数组函数,写代码天天用,必须掌握。

一、array_column — 取数组某一列

用法

$users = [
    ['id' => 1, 'name' => '张三'],
    ['id' => 2, 'name' => '李四'],
    ['id' => 3, 'name' => '王五']
];

$names = array_column($users, 'name');
// 结果:['张三', '李四', '王五']

取列并指定键

$userMap = array_column($users, 'name', 'id');
// 结果:[1 => '张三', 2 => '李四', 3 => '王五']

二、array_map — 遍历处理数组

用法

$numbers = [1, 2, 3, 4, 5];

$squares = array_map(function($n) {
    return $n * $n;
}, $numbers);
// 结果:[1, 4, 9, 16, 25]

实际应用

// 把所有名字转成大写
$names = ['zhang', 'li', 'wang'];
$upperNames = array_map('strtoupper', $names);
// 结果:['ZHANG', 'LI', 'WANG']

三、array_filter — 过滤数组

用法

$numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

$even = array_filter($numbers, function($n) {
    return $n % 2 == 0;
});
// 结果:[2, 4, 6, 8, 10]

过滤空值

$arr = ['a', '', 0, null, 'b', '0'];
$result = array_filter($arr);
// 结果:['a', 'b']

四、array_merge — 数组合并

用法

$arr1 = ['a', 'b'];
$arr2 = ['c', 'd'];

$merged = array_merge($arr1, $arr2);
// 结果:['a', 'b', 'c', 'd']

关联数组合并

$arr1 = ['name' => '张三', 'age' => 20];
$arr2 = ['age' => 25, 'city' => '北京'];

$merged = array_merge($arr1, $arr2);
// 结果:['name' => '张三', 'age' => 25, 'city' => '北京']
// 注意:age被后面的覆盖了

五、in_array — 判断值是否在数组里

用法

$fruits = ['apple', 'banana', 'orange'];

if (in_array('apple', $fruits)) {
    echo '有苹果';
}

注意严格模式

$arr = [1, '2', '3'];

in_array(2, $arr); // true(因为类型转换了)
in_array(2, $arr, true); // false(严格模式)

六、array_keys — 取数组的键

用法

$user = ['name' => '张三', 'age' => 20, 'city' => '北京'];

$keys = array_keys($user);
// 结果:['name', 'age', 'city']

找某个值的键

$ages = [
    '张三' => 20,
    '李四' => 25,
    '王五' => 30
];

$keys = array_keys($ages, 25);
// 结果:['李四']

七、array_values — 取数组的值

用法

$user = ['name' => '张三', 'age' => 20, 'city' => '北京'];

$values = array_values($user);
// 结果:['张三', 20, '北京']

八、array_sum — 数组求和

用法

$numbers = [1, 2, 3, 4, 5];

$sum = array_sum($numbers);
// 结果:15

九、count — 统计数组长度

用法

$fruits = ['apple', 'banana', 'orange'];

$count = count($fruits);
// 结果:3

多维数组

$users = [
    ['name' => '张三'],
    ['name' => '李四']
];

count($users); // 2
count($users, COUNT_RECURSIVE); // 4(递归统计)

十、explode / implode — 字符串和数组互转

explode — 字符串转数组

$str = 'apple,banana,orange';

$fruits = explode(',', $str);
// 结果:['apple', 'banana', 'orange']

implode — 数组转字符串

$fruits = ['apple', 'banana', 'orange'];

$str = implode(',', $fruits);
// 结果:'apple,banana,orange'

十一、常用场景

场景1:从数据库取数据,提取某列

$users = Db::name('user')->select();
$names = array_column($users, 'name');

场景2:过滤空值

$list = array_filter($list);

场景3:判断是否存在

if (in_array($userId, $adminIds)) {
    // 是管理员
}

场景4:数组去重后转字符串

$tags = array_unique($tags);
$tagStr = implode(',', $tags);

十二、常见坑

坑1:array_merge关联数组键名覆盖

问题: 相同键名会被覆盖。

解决: 用+号或array_replace。

坑2:in_array类型不严格

问题: in_array(0, ['a', 'b'])返回true。

解决: 加第三个参数true,严格模式。

坑3:count返回0

问题: 传了null进去。

解决: 先判断是不是数组。

总结

10个必须掌握的数组函数:

  1. array_column — 取某一列
  2. array_map — 遍历处理
  3. array_filter — 过滤
  4. array_merge — 合并
  5. in_array — 判断存在
  6. array_keys — 取键
  7. array_values — 取值
  8. array_sum — 求和
  9. count — 统计长度
  10. explode/implode — 字符串数组互转

记住:

  • 天天用的就是这10个
  • 写代码前先想想有没有现成函数
  • 别自己写循环,用内置函数更快

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:57 

Nginx配置静态资源缓存,提升网站速度

前言

每次访问网站都要重新下载图片、CSS、JS?太浪费了!配置Nginx静态资源缓存,让浏览器本地缓存这些文件,下次访问直接从本地读,速度快好几倍。

一、什么是浏览器缓存

浏览器访问网站后,把图片、CSS、JS等静态文件存在本地。下次再访问,直接从本地读,不用再从服务器下载。

好处:

  • 加载更快
  • 省带宽
  • 减轻服务器压力

二、配置静态资源缓存

打开Nginx配置文件,在server段加:

location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
    expires 30d;
    add_header Cache-Control "public, no-transform";
}

参数解释

参数 含义
expires 30d 缓存30天
expires 1h 缓存1小时
expires 1M 缓存1个月
Cache-Control public 允许所有缓存

不同文件缓存时间

# 图片缓存30天
location ~* \.(png|jpg|jpeg|gif|ico|svg)$ {
    expires 30d;
}

# CSS/JS缓存7天
location ~* \.(css|js)$ {
    expires 7d;
}

# HTML不缓存(保证更新及时)
location ~* \.html$ {
    expires -1;
}

三、验证配置

nginx -t

重载Nginx:

nginx -s reload

四、验证是否生效

方法1:curl测试

curl -I https://www.0454.wang/static/logo.png

看到响应头有:

Cache-Control: max-age=2592000
Expires: ...

就是生效了。

方法2:浏览器F12

按F12 → Network → 点图片请求 → 看Response Headers:

Cache-Control: max-age=2592000

五、缓存更新策略

问题: 缓存了30天,但我更新了图片怎么办?

方法1:文件名加版本号

<img src="/logo.png?v=20240101">

浏览器看到文件名变了,就会重新下载。

方法2:文件名加hash

<img src="/logo.abc123.png">

构建工具自动加hash,内容变了hash就变。

六、常见坑

坑1:HTML也被缓存了

问题: 更新了网站内容,但用户看到的还是旧的。

解决: HTML不缓存

location ~* \.html$ {
    expires -1;
}

坑2:缓存时间太长

问题: 图片更新了,用户30天都看不到新的。

解决: 用版本号或hash。

坑3:CDN缓存了旧文件

问题: Nginx配置了,但CDN没刷新。

解决: 去CDN后台刷新缓存。

坑4:没加expires就没缓存

问题: 配置了但不生效。

检查: 看location有没有匹配到。

七、完整配置示例

server {
    listen 80;
    server_name www.0454.wang;
    root /var/www/html;

    # HTML不缓存
    location ~* \.html$ {
        expires -1;
    }

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

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

八、缓存时间怎么设

文件类型 推荐缓存时间
图片(png/jpg) 30天
CSS/JS 7天
HTML 不缓存
字体文件 30天

九、怎么清除缓存

浏览器端

  • 强制刷新:Ctrl + F5
  • 清除浏览器缓存

服务器端

  • 更新文件名版本号
  • 重载Nginx

总结

记住:

  1. 静态资源加缓存,提升速度
  2. 图片缓存30天
  3. CSS/JS缓存7天
  4. HTML不缓存
  5. 更新了就改文件名版本号
  6. 用curl验证是否生效

最常用配置:

location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
    expires 30d;
    add_header Cache-Control "public";
}

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:56 

MySQL索引失效的10种场景,别再踩坑了

前言

明明加了索引,但查询还是慢?explain一看type是ALL,索引根本没生效!本文总结10种索引失效的场景,帮你避坑。

一、对索引字段用函数

失效写法

SELECT * FROM users WHERE LEFT(name, 3) = '张三';
SELECT * FROM users WHERE DATE(create_time) = '2024-01-01';

正确写法

SELECT * FROM users WHERE name LIKE '张三%';
SELECT * FROM users WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';

原因: 对索引字段用函数,MySQL不知道索引值会变成什么,索引用不上。

二、索引字段用运算

失效写法

SELECT * FROM users WHERE age + 1 = 20;
SELECT * FROM orders WHERE price * 2 > 100;

正确写法

SELECT * FROM users WHERE age = 19;
SELECT * FROM orders WHERE price > 50;

原因: 运算后索引值变了,MySQL没法用索引。

三、最左前缀原则不满足

联合索引

INDEX idx_name_age_city(name, age, city)

失效写法

SELECT * FROM users WHERE age = 20; -- 跳过了name
SELECT * FROM users WHERE city = '北京'; -- 跳过了name和age

正确写法

SELECT * FROM users WHERE name = '张三';
SELECT * FROM users WHERE name = '张三' AND age = 20;

原则: 联合索引要从最左边的字段开始用,不能跳过。

四、like以%开头

失效写法

SELECT * FROM users WHERE name LIKE '%张三%';
SELECT * FROM users WHERE name LIKE '%张三';

正确写法

SELECT * FROM users WHERE name LIKE '张三%';

原因: 以%开头,MySQL不知道从哪开始找,索引用不上。

五、类型不匹配

失效写法

-- phone是varchar类型
SELECT * FROM users WHERE phone = 13800138000; -- 传了数字

正确写法

SELECT * FROM users WHERE phone = '13800138000'; -- 传字符串

原因: 类型不匹配,MySQL会做隐式转换,索引用不上。

六、OR两边有没索引的字段

失效写法

-- name有索引,age没索引
SELECT * FROM users WHERE name = '张三' OR age = 20;

原因: OR只要有一边没索引,MySQL就干脆全表扫描。

解决

-- age也加索引
ALTER TABLE users ADD INDEX idx_age(age);

七、使用NOT、!=、<>

失效写法

SELECT * FROM users WHERE name != '张三';
SELECT * FROM users WHERE age NOT IN (18, 20);

原因: 不等于操作,MySQL觉得要查大部分数据,不如全表扫描。

解决

-- 改成范围查询
SELECT * FROM users WHERE name > '张三';

八、is null / is not null

这个要看情况,有的版本会失效,有的不会。

建议: 尽量让字段不要为null,设默认值。

九、查询条件用了函数或表达式

失效写法

SELECT * FROM users WHERE ABS(age) = 20;
SELECT * FROM users WHERE age / 2 = 10;

正确写法

SELECT * FROM users WHERE age = 20;

十、MySQL自己判断不走索引

有时候SQL写法没问题,但MySQL优化器觉得走索引还不如全表扫描。

比如: 表数据很少,或者要查的数据占表的大部分。

解决: 用FORCE INDEX强制走索引

SELECT * FROM users FORCE INDEX(idx_name) WHERE name = '张三';

十一、怎么检查索引有没有生效

用explain

EXPLAIN SELECT * FROM users WHERE name = '张三';

看key字段:

  • key有值:用了索引
  • key是NULL:没用到索引

看type字段:

  • type=ALL:全表扫描
  • type=ref/range:用了索引

十二、常见坑

坑1:加了索引但SQL写法不对

解决: 按上面10种场景检查。

坑2:索引加太多

索引不是越多越好,写的时候要维护索引,会变慢。

建议: 单表索引不超过5个。

坑3:区分度低的字段不加索引

比如gender(男/女),加索引没用。

原则: 区分度高的字段加索引。

坑4:隐式类型转换

例子: varchar字段传了数字。

解决: 传参时类型要匹配。

十三、索引使用原则

  1. 对WHERE条件的字段加索引
  2. 对ORDER BY的字段加索引
  3. 联合索引遵守最左前缀
  4. 不要对索引字段用函数
  5. 不要对索引字段做运算
  6. like不要以%开头
  7. 类型要匹配
  8. 区分度高的字段加索引

总结

10种索引失效场景:

  1. 对索引字段用函数
  2. 索引字段用运算
  3. 最左前缀原则不满足
  4. like以%开头
  5. 类型不匹配
  6. OR两边有没索引的字段
  7. 使用NOT、!=、<>
  8. is null / is not null
  9. 查询条件用了表达式
  10. MySQL自己判断不走索引

记住:

  • 用explain检查索引有没有生效
  • key=NULL就是没用到索引
  • type=ALL就是全表扫描
  • 避开上面10种场景

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:54 

Docker常用命令大全,新手必看

前言

Docker现在用得越来越多,但很多新手记不住命令。本文整理了最常用的Docker命令,收藏起来随用随查。

一、镜像相关命令

拉取镜像

docker pull nginx:latest

查看本地镜像

docker images

删除镜像

docker rmi 镜像ID

强制删除镜像

docker rmi -f 镜像ID

搜索镜像

docker search nginx

二、容器相关命令

查看运行中的容器

docker ps

查看所有容器(包括停止的)

docker ps -a

启动容器

docker start 容器名/容器ID

停止容器

docker stop 容器名/容器ID

重启容器

docker restart 容器名/容器ID

删除容器

docker rm 容器名/容器ID

强制删除运行中的容器

docker rm -f 容器名/容器ID

进入容器

docker exec -it 容器名/容器ID /bin/bash

查看容器日志

docker logs 容器名/容器ID

实时查看日志

docker logs -f 容器名/容器ID

查看容器详细信息

docker inspect 容器名/容器ID

查看容器IP

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 容器名

查看容器资源占用

docker stats

三、运行容器

后台运行

docker run -d --name mynginx -p 80:80 nginx

参数解释:

  • -d:后台运行
  • --name:容器名
  • -p:端口映射(宿主端口:容器端口)

运行并进入容器

docker run -it ubuntu /bin/bash

挂载目录

docker run -d -v /host/path:/container/path nginx

环境变量

docker run -e MYSQL_ROOT_PASSWORD=123456 mysql

自动重启

docker run --restart=always -d nginx

四、清理命令

停止所有容器

docker stop $(docker ps -q)

删除所有停止的容器

docker container prune

删除所有未使用的镜像

docker image prune -a

一键清理

docker system prune -a

五、Docker Compose命令

启动

docker-compose up -d

停止

docker-compose down

查看状态

docker-compose ps

查看日志

docker-compose logs -f

重启

docker-compose restart

六、常用场景

运行MySQL

docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v /data/mysql:/var/lib/mysql \
  mysql:5.7

运行Nginx

docker run -d \
  --name nginx \
  -p 80:80 \
  -v /www:/usr/share/nginx/html \
  nginx

运行Redis

docker run -d \
  --name redis \
  -p 6379:6379 \
  redis:alpine

七、常见坑

坑1:容器启动后马上退出

原因: 容器没有前台进程。

解决: 加-it或者tail -f /dev/null。

坑2:端口映射不对

问题: 容器里跑的是80端口,映射成了8080:80。

解决: 确认容器内端口和宿主机端口。

坑3:数据丢失

问题: 容器删了数据就没了。

解决: 一定要挂载数据卷-v。

坑4:镜像太大

解决: 用alpine版本,比如redis:alpine。

八、命令速查表

功能 命令
查看运行中容器 docker ps
查看所有容器 docker ps -a
查看镜像 docker images
拉取镜像 docker pull 镜像名
运行容器 docker run -d --name 名字 -p 端口:端口 镜像
进入容器 docker exec -it 名字 /bin/bash
查看日志 docker logs -f 名字
停止容器 docker stop 名字
删除容器 docker rm 名字
删除镜像 docker rmi 镜像ID
清理 docker system prune -a

总结

记住:

  1. docker ps看容器
  2. docker images看镜像
  3. docker run启动容器
  4. docker exec -it进入容器
  5. docker logs -f看日志
  6. docker system prune清理

最常用:

docker ps
docker images
docker exec -it 容器名 /bin/bash
docker logs -f 容器名

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:53 

Linux磁盘满了怎么清理,大文件查找命令

前言

网站突然打不开了?SSH连不上了?一查磁盘100%满了!本文讲怎么查找大文件、清理磁盘空间。

一、查看磁盘使用情况

df命令

df -h

输出:

Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G   38G  2.0G  95% /

看什么:

  • Use%:使用率,超过90%就要注意了
  • Avail:可用空间

查看当前目录占用

du -sh

二、查找大文件

查找大于100M的文件

find / -type f -size +100M 2>/dev/null

解释:

  • /:从根目录开始找
  • -type f:只找文件
  • -size +100M:大于100M
  • 2>/dev/null:忽略错误信息

查找大于1G的文件

find / -type f -size +1G 2>/dev/null

按大小排序

find / -type f -size +100M 2>/dev/null | xargs du -h | sort -rh

三、查找大目录

查看各目录大小

du -sh /*

一级一级往下找

du -sh /var/*
du -sh /var/log/*
du -sh /var/lib/*

查看前10大目录

du -sh /* | sort -rh | head -10

四、常见大文件位置

1. 日志文件

/var/log/
/var/log/messages
/var/log/nginx/
/var/log/mysql/

2. 临时文件

/tmp/
/var/tmp/

3. 邮件

/var/spool/mail/

4. 数据库文件

/var/lib/mysql/

5. Docker镜像

/var/lib/docker/

6. 上传文件

/www/wwwroot/
/var/www/html/

五、清理方法

清理日志文件

方法1:清空文件

> /var/log/messages

方法2:删除旧日志

find /var/log -name "*.log" -mtime +30 -delete

删除30天前的日志。

清理yum缓存

yum clean all

清理Docker

docker system prune -a

清理临时文件

rm -rf /tmp/*

注意: 正在用的文件别删。

清理旧内核

package-cleanup --oldkernels --count=1

六、查看文件被谁占用

问题: 删了文件但磁盘没释放?

原因: 有进程还在写这个文件。

查看被删除但还被占用的文件

lsof | grep deleted

重启相关进程

systemctl restart nginx

七、常见坑

坑1:删了文件但空间没释放

原因: 进程还在写这个文件。

解决: 重启进程,或者用> 文件名清空。

坑2:日志一直涨

解决: 配置logrotate日志切割。

坑3:Docker占了很多空间

docker system df

清理:

docker system prune -a

八、自动清理脚本

写个脚本定期清理:

#!/bin/bash
# 清理30天前的日志
find /var/log -name "*.log" -mtime +30 -delete
# 清理yum缓存
yum clean all
# 清理Docker
docker system prune -f

加到crontab:

0 3 * * * /bin/bash /root/clean.sh

总结

记住:

  1. 用df -h看磁盘使用率
  2. 用du -sh /*看哪个目录大
  3. 用find / -size +100M找大文件
  4. 先看日志,再看Docker,再看临时文件
  5. 删文件前确认不用了
  6. 删了没释放就重启进程

最常用:

df -h
du -sh /*
find / -type f -size +100M 2>/dev/null

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:52 

Nginx配置Gzip压缩,提升网站加载速度

前言

网站打开慢?带宽不够?开启Nginx的Gzip压缩,能把文件体积压缩70%以上,加载速度立马提升好几倍。本文讲怎么配置。

一、什么是Gzip

Gzip是一种压缩算法,Nginx在发给浏览器之前先把文件压缩,浏览器收到后自动解压。

效果:

  • HTML/CSS/JS文件压缩70%左右
  • 减少带宽
  • 加载更快

二、配置Gzip

打开Nginx配置文件:

vi /etc/nginx/nginx.conf

在http段里加:

gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
gzip_vary on;
gzip_buffers 16 8k;
gzip_http_version 1.1;

参数解释

参数 含义
gzip on 开启gzip
gzip_min_length 1k 小于1k的文件不压缩
gzip_comp_level 6 压缩级别1-9,6最合适
gzip_types 压缩哪些类型的文件
gzip_vary on 给响应头加Vary: Accept-Encoding
gzip_buffers 压缩缓冲区

压缩级别说明

级别 效果
1 最快,压缩率最低
6 推荐,平衡
9 最慢,压缩率最高

推荐用6,不要用9,太吃CPU。

三、验证配置

nginx -t

重载Nginx:

nginx -s reload

四、验证是否生效

方法1:curl测试

curl -I -H "Accept-Encoding: gzip" https://www.0454.wang/

看到响应头有:

Content-Encoding: gzip

就是生效了。

方法2:浏览器F12

按F12打开开发者工具 → Network → 点请求 → 看Response Headers:

Content-Encoding: gzip

五、哪些文件该压缩

该压缩的

  • HTML文件
  • CSS文件
  • JS文件
  • JSON数据
  • SVG图片

不该压缩的

  • 图片(jpg/png):本身已经压缩过了
  • 视频:太大,压缩没意义
  • 压缩包(zip/rar):已经压缩过了

六、常见坑

坑1:只在http段配了,但server段覆盖了

检查: 看server段有没有gzip off。

坑2:gzip_types没写全

问题: JS文件没压缩。

解决: 加上application/javascript。

坑3:压缩级别太高

问题: 用了9,CPU飙升。

解决: 改成6。

坑4:CDN缓存了旧文件

问题: 配置了gzip但CDN没生效。

解决: 刷新CDN缓存。

七、完整配置示例

http {
    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
    gzip_vary on;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;

    server {
        listen 80;
        server_name www.0454.wang;
        root /var/www/html;
        index index.html;
    }
}

八、对比效果

没开Gzip

HTML文件:100KB

开了Gzip

HTML文件:25KB

省了75%!

总结

记住:

  1. 开启gzip on
  2. 压缩级别用6
  3. gzip_types要写全
  4. 图片不要压缩
  5. 用curl验证是否生效

最常用配置:

gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_vary on;

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:51 

MySQL慢查询排查,explain分析SQL执行计划

前言

网站突然变慢,数据库CPU100%?多半是慢查询搞的鬼。本文讲怎么开启慢查询日志、用explain分析SQL执行计划,找到慢的原因。

一、开启慢查询日志

临时开启

-- 查看慢查询是否开启
SHOW VARIABLES LIKE 'slow_query_log';

-- 开启慢查询
SET GLOBAL slow_query_log = ON;

-- 超过1秒就算慢查询
SET GLOBAL long_query_time = 1;

永久开启

编辑my.cnf:

vi /etc/my.cnf

加:

slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1

重启MySQL:

systemctl restart mysqld

看慢查询日志

tail -f /var/log/mysql/slow.log

二、explain是什么

explain用来分析SQL是怎么执行的,看有没有用索引、扫了多少行。

用法

EXPLAIN SELECT * FROM users WHERE name = '张三';

三、explain字段解释

id

SELECT的标识符,越大越先执行。

select_type

查询类型:

  • SIMPLE:简单查询
  • PRIMARY:主查询
  • SUBQUERY:子查询

table

查的哪张表。

type(最重要)

访问类型,从好到差:

类型 含义
system 表只有一行
const 主键或唯一索引匹配,只查一行
eq_ref 主键或唯一索引匹配
ref 普通索引匹配
range 索引范围查询(between、>、<)
index 扫整个索引树
ALL 全表扫描(最坏!)

重点看: 出现ALL就是全表扫描,要加索引。

possible_keys

可能用到的索引。

key

实际用到的索引。

key是NULL就是没用到索引。

rows

预估要扫描多少行。

越大越慢。

Extra

额外信息:

值 含义
Using where 服务器在存储引擎返回后再过滤
Using index 用了覆盖索引,不错
Using temporary 用了临时表,不好
Using filesort 用了文件排序,不好

四、常见慢查询原因

原因1:没加索引

SELECT * FROM users WHERE name = '张三';

name字段没索引 → 全表扫描 → 慢。

解决: 加索引

ALTER TABLE users ADD INDEX idx_name(name);

原因2:索引失效

虽然加了索引,但SQL写法不对,索引用不上。

索引失效的情况:

  1. 对索引字段用函数:

    SELECT * FROM users WHERE LEFT(name, 3) = '张三'; -- 索引失效
  2. 索引字段用运算:

    SELECT * FROM users WHERE age + 1 = 20; -- 索引失效
  3. 最左前缀原则不满足:

    -- 联合索引(a, b, c)
    SELECT * FROM t WHERE b = 1; -- 索引失效
  4. like以%开头:

    SELECT * FROM users WHERE name LIKE '%张三%'; -- 索引失效
  5. 类型不匹配:

    -- phone是varchar
    SELECT * FROM users WHERE phone = 13800138000; -- 索引失效

原因3:SELECT *

查了不需要的字段,占内存。

解决: 只查需要的字段

SELECT id, name FROM users WHERE ...

原因4:大表分页越查越慢

SELECT * FROM users ORDER BY id LIMIT 100000, 10;

慢的原因: 要先扫10万行再跳过。

解决:

SELECT u.* FROM users u
INNER JOIN (SELECT id FROM users ORDER BY id LIMIT 100000, 10) t
ON u.id = t.id;

五、实战案例

慢SQL

SELECT * FROM orders WHERE user_id = 123 AND status = 1 ORDER BY create_time DESC LIMIT 10;

explain分析

EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 1 ORDER BY create_time DESC LIMIT 10;

发现: type是ALL,key是NULL → 全表扫描。

加索引

ALTER TABLE orders ADD INDEX idx_user_status_time(user_id, status, create_time);

再explain: type变成ref,key是idx_user_status_time → 用上索引了。

六、优化原则

  1. WHERE条件加索引
  2. ORDER BY字段加索引
  3. 联合索引遵守最左前缀
  4. **不要SELECT ***
  5. 避免在索引字段上用函数
  6. 大分页用子查询优化

七、常见坑

坑1:加了索引但没用

原因: SQL写法导致索引失效。

排查: explain看key字段。

坑2:索引加太多

索引不是越多越好,写的时候要维护索引,会变慢。

建议: 单表索引不超过5个。

坑3:区分度低的字段不加索引

比如gender(男/女),加索引没用。

原则: 区分度高的字段加索引。

总结

记住:

  1. 开启慢查询日志找慢SQL
  2. 用explain分析SQL执行计划
  3. type=ALL就是全表扫描,要加索引
  4. key=NULL就是没用到索引
  5. rows越大越慢
  6. 索引失效要避免

最常用:

EXPLAIN SELECT * FROM users WHERE ...

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:49 

Linux查看端口被占用,lsof和netstat命令

前言

启动服务的时候报"Address already in use"?想知道某个端口被哪个进程占了?Linux查看端口占用最常用的就是lsof和netstat命令,本文讲清楚。

一、为什么要查端口

  • 启动服务报端口被占
  • 看某个服务有没有启动
  • 排查安全问题,看有没有可疑端口

二、lsof命令

查看某个端口被哪个进程占用

lsof -i:端口号

例子: 查80端口

lsof -i:80

输出:

COMMAND  PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   1234  root    6u  IPv4  12345      0t0  TCP *:http (LISTEN)

看什么:

  • COMMAND:进程名(nginx)
  • PID:进程ID(1234)
  • USER:运行用户(root)

查看所有TCP端口

lsof -i tcp

查看所有UDP端口

lsof -i udp

杀掉占用端口的进程

kill -9 $(lsof -ti:80)

三、netstat命令

查看某个端口

netstat -tlnp | grep 端口号

例子: 查80端口

netstat -tlnp | grep :80

输出:

tcp  0  0  0.0.0.0:80  0.0.0.0:*  LISTEN  1234/nginx

看什么:

  • 最后一列:进程ID/进程名(1234/nginx)

参数解释

参数 含义
-t TCP端口
-l 只看监听中的
-n 显示数字,不解析域名
-p 显示进程名和PID

查看所有监听端口

netstat -tlnp

四、ss命令(推荐)

ss是netstat的升级版,更快。

查看某个端口

ss -tlnp | grep 端口号

例子:

ss -tlnp | grep :80

查看所有监听端口

ss -tlnp

五、三种命令对比

命令 优点 缺点
lsof 信息全,能看文件 慢
netstat 经典,大家都用 慢
ss 最快,推荐 新命令

推荐: 用ss,最快。

六、常见场景

场景1:启动Nginx报端口被占

nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)

排查:

lsof -i:80

解决: 杀掉占用的进程

kill -9 进程ID

场景2:看MySQL有没有启动

ss -tlnp | grep 3306

有输出就是启动了。

场景3:看SSH端口

ss -tlnp | grep sshd

七、常见坑

坑1:没加sudo看不到进程

普通用户只能看自己的进程,看不到别人的。

解决: 加sudo

sudo lsof -i:80
sudo netstat -tlnp

坑2:netstat命令找不到

CentOS7最小化安装没有netstat。

安装:

yum install -y net-tools

坑3:lsof命令找不到

安装:

yum install -y lsof

八、常用命令汇总

# 查80端口被谁占了
lsof -i:80
netstat -tlnp | grep :80
ss -tlnp | grep :80

# 看所有监听端口
netstat -tlnp
ss -tlnp

# 杀掉占用80端口的进程
kill -9 $(lsof -ti:80)

总结

记住:

  1. 查端口用 lsof -i:端口号
  2. 或者用 ss -tlnp | grep 端口号
  3. 看最后一列就是进程名和PID
  4. 杀掉用 kill -9 进程ID
  5. 推荐用ss,最快

最常用:

ss -tlnp | grep :80

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:48 

Redis缓存穿透、击穿、雪崩区别和解决

前言

用Redis做缓存,经常听到三个词:缓存穿透、缓存击穿、缓存雪崩。很多人搞不清区别,本文一次讲明白,附解决方法。

一、三个概念对比

问题 是什么 谁在访问 影响
缓存穿透 查一个不存在的数据,缓存没有,每次都去查数据库 恶意攻击/大量无效请求 数据库压力大
缓存击穿 一个热点key突然过期,大量请求同时打过去 正常用户 数据库瞬间压力大
缓存雪崩 大量key同时过期,或者Redis挂了 正常用户 数据库直接崩

二、缓存穿透

是什么

黑客故意查一个数据库里根本不存在的数据,缓存里也没有,每次请求都打到数据库。

例子: 查用户ID=-1,数据库里没有,缓存里也没有,每次都查数据库。

危害

数据库被大量无效请求打挂。

解决方法

方法1:缓存空值

数据库查不到,也往缓存里写一个空值,设个短过期时间。

// 先查缓存
$user = $redis->get("user:$id");

if ($user === false) {
    // 查数据库
    $user = mysqli_query("SELECT * FROM users WHERE id = $id");

    if (!$user) {
        // 查不到也缓存,设60秒过期
        $redis->setex("user:$id", 60, "null");
    } else {
        $redis->setex("user:$id", 3600, $user);
    }
}

方法2:布隆过滤器

在缓存前面加一层布隆过滤器,快速判断这个ID存在不存在。

不存在的直接返回,不用查数据库。

方法3:接口校验

在接口层就做参数校验,比如用户ID必须大于0,不合法的直接拒绝。

三、缓存击穿

是什么

一个热点key(比如首页热点新闻),突然过期了,这时候刚好有大量用户同时访问,所有请求都打到数据库。

和穿透的区别: 这个key本来是存在的,只是过期了。

危害

数据库瞬间压力暴增。

解决方法

方法1:互斥锁

只让一个线程去查数据库,其他线程等一下。

$lockKey = "lock:user:$id";

// 尝试加锁
if ($redis->setnx($lockKey, 1)) {
    // 加锁成功,查数据库
    $user = mysqli_query("SELECT * FROM users WHERE id = $id");
    $redis->setex("user:$id", 3600, $user);
    $redis->del($lockKey);
} else {
    // 加锁失败,等一下再读缓存
    sleep(100);
    $user = $redis->get("user:$id");
}

方法2:热点key不过期

热点数据不设过期时间,后台定时更新。

方法3:提前预热

在热点数据快过期前,主动后台更新缓存。

四、缓存雪崩

是什么

大量key在同一时间过期,或者Redis整个挂了,所有请求都打到数据库。

和击穿的区别:

  • 击穿:一个key过期
  • 雪崩:大量key同时过期

危害

数据库直接被打挂,整个服务不可用。

解决方法

方法1:过期时间加随机值

不要所有key都设3600秒过期,加个随机数错开。

// 基础3600秒,加0-600秒随机
$expire = 3600 + rand(0, 600);
$redis->setex("user:$id", $expire, $data);

方法2:Redis集群

Redis挂了还有备用的,不要单点。

方法3:服务降级

数据库扛不住的时候,直接返回默认数据或者报错,不要让请求都打过去。

方法4:热点数据永不过期

核心数据不设过期,后台异步更新。

五、总结对比

问题 原因 解决
缓存穿透 查不存在的数据 缓存空值 + 布隆过滤器
缓存击穿 热点key过期 互斥锁 + 热点永不过期
缓存雪崩 大量key同时过期 过期时间加随机 + Redis集群

六、常见坑

坑1:把穿透和击穿搞混

  • 穿透:数据本来就不存在
  • 击穿:数据存在,但缓存过期了

坑2:缓存空值不设过期时间

如果不设过期时间,以后这个数据真的有了,缓存还是空的。

解决: 空值也要设过期时间(比如60秒)。

坑3:互斥锁死锁

加了锁没释放,其他线程一直等。

解决: 锁也要设过期时间。

坑4:随机值加太小

所有key还是差不多同时过期。

建议: 随机范围至少几百秒。

总结

记住:

  1. 穿透:查不存在的数据 → 缓存空值 + 布隆过滤器
  2. 击穿:热点key过期 → 互斥锁 + 热点永不过期
  3. 雪崩:大量key同时过期 → 过期时间加随机 + Redis集群

最简单的防雪崩:

$expire = 3600 + rand(0, 600);
$redis->setex($key, $expire, $data);

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:47 

服务器SSH安全加固,改端口和密钥登录

前言

服务器刚买,SSH一直被暴力破解?默认22端口全是黑客在扫。本文讲怎么SSH安全加固:改端口、禁用密码登录、用密钥登录。

一、为什么要加固SSH

默认SSH配置太危险:

  • 端口22是公开的,黑客都扫
  • root密码登录容易被暴力破解
  • 密码可能被盗

二、第一步:改SSH端口

编辑SSH配置

vi /etc/ssh/sshd_config

找到:

#Port 22

改成:

Port 22222

(随便选个1024-65535之间的端口)

防火墙开放新端口

firewalld:

firewall-cmd --permanent --add-port=22222/tcp
firewall-cmd --reload

iptables:

iptables -I INPUT -p tcp --dport 22222 -j ACCEPT

重启SSH

systemctl restart sshd

注意: 改完端口后,连接SSH要用新端口:

ssh -p 22222 root@服务器IP

三、第二步:生成密钥对

在你自己电脑上生成

ssh-keygen -t rsa -b 4096

一路回车,生成两个文件:

  • ~/.ssh/id_rsa (私钥,自己留着)
  • ~/.ssh/id_rsa.pub (公钥,传到服务器)

把公钥传到服务器

ssh-copy-id -p 22222 root@服务器IP

或者手动传:

# 服务器上创建.ssh目录
mkdir -p ~/.ssh
chmod 700 ~/.ssh

# 把公钥内容追加到authorized_keys
vi ~/.ssh/authorized_keys
# 把id_rsa.pub的内容粘进去

chmod 600 ~/.ssh/authorized_keys

测试密钥登录

ssh -p 22222 root@服务器IP

不用输密码就能登录就对了。

四、第三步:禁用密码登录

编辑SSH配置

vi /etc/ssh/sshd_config

找到:

#PasswordAuthentication yes

改成:

PasswordAuthentication no

找到:

#PermitRootLogin yes

改成:

PermitRootLogin prohibit-password

(允许root用密钥登录,但不允许密码登录)

重启SSH

systemctl restart sshd

五、第四步:其他安全设置

限制登录尝试次数

防止暴力破解:

MaxAuthTries 3

登录超时时间

ClientAliveInterval 300
ClientAliveCountMax 2

禁止空密码登录

PermitEmptyPasswords no

只允许特定用户登录

AllowUsers root

六、安装fail2ban

自动封禁暴力破解的IP。

安装

yum install -y epel-release
yum install -y fail2ban

配置

vi /etc/fail2ban/jail.local

加内容:

[sshd]
enabled = true
port = 22222
filter = sshd
logpath = /var/log/secure
maxretry = 3
bantime = 86400

启动

systemctl start fail2ban
systemctl enable fail2ban

七、完整配置文件参考

vi /etc/ssh/sshd_config
Port 22222
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
PermitEmptyPasswords no

改完重启:

systemctl restart sshd

八、常见坑

坑1:改完端口连不上了

原因: 防火墙没开放新端口。

解决:

  • 云服务器还要在控制台安全组里开放端口
  • 先开新窗口测试,别关旧窗口

坑2:密钥登录失败

原因: 权限不对。

检查:

ls -ld ~/.ssh
ls -l ~/.ssh/authorized_keys

正确权限:

  • .ssh目录:700
  • authorized_keys:600

坑3:禁用密码后进不去了

原因: 密钥没配好就禁用密码了。

解决: 一定先测试密钥登录成功,再禁用密码。

坑4:云服务器安全组没开

改了端口,防火墙也开了,但还是连不上。

原因: 云服务商的安全组没开。

解决: 去云控制台(阿里云/腾讯云)安全组里开放端口。

总结

记住:

  1. 改SSH默认端口(22改成别的)
  2. 生成密钥对,传公钥到服务器
  3. 测试密钥登录成功
  4. 禁用密码登录
  5. 安装fail2ban自动封IP
  6. 云服务器记得开安全组

安全加固顺序:

改端口 → 配密钥 → 测试登录 → 禁密码 → 装fail2ban

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:46 

网站被黑了怎么排查,后门查杀方法

前言

网站突然被挂了黑链、被跳转、内容被篡改?别慌,本文讲网站被黑后怎么排查、怎么找后门、怎么清理。

一、怎么判断被黑了

常见现象

  1. 网站打开变成别的页面(赌博、色情)
  2. 网站里多了很多不认识的页面
  3. 百度搜索显示的标题不对
  4. 网站突然变慢、CPU飙升
  5. 后台密码被改了

二、第一步:找最近修改的文件

黑客一般会往网站目录里传后门文件。

找最近24小时修改的文件

find /www/wwwroot/你的站/ -mtime -1 -type f

找最近7天修改的文件

find /www/wwwroot/你的站/ -mtime -7 -type f

找最近修改的PHP文件

find /www/wwwroot/你的站/ -name "*.php" -mtime -7

重点看: 你不认识的、突然冒出来的PHP文件。

三、第二步:找可疑代码

找一句话木马

黑客常用的一句话后门:

<?php @eval($_POST['cmd']); ?>

搜索:

grep -r "eval(" /www/wwwroot/你的站/
grep -r "assert(" /www/wwwroot/你的站/
grep -r "base64_decode(" /www/wwwroot/你的站/
grep -r "str_rot13(" /www/wwwroot/你的站/

找加密的后门

很多后门是加密的:

grep -r "gzinflate(" /www/wwwroot/你的站/
grep -r "gzuncompress(" /www/wwwroot/你的站/
grep -r "strrev(" /www/wwwroot/你的站/

找下载文件的后门

grep -r "file_put_contents(" /www/wwwroot/你的站/
grep -r "fwrite(" /www/wwwroot/你的站/

四、第三步:看Web日志

看Nginx访问日志,找可疑的请求。

看最近的访问

tail -100 /var/log/nginx/access.log

找可疑的POST请求

grep "POST" /var/log/nginx/access.log | tail -50

找访问可疑文件的

grep ".php" /var/log/nginx/access.log | grep "cmd"

五、第四步:看进程

看有没有可疑进程在跑。

ps aux

重点看:

  • 不认识的进程
  • 占CPU很高的进程
  • 从/tmp目录跑的进程

杀掉可疑进程:

kill -9 进程ID

六、第五步:看定时任务

黑客会加定时任务,维持后门。

crontab -l

看有没有不认识的定时任务。

还有系统级的:

cat /etc/crontab
ls /etc/cron.d/

七、第六步:看后门文件特征

常见后门文件名

  • shell.php
  • cmd.php
  • 1.php
  • x.php
  • upload.php
  • webshell.php

看文件权限

后门一般是777权限:

find /www/wwwroot/你的站/ -perm 777

八、清理方法

方法1:删除可疑文件

确认是后门的直接删:

rm -f /www/wwwroot/你的站/可疑文件.php

方法2:重新上传程序

最安全的方法: 备份数据库,重新上传干净的程序,再恢复数据库。

方法3:改所有密码

  • 后台密码
  • 数据库密码
  • FTP密码
  • SSH密码

九、预防措施

1. 程序及时更新

WordPress、ThinkPHP等程序有漏洞要及时更新。

2. 上传目录禁执行

location ~* /upload/.*\.php$ {
    deny all;
}

3. 定期备份

每天自动备份网站文件和数据库。

4. 改后台地址

不要用默认的admin后台地址。

5. 用安全插件

WordPress可以装Wordfence等安全插件。

十、常见坑

坑1:只删文件不改密码

黑客可能已经留了别的后门,删一个没用。

正确: 删文件 + 改所有密码 + 看定时任务。

坑2:备份也被感染了

备份文件里也有后门,恢复后又被黑了。

解决: 备份也要查。

坑3:只查网站目录

黑客可能在系统里留了后门(比如/tmp目录)。

解决: 也要查系统进程、定时任务。

总结

记住:

  1. 先找最近修改的文件
  2. 再找可疑代码(eval、base64_decode)
  3. 看Web日志和进程
  4. 看定时任务
  5. 删后门 + 改所有密码
  6. 最安全是重新上传干净程序

最快排查:

find /www/wwwroot/你的站/ -name "*.php" -mtime -7
grep -r "eval(" /www/wwwroot/你的站/

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:44 

Docker容器数据备份和迁移方法

前言

Docker容器删了数据就没了?想把容器从一台服务器迁到另一台?本文讲Docker容器数据备份和迁移的几种方法,包括容器快照、数据卷备份、镜像导出。

一、Docker数据存哪里

Docker数据有两种存法:

  1. 容器内部:数据存在容器的可写层,容器删了数据就没了
  2. 数据卷(Volume):数据存在宿主机上,容器删了数据还在

重要: 生产环境一定要用数据卷,不然容器一删数据全没。

二、方法1:容器快照(commit)

把容器当前状态打包成一个新镜像。

步骤

第一步:查看容器ID

docker ps

找到要备份的容器ID。

第二步:提交成新镜像

docker commit 容器ID 备份镜像名:标签

例子:

docker commit mynginx nginx-backup:v1

第三步:导出镜像成文件

docker save -o nginx-backup.tar nginx-backup:v1

第四步:传到新服务器

scp nginx-backup.tar root@新服务器IP:/root/

第五步:新服务器导入

docker load -i nginx-backup.tar

第六步:新服务器启动容器

docker run -d --name mynginx -p 80:80 nginx-backup:v1

优缺点

优点: 简单,整个容器都打包了
缺点: 镜像文件大,不适合大数据量

三、方法2:数据卷备份

如果用了数据卷,直接备份数据卷里的文件。

步骤

第一步:看数据卷挂载在哪

docker inspect 容器名 | grep Mounts

输出里找 Source,就是宿主机上的路径。

例子:

"Mounts": [
    {
        "Source": "/data/mysql",
        "Destination": "/var/lib/mysql"
    }
]

数据在宿主机的 /data/mysql 目录。

第二步:打包备份

tar -zcvf mysql-backup.tar.gz /data/mysql

第三步:传到新服务器

scp mysql-backup.tar.gz root@新服务器IP:/root/

第四步:新服务器解压

tar -zxvf mysql-backup.tar.gz -C /data/

第五步:新服务器启动容器,挂载数据卷

docker run -d --name mysql -v /data/mysql:/var/lib/mysql -p 3306:3306 mysql:5.7

优缺点

优点: 数据和容器分离,灵活
缺点: 要自己管理数据目录

四、方法3:docker cp拷贝文件

把容器里的文件拷出来。

拷出来

docker cp 容器名:/容器内路径 宿主机路径

例子:

docker cp mynginx:/usr/share/nginx/html /data/backup/html

拷进去

docker cp 宿主机路径 容器名:/容器内路径

例子:

docker cp /data/backup/html/index.html mynginx:/usr/share/nginx/html/

五、MySQL容器备份

备份MySQL数据

docker exec 容器名 mysqldump -uroot -p123456 数据库名 > backup.sql

例子:

docker exec mysql mysqldump -uroot -p123456 test > test.sql

恢复MySQL数据

docker exec -i 容器名 mysql -uroot -p123456 数据库名 < backup.sql

例子:

docker exec -i mysql mysql -uroot -p123456 test < test.sql

六、常见坑

坑1:容器删了数据没了

没用数据卷,容器删了数据全没。

解决: 一定要用 -v 挂载数据卷。

坑2:commit的镜像太大

把整个容器都打包了,包括临时文件。

解决: 尽量用数据卷备份,不要用commit。

坑3:数据权限不对

备份恢复后,容器里读不了数据。

原因: 文件所有者不对。

解决: 改权限:

chown -R 999:999 /data/mysql

(MySQL容器用的是999用户)

坑4:数据库没停就备份

MySQL还在写数据,备份出来不一致。

解决: 用mysqldump逻辑备份,不要直接拷文件。

总结

记住:

  1. 生产环境一定要用数据卷(-v)
  2. 小容器用commit快照备份
  3. 大数据量直接备份数据卷目录
  4. 数据库用mysqldump逻辑备份
  5. 备份完要测试能不能恢复

最常用:

# 备份数据卷
tar -zcvf backup.tar.gz /data/容器数据/

# 恢复数据卷
tar -zxvf backup.tar.gz -C /data/

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:43 

MySQL数据库备份和恢复,mysqldump完整教程

前言

数据库备份是每个站长的基本功。MySQL最常用的备份工具就是mysqldump,简单好用。本文讲怎么用mysqldump备份和恢复数据库,包括完整命令和常见坑。

一、mysqldump是什么

mysqldump是MySQL自带的备份工具,把数据库导出成SQL文件,需要的时候再导入回去。

特点:

  • 免费,MySQL自带
  • 导出的是SQL文本,方便查看和编辑
  • 适合小数据库(几十G以内)

二、备份数据库

备份单个数据库

mysqldump -u用户名 -p密码 数据库名 > 备份文件.sql

例子:

mysqldump -uroot -p123456 test > test.sql

注意: -p和密码之间没有空格。

备份多个数据库

mysqldump -uroot -p123456 --databases db1 db2 db3 > dbs.sql

备份所有数据库

mysqldump -uroot -p123456 --all-databases > all.sql

备份单个表

mysqldump -uroot -p123456 test users > users.sql

备份多个表

mysqldump -uroot -p123456 test users orders > tables.sql

三、常用参数

带删除表语句(--add-drop-table)

导出的SQL里会有 DROP TABLE IF EXISTS,恢复的时候先删表再建表。

mysqldump -uroot -p123456 --add-drop-table test > test.sql

默认就是开的。

不带建表语句(--no-create-info)

只导数据,不导表结构。

mysqldump -uroot -p123456 --no-create-info test > data.sql

只导表结构(--no-data)

只导表结构,不导数据。

mysqldump -uroot -p123456 --no-data test > structure.sql

压缩备份

mysqldump -uroot -p123456 test | gzip > test.sql.gz

解压:

gunzip test.sql.gz

指定端口

mysqldump -uroot -p123456 -P 3307 test > test.sql

指定主机

mysqldump -uroot -p123456 -h 1.2.3.4 test > test.sql

四、恢复数据库

方法1:用mysql命令

mysql -u用户名 -p密码 数据库名 < 备份文件.sql

例子:

mysql -uroot -p123456 test < test.sql

方法2:登录MySQL后source

mysql -uroot -p123456
mysql> use test;
mysql> source /root/test.sql;

恢复压缩备份

gunzip < test.sql.gz | mysql -uroot -p123456 test

五、定时备份脚本

写个备份脚本

#!/bin/bash

# 数据库配置
DB_USER="root"
DB_PASS="123456"
DB_NAME="test"

# 备份目录
BACKUP_DIR="/data/backup"

# 文件名(带日期)
DATE=$(date +%Y%m%d_%H%M%S)
FILE_NAME="${DB_NAME}_${DATE}.sql.gz"

# 备份
mysqldump -u${DB_USER} -p${DB_PASS} ${DB_NAME} | gzip > ${BACKUP_DIR}/${FILE_NAME}

# 删除7天前的备份
find ${BACKUP_DIR} -name "*.sql.gz" -mtime +7 -delete

echo "备份完成:${FILE_NAME}"

加执行权限

chmod +x /root/backup.sh

加到crontab

每天凌晨3点备份:

crontab -e

加一行:

0 3 * * * /root/backup.sh

六、常见坑

坑1:密码写在命令行里不安全

-p123456 会在进程列表里看到密码。

更安全的方法:

mysqldump -uroot -p test > test.sql

然后手动输入密码。

或者用配置文件:

# ~/.my.cnf
[mysqldump]
user=root
password=123456

然后直接:

mysqldump test > test.sql

坑2:大表备份很慢

数据多了以后,mysqldump会锁表,影响业务。

解决: 用 --single-transaction 参数:

mysqldump -uroot -p123456 --single-transaction test > test.sql

这个参数适合InnoDB,不锁表。

坑3:字符集不对

导出的SQL乱码。

加参数:

mysqldump -uroot -p123456 --default-character-set=utf8mb4 test > test.sql

坑4:自增ID不连续

恢复后自增ID变了。

正常现象,不用管。

坑5:备份文件太大

用gzip压缩:

mysqldump -uroot -p123456 test | gzip > test.sql.gz

七、备份验证

备份完了要验证一下能不能恢复。

看文件大小

ls -lh test.sql

不能是0字节。

看文件内容

head -20 test.sql

看开头是不是正常的SQL。

测试恢复

找个测试库恢复一下:

mysql -uroot -p123456 test_restore < test.sql

总结

记住:

  1. 备份:mysqldump -uroot -p123456 数据库 > 备份.sql
  2. 恢复:mysql -uroot -p123456 数据库 < 备份.sql
  3. 大表加 --single-transaction 不锁表
  4. 用gzip压缩备份文件
  5. 定时备份要自动删旧备份
  6. 备份完要验证

最常用:

# 备份
mysqldump -uroot -p123456 --single-transaction test | gzip > test.sql.gz

# 恢复
gunzip < test.sql.gz | mysql -uroot -p123456 test

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:41 

Nginx 502 Bad Gateway常见原因和解决办法

前言

网站突然打不开了,浏览器显示"502 Bad Gateway"?这是Nginx最常见的报错之一。本文讲502是什么意思、常见原因、怎么排查和解决。

一、502是什么意思

502 Bad Gateway = Nginx作为代理,从后端服务器(比如PHP-FPM)拿不到正常的响应。

简单说:Nginx收到了你的请求,然后去问后端"给我数据",后端没理它或者报错了,Nginx就返回502。

二、常见原因

原因1:PHP-FPM没启动

后端PHP服务没跑,Nginx找不到人。

检查:

ps aux | grep php-fpm

没输出就是没启动。

启动:

systemctl start php-fpm

或者:

service php-fpm start

原因2:PHP-FPM端口不对

Nginx配置里写的PHP-FPM地址不对。

比如:

fastcgi_pass 127.0.0.1:9000;

但PHP-FPM实际监听的是 /tmp/php-cgi.sock。

检查PHP-FPM监听:

netstat -tlnp | grep php-fpm

或者:

ss -tlnp | grep php-fpm

原因3:PHP代码报错

PHP代码有致命错误,直接挂了。

看PHP错误日志:

tail -f /var/log/php-fpm/www-error.log

或者:

tail -f /var/log/php/error.log

原因4:PHP超时了

程序跑太慢,超过Nginx或PHP-FPM的超时时间。

改Nginx超时:

fastcgi_read_timeout 300;

改PHP-FPM超时:

request_terminate_timeout = 300

原因5:PHP-FPM进程数不够

请求太多,PHP-FPM忙不过来。

看进程数:

ps aux | grep php-fpm | wc -l

改配置:

pm.max_children = 50
pm.start_servers = 20

原因6:内存不够了

PHP进程占满内存,系统把PHP杀了。

看内存:

free -h

看dmesg:

dmesg | grep oom

三、排查步骤

第一步:看Nginx错误日志

tail -f /var/log/nginx/error.log

常见报错:

报错 含义
connect() failed (111: Connection refused) PHP-FPM没启动
Primary script unknown 文件路径不对
upstream timed out PHP超时了
recv() failed (104: Connection reset by peer) PHP-FPM挂了

第二步:看PHP-FPM状态

systemctl status php-fpm

看是不是running。

第三步:看PHP错误日志

tail -f /var/log/php-fpm/www-error.log

看PHP代码有没有报错。

第四步:看系统资源

top
free -h
df -h

看CPU、内存、磁盘够不够。

四、常见解决方法

方法1:重启PHP-FPM

systemctl restart php-fpm

方法2:改Nginx配置

location ~ \.php$ {
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include        fastcgi_params;

    # 超时时间
    fastcgi_read_timeout 300;
    fastcgi_send_timeout 300;
}

方法3:改PHP-FPM进程数

pm = dynamic
pm.max_children = 50
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30

改完重启:

systemctl restart php-fpm

方法4:开PHP错误显示

display_errors = On
error_reporting = E_ALL

五、常见坑

坑1:只重启Nginx,不重启PHP-FPM

502是后端的问题,重启Nginx没用。

正确: 重启PHP-FPM。

坑2:文件路径不对

fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;

错! 应该是:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

坑3:PHP-FPM版本不对

装了PHP7但Nginx还找PHP5的sock。

检查:

ls -la /var/run/php/

坑4:权限问题

PHP-FPM没权限读网站目录的文件。

解决:

chown -R www:www /www/wwwroot/

总结

记住:

  1. 502 = Nginx拿不到后端响应
  2. 先看Nginx错误日志
  3. 再看PHP-F启没启动
  4. 然后看PHP代码有没有报错
  5. 最后看系统资源够不够

最快排查:

tail -f /var/log/nginx/error.log
systemctl status php-fpm

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:40 

Linux查看系统负载,uptime和w命令详解

前言

服务器卡不卡,先看负载。Linux看系统负载最常用的就是uptime和w命令,一行输出就能看到系统跑了多久、现在有多少人登录、负载高不高。本文讲怎么看这些信息。

一、uptime命令

直接敲

uptime

输出:

 07:39:01 up 35 days,  2:15,  2 users,  load average: 0.08, 0.03, 0.00

逐个解释

字段 含义
07:39:01 当前时间
up 35 days, 2:15 系统运行了35天2小时15分
2 users 当前有2个用户登录
load average: 0.08, 0.03, 0.00 系统负载,分别是1分钟、5分钟、15分钟

负载怎么看

load average三个数:

  • 第一个:最近1分钟的平均负载
  • 第二个:最近5分钟的平均负载
  • 第三个:最近15分钟的平均负载

判断标准:

负载值 状态
0.00-0.70 正常
0.70-1.00 注意一下
1.00-2.00 有点高
2.00以上 很高了,要排查

注意: 负载和CPU核数有关。比如4核CPU,负载4.00才是满。

看CPU核数

nproc
# 4

或者:

cat /proc/cpuinfo | grep processor | wc -l
# 4

判断: 负载小于CPU核数就是正常的。

二、w命令

直接敲

w

输出:

 07:39:01 up 35 days,  2:15,  2 users,  load average: 0.08, 0.03, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
root     pts/0    1.2.3.4          07:35    5.00s  0.03s  0.00s w
www      pts/1    5.6.7.8          07:30    8:12   0.01s  0.01s -bash

逐个解释

第一行和uptime一样。

下面是登录用户列表:

字段 含义
USER 用户名
TTY 终端类型(pts/0表示远程连接)
FROM 从哪个IP登录的
LOGIN@ 登录时间
IDLE 空闲时间
JCPU 整个终端所有进程用的CPU时间
PCPU 当前进程用的CPU时间
WHAT 当前在跑什么命令

三、其他查看命令

who命令

看谁登录了:

who

输出:

root     pts/0        2026-10-09 07:35 (1.2.3.4)
www      pts/1        2026-10-09 07:30 (5.6.7.8)

last命令

看登录历史:

last

输出最近登录的记录。

lastb命令

看失败的登录:

lastb

四、负载高了怎么排查

第一步:看CPU

top

按P按CPU排序,看哪个进程占CPU多。

第二步:看内存

free -h

看内存够不够,有没有用swap。

第三步:看磁盘

df -h

看磁盘满了没。

第四步:看IO

iostat

看磁盘IO高不高。

五、常见坑

坑1:负载高不代表CPU高

负载包括:

  • 正在用CPU的进程
  • 等着CPU的进程
  • 等着磁盘IO的进程

可能是磁盘IO高,CPU不高,但负载也高。

坑2:负载和核数对比

1核CPU,负载1.0就是满了。
4核CPU,负载4.0才是满了。

别光看数字,要看CPU核数。

坑3:三个负载数的区别

  • 1分钟高,5分钟、15分钟不高:可能是突然有个进程跑了一下
  • 15分钟都高:说明系统一直很忙

总结

记住:

  1. uptime 看系统运行时间和负载
  2. w 看谁登录了、在干嘛
  3. 负载三个数:1分钟、5分钟、15分钟
  4. 负载要和CPU核数对比
  5. 负载高了用top、free、df、iostat排查

最常用:

uptime    # 看负载
w         # 看谁在登录
top       # 看哪个进程占CPU

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:39 

PHP实现简单分页,SQL limit用法

前言

做网站列表页,数据太多一页显示不完,需要分页。PHP+MySQL做分页很简单,就是用SQL的limit。本文写个完整的分页示例,直接拿去用。

一、分页原理

分页就两件事:

  1. 查总条数:看看一共有多少条数据
  2. 查当前页数据:用limit取当前页要显示的那几条

公式:

偏移量 = (当前页 - 1) × 每页条数

比如每页10条,第3页:

偏移量 = (3 - 1) × 10 = 20
SQL: SELECT * FROM users LIMIT 20, 10

二、SQL limit用法

语法

SELECT * FROM 表名 LIMIT 偏移量, 每页条数

例子

-- 取前10条
SELECT * FROM users LIMIT 0, 10;

-- 取第11-20条
SELECT * FROM users LIMIT 10, 10;

-- 取第21-30条
SELECT * FROM users LIMIT 20, 10;

三、完整分页代码

第一步:接收当前页码

<?php
// 当前页,默认第1页
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;

// 每页显示多少条
$pageSize = 10;

// 计算偏移量
$offset = ($page - 1) * $pageSize;

第二步:查总条数

// 连接数据库
$conn = mysqli_connect('127.0.0.1', 'root', '123456', 'test', 3306);
mysqli_set_charset($conn, 'utf8mb4');

// 查总条数
$sql = "SELECT COUNT(*) as total FROM users";
$result = mysqli_query($conn, $sql);
$row = mysqli_fetch_assoc($result);
$total = $row['total'];

// 算总页数
$totalPage = ceil($total / $pageSize);

第三步:查当前页数据

// 查当前页的数据
$sql = "SELECT id, name, age FROM users ORDER BY id DESC LIMIT $offset, $pageSize";
$result = mysqli_query($conn, $sql);

// 取数据
$list = [];
while ($row = mysqli_fetch_assoc($result)) {
    $list[] = $row;
}

第四步:显示数据和分页导航

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>用户列表</title>
</head>
<body>

<h1>用户列表</h1>

<table border="1">
    <tr>
        <th>ID</th>
        <th>姓名</th>
        <th>年龄</th>
    </tr>
    <?php foreach ($list as $item): ?>
    <tr>
        <td><?php echo $item['id']; ?></td>
        <td><?php echo $item['name']; ?></td>
        <td><?php echo $item['age']; ?></td>
    </tr>
    <?php endforeach; ?>
</table>

<p>共 <?php echo $total; ?> 条,第 <?php echo $page; ?> / <?php echo $totalPage; ?> 页</p>

<p>
    <?php if ($page > 1): ?>
    <a href="?page=<?php echo $page - 1; ?>">上一页</a>
    <?php endif; ?>

    <?php for ($i = 1; $i <= $totalPage; $i++): ?>
        <?php if ($i == $page): ?>
            <strong><?php echo $i; ?></strong>
        <?php else: ?>
            <a href="?page=<?php echo $i; ?>"><?php echo $i; ?></a>
        <?php endif; ?>
    <?php endfor; ?>

    <?php if ($page < $totalPage): ?>
    <a href="?page=<?php echo $page + 1; ?>">下一页</a>
    <?php endif; ?>
</p>

</body>
</html>

四、封装成分页函数

function pagination($page, $totalPage, $url = '')
{
    $html = '';

    if ($page > 1) {
        $html .= '<a href="' . $url . '?page=' . ($page - 1) . '">上一页</a> ';
    }

    for ($i = 1; $i <= $totalPage; $i++) {
        if ($i == $page) {
            $html .= '<strong style="margin:0 5px;">' . $i . '</strong> ';
        } else {
            $html .= '<a href="' . $url . '?page=' . $i . '" style="margin:0 5px;">' . $i . '</a> ';
        }
    }

    if ($page < $totalPage) {
        $html .= '<a href="' . $url . '?page=' . ($page + 1) . '">下一页</a>';
    }

    return $html;
}

// 使用
echo pagination($page, $totalPage);

五、常见坑

坑1:页码没做限制

用户输入 ?page=999,结果查出来是空的。

解决: 限制页码范围:

$page = max(1, min($page, $totalPage));

坑2:offset算错了

$offset = $page * $pageSize; // 错!

正确:

$offset = ($page - 1) * $pageSize; // 对!

坑3:总条数查错了

带条件查询的时候,总条数也要带条件。

比如:

// 带条件查数据
$sql = "SELECT * FROM users WHERE status = 1 LIMIT $offset, $pageSize";

// 总条数也要带条件
$sql = "SELECT COUNT(*) FROM users WHERE status = 1";

坑4:大表分页越查越慢

数据多了以后,LIMIT 100000, 10 会很慢。

原因: MySQL要先扫描前面10万条再跳过。

优化:

-- 先查id,再关联
SELECT u.* FROM users u
INNER JOIN (SELECT id FROM users ORDER BY id LIMIT 100000, 10) t
ON u.id = t.id;

六、带搜索条件的分页

// 搜索关键词
$keyword = isset($_GET['keyword']) ? trim($_GET['keyword']) : '';

// 拼WHERE
$where = "WHERE 1=1";
if ($keyword) {
    $where .= " AND name LIKE '%$keyword%'";
}

// 总条数
$sql = "SELECT COUNT(*) as total FROM users $where";

// 当前页数据
$sql = "SELECT * FROM users $where ORDER BY id DESC LIMIT $offset, $pageSize";

总结

记住:

  1. 分页就是 LIMIT 偏移量, 每页条数
  2. 偏移量 = (当前页 - 1) × 每页条数
  3. 先查总条数,再算总页数
  4. 页码要做限制,不能超出范围
  5. 带条件查询时,总条数也要带条件

核心公式:

$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$pageSize = 10;
$offset = ($page - 1) * $pageSize;

$sql = "SELECT * FROM users LIMIT $offset, $pageSize";

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:37 

Redis 缓存穿透解决

Redis 缓存穿透解决

缓存穿透是常见问题,恶意请求能把数据库打挂。

1. 什么是缓存穿透

现象: 请求的数据在缓存和数据库中都不存在,每次请求都直接打到数据库。

危害:

  • 恶意攻击时数据库压力大
  • 正常请求被拖慢
  • 数据库CPU飙升

2. 为什么会穿透

  • 查询一个不存在的ID
  • 缓存没有命中
  • 数据库也查不到
  • 请求直接穿透到数据库

3. 解决方案一:缓存空值

思路: 即使数据库查不到,也在Redis存一个空值,设置短过期时间。

function getUser($id) {
    $key = "user:$id";
    $user = $redis->get($key);

    if ($user !== false) {
        // 空值标记
        if ($user === 'null') {
            return null;
        }
        return json_decode($user, true);
    }

    // 查数据库
    $user = $db->query("SELECT * FROM users WHERE id = $id");

    if ($user) {
        // 存在就缓存1小时
        $redis->setex($key, 3600, json_encode($user));
    } else {
        // 不存在也缓存,5分钟过期
        $redis->setex($key, 300, 'null');
    }

    return $user;
}

优点: 简单有效
缺点: 占内存,可能被恶意key刷爆

4. 解决方案二:布隆过滤器

思路: 在缓存前面加一层布隆过滤器,不存在的key直接拦截。

原理:

  • 所有存在的数据先存到布隆过滤器
  • 请求先过布隆过滤器
  • 不存在直接返回,不查缓存和数据库

PHP 示例(用 Redis 的布隆过滤器模块):

# 加载 RedisBloom 模块
redis-cli BF.ADD user:1
redis-cli BF.EXISTS user:1
function getUser($id) {
    $key = "user:$id";

    // 先过布隆过滤器
    if (!$redis->rawCommand('BF.EXISTS', 'user_filter', $id)) {
        return null; // 一定不存在
    }

    // 布隆说可能存在,再查缓存
    $user = $redis->get($key);
    if ($user) {
        return json_decode($user, true);
    }

    // 查数据库...
}

优点: 内存占用小,效率高
缺点: 有误判率,实现复杂

5. 解决方案三:参数校验

思路: 从源头过滤非法请求。

function getUser($id) {
    // 参数校验
    if ($id <= 0 || !is_numeric($id)) {
        return null;
    }

    // 正常查询逻辑...
}

适用: ID类查询,不合法直接拦截

6. 三种方案对比

方案 优点 缺点 适用场景
缓存空值 简单 占内存 数据量不大
布隆过滤器 高效 误判,复杂 数据量大
参数校验 最直接 只防非法 ID类请求

7. 生产建议

1)组合使用

  • 先做参数校验
  • 再缓存空值
  • 大流量用布隆过滤器

2)空值过期时间要短

  • 5分钟到10分钟
  • 不要太长

3)监控异常请求

  • 发现大量404要警惕
  • 及时加防护

4)接口限流

  • 用 Nginx 或网关限流
  • 防止恶意刷

8. 缓存雪崩和击穿

缓存穿透: 查不存在的数据
缓存击穿: 热点key过期,大量请求打数据库
缓存雪崩: 大量key同时过期

区分清楚: 三个问题现象类似但原因不同,解决方案也不同。

总结

Redis 缓存穿透解决要点:

  • 查不存在的数据穿透到数据库
  • 缓存空值是最简单方案
  • 布隆过滤器适合大数据量
  • 参数校验从源头拦截
  • 空值过期时间要短
  • 生产建议组合使用
  • 注意和雪崩、击穿区分开
  • 监控异常请求
  • 接口加限流

缓存穿透是面试高频题,也是生产常见问题,一定要掌握。


emer 发布于  2026-10-9 07:36 

PHP报错Permission denied权限问题解决

前言

PHP写文件、读文件、上传文件,突然报"Permission denied"?这是Linux文件权限问题,最常见就是文件或目录的所有者不对、权限不够。本文讲怎么排查和解决。

一、报错长啥样

Warning: fopen(/data/test.txt): failed to open stream: Permission denied in /www/test.php on line 5

意思:PHP没权限操作这个文件/目录。

二、常见原因

原因1:文件/目录权限不够

PHP没有读或写的权限。

原因2:文件/目录所有者不对

PHP用www用户跑,但文件是root创建的,www没权限。

原因3:SELinux开着

CentOS默认开SELinux,即使权限对了也不让访问。

原因4:目录权限不够

要写文件的话,目录本身也要有写权限。

三、排查步骤

第一步:看报错的文件路径

报错信息里会告诉你是哪个文件:

Permission denied: /data/upload/test.jpg

就是 /data/upload/ 这个目录的问题。

第二步:看文件权限

ls -l /data/upload/

输出类似:

-rw-r--r-- 1 root root 1024 Oct  9 10:00 test.txt

看第一个字段:

  • -rw-r--r-- 表示所有者能读写,其他人只能读

看所有者:

  • root root 表示所有者和组都是root

第三步:看PHP用什么用户跑

ps aux | grep php-fpm

输出里会看到:

www   1234  0.0  0.5 123456 7890  ?  S  10:00  0:00 php-fpm: pool www

PHP是用 www 用户跑的。

如果文件所有者是root,那www用户就没权限写。

四、解决方法

方法1:改文件所有者

把文件/目录的所有者改成PHP的用户:

# 改单个文件
chown www:www /data/upload/test.txt

# 改整个目录(包括里面的文件)
chown -R www:www /data/upload/

推荐这个方法,最安全。

方法2:改权限

给所有人加权限:

# 文件加写权限
chmod 666 /data/upload/test.txt

# 目录加写权限
chmod 777 /data/upload/

注意: 777太不安全了,尽量用方法1改所有者。

方法3:看SELinux

CentOS开了SELinux的话,即使权限对了也不行。

检查SELinux:

getenforce

输出 Enforcing 就是开着。

临时关掉:

setenforce 0

永久关掉:

vi /etc/selinux/config

把:

SELINUX=enforcing

改成:

SELINUX=disabled

重启服务器生效。

或者只给目录加SELinux权限:

chcon -R -t httpd_sys_rw_content_t /data/upload/

五、常见场景

场景1:上传图片报错

用户上传图片,报Permission denied。

原因: uploads目录所有者不对。

解决:

chown -R www:www /www/wwwroot/你的站/uploads/

场景2:写日志报错

PHP写日志文件,报Permission denied。

原因: 日志目录没权限。

解决:

chown -R www:www /var/log/你的应用/

场景3:session不存

PHP session存不进去。

原因: session目录没权限。

解决:

chown -R www:www /var/lib/php/session/

场景4:缓存目录没权限

ThinkPHP/Laravel缓存写不进去。

原因: runtime目录没权限。

解决:

chown -R www:www runtime/

六、常用命令

看文件权限

ls -l 文件名

看目录权限

ls -ld 目录名

改所有者

chown 用户:组 文件
chown -R 用户:组 目录

改权限

chmod 755 目录
chmod 644 文件

七、权限数字含义

数字 含义
7 读+写+执行 (4+2+1)
6 读+写 (4+2)
5 读+执行 (4+1)
4 只读

常用:

  • 目录:755
  • 文件:644
  • 上传目录:775(或改所有者)

八、常见坑

坑1:只改了文件,没改目录

要写文件的话,目录本身也要有写权限。

解决: 目录也要改所有者。

坑2:递归改权限

chmod -R 777 目录

太危险了,所有人都能写。

解决: 优先改所有者,不要随便777。

坑3:SELinux偷偷开着

CentOS7以后默认开SELinux,权限对了也不行。

检查:

getenforce

坑4:父目录没权限

比如 /data/upload/ 有权限了,但 /data/ 没权限,还是进不去。

解决: 从根开始一层一层检查。

总结

记住:

  1. 报错先看是哪个文件/目录
  2. 看文件所有者是不是PHP的用户(一般是www)
  3. 不是就 chown -R www:www 目录
  4. 还不行就检查SELinux
  5. 尽量改所有者,不要随便777

最常用:

chown -R www:www /你的目录/

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-9 07:36 

Nginx 配置HTTPS证书

Nginx 配置HTTPS证书

现在网站都要HTTPS,配置证书是必备技能。

1. 为什么要HTTPS

  • 加密传输,防窃听
  • 浏览器安全标识
  • SEO 排名加分
  • 现在主流浏览器都要求HTTPS

2. 证书文件

一般有两个文件:

  • .crt:证书文件
  • .key:私钥文件
# 证书文件放在这里
/etc/nginx/ssl/
├── example.com.crt
└── example.com.key

3. 基本HTTPS配置

server {
    listen 443 ssl;
    server_name example.com;

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

    # 其他配置
    location / {
        root /var/www/html;
        index index.html;
    }
}

4. HTTP 跳转 HTTPS

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

5. 优化配置

server {
    listen 443 ssl http2;
    server_name example.com;

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

    # 优化SSL配置
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # 加密套件
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

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

6. 检查配置

# 检查配置语法
nginx -t

# 重载配置
nginx -s reload

7. 证书申请

免费证书:

  • Let's Encrypt(用 certbot 申请)
  • 阿里云免费证书
  • 腾讯云免费证书

certbot 申请:

# 安装 certbot
yum install certbot python3-certbot-nginx

# 自动申请并配置
certbot --nginx -d example.com

自动续期:

# 测试续期
certbot renew --dry-run

# 加入定时任务
0 3 * * * certbot renew --quiet

8. 常见问题

1)证书不生效

  • 检查证书路径对不对
  • 检查文件权限
  • 重载 Nginx

2)浏览器提示不安全

  • 证书过期了
  • 域名不匹配
  • 中间证书没装全

3)HTTPS 访问慢

  • 开启 HTTP/2
  • 优化 SSL 会话缓存

4)证书续期失败

  • 检查域名解析
  • 检查80端口是否通
  • 手动运行 certbot 看报错

9. 证书有效期

  • 免费证书一般 3 个月
  • 要设置自动续期
  • 不然到期网站就挂了

10. 生产建议

1)强制 HTTPS

  • HTTP 全部 301 跳转
  • 开启 HSTS

2)开启 HTTP/2

  • listen 443 ssl http2
  • 提升访问速度

3)定期检查证书

  • 监控证书到期时间
  • 提前续期

4)中间证书

  • 有些证书要把中间证书合并
  • 不然浏览器不信任

总结

Nginx 配置HTTPS要点:

  • 配置证书和私钥路径
  • HTTP 跳转 HTTPS
  • 开启 HTTP/2
  • 优化 SSL 会话缓存
  • 用 certbot 免费申请
  • 设置自动续期
  • 定期检查证书有效期
  • 开启 HSTS
  • 注意中间证书问题

HTTPS 是网站标配,一定要配置好。


emer 发布于  2026-10-9 07:35