Linux wget下载文件常见问题汇总(2026最新整理) Linux
wget下载文件说难不难,说简单也不简单。会用只要十分钟,用好却需要一些经验积累。本文把这些经验浓缩成几个要点,帮你少走弯路。
服务器被暴力SSH爆破防护与封禁IP方法
公网服务器的 22 端口几乎每天都会遭遇自动化暴力破解:脚本批量尝试 root、admin 等常见账号和弱口令。一旦密码被撞开,服务器就会被植入木马、用来挖矿或发起攻击。防护要分层:先把 SSH 本身改难攻,再用防火墙或 fail2ban 自动封禁异常 IP,最后关闭不必要的暴露。本文给出可直接落地的一套做法。
一、先看是否正在被爆破
查看 SSH 登录日志:
# CentOS7
tail -f /var/log/secure
# Ubuntu/Debian
tail -f /var/log/auth.log
统计失败登录次数最多的 IP:
grep 'Failed password' /var/log/secure | awk '{print $11}' | sort | uniq -c | sort -rn | head
大量来自同一 IP 的 Failed password,就是典型爆破。
二、加固 SSH 基础配置
编辑 /etc/ssh/sshd_config:
# 禁止 root 直接登录(先建好普通用户再关)
PermitRootLogin no
# 关闭密码登录,改用密钥
PasswordAuthentication no
PubkeyAuthentication yes
# 改一个高位端口,例如 2222,避开默认扫描
Port 2222
# 限制单连接认证尝试次数
MaxAuthTries 3
# 空闲超时自动断开
ClientAliveInterval 300
ClientAliveCountMax 2
保存后重启:
systemctl restart sshd
务必先在新窗口测试密钥能登录,再关闭密码,避免把自己锁死。
三、手动封禁单个恶意 IP
临时封禁(重启失效):
# firewalld
firewall-cmd --add-rich-rule='rule family=ipv4 source address=1.2.3.4 reject'
# iptables
iptables -I INPUT -s 1.2.3.4 -j DROP
永久封禁 firewalld:
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=1.2.3.4 reject'
firewall-cmd --reload
四、用 fail2ban 自动封禁(推荐)
fail2ban 监控日志,达到失败次数阈值自动封 IP,最省心。
# CentOS7 安装
yum install -y epel-release
yum install -y fail2ban
新建配置 /etc/fail2ban/jail.local:
[sshd]
enabled = true
port = 2222
maxretry = 3
findtime = 10m
bantime = 1h
含义:10 分钟内失败 3 次,封禁 1 小时。启动并开机自启:
systemctl enable --now fail2ban
查看封禁列表:
fail2ban-client status sshd
五、其他补充防护
- 云平台安全组只放行你自己的 IP 访问 22/2222,比纯靠服务端更硬。
- 用密钥登录,私钥设置密码口令,禁用所有弱密码账号。
- 定期查看日志,清理长期不用的账号。
- 配合防火墙只允许必要端口对外,关闭多余服务。
- 开启系统日志异地留存,便于事后追溯。
常见坑
- 直接关 PermitRootLogin 却没配好普通用户,导致谁都登不进。
- 改了端口没在防火墙放行,新端口连不上。
- fail2ban 的 port 写成默认 22,改端口后没同步,监控失效。
- 只封禁不断源,爆破流量仍消耗带宽,安全组白名单更有效。
- 误封自己 IP:运维前把本机 IP 加入白名单。
总结
防护顺序:密钥登录 + 改端口 + 禁 root 密码 → fail2ban 自动封失败 IP → 云安全组白名单。先看日志确认攻击来源,再分层收敛暴露面,比单纯改一个强密码可靠得多。
遇到问题加QQ23979811 协助处理
Linux查看CPU内存负载top命令详解
top 是 Linux 上最常用的实时资源监控工具,一屏看到 CPU、内存、负载和进程排行。很多人打开 top 只看一眼数字就关掉,却读不懂各字段含义,导致判断不出到底是 CPU 忙、内存不够还是 I/O 卡顿。本文把 top 顶部统计区和进程表逐列讲清,并给出常用交互按键。
一、启动与退出
top
直接进入实时界面,按 q 退出;想刷新间隔设为 2 秒:
top -d 2
只看某个进程:
top -p 1234
二、顶部统计区逐行解读
第一行(负载行):
up 120 days, load average: 0.25, 0.18, 0.10
- up 是开机时长;load average 三个数分别是 1 分钟、5 分钟、15 分钟平均负载。负载值超过 CPU 核心数就说明排队了。
第二、三行是 CPU 占用拆分:
Cpu(s): 1.2%us, 0.5%sy, 0.0%ni, 98.1%id, 0.2%wa
- us 用户进程占用,sy 内核占用,id 空闲,wa 等待 I/O。wa 高说明瓶颈在磁盘,不在 CPU。
内存两行:
KiB Mem : total free used buff/cache
KiB Swap: total free used avail
注意 buff/cache 是缓存,不是被浪费;真正可用看 avail(可用内存),used 高不必紧张。
三、进程表关键列
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
- PID 进程号;%CPU、%MEM 分别是 CPU 和内存占用。
- RES 是进程真正占用的物理内存,看内存问题主要看它。
- S 是状态:R 运行、S 睡眠、Z 僵尸进程。
- TIME+ 是累计占用 CPU 时间。
按 %CPU 排序(默认),大写 M 按内存排序,大写 P 切回 CPU 排序。
四、常用交互按键
P按 CPU 占用降序;M按内存降序;T按累计时间排序。k输入 PID 直接杀进程;r调整进程优先级(renice)。1展开显示每个核心的 CPU;f自定义显示列。q退出。
五、判断瓶颈的经验
- %us 长期接近 100%:计算密集,先找高 CPU 进程。
- %wa 高:磁盘 I/O 堵,换 iostat、iotop 细看。
- avail 很低、swap used 上涨:内存不足,会触发换页卡顿。
- 负载高但 CPU 不高:多为 I/O 等待或大量进程排队。
- 想看历史趋势别只靠 top,配合 sar、vmstat 采样。
常见坑
- 把 buff/cache 当成“内存被占满”,其实缓存可回收,看 avail 才准。
- 负载数值不和核心数比,单核机器负载到 1 就已经饱和。
- top 里 %CPU 可能超过 100%,那是多核累加,属正常。
- 一次性快照不能说明问题,要看持续趋势。
- 杀进程用 k 前先确认 PID,别误杀关键服务。
总结
top 先读负载和 CPU 的 us/wa 区分计算还是 I/O 瓶颈,内存看 RES 和 avail 而非 used,进程表按 P/M 排序定位高占用 PID,再用 k 处理。日常排查配合 iostat、vmstat、sar 更可靠。
遇到问题加QQ23979811 协助处理
服务器SSH连接慢DNS反向解析解决办法
现象:用 ssh 登录服务器,输完密码前要干等十几秒甚至更久,登录后一切正常。这种“卡在登录前”的慢,绝大多数不是网络带宽问题,而是 sshd 默认开启了反向 DNS 解析:它拿到客户端 IP 后会去做一次 PTR 反查,查不到或 DNS 超时,就一直等到超时才放行。关掉这个反查,登录立刻变快。
一、原理:sshd 的反向解析
默认配置里 sshd 会对来源 IP 做反向 DNS,并记录到日志的主机名。如果服务器本身 DNS 不通、或者客户端 IP 没有 PTR 记录,反查会阻塞到超时,表现就是“连得上但很慢”。
二、关闭反向 DNS(核心修改)
编辑 sshd 配置:
vim /etc/ssh/sshd_config
找到并修改(没有就新增):
UseDNS no
GSSAPIAuthentication no
- UseDNS no:关闭反向 DNS 解析,这是解决慢的关键。
- GSSAPIAuthentication no:关闭 GSSAPI 认证探测,进一步减少等待。
三、重启 sshd 生效
# CentOS7 / Systemd
systemctl restart sshd
# 老版本或 Ubuntu
service sshd restart
注意:务必另开一个连接先测试,不要关掉当前会话,防止配置写错把自己挡在门外。
四、验证是否还慢
新开一个终端用 -v 看握手过程:
ssh -v root@服务器IP
观察日志,如果不再出现 “reverse mapping”、“Trying GSSAPI” 之类的等待,且输入提示立即出现,说明生效。
五、其他可能导致 SSH 慢的原因
- DNS 服务器本身慢:/etc/resolv.conf 里的 nameserver 不可达,可临时改成公共 DNS 或先在 UseDNS 关闭后观察。
- 认证探测过多:在 sshd_config 里关闭不需要的认证方式:
PubkeyAuthentication yes
PasswordAuthentication yes
KbdInteractiveAuthentication no
UsePAM yes
- 客户端到服务器网络抖动:用 ping、mtr 看延迟丢包,区分是服务端配置还是链路问题。
- 防火墙限流或 fail2ban 误判:第一次卡、之后正常,多为反查;一直被拒才看防火墙。
- 登录后慢另说:登录成功才慢,通常是 ~/.bashrc、motd、脚本加载,和反查无关。
常见坑
- 只改了 UseDNS 没重启 sshd,配置不生效,必须 restart。
- 改错文件:客户端是 ssh_config,服务端是 sshd_config,别弄混。
- 关了反查但仍慢,多为 GSSAPI 或 DNS 服务器问题,两项一起关。
- 远程改配置断连风险高,保留一个已登录窗口做兜底。
- 公网服务器建议配合密钥登录并关闭密码登录,既快又安全。
总结
SSH 登录前卡顿先查反向 DNS:在 /etc/ssh/sshd_config 设置 UseDNS no 和 GSSAPIAuthentication no,重启 sshd 即可秒连。仍慢再依次排查 DNS 服务器、认证探测和网络链路。
遇到问题加QQ23979811 协助处理
Linux crontab定时任务不执行原因排查
crontab 是 Linux 最常用的定时任务工具,很多人写完任务发现“到点就是不跑”,却不知道问题出在哪。crontab 不执行几乎都可以归为五类:时间格式错、服务没起、环境变量缺、权限或路径错、日志被吞。本文按排查顺序给出可直接照做的命令。
一、先确认 crond 服务在运行
systemctl status crond
如果是 inactive,先启动并设开机自启:
systemctl start crond
systemctl enable crond
部分发行版服务名是 cron(Ubuntu/Debian):
systemctl status cron
二、查看当前用户的定时任务
crontab -l
查看系统级任务:
cat /etc/crontab
ls /etc/cron.d/
注意:crontab -l 看的是当前用户;root 的任务要切到 root 下看,别拿普通用户去核对。
三、时间格式最容易写错
crontab 分五段:分 时 日 月 周,例如每天凌晨 2 点执行:
0 2 * * * /root/backup.sh
常见错误:
- 写成 5 段以外的字段数,直接不执行。
- 分钟位留空或写成 表示每分钟,别和“每天”混淆。
- 想每 5 分钟写 /5,而不是 /5。
四、环境变量与绝对路径
task 运行在极简环境里,PATH 只有 /usr/bin:/bin,脚本里直接写命令名往往找不到。必须用绝对路径:
# 错误:依赖 PATH
* * * * * python3 /root/run.py
# 正确:命令也写绝对路径
* * * * * /usr/bin/python3 /root/run.py
脚本开头显式声明环境:
#!/bin/bash
PATH=/usr/local/sbin:/usr/bin:/bin
export PATH
五、查看 cron 日志定位
# CentOS7
tail -f /var/log/cron
# Ubuntu/Debian
tail -f /var/log/syslog | grep CRON
到点时日志里有没有出现这条任务,是判断“到底有没有被调度”的关键。也可以在任务末尾把输出重定向到文件:
0 2 * * * /root/backup.sh >> /root/backup.log 2>&1
2>&1 把错误输出也收进来,否则报错会被直接吞掉,表面看像没执行。
六、权限与其他拦路虎
- 脚本没有可执行权限:chmod +x /root/backup.sh。
- 脚本本身报错,但没重定向,看不到。
- SELinux 或防火墙拦截脚本里的网络操作。
- 系统时区不对,任务按 UTC 跑,和你以为的北京时间差 8 小时:timedatectl 查看。
- /etc/cron.deny、/etc/cron.allow 把用户禁用了。
常见坑
- 只在命令行手动测试成功,不代表 cron 环境能跑,重点是缺环境变量和绝对路径。
- 改完任务没保存或改错用户,crontab -l 核对不到等于没写。
- 忽略 2>&1,报错被吞,以为“没反应”。
- 时区错位,任务在你睡着时的另一时刻执行。
- 脚本换行要用 LF,Windows 存成 CRLF 会导致执行异常。
总结
排查顺序:crond 是否运行 → crontab -l 核对格式 → 命令与脚本改用绝对路径 → 加 2>&1 重定向看日志 → 检查权限、时区、SELinux。绝大多数“不执行”都卡在环境变量和绝对路径这一步。
遇到问题加QQ23979811 协助处理
Linux磁盘占用100%排查与大文件清理方法
服务器磁盘占用 100% 是运维高频故障:日志写不进、网站 500、数据库无法启动,往往都是根分区被占满。新手容易一上来就删文件,结果删错系统文件。正确做法是先定位“哪个分区满、哪个目录大、哪个文件最占地方”,再有针对性清理。本文给出一套从粗到细的排查命令。
一、先看哪个分区满了
df -h
看 Use% 一列,达到 100% 的就是出问题的分区。重点关注根分区 / 和 /var、/home 等挂载点。加 -i 看 inode 是否用尽:
df -i
有些情况空间没满但 inode 耗尽(大量小文件),也会报磁盘满。
二、定位哪个目录最占空间
从根目录逐级往下找,du 统计目录大小:
du -h --max-depth=1 / 2>/dev/null
进入最大的那个目录继续同样操作,例如:
du -h --max-depth=1 /var 2>/dev/null
du -h --max-depth=1 /var/log 2>/dev/null
参数 --max-depth=1 只看一层,避免刷屏;2>/dev/null 忽略无权限报错。
三、找出最大的单个文件
# 在 /var/log 下找大于 100M 的文件
find /var/log -type f -size +100M -exec ls -lh {} \;
# 全盘查找大于 500M 的文件并按大小排序
find / -type f -size +500M 2>/dev/null | xargs ls -lhS
四、常见占用大户与清理
- 系统日志:/var/log 下的大日志,可先清空而非删除文件:
cat /dev/null > /var/log/messages
- 软件包缓存(CentOS):
yum clean all
-
回收站与临时文件:/tmp、/var/tmp、~/.cache。
-
Docker 镜像和容器日志(如果装了 Docker):
docker system prune -af
- Nginx、PHP、MySQL 的业务日志,用 logrotate 定期切割。
五、特殊情况:文件已删但空间没释放
进程仍持有已删除文件的句柄,空间不会立刻回收:
lsof +L1 | head
找到对应进程重启,或直接重启服务释放句柄。这是“删了大文件 df 还是 100%”的经典原因。
常见坑
- 直接 rm 系统关键目录,可能导致系统无法启动,务必先确认用途。
- du 和 df 对不上,多是隐藏的已删除句柄文件,用 lsof +L1 查。
- 只清文件不设切割,过几天又满,应配合 logrotate 自动化。
- inode 耗尽(df -i 满)删大文件无效,要清小文件。
- 清业务日志前确认没有正在写入,避免影响排查。
总结
排查顺序:df -h 找满的分区 → du --max-depth=1 逐层找大目录 → find 定位大文件 → 清空日志/清缓存,最后用 lsof +L1 处理已删未释放句柄,并配置 logrotate 防止复发。
遇到问题加QQ23979811 协助处理
CentOS7关闭SELinux临时和永久关闭方法
SELinux(安全增强型 Linux)是 CentOS7 默认自带的强制访问控制系统,本意是限制进程对文件的越权访问,但在自建 Web 环境时,它经常莫名其妙地拦截 Nginx、PHP-FPM、数据库读写,表现为“权限明明给了却报 403、访问被拒”。排查成本高,很多新手会直接选择关闭。本文讲清临时关闭和永久关闭两种方式,以及验证方法。
一、先查看 SELinux 当前状态
getenforce
返回 Enforcing 表示强制开启,Permissive 只记录不拦截,Disabled 表示已关闭。也可以用:
sestatus
看 Current mode 一行确认当前模式。
二、临时关闭(重启后失效)
只对本次运行生效,适合临时测试,服务器重启后 SELinux 会重新回到 Enforcing:
setenforce 0
setenforce 0 = 进入 Permissive(临时放行但记录日志),setenforce 1 = 切回 Enforcing。执行后再用 getenforce 确认变为 Permissive。
注意:setenforce 无法在 Disabled 状态下切换,这是正常现象。
三、永久关闭(重启后依然关闭)
永久关闭要改配置文件 /etc/selinux/config:
vim /etc/selinux/config
找到这一行:
SELINUX=enforcing
改成:
SELINUX=disabled
保存退出后必须重启服务器才生效:
reboot
重启后再执行 getenforce,显示 Disabled 才算永久关闭成功。
四、只对个别目录放行(推荐替代方案)
如果不想整体关闭,可以用 semanage 给网站目录打标签,而不是一刀切关掉 SELinux:
# 给网页目录设置 httpd 可访问标签
semanage fcontext -a -t httpd_sys_content_t '/www/wwwroot(/.*)?'
restorecon -Rv /www/wwwroot
生产环境更建议保留 SELinux、只修正标签,安全性更好。
常见坑
- 改了 config 却没重启:getenforce 仍显示 Enforcing,永久配置必须重启才加载。
- 只执行 setenforce 0 就以为永久关闭:重启后又被拦截,这是最常见误会。
- 配置里 SELINUXTYPE 不要乱删,只改 SELINUX= 这一行的值。
- 部分云镜像默认就是 Disabled,setenforce 会报错,属正常。
- 关闭 SELinux 会降低系统安全性,公网服务器务必配合防火墙和最小权限使用。
总结
临时关闭用 setenforce 0,重启即失效;永久关闭改 /etc/selinux/config 把 SELINUX 改为 disabled 再 reboot,最后用 getenforce 验证。生产环境能靠 semanage 修正标签解决的,尽量不要整体关闭。
遇到问题加QQ23979811 协助处理
Linux查看开放端口netstat和ss命令教程
在 Linux 运维中,查看服务器到底开放了哪些端口、哪个进程在监听某个端口,是排查网站访问失败、端口冲突、安全扫描的基本功。常用工具是老命令 netstat 和新命令 ss,两者输出接近,但 ss 性能更好、参数更简洁。本文把常用写法一次讲清。
一、netstat 查看监听端口
netstat 在很多发行版里属于 net-tools 包,最小化安装可能没有,先装:
# CentOS7
yum install -y net-tools
# Ubuntu/Debian
apt install -y net-tools
查看所有处于监听状态的 TCP 端口:
netstat -tlnp
参数含义:-t 只看 TCP,-l 只看监听(LISTEN)状态,-n 以数字显示端口不反解域名,-p 显示占用端口的进程名和 PID。
只看某个端口,例如 80:
netstat -tlnp | grep :80
查看所有连接(含已建立的):
netstat -anp
二、ss 命令(推荐,更快)
socket statistics(ss)是 iproute2 自带工具,速度比 netstat 快,参数几乎一一对应:
ss -tlnp
只看监听端口、数字显示、带进程:
ss -ltnp
查看某个端口被谁占用:
ss -tlnp | grep :443
只看 TCP 或 UDP:
ss -tlnp # TCP
ss -ulnp # UDP
三、输出字段怎么读
以 netstat -tlnp 为例,一行大致是:
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1024/sshd
- Local Address 里的 0.0.0.0:22 表示监听本机所有网卡的 22 端口;127.0.0.1:3306 表示只允许本机访问。
- State 为 LISTEN 才是正在监听等待连接。
- PID/Program name 就是占用进程,排查端口冲突直接看它。
四、结合 lsof 精确定位
如果只想查单个端口由哪个进程打开,lsof 更直观:
lsof -i:80
没有 lsof 先安装:yum install -y lsof。
常见坑
- 只看 LISTEN 不够:外部能否访问还要叠加防火墙、安全组、绑定地址(0.0.0.0 才对外)。
- netstat 提示 command not found:缺 net-tools 包,yum install net-tools。
- -p 查不到进程名:普通用户看不到其他用户进程,加 sudo 或切 root。
- 端口监听在 127.0.0.1:外部永远连不上,这是很多人“服务起了却访问不到”的原因。
- ss 和 netstat 输出不一致时,以 ss 为准,它直接读内核连接表,更实时。
总结
日常排查推荐 ss -tlnp 一把梭,需要兼容老系统时用 netstat -tlnp,再配合 grep 端口号和 lsof -i:端口 定位进程。记住“监听地址 + 防火墙 + 安全组”三层都放开,端口才真正对外可用。
遇到问题加QQ23979811 协助处理
CentOS7 firewalld开放80 443端口永久生效命令
CentOS7 默认使用 firewalld 作为防火墙管理工具,相比老版本的 iptables 服务,它把端口管理拆成了“永久配置(permanent)”和“当前生效(runtime)”两层。很多新手只加了规则却没加 --permanent,重启后端口又被封掉,这就是“开放端口不生效”的最常见原因。本文给出一套可直接复制的完整命令。
一、查看 firewalld 状态
先确认防火墙是否在运行:
systemctl status firewalld
firewall-cmd --state
输出 running 表示正在运行;如果是 not running,先启动并设置开机自启:
systemctl start firewalld
systemctl enable firewalld
二、开放 80、443 端口(永久生效)
推荐用 --permanent 写入永久规则,再 reload 让其立即生效:
# 开放单个端口
firewall-cmd --permanent --add-port=80/tcp
firewall-cmd --permanent --add-port=443/tcp
# 重载规则,使永久配置立即生效
firewall-cmd --reload
也可以一次性开放多个端口:
firewall-cmd --permanent --add-port=80/tcp --add-port=443/tcp
firewall-cmd --reload
三、通过服务名开放(更简洁)
firewalld 内置了 http、https 服务定义,可以直接按服务开放:
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
四、查看已开放的端口和服务
# 查看永久配置里开放的端口
firewall-cmd --permanent --list-ports
# 查看永久配置里开放的服务
firewall-cmd --permanent --list-services
# 查看当前运行时生效的全部规则
firewall-cmd --list-all
五、移除端口(不需要时)
firewall-cmd --permanent --remove-port=443/tcp
firewall-cmd --reload
常见坑
- 只加规则不加 --permanent:当前能访问,但 firewalld 重载或服务器重启后规则丢失,这是最普遍的踩坑点。
- 加了 --permanent 却没 reload:永久配置已写入但运行时还没加载,必须执行 firewall-cmd --reload。
- 协议别写错:Web 端口是 tcp,写成 udp 无效。
- 云服务器还要在安全组放行:firewalld 只管系统内防火墙,阿里云、腾讯云等还要在控制台安全组里开放 80/443。
- 端口通了网站仍打不开:还要检查 Nginx/Apache 是否正常监听、SELinux 是否拦截。
总结
开放端口的标准顺序是:firewall-cmd --permanent --add-port=端口/协议 → firewall-cmd --reload → --list-ports 核对。记住“永久配置 + 重载”两步缺一不可,再配合云平台安全组,端口才能真正对外可达。
遇到问题加QQ23979811 协助处理
MySQL 按日期分组统计快速上手指南(完整版) MySQL
用MySQL做开发,按日期分组统计属于那种「平时不觉得,出问题才发现重要」的知识点。提前花十分钟了解它,胜过事后花几小时排查。
PHP foreach引用遍历的尾变量陷阱
用 & 引用遍历数组是 PHP 里提升效率的常用写法,但它留下的“尾变量”是一个非常隐蔽的 bug 来源,很多资深工程师都在这里翻过车。本文用一个可复现的例子讲清原理和两种安全写法。
一、问题代码
$arr = [1, 2, 3];
foreach ($arr as &$v) {
$v *= 2;
}
// 循环结束,$v 仍然引用着数组最后一个元素
$other = 99;
// 注意:下面如果再次用到 $v,会悄悄改写原数组
$v = 99;
var_dump($arr); // [2, 4, 99],最后一项被偷偷改掉
循环结束后,$v 并没有被释放,它依然是数组最后一个元素的引用。后续任何对 $v 的赋值,都会直接写到原数组里。
二、为什么会这样
PHP 的引用是符号表上的绑定关系。foreach 用 &$v 时,$v 指向当前元素;循环走完,这个引用绑定不会自动断开,$v 继续指向数组最后一项。后面一旦复用同名变量,就发生覆盖,而且不会报任何错误。
三、两种安全写法
第一种,循环结束立即 unset 断开引用:
$arr = [1, 2, 3];
foreach ($arr as &$v) {
$v *= 2;
}
unset($v); // 关键:断开引用,后续复用不再影响数组
第二种,干脆不用引用,改用键名修改,从根本上避免引用:
foreach ($arr as $k => $v) {
$arr[$k] = $v * 2;
}
四、延伸:嵌套与变量复用
如果在同一个作用域里,后面又写了一个普通 foreach,并且复用了 $v,尾引用会把第二个循环的最后一次值写进第一个数组,造成难以排查的数据错乱。规范做法是:引用遍历后一定 unset,或者不同循环使用不同的变量名。
常见坑
- 只在循环体内改值,忘记循环后 unset,是最普遍的诱因。
- 单元测试很难覆盖到这个问题,因为它依赖变量复用的先后顺序。
- 数组为空时没有引用,unset 也不会报错,写法可以放心保留。
- 不要依赖“循环变量出了作用域就自动释放”的直觉,PHP 的引用不会随循环结束自动解除。
总结
引用遍历本身没有错,错在循环后没有断开引用。养成“引用遍历必 unset”的习惯,或者直接用键名改写,就能彻底避开这个隐蔽陷阱,让数组操作更可控。
PHP清空数组的几种正确写法与区别
在 PHP 开发中,清空数组是高频操作,但很多人分不清“清空元素但保留变量”和“彻底销毁变量”的区别,误用会导致后续逻辑报错。本文梳理三种常用写法,并说明它们的适用场景与坑点,适合新手和日常代码审查时对照。
一、$arr = []:重新赋值(最常用)
直接给数组赋一个空数组,变量本身仍然存在,只是内容被替换为空。
$arr = [1, 2, 3];
$arr = [];
// $arr 现在是空数组,后续还能继续 push
$arr[] = 4;
适合循环处理、分页结果重置、缓存清空等场景,是日常开发最推荐的写法。性能开销小,逻辑直观,不会产生未定义变量警告。
二、unset($arr):彻底销毁变量
unset 会把变量本身从内存中删除,再次访问会抛出 Undefined variable 警告。
$arr = [1, 2, 3];
unset($arr);
// 再次使用 $arr 会触发未定义变量警告
如果只想清空数组内容、保留变量,不要用 unset;unset 适合确认不再使用该变量、主动释放内存的场景。
如果只想删除数组中的某个元素,而不是清空整个数组:
unset($arr['key']);
三、array_splice 清空全部元素
array_splice 可以一次性移除从指定下标开始的全部元素:
$arr = [1, 2, 3];
array_splice($arr, 0);
// $arr 现在为空
这种写法较少单独使用,一般在需要连带保持数组引用关系时才考虑。
常见坑
- 混淆“清空”和“销毁”:需要保留变量就用
= [],不要用 unset。 - 引用传参后清空:若数组被多个变量引用,
= []只替换当前变量指向,不会影响原引用,必要时改用传引用清空。 - 不要用
$arr = null代替清空:null 和空数组在 isset、empty、count 判断下行为不同,count(null) 在高版本 PHP 会告警。 - 超大数组释放:数组占用大量内存时,unset 后可配合 gc_collect_cycles() 主动回收。
总结
日常清空数组优先 $arr = [];确认不再使用变量才用 unset($arr);删除单个元素用 unset($arr['key'])。选对写法,能避免大量“未定义变量”和类型判断的隐性 bug,让代码更健壮。
PHP PDO 预处理防 SQL 注入
PHP PDO 预处理防 SQL 注入
一、什么是 SQL 注入
用户提交的数据被拼进 SQL 语句,攻击者输入恶意片段篡改查询逻辑,例如登录处传入 ' OR '1'='1 直接绕过密码校验,甚至拖库、删表。根本原因是把用户输入当成 SQL 语法的一部分拼接。防御核心是让用户输入永远只作为数据,不参与语法解析,PDO 预处理正是解决这个问题。
二、预处理原理
预处理分两步:先把 SQL 模板发给数据库编译,占位符 ? 或 :name 位置已定;再单独把参数发给数据库,参数只当值传递,不会被解析成 SQL 命令。即使参数里带引号、注释、OR,也只是普通字符串。
三、正确写法:问号占位符
<?php
$pdo = new PDO('mysql:host=127.0.0.1;dbname=blog', 'root', '123456', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$stmt = $pdo->prepare('SELECT * FROM user WHERE username = ? AND password = ?');
$stmt->execute([$username, $password]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
prepare 只放固定 SQL 和占位符,用户变量全部放进 execute 数组,绝不手写拼接。
四、命名占位符
参数多时用命名占位符更清晰:
$stmt = $pdo->prepare(
'INSERT INTO article (title, content, uid) VALUES (:title, :content, :uid)'
);
$stmt->execute([
':title' => $title,
':content' => $content,
':uid' => $uid,
]);
五、查询结果与防止数值型注入
$stmt = $pdo->prepare('SELECT * FROM article WHERE gid = ?');
$stmt->bindValue(1, $gid, PDO::PARAM_INT); // 指定整数类型
$stmt->execute();
用 bindValue 并指定 PARAM_INT,即使传入字符串也会被强转,多一层保险。
六、哪些地方仍会出问题
- 动态表名、列名、ORDER BY 字段不能用占位符绑定,数据库不允许占位符代替标识符,这类要走白名单校验:
$allow = ['gid', 'title', 'created_at']; $order = in_array($sort, $allow) ? $sort : 'gid'; - 用字符串拼接 SQL 再预处理,等于白做;
- 仅对 WHERE 条件预处理,LIKE、IN 子句照样拼接;
- 关闭了 PDO 错误模式,注入发生时无法及时发现。
七、额外加固
- 输入层做类型与格式校验,最小权限数据库账号,不给库删权限;
- 开启 PDO ERRMODE_EXCEPTION,错误抛异常由全局捕获;
- 敏感操作配合参数白名单和二次确认。
八、常见误区
- 以为转义 addslashes 就够,特殊字符绕过仍可能注入;
- 占位符只绑定值,却把排序字段、表名直接拼接;
- 用 mysql_ 旧扩展,早已废弃不安全;
- 预处理只对一条语句,循环里每次重新 prepare 反而低效,应复用 stmt。
九、总结
PDO 预处理靠「先编译模板、再传参数」把用户输入和 SQL 语法隔离,是防注入的标准方案。值用占位符绑定,标识符走白名单,指定参数类型,配合异常模式和最小权限,就能从根源杜绝 SQL 注入。任何时候都不要把用户输入直接拼进 SQL 字符串。
Nginx Gzip 压缩提速配置详解
Nginx Gzip 压缩提速配置详解
一、压缩为什么能提速
网页里的 HTML、CSS、JS、JSON 都是文本,重复字符多,直接传输体积大。Gzip 在服务器端把响应压缩成更小的数据包,浏览器收到后自动解压,文本资源通常能压缩到原来的 20%~40%,减少带宽占用、加快首屏加载。图片、压缩包本身已压缩,不需要再 Gzip。
二、基础配置
在 nginx.conf 的 http 块开启:
gzip on;
gzip_min_length 1k; # 小于1KB不压缩,避免小文件得不偿失
gzip_comp_level 6; # 压缩等级1~9,6性价比最高
gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
gzip_vary on; # 响应头加 Vary: Accept-Encoding,区分是否支持压缩
gzip on 总开关;gzip_types 指定要压缩的 MIME 类型,默认只压 text/html,JS、CSS、JSON 必须手动加上。
三、关键参数说明
gzip_min_length:只压缩超过该大小的响应,过小文件压缩收益低于 CPU 消耗;gzip_comp_level:等级越高压缩率越大但 CPU 越高,生产用 5~6,不要盲目拉满;gzip_buffers:压缩缓冲区大小,默认即可;gzip_vary on:告诉 CDN 和代理按 Accept-Encoding 分别缓存,避免给不支持压缩的客户端发压缩内容。
四、静态资源与 Brotli
对已经预压缩的文件(如 .gz 版本)可直接用 gzip_static on,跳过实时压缩,减少 CPU:
gzip_static on;
现代浏览器还支持 Brotli,压缩率比 Gzip 更高,Nginx 需额外模块,生产可作为增强,Gzip 保证兼容性。
五、验证是否生效
浏览器 F12 Network 看响应头:
Content-Encoding: gzip
存在该头且资源体积明显变小即生效。也可用 curl:
curl -I -H "Accept-Encoding: gzip" https://example.com/app.js
六、常见误区
- 只开 gzip on 不配 gzip_types,JS、CSS 没被压缩;
- 压缩等级拉到 9,CPU 飙升收益却很小;
- 对 PNG、JPG、视频、zip 也压缩,纯属浪费;
- 忘记 gzip_vary,代理缓存把压缩版发给不支持的浏览器;
- 小文件也压,增加计算开销反而变慢。
七、总结
Gzip 配置核心是开关、阈值、等级、类型四件套:开总开关、设最小长度、选 6 级、补齐文本类型并加 Vary。配合 gzip_static 预压缩和 Brotli 增强,用少量 CPU 换取大幅带宽节省,是 Nginx 站点提速的基础优化项。
CSS Flex 弹性布局入门指南
CSS Flex 弹性布局入门指南
一、为什么用 Flex
传统布局靠 float、position、margin 拼,居中、等分、自适应宽度都很麻烦,移动端多屏适配尤其痛苦。Flex 弹性布局把容器当成一个弹性盒子,子元素沿主轴排列,自动分配空间,几行代码就能实现水平垂直居中、等分列、两端对齐,是现代前端布局的首选。
二、两个核心概念
- 容器(flex container):给父元素设
display: flex,它就是弹性容器; - 项目(flex item):容器里的直接子元素,按主轴排列。
主轴由 flex-direction 决定,交叉轴与之垂直。
三、容器常用属性
.box {
display: flex;
flex-direction: row; /* 主轴方向:水平从左到右 */
justify-content: center; /* 主轴对齐:居中 */
align-items: center; /* 交叉轴对齐:垂直居中 */
flex-wrap: wrap; /* 超出换行 */
gap: 12px; /* 项目间距 */
}
justify-content:主轴对齐,可选 flex-start、center、flex-end、space-between(两端贴边、中间均分)、space-around;align-items:交叉轴对齐,可选 stretch、center、flex-start、flex-end;flex-wrap:nowrap 不换行,wrap 换行。
四、项目常用属性
.item {
flex: 1; /* 等比占据剩余空间 */
flex-grow: 1; /* 放大比例 */
flex-shrink: 0; /* 不缩小 */
align-self: flex-end; /* 单独覆盖交叉轴对齐 */
}
flex: 1 是开发最常用简写,让所有项目平分剩余宽度,卡片列表、导航项等分就靠它。flex-shrink: 0 防止内容被挤压变形。
五、经典场景:水平垂直居中
.center {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
}
一行 flex 三属性搞定居中,比 margin 负值、absolute 定位简洁可靠。
六、常见误区
- 给容器设了 flex 却在子元素上用 float,float 失效;
- 忘记
flex-wrap: wrap,屏幕变窄项目挤成一行不换行; flex: 1和固定宽度冲突,导致布局错乱;- 混淆 justify-content(主轴)和 align-items(交叉轴),居中方向写反;
- 嵌套 flex 时内层项目继承规则,尺寸没控制好溢出。
七、总结
Flex 用容器属性控制主轴排列、交叉轴对齐和换行,用项目属性 flex 分配剩余空间。掌握 flex 方向、justify-content、align-items、flex:1 这几个核心,配合 gap 设间距,就能快速写出自适应的导航、卡片、居中布局,是前端必会的基础能力。
PHP Composer 自动加载原理解析
PHP Composer 自动加载原理解析
一、为什么需要自动加载
老式 PHP 项目每个类文件都要手动 require、include,类一多,引入语句散落各处,维护痛苦。自动加载的思路是:用到某个类时再去加载对应文件,由框架或 Composer 统一注册加载逻辑,业务代码完全不用写 require。理解 Composer 自动加载,能看懂报错类找不到的根因,也方便自己写库。
二、spl_autoload_register 基础
PHP 提供 spl_autoload_register 注册回调,类被实例化但没定义时依次调用注册的函数,去找到文件并 include:
spl_autoload_register(function ($class) {
$file = __DIR__ . '/src/' . str_replace('\\', '/', $class) . '.php';
if (file_exists($file)) require $file;
});
$user = new App\Models\User();
类名 App\Models\User 按命名空间转成路径 App/Models/User.php,这就是 PSR-4 的雏形。
三、四种加载方式
Composer 在 composer.json 里声明,安装后生成 vendor/autoload.php,提供四种机制:
1. PSR-4(最常用)
命名空间前缀映射到目录,命名空间与目录结构对应:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
App\Service\Pay 对应 src/Service/Pay.php,不要求类名和目录前缀重复。
2. PSR-0
旧规范,类名里的下划线也转目录,性能一般,新项目少用。
3. classmap
把所有类和文件路径生成一张映射表,一次性扫描目录,适合类少、想快速加载的库。
4. files
直接按文件列表 always require,用于需要全局函数的文件。
四、加载流程
首次引入:
require __DIR__ . '/vendor/autoload.php';
当代码 new 某个类 而该类未定义时,Composer 按注册顺序依次尝试:先查 classmap 直接命中,再按 PSR-4 前缀拼接路径找文件,找到就 include,找不到继续下一个加载器,最后抛类不存在错误。
五、优化与缓存
composer dump-autoload -o生成优化 classmap,跳过运行时路径拼接,生产环境必用;composer dump-autoload改完命名空间或目录后重新生成映射;- 新增类没生效,先检查是否 PSR-4 前缀对得上、是否重新 dump。
六、常见误区
- 忘了引入 vendor/autoload.php,所有第三方类报找不到;
- 类命名空间和目录结构不一致,PSR-4 加载失败;
- 改了类位置不执行 dump-autoload,缓存映射还是旧的;
- 大小写不匹配:Linux 文件系统区分大小写,类名 User 和文件 User.php 必须一致;
- 把 PSR-4 当成 require 手动写,重复加载。
七、总结
Composer 自动加载本质是 spl_autoload_register 注册回调,按 PSR-4 前缀把命名空间映射成文件路径,用到类时才加载。生产用 dump-autoload -o 优化,遇到类找不到先查命名空间、目录、大小写和重新生成。掌握这套机制,第三方库和自研组件的引入就不会再盲目试错。
常见 HTTP 状态码含义详解
常见 HTTP 状态码含义详解
一、状态码的作用
浏览器每次访问网站,服务器响应都会带一个三位数字状态码,用来告诉客户端这次请求的结果。排查前端报错、接口联调、Nginx 反代问题时,第一步就是看状态码。状态码按首位分五大类:2 开头成功,3 开头重定向,4 开头客户端错误,5 开头服务端错误,熟练记住常用码能快速定位问题。
二、2xx 成功
- 200 OK:请求成功,正常返回内容;
- 201 Created:资源创建成功,常用于 POST 提交新增数据;
- 204 No Content:成功但无返回体,PUT、DELETE 常用。
三、3xx 重定向
- 301 Moved Permanently:永久重定向,搜索引擎会迁移权重,域名更换、http 跳 https 常用;
- 302 Found:临时重定向,不转移权重;
- 304 Not Modified:资源未修改,浏览器用本地缓存,配合 ETag、Last-Modified,不重复下载。
四、4xx 客户端错误
- 400 Bad Request:请求参数格式错误,后端解析失败;
- 401 Unauthorized:未认证,缺少或凭证错误,需要登录;
- 403 Forbidden:已认证但无权限,或服务器拒绝访问(目录无权限、IP 被拦);
- 404 Not Found:资源不存在,路由错误或文件被删;
- 405 Method Not Allowed:请求方法不允许,如接口只接受 GET 却用 POST;
- 413 Payload Too Large:上传文件超过服务器限制;
- 429 Too Many Requests:请求过于频繁,被限流。
五、5xx 服务端错误
- 500 Internal Server Error:服务器内部错误,代码异常、PHP 致命报错;
- 502 Bad Gateway:反向代理收到后端无效响应,后端挂了或端口不通;
- 503 Service Unavailable:服务暂时不可用,过载或维护中;
- 504 Gateway Timeout:网关等待后端响应超时,后端处理太慢;
- 500 与 502 区别:500 是后端自己报错,502/504 是代理连不上或等不到后端。
六、调试思路
前端看到异常先看状态码:4xx 一般是请求参数、权限、地址问题,改前端;5xx 是后端或服务器问题,查服务日志。配合浏览器 Network 面板看请求 URL、响应头和返回体,比只看页面白屏高效得多。
七、常见误区
- 把 403 当 404,其实是权限拒绝,不是找不到;
- 301 和 302 混用,301 会被浏览器长期缓存,改不回来;
- 502 只盯后端代码,忽略 Nginx 到后端的网络和端口;
- 401 和 403 分不清,401 是没登录,403 是登录了也不让进。
八、总结
记住 200/301/304/400/401/403/404/405/413/429/500/502/503/504 这十几个高频码,就能覆盖绝大多数排障场景。状态码是客户端和服务器沟通的语言,看懂它,前后端联调和线上故障定位都会快很多。
MySQL 慢查询优化实战指南
MySQL 慢查询优化实战指南
一、先定位慢在哪
数据库变慢不能靠猜,第一步是打开慢查询日志,把执行时间超过阈值的 SQL 记录下来:
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- 超过1秒记录
SHOW VARIABLES LIKE 'slow_query_log_file';
再用 EXPLAIN 分析目标 SQL,重点看 type、key、rows、Extra 四列:type 出现 ALL 表示全表扫描,key 为 NULL 说明没走索引,rows 预估扫描行数过大,Extra 出现 Using filesort、Using temporary 都是危险信号。
二、索引优化是核心
1. 给高频查询字段建索引
ALTER TABLE order_table ADD INDEX idx_uid_status (user_id, status);
联合索引遵循最左前缀,查询条件从左到右匹配,跳过中间列后面的列就用不上。
2. 避免索引失效
- 对索引列做函数、运算:
WHERE DATE(create_time) = '2026-01-01'会失效,改成范围比较; - 隐式类型转换:字符串字段用数字查询会导致索引失效;
LIKE '%xxx'前置百分号无法走索引;OR连接的字段有一个无索引,整体失效;!=、NOT IN往往不走索引。
三、改写低效 SQL
小结果集驱动大表
-- 反例:先扫大表再关联
SELECT * FROM a JOIN b ON a.id = b.a_id WHERE b.type = 1;
-- 优化:先过滤小表,再关联
SELECT a.* FROM (SELECT id FROM b WHERE type = 1 LIMIT 100) t JOIN a ON a.id = t.a_id;
分页深翻页优化
LIMIT 100000, 20 会扫描十万行再丢弃,改用主键游标:
SELECT * FROM order_table WHERE id > 100000 ORDER BY id LIMIT 20;
禁止 SELECT *
只查需要的列,减少回表和网络传输,覆盖索引还能避免回表。
四、表结构与配置优化
- 大字段(TEXT、BLOB)拆到副表,主表只存常用字段;
- 避免过度索引,索引越多写入越慢,删无用索引;
- 合理使用覆盖索引,查询列都在索引里就不用回表;
- 调整 buffer_pool_size,一般设为物理内存的 50%~70%;
- 慢 SQL 配合业务加缓存,把读压力转移到 Redis。
五、压测与验证
用 EXPLAIN ANALYZE 看真实执行计划,优化前后对比扫描行数和耗时。上线前用慢日志复刻 SQL 在测试库压测,避免只在小表上验证就以为有效。
六、常见误区
- 盲目给每个字段加索引,拖慢写入;
- 建了联合索引却从非最左列查询,以为没生效;
- 只看执行时间不看 EXPLAIN,误判瓶颈;
- 深分页一直 LIMIT 偏移量,越翻越慢;
- 数据库扛不住就堆硬件,忽略索引和 SQL 本身的问题。
七、总结
慢查询优化链路:开慢日志定位、EXPLAIN 找问题、补索引并避开失效场景、改写 SQL 减小扫描范围、配合表结构和缓存。优化的本质是减少扫描行数和回表次数,先靠定位工具找到真凶,再针对性调整,而不是凭感觉乱建索引。
Python venv 虚拟环境搭建详解
Python venv 虚拟环境搭建详解
一、为什么需要虚拟环境
Python 全局环境只有一份,多个项目依赖的第三方包版本经常冲突:项目 A 要 Django 3.2,项目 B 要 Django 4.2,全局装只能二选一,升级一个包又可能搞坏另一个项目。venv 是 Python 官方自带的轻量级隔离工具,为每个项目创建独立的包目录,项目之间互不干扰,不需要安装额外软件,是新手和线上部署的首选。
二、创建与激活环境
Python 3.3 之后内置 venv,无需 pip 安装。在项目目录执行:
# 创建名为 .venv 的虚拟环境
python3 -m venv .venv
# Linux / macOS 激活
source .venv/bin/activate
# Windows PowerShell
.venv\Scripts\Activate.ps1
激活后命令行前面出现 (.venv) 标识,此时 pip 安装的包都进入这个环境,不影响全局。
三、安装依赖与导出
激活状态下用 pip 安装:
pip install requests==2.31.0
pip install flask
把当前环境依赖导出为 requirements.txt,方便别人复现:
pip freeze > requirements.txt
新机器安装依赖:
pip install -r requirements.txt
四、退出与删除
deactivate
退出激活回到全局环境。整个虚拟环境就是项目里的 .venv 文件夹,直接删除目录即可彻底清理,不会残留。
五、不同 Python 版本并存
python3 指向哪个解释器,venv 就复制哪个版本。需要指定版本时用对应命令:
python3.10 -m venv .venv310
一个项目绑定一个解释器版本,避免版本混用。
六、与 conda、poetry 的区别
- venv:官方轻量,只隔离包,不管理 Python 版本,适合简单项目;
- conda:同时管理解释器版本和包,适合科学计算、多版本共存;
- poetry:在 venv 基础上加依赖锁文件和打包发布,适合现代 Python 工程。
中小项目优先 venv,复杂依赖再上 poetry。
七、常见误区
- 忘了激活就 pip install,包装到全局,环境白建;
- 把
.venv提交进 Git,体积巨大,应加入.gitignore; - requirements.txt 里写死所有间接依赖,跨平台安装报错;
- 删除环境直接 kill 进程,其实只要删文件夹;
- 多个项目共用一个 venv,失去隔离意义。
八、总结
venv 的核心是 python -m venv 创建隔离目录、source 激活、pip 安装、freeze 导出、deactivate 退出。配合 requirements.txt 实现环境复现,用 .gitignore 排除环境目录。掌握这套流程,就能解决 Python 多项目依赖冲突,是开发和部署的基本功。
Java 金额格式化输出入门教程(附代码示例) Java
用Java做开发,金额格式化输出属于那种「平时不觉得,出问题才发现重要」的知识点。提前花十分钟了解它,胜过事后花几小时排查。