Redis 持久化机制
Redis 持久化机制
Redis 是内存数据库,断电数据就没了,所以需要持久化。
1. 为什么要持久化
- Redis 数据在内存中
- 服务器断电/重启数据丢失
- 持久化把数据存到磁盘
- 重启后可以恢复数据
2. RDB 快照
原理: 定时把内存数据全量保存到磁盘。
优点:
- 恢复速度快
- 文件小
- 适合备份
缺点:
- 可能丢失最后一次快照后的数据
- fork子进程可能阻塞
配置:
# redis.conf
save 900 1 # 900秒内1个key变化就保存
save 300 10 # 300秒内10个key变化
save 60 10000 # 60秒内10000个key变化
手动触发:
# 前台保存(阻塞)
redis-cli save
# 后台保存(不阻塞)
redis-cli bgsave
3. AOF 日志
原理: 记录每个写命令,重启时重放。
优点:
- 数据更安全(最多丢1秒)
- 日志可读
缺点:
- 文件比 RDB 大
- 恢复速度慢
配置:
# redis.conf
appendonly yes # 开启AOF
appendfilename "appendonly.aof"
# 同步策略
appendfsync everysec # 每秒同步(推荐)
# appendfsync always # 每次写都同步
# appendfsync no # 交给操作系统
AOF 重写:
# 手动触发AOF重写
redis-cli bgrewriteaof
4. RDB vs AOF 对比
| 对比项 | RDB | AOF |
|---|---|---|
| 数据安全 | 可能丢数据 | 更安全 |
| 文件大小 | 小 | 大 |
| 恢复速度 | 快 | 慢 |
| 性能影响 | fork阻塞 | 较小 |
| 适用场景 | 备份/容灾 | 数据安全要求高 |
5. 混合持久化
Redis 4.0+ 支持混合持久化:
- AOF 重写时前半段用 RDB 格式
- 后半段增量用 AOF 格式
- 结合两者优点
配置:
aof-use-rdb-preamble yes
6. 生产建议
1)数据不敏感用 RDB
- 缓存场景
- 丢点数据没关系
2)数据重要用 AOF
- 每秒同步
- 最多丢1秒数据
3)生产环境推荐
- 同时开 RDB + AOF
- 混合持久化
- 定期备份 RDB 文件
7. 数据恢复
RDB 恢复:
# 把 dump.rdb 放到数据目录
# 重启 Redis 自动加载
AOF 恢复:
# 开启 appendonly yes
# 重启自动重放AOF日志
8. 常见问题
1)fork 阻塞怎么办
- 数据量大 fork 慢
- 用 AOF 代替
- 减少单实例数据量
2)AOF 文件太大
- 定期触发重写
- 设置自动重写阈值
3)恢复慢怎么办
- 用 RDB 恢复快
- 混合持久化更优
总结
Redis 持久化要点:
- RDB 是快照,恢复快但可能丢数据
- AOF 是日志,数据安全但文件大
- 生产建议同时开 RDB + AOF
- 混合持久化结合两者优点
- 缓存用 RDB,重要数据用 AOF
- 定期备份持久化文件
- fork 阻塞要关注数据量
持久化是 Redis 生产必备配置,一定要重视。
MySQL大表分页越查越慢,优化方案
数据量小的时候分页很快,一旦表数据上百万条,翻到后面的页就慢得要死?这是MySQL分页最常见的问题,下面说几个实用的优化方案。
1. 先看为什么会慢
SELECT * FROM 表名 ORDER BY id LIMIT 100000, 10;
这个SQL的意思是:扫描前100010条,丢掉前100000条,返回后10条。
数据量越大,扫描的行数越多,就越慢。这就是为什么越往后翻越慢。
2. 方案一:子查询优化(最常用)
SELECT * FROM 表名
WHERE id >= (SELECT id FROM 表名 ORDER BY id LIMIT 100000, 1)
LIMIT 10;
原理:
- 子查询先通过索引找到第100001条的id
- 然后直接从这个id开始往后查
- 不需要扫描前10万条
速度对比:
- 原SQL:扫描10万+行,耗时几秒
- 优化后:走索引,几毫秒就出来
3. 方案二:游标分页(推荐用在后台)
-- 第一页
SELECT * FROM 表名 ORDER BY id LIMIT 10;
-- 第二页(记住上一页最后一条的id)
SELECT * FROM 表名 WHERE id > 100 ORDER BY id LIMIT 10;
-- 第三页
SELECT * FROM 表名 WHERE id > 110 ORDER BY id LIMIT 10;
优点:
- 永远都是走索引,不管翻到第几页都一样快
- 适合无限滚动加载
缺点:
- 不能直接跳到第100页
- 不适合传统的"上一页/下一页"翻页
4. 方案三:延迟关联
SELECT t.* FROM 表名 t
INNER JOIN (
SELECT id FROM 表名 ORDER BY id LIMIT 100000, 10
) tmp ON t.id = tmp.id;
原理:
- 先通过索引查出需要的10个id
- 再用这10个id去关联原表查完整数据
- 避免回表太多行
5. 方案四:限制最大翻页深度
如果不是必须让用户翻到很后面,可以直接限制:
-- 最多只允许翻100页(1000条数据)
IF 页码 > 100 THEN
-- 提示用户"请搜索更精确的条件"
END IF;
一般用户也不会翻到很后面的页,限制一下性能就好很多。
6. 方案五:用ES做搜索
如果是文章、商品之类的内容,直接用Elasticsearch做全文搜索和分页,MySQL只存原始数据。
ES天生适合做大数据量的分页搜索,MySQL不是干这个的。
常见坑
坑1:ORDER BY 字段没加索引
ORDER BY 的字段一定要加索引,不然不管怎么优化都是全表扫描。
坑2:OFFSET 太大了
OFFSET 100万,不管怎么优化都慢,直接用游标分页。
*坑3:SELECT 不要用**
只查需要的字段,减少回表数据量。
遇到问题加QQ23979811 协助处理
PHP session不生效,cookie配置检查
前言
PHP用session存用户登录状态,结果刷新页面就掉了?session不生效太常见了,一般就是cookie配置不对、路径不对、或者目录没权限。本文讲怎么排查和解决。
一、session工作原理
简单说:
- 浏览器访问网站
- PHP创建session,生成session_id
- session_id通过cookie发给浏览器
- 浏览器每次请求带上这个cookie
- PHP根据cookie里的session_id找到对应数据
核心: session靠cookie传session_id,cookie不行session就没了。
二、常见原因
原因1:cookie没设置对
session.cookie_path、session.cookie_domain配置不对。
原因2:session保存目录没权限
session文件存不进去。
原因3:session.auto_start没开
或者开了但和代码冲突。
原因4:跨域问题
主域和子域之间cookie不共享。
原因5:HTTPS下cookie的secure问题
cookie设了secure,但用http访问。
三、排查步骤
第一步:检查session有没有启动
<?php
session_start();
// 存个值
$_SESSION['test'] = 'hello';
// 看有没有
echo '<pre>';
print_r($_SESSION);
echo '</pre>';
刷新页面,看test还在不在。
第二步:看cookie里有没有PHPSESSID
按F12打开浏览器开发者工具 → Application → Cookies。
看有没有 PHPSESSID 这个cookie。
没有的话: 就是cookie没设对。
有的话: 那就是session保存目录的问题。
四、php.ini配置检查
找到php.ini位置
php --ini
关键配置
;session保存目录
session.save_path = "/var/lib/php/session"
;是否自动启动
session.auto_start = 0
;cookie路径
session.cookie_path = /
;cookie域名
session.cookie_domain =
;cookie有效期(秒)
session.cookie_lifetime = 0
;是否仅https
session.cookie_secure = 0
;是否仅http(防XSS)
session.cookie_httponly = 1
五、常见坑和解决
坑1:session目录没权限
session文件写不进去。
检查:
ls -ld /var/lib/php/session
解决:
chmod 777 /var/lib/php/session
chown apache:apache /var/lib/php/session
或者看php-fpm用的哪个用户。
坑2:cookie_path不对
比如网站装在子目录,但cookie_path设成了别的。
解决:
ini_set('session.cookie_path', '/');
session_start();
坑3:cookie_domain不对
多域名访问时,cookie只在一个域名下有效。
解决:
ini_set('session.cookie_domain', '.example.com');
注意前面有个点,表示所有子域名都生效。
坑4:HTTPS下设了secure
cookie_secure=1表示只在https下传cookie,用http访问就没了。
解决:
session.cookie_secure = 0
或者全站用https。
坑5:session.auto_start冲突
代码里已经session_start()了,但php.ini里也开了auto_start,冲突。
解决:
session.auto_start = 0
六、代码里动态设置
不想改php.ini,可以在代码里设:
<?php
// 必须在session_start()之前
ini_set('session.cookie_path', '/');
ini_set('session.cookie_domain', '.example.com');
ini_set('session.cookie_lifetime', 0);
session_start();
$_SESSION['user_id'] = 1;
七、检查session是否生效
写个测试页:
<?php
session_start();
// 第一次访问,设置
if (!isset($_SESSION['count'])) {
$_SESSION['count'] = 1;
} else {
$_SESSION['count']++;
}
echo "访问次数: " . $_SESSION['count'];
echo "<br>";
echo "Session ID: " . session_id();
echo "<br>";
echo "Session 文件: " . session_save_path() . "/sess_" . session_id();
刷新页面,数字应该一直涨。
如果不涨: 就是session没存住。
八、常见场景
场景1:登录后刷新就掉
原因: cookie_path或domain不对。
解决: 检查cookie配置。
场景2:换了个子域名就掉
比如 www.example.com 登录了,跳到 bbs.example.com 就没登录。
原因: cookie_domain没设成 .example.com。
解决:
ini_set('session.cookie_domain', '.example.com');
场景3:从http跳到https就掉
原因: cookie_secure=1,但http不带cookie。
解决: 全站用https,或者关掉secure。
场景4:session目录满了
原因: session文件太多。
解决: 清理旧session,或者设gc_maxlifetime。
session.gc_maxlifetime = 1440
九、快速排查清单
- F12看cookie里有没有PHPSESSID
- 没有 → 查cookie_path、cookie_domain、cookie_secure
- 有 → 查session目录权限
- 都没问题 → 看session.auto_start有没有冲突
- 跨域问题 → cookie_domain设成 .主域名
总结
记住:
- session靠cookie传session_id
- 先看cookie里有没有PHPSESSID
- 没有就查cookie配置
- 有就查session目录权限
- 跨域名记得设cookie_domain=.主域名
最常用:
ini_set('session.cookie_path', '/');
session_start();
遇到问题加QQ23979811 协助处理
PHP 过滤和验证数据
PHP 过滤和验证数据
用户输入不可信,必须过滤验证,这是Web开发的基本功。
1. 为什么要过滤验证
- 防止 SQL 注入
- 防止 XSS 攻击
- 保证数据格式正确
- 防止脏数据入库
2. filter_var 函数
PHP 内置过滤器,很好用。
验证邮箱
$email = 'test@example.com';
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo '邮箱格式正确';
} else {
echo '邮箱格式错误';
}
验证数字
$age = '20abc';
if (filter_var($age, FILTER_VALIDATE_INT)) {
echo '是整数';
}
验证URL
$url = 'https://example.com';
if (filter_var($url, FILTER_VALIDATE_URL)) {
echo 'URL格式正确';
}
验证IP
$ip = '192.168.1.1';
if (filter_var($ip, FILTER_VALIDATE_IP)) {
echo 'IP格式正确';
}
3. filter_input 获取并过滤
从 GET/POST 获取并过滤:
// 获取并过滤GET参数
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
// 获取并过滤POST参数
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
4. 过滤输入
过滤HTML标签
$str = '<script>alert(1)</script>你好';
// 去除HTML标签
echo strip_tags($str);
// HTML转义(安全输出)
echo htmlspecialchars($str);
过滤特殊字符
$str = "hello ' world";
// 转义单引号
echo addslashes($str);
5. 常用验证场景
验证手机号
function checkPhone($phone) {
return preg_match('/^1[3-9]\d{9}$/', $phone);
}
if (checkPhone('13800138000')) {
echo '手机号正确';
}
验证身份证
function checkIdCard($idcard) {
return preg_match('/^\d{17}[\dXx]$/', $idcard);
}
验证用户名
// 4-16位,字母数字下划线
function checkUsername($name) {
return preg_match('/^\w{4,16}$/', $name);
}
6. 安全建议
1)所有外部输入都要验证
- GET、POST、COOKIE
- 不要相信任何用户输入
2)用预处理防SQL注入
- 不要自己拼接SQL
- 用 PDO 预处理
3)输出时转义
- 输出到HTML要 htmlspecialchars
- 防止 XSS 攻击
4)白名单验证
- 只接受允许的值
- 不要用黑名单
7. 完整示例
// 接收用户注册信息
$username = trim($_POST['username']);
$email = trim($_POST['email']);
$age = (int)$_POST['age'];
$errors = [];
// 验证用户名
if (!preg_match('/^\w{4,16}$/', $username)) {
$errors[] = '用户名4-16位字母数字下划线';
}
// 验证邮箱
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = '邮箱格式错误';
}
// 验证年龄
if ($age < 18 || $age > 100) {
$errors[] = '年龄必须18-100岁';
}
if (empty($errors)) {
// 验证通过,入库
echo '注册成功';
} else {
print_r($errors);
}
总结
PHP 过滤和验证数据要点:
- 所有用户输入都不可信
- 用 filter_var 验证格式
- 用 filter_input 获取并过滤
- 输出用 htmlspecialchars 防XSS
- 用预处理防SQL注入
- 用正则验证手机号、用户名
- 白名单验证比黑名单好
- 不要自己拼接SQL字符串
做Web开发,安全永远第一位。
MySQL数据库导入sql文件报错解决
导入sql文件的时候各种报错?下面整理了最常见的导入报错和解决办法,照着排查就行。
1. 先看最常见的导入命令
mysql -u root -p 数据库名 < /path/to/你的文件.sql
或者进入MySQL后用source:
mysql -u root -p
USE 数据库名;
source /path/to/你的文件.sql;
2. 报错1:Unknown database
原因: 数据库不存在,你直接导入了。
解决: 先创建数据库再导入:
CREATE DATABASE 数据库名 DEFAULT CHARACTER SET utf8mb4;
然后再执行导入命令。
3. 报错2:Access denied
原因: 权限不够,或者密码错了。
解决: 确认用户名密码对不对,给用户授权:
GRANT ALL PRIVILEGES ON *.* TO '用户名'@'localhost' IDENTIFIED BY '密码';
FLUSH PRIVILEGES;
4. 报错3:File not found
原因: 文件路径不对,或者文件没权限读。
解决:
- 用绝对路径,不要用相对路径
- 确认文件存在:
ls -l /path/to/file.sql - 给文件加读权限:
chmod 644 file.sql
5. 报错4:Duplicate entry
原因: 导入的表已经有数据了,主键冲突。
解决:
- 先清空表再导入:
TRUNCATE TABLE 表名; - 或者导入SQL文件开头加上
DROP TABLE IF EXISTS 表名;
6. 报错5:Unknown collation
原因: 字符集排序规则不一致,比如导出用的是 utf8mb4_general_ci,导入的数据库是 utf8_general_ci。
解决: 导入前设置字符集:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 导入你的SQL
SET FOREIGN_KEY_CHECKS = 1;
7. 报错6:MySQL server has gone away
原因: SQL文件太大,导入超时了。
解决: 修改MySQL配置:
# /etc/my.cnf
max_allowed_packet = 64M
wait_timeout = 300
interactive_timeout = 300
然后重启MySQL:
systemctl restart mysqld
8. 报错7:Syntax error
原因: SQL文件格式不对,或者MySQL版本不兼容。
解决:
- 导出的时候选择兼容版本
- 去掉注释行(-- 开头的)
- 用文本编辑器打开检查一下有没有乱码
9. 大文件导入更快的方法
如果SQL文件几百MB,用mysql命令行比phpMyAdmin快10倍:
mysql -u root -p 数据库名 < big_file.sql
还可以开事务加速:
SET autocommit = 0;
SET unique_checks = 0;
SET foreign_key_checks = 0;
-- 导入完成后
SET autocommit = 1;
SET unique_checks = 1;
SET foreign_key_checks = 1;
COMMIT;
遇到问题加QQ23979811 协助处理
MySQL 主键和自增
MySQL 主键和自增
主键是表的重要概念,搞懂怎么用很有必要。
1. 什么是主键
主键(Primary Key)是表中唯一标识一行记录的字段。
特点:
- 唯一性: 每行数据主键不能重复
- 非空性: 主键不能为 NULL
- 一张表只能有一个主键
2. 创建主键
建表时指定
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
);
自增主键(最常用)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
3. 自增主键特点
- 自动增长: 插入数据不用指定ID,自动+1
- 从1开始: 默认从1开始
- 不重复: 保证唯一
-- 插入数据不用写id
INSERT INTO users (name) VALUES ('张三');
-- id自动变成1
-- 再插入自动变成2
4. 查看自增值
-- 查看表当前自增值
SHOW TABLE STATUS LIKE 'users';
-- 查看建表语句
SHOW CREATE TABLE users;
5. 修改自增值
-- 设置下一次自增值为100
ALTER TABLE users AUTO_INCREMENT = 100;
6. 联合主键
多个字段组成主键:
CREATE TABLE user_role (
user_id INT,
role_id INT,
PRIMARY KEY (user_id, role_id)
);
7. 主键设计建议
1)用自增整数做主键
- INT 或 BIGINT
- 查询快,占用空间小
- 不要用 UUID 做主键
2)为什么不用字符串做主键
- 占空间大
- 查询慢
- 自增效率高
3)什么时候用联合主键
- 中间表(如用户角色关联表)
- 没有合适的单字段做主键
4)BIGINT 还是 INT
- 一般 INT 够用(约21亿)
- 数据量大用 BIGINT
8. 常见问题
1)删除数据后ID不连续
- 正常现象,自增只增不减
- 不要依赖ID连续
2)自增值跳号
- 删除了中间数据
- 插入失败也会占用ID
- 正常现象
3)为什么不用手机号做主键
- 可能会变
- 字符串查询慢
- 用自增ID更稳妥
总结
MySQL 主键和自增要点:
- 主键唯一且非空
- 一张表只能一个主键
- 自增主键最常用
- 用 INT 或 BIGINT
- 不要用 UUID 做主键
- 中间表可以用联合主键
- ID不连续是正常现象
主键设计对表性能影响很大,一定要重视。
Nginx 访问日志配置
Nginx 访问日志配置
访问日志是排查问题和分析流量的重要依据,配置好很有必要。
1. 默认日志位置
# 访问日志
/var/log/nginx/access.log
# 错误日志
/var/log/nginx/error.log
2. 日志格式
Nginx 默认有两种日志格式:
combined(默认)
log_format combined '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
common
log_format common '$remote_addr [$time_local] "$request" $status $body_bytes_sent';
3. 自定义日志格式
在 nginx.conf 的 http 块添加:
http {
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/access.log main;
}
4. 常用日志变量
- $remote_addr: 客户端IP
- $time_local: 本地时间
- $request: 请求行
- $status: 状态码
- $body_bytes_sent: 发送字节数
- $http_referer: 来源页面
- $http_user_agent: 浏览器信息
- $request_time: 请求总耗时
- $upstream_response_time: 后端响应时间
5. 按站点分开日志
不同站点用不同日志文件:
server {
listen 80;
server_name a.com;
access_log /var/log/nginx/a.access.log;
}
server {
listen 80;
server_name b.com;
access_log /var/log/nginx/b.access.log;
}
6. 关闭日志
静态资源不记日志:
location ~* \.(jpg|jpeg|png|gif|css|js)$ {
access_log off;
}
7. 日志切割
Nginx 自带日志切割:
http {
# 每天切割,保留30天
access_log /var/log/nginx/access.log main;
log_not_found off;
}
也可以用 logrotate:
# 配置文件
/etc/logrotate.d/nginx
8. 分析日志
统计访问量
# 统计总请求数
wc -l /var/log/nginx/access.log
# 统计独立IP
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
统计状态码
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c
统计访问最多的URL
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
总结
Nginx 访问日志配置要点:
- 默认日志在 /var/log/nginx/
- combined 是默认格式
- 自定义格式添加 $request_time 等变量
- 不同站点分开日志文件
- 静态资源可以关闭日志
- 日志要定期切割
- 用 awk 分析日志很方便
做好日志配置,排查问题和分析流量都有用。
PHP 时间日期处理
PHP 时间日期处理
时间日期是开发常用功能,这些函数要记住。
1. 获取当前时间
// 当前时间戳
echo time();
// 当前日期时间
echo date('Y-m-d H:i:s');
// 今天日期
echo date('Y-m-d');
2. 时间戳和日期互转
时间戳转日期
$time = time();
echo date('Y-m-d H:i:s', $time);
日期转时间戳
$date = '2024-01-01 12:00:00';
$time = strtotime($date);
echo $time;
3. 常用日期格式
// 2024-01-01
echo date('Y-m-d');
// 2024-01-01 12:00:00
echo date('Y-m-d H:i:s');
// 2024年01月01日
echo date('Y年m月d日');
// 2024/01/01
echo date('Y/m/d');
// 星期几
echo date('l'); // Sunday
4. 常用格式字符
- Y: 4位年
- m: 2位月
- d: 2位日
- H: 24小时制
- i: 分钟
- s: 秒
5. 日期加减
// 明天
echo date('Y-m-d', strtotime('+1 day'));
// 昨天
echo date('Y-m-d', strtotime('-1 day'));
// 一周后
echo date('Y-m-d', strtotime('+1 week'));
// 一个月后
echo date('Y-m-d', strtotime('+1 month'));
// 一年后
echo date('Y-m-d', strtotime('+1 year'));
6. 计算时间差
$start = strtotime('2024-01-01');
$end = strtotime('2024-01-10');
// 相差天数
$diff = ($end - $start) / 86400;
echo $diff . '天';
7. DateTime 类(推荐)
// 创建日期对象
$date = new DateTime('2024-01-01');
// 格式化输出
echo $date->format('Y-m-d H:i:s');
// 加一天
$date->add(new DateInterval('P1D'));
echo $date->format('Y-m-d');
// 计算两个日期差
$start = new DateTime('2024-01-01');
$end = new DateTime('2024-01-10');
$diff = $start->diff($end);
echo $diff->days . '天';
8. 常用场景
1)几天前
function timeAgo($time) {
$diff = time() - $time;
if ($diff < 60) return '刚刚';
if ($diff < 3600) return floor($diff/60) . '分钟前';
if ($diff < 86400) return floor($diff/3600) . '小时前';
return floor($diff/86400) . '天前';
}
2)生日计算
$birthday = '1990-01-01';
$age = floor((time() - strtotime($birthday)) / 31536000);
echo $age . '岁';
总结
PHP 时间日期处理要点:
- time() 获取时间戳
- date() 格式化日期
- strtotime() 日期转时间戳
- 用 +1 day 等做日期加减
- 推荐用 DateTime 类
- 时间差除以86400得天数
- 注意时区设置
做开发时间日期天天用,这些函数必须熟。
Linux 查看磁盘空间
Linux 查看磁盘空间
服务器磁盘满了怎么办?这些命令帮你快速排查。
1. df 查看整体磁盘
基本用法
# 查看磁盘使用情况
df
# 人性化显示(带M、G单位)
df -h
常用参数
- -h: 人性化显示大小
- -T: 显示文件系统类型
- -i: 查看 inode 使用情况
# 常用组合
df -hT
输出说明:
- Filesystem: 磁盘分区
- Size: 总大小
- Used: 已用
- Avail: 可用
- Use%: 使用率
- Mounted on: 挂载点
2. du 查看目录大小
查看当前目录大小
# 当前目录总大小
du -sh
# 查看指定目录
du -sh /var/log
查看子目录大小
# 查看当前目录下每个子目录大小
du -sh *
# 按大小排序
du -sh * | sort -rh
常用参数
- -s: 只显示总计
- -h: 人性化显示
- --max-depth=1: 只看一层
# 查看根目录下各目录大小
du -sh /* --max-depth=1
3. 查找大文件
查找大于100M的文件
find / -type f -size +100M -exec ls -lh {} \;
查找最近修改的大文件
find /var -type f -size +50M -mtime -7
4. 常见排查场景
1)磁盘满了
# 先看哪个分区满了
df -h
# 找到大目录
du -sh /* | sort -rh
2)日志占空间
# 查看日志目录大小
du -sh /var/log
# 清空大日志文件
> /var/log/large.log
3)inode满了
# 查看inode使用
df -i
5. 清理磁盘
清理日志文件
# 清空文件(不删除文件)
> /var/log/messages
# 删除旧日志
rm /var/log/*.old
清理缓存
# 清理包缓存
yum clean all
清理 Docker 没用的镜像
docker system prune -a
总结
Linux 查看磁盘空间要点:
- df -h 查看整体磁盘使用
- du -sh 查看目录大小
- find 查找大文件
- 磁盘满了先看分区,再找大目录
- 日志文件要定期清理
- 注意 inode 也可能满
- 用 sort -rh 按大小排序
运维排查磁盘问题,这些命令天天用。
PHP 生成随机密码
PHP 生成随机密码
注册、重置密码功能经常要生成随机密码,这几种方法要会。
1. 简单随机字符串
function generatePassword($length = 8) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= $chars[mt_rand(0, strlen($chars) - 1)];
}
return $password;
}
// 生成8位密码
echo generatePassword(8);
2. 带特殊字符的密码
function generateStrongPassword($length = 12) {
$lower = 'abcdefghijklmnopqrstuvwxyz';
$upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$numbers = '0123456789';
$special = '!@#$%^&*()';
$all = $lower . $upper . $numbers . $special;
$password = '';
// 确保至少包含每种字符
$password .= $lower[mt_rand(0, strlen($lower) - 1)];
$password .= $upper[mt_rand(0, strlen($upper) - 1)];
$password .= $numbers[mt_rand(0, strlen($numbers) - 1)];
$password .= $special[mt_rand(0, strlen($special) - 1)];
// 剩余随机
for ($i = 4; $i < $length; $i++) {
$password .= $all[mt_rand(0, strlen($all) - 1)];
}
// 打乱顺序
return str_shuffle($password);
}
echo generateStrongPassword(12);
3. 更安全的 random_bytes
PHP 7+ 推荐用 random_bytes,更安全:
function generateSecurePassword($length = 12) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
$max = strlen($chars) - 1;
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= $chars[random_int(0, $max)];
}
return $password;
}
echo generateSecurePassword(16);
4. 生成验证码
function generateCode($length = 6) {
$chars = '0123456789';
$code = '';
for ($i = 0; $i < $length; $i++) {
$code .= $chars[random_int(0, 9)];
}
return $code;
}
// 6位数字验证码
echo generateCode(6);
5. 生成随机 Token
function generateToken($length = 32) {
return bin2hex(random_bytes($length / 2));
}
// 32位随机token
echo generateToken();
6. 安全建议
1)用 random_int 不用 mt_rand
- random_int 是密码安全随机数
- mt_rand 不够安全
2)密码长度
- 普通密码:8-12位
- 强密码:12位以上
- Token:32位以上
3)密码强度
- 大小写字母+数字+特殊字符
- 避免纯数字或纯字母
4)不要自己写随机算法
- 用 PHP 内置函数
- 不要用时间戳做随机种子
总结
PHP 生成随机密码要点:
- 简单密码用 mt_rand 拼接
- 强密码包含大小写+数字+特殊字符
- 安全密码用 random_int
- Token 用 bin2hex(random_bytes())
- 验证码用纯数字
- 推荐用 PHP 7+ 的 random_int
- 不要自己写随机算法
做用户系统,生成密码和验证码是必备功能。
MySQL 常用数据类型
MySQL 常用数据类型
建表选对数据类型很重要,这些常用类型要记住。
1. 数值类型
整数类型
- TINYINT: 1字节,范围 -128~127
- SMALLINT: 2字节,范围 -32768~32767
- INT: 4字节,常用,范围约 ±21亿
- BIGINT: 8字节,大数值
小数类型
- FLOAT: 单精度浮点数
- DOUBLE: 双精度浮点数
- DECIMAL: 精确小数,存金额用这个
-- 金额用 DECIMAL(总位数, 小数位数)
price DECIMAL(10,2) -- 最多10位,2位小数
2. 字符串类型
CHAR: 定长字符串,比如 CHAR(10) 存不满补空格
- 适合固定长度,比如手机号、性别
VARCHAR: 变长字符串,最常用
- VARCHAR(255),存多少占多少
- 适合变长内容,比如用户名、标题
TEXT: 长文本
- TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT
- 存文章内容、备注等
常用选择:
- 短文本:VARCHAR(50)
- 长文本:TEXT
- 固定长度:CHAR
3. 日期时间类型
DATE: 日期,格式 YYYY-MM-DD
TIME: 时间,格式 HH:MM:SS
DATETIME: 日期+时间,范围 1000-9999年
TIMESTAMP: 时间戳,范围 1970-2038年,自动更新
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
4. 常用字段设计示例
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL COMMENT '用户名',
age TINYINT UNSIGNED COMMENT '年龄',
phone CHAR(11) COMMENT '手机号',
money DECIMAL(10,2) DEFAULT 0 COMMENT '余额',
status TINYINT DEFAULT 1 COMMENT '状态:1正常 0禁用',
content TEXT COMMENT '简介',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5. 选择建议
1)主键ID
- 用 INT 或 BIGINT
- 不要用 UUID 做主键
2)金额
- 用 DECIMAL,不要用 FLOAT/DOUBLE
- 避免精度丢失
3)状态字段
- 用 TINYINT,不要用 VARCHAR 存 "正常/禁用"
4)时间
- 创建时间用 DATETIME
- 更新时间用 TIMESTAMP 自动更新
5)字符串
- 短文本用 VARCHAR
- 长文本用 TEXT
- 固定长度用 CHAR
6. 注意事项
- 不要过度设计: 够用就行,不要字段太大
- 能用数字不用字符串: 排序、计算都快
- 避免 NULL: 尽量设默认值,NULL 不好查询
- 加注释: 每个字段都加 COMMENT
总结
MySQL 常用数据类型要点:
- INT/BIGINT:整数
- DECIMAL:金额
- VARCHAR:字符串
- TEXT:长文本
- DATETIME/TIMESTAMP:时间
- 主键用 INT 自增
- 金额用 DECIMAL
- 状态用 TINYINT
- 字段加注释
建表选对类型,性能和存储都好。
Docker 查看容器日志
Docker 查看容器日志
容器出问题怎么排查?先看日志,这些命令要会。
1. 查看容器日志
基本命令
# 查看全部日志
docker logs 容器名
# 查看容器ID
docker logs 容器ID
2. 常用参数
看最后几行
# 最后10行
docker logs --tail 10 容器名
# 最后20行
docker logs --tail 20 容器名
实时跟踪日志(最常用)
docker logs -f 容器名
带时间戳
docker logs -t 容器名
组合使用
# 实时跟踪 + 时间戳 + 最后10行
docker logs -f -t --tail 10 容器名
3. 按时间查看
# 最近10分钟的日志
docker logs --since 10m 容器名
# 最近1小时的日志
docker logs --since 1h 容器名
# 指定时间开始
docker logs --since "2024-01-01T00:00:00" 容器名
4. 多容器日志
docker-compose 查看日志
# 查看所有服务日志
docker-compose logs
# 实时跟踪
docker-compose logs -f
# 查看指定服务
docker-compose logs nginx
# 看最后50行
docker-compose logs --tail 50 nginx
5. 日志持久化
json-file 日志驱动(默认)
- 日志存在 /var/lib/docker/containers/
- 单个容器日志文件
配置日志大小限制
# docker-compose.yml
services:
app:
image: nginx
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
6. 常见排查场景
1)容器启动失败
docker logs 容器名
看最后几行报错信息。
2)接口报错
docker logs -f --tail 100 容器名
实时看请求日志。
3)查某个时间点的错误
docker logs --since "2024-01-01T10:00:00" 容器名 | grep "error"
7. 日志清理
清理单个容器日志
# 找到日志文件
sudo find /var/lib/docker/containers/ -name "*.log"
# 清空日志
sudo sh -c "echo > /var/lib/docker/containers/容器ID/容器ID-json.log"
重启容器自动清理
docker restart 容器名
总结
Docker 查看容器日志要点:
- docker logs 查看日志
- -f 实时跟踪最常用
- --tail 看最后几行
- -t 带时间戳
- --since 按时间查
- docker-compose logs 多容器
- 配置日志大小防止占满磁盘
- 排查问题先看日志
容器运维,看日志是第一步。
PHP PDO 连接数据库
PHP PDO 连接数据库
PDO 是 PHP 推荐的数据库连接方式,支持多种数据库,防注入。
1. 什么是 PDO
PDO(PHP Data Objects)是 PHP 数据库抽象层,统一接口操作多种数据库。
2. 为什么用 PDO
- 防SQL注入: 预处理语句
- 支持多数据库: MySQL、PostgreSQL、SQLite 等
- 面向对象: 写法更规范
- 错误处理: 统一的错误模式
3. 连接数据库
// 数据库配置
$host = '127.0.0.1';
$dbname = 'test';
$user = 'root';
$pass = 'root';
try {
// 连接数据库
$pdo = new PDO(
"mysql:host=$host;dbname=$dbname;charset=utf8mb4",
$user,
$pass,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
);
echo '连接成功';
} catch(PDOException $e) {
die('连接失败:' . $e->getMessage());
}
4. 查询数据
查询多条
$stmt = $pdo->query("SELECT * FROM users");
$users = $stmt->fetchAll();
查询单条
$stmt = $pdo->query("SELECT * FROM users WHERE id = 1");
$user = $stmt->fetch();
5. 预处理语句(防注入)
查询
// 问号占位符
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([1]);
$user = $stmt->fetch();
// 命名占位符
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute([':id' => 1]);
$user = $stmt->fetch();
插入
$stmt = $pdo->prepare("INSERT INTO users (name, age) VALUES (:name, :age)");
$stmt->execute([
':name' => '张三',
':age' => 20
]);
更新
$stmt = $pdo->prepare("UPDATE users SET name = :name WHERE id = :id");
$stmt->execute([
':name' => '李四',
':id' => 1
]);
删除
$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
$stmt->execute([':id' => 1]);
6. 获取自增ID
$pdo->exec("INSERT INTO users (name) VALUES ('王五')");
$id = $pdo->lastInsertId();
7. 事务处理
try {
$pdo->beginTransaction();
$pdo->exec("INSERT INTO orders ...");
$pdo->exec("UPDATE stock ...");
$pdo->commit();
echo '事务成功';
} catch(Exception $e) {
$pdo->rollBack();
echo '事务失败:' . $e->getMessage();
}
8. 常用配置
// 设置返回关联数组
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
// 错误模式
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
// 关闭预处理模拟(真正服务端预处理)
PDO::ATTR_EMULATE_PREPARES => false
总结
PHP PDO 连接数据库要点:
- PDO 是统一数据库接口
- 预处理语句防 SQL 注入
- 支持事务操作
- 配置错误模式方便调试
- 推荐用命名占位符
- 生产环境关闭预处理模拟
- 记得设置 utf8mb4 字符集
做数据库开发,PDO 比 mysqli 更好用。
Linux 查看系统日志
Linux 查看系统日志
排查问题先看日志,这些命令要记住。
1. 系统日志位置
/var/log/messages # 系统主日志
/var/log/syslog # Debian/Ubuntu 系统日志
/var/log/boot.log # 启动日志
/var/log/cron # 定时任务日志
/var/log/secure # 安全日志(登录、sudo等)
/var/log/nginx/ # Nginx 日志
/var/log/httpd/ # Apache 日志
/var/log/mysql/ # MySQL 日志
2. 基本查看命令
cat:查看全部
cat /var/log/messages
tail:看末尾几行(最常用)
# 看最后10行
tail /var/log/messages
# 看最后20行
tail -n 20 /var/log/messages
# 实时跟踪日志(最常用)
tail -f /var/log/messages
head:看开头几行
head -n 10 /var/log/messages
3. 搜索日志
grep:搜索关键词
# 搜索错误信息
grep "error" /var/log/messages
# 不区分大小写
grep -i "error" /var/log/messages
# 显示行号
grep -n "error" /var/log/messages
# 反向匹配(不包含error)
grep -v "debug" /var/log/messages
结合管道
# 实时日志里搜索错误
tail -f /var/log/messages | grep "error"
# 搜索最近的nginx错误
tail -n 1000 /var/log/nginx/error.log | grep "500"
4. journalctl 查看 systemd 日志
# 查看全部日志
journalctl
# 看最后100行
journalctl -n 100
# 实时跟踪
journalctl -f
# 按服务查看
journalctl -u nginx
# 按时间查看
journalctl --since "1 hour ago"
journalctl --since "2024-01-01" --until "2024-01-02"
5. 常用排查场景
1)服务器登录失败
grep "Failed password" /var/log/secure
2)服务起不来
systemctl status nginx
journalctl -u nginx -n 50
3)磁盘满了
df -h
du -sh /var/log/*
4)定时任务没执行
cat /var/log/cron
6. 日志清理
手动删除大日志
# 查看日志大小
du -sh /var/log/*
# 清空日志(不删除文件)
> /var/log/messages
# 删除旧日志
rm /var/log/messages.1
logrotate 自动切割
- Linux 自带日志切割工具
- 配置文件在 /etc/logrotate.conf
- 一般不用管,自动轮转
总结
Linux 查看日志要点:
- 日志都在 /var/log/ 目录
- tail -f 实时看日志最常用
- grep 搜索关键词
- journalctl 查 systemd 服务日志
- 排查问题先看错误日志
- 日志太大要及时清理
- 系统自动轮转不用手动管
运维排查,看日志是第一步。
MySQL 视图的使用
MySQL 视图的使用
视图是虚拟表,用好了能简化查询、保护数据。
1. 什么是视图
视图(View)是基于 SQL 查询结果的虚拟表,本身不存数据,数据来自真实表。
2. 为什么用视图
1)简化查询
- 复杂查询封装成视图
- 直接查视图就行
2)权限控制
- 只暴露部分字段给用户
- 敏感字段不暴露
3)数据独立
- 表结构变了,视图不变
- 上层应用不用改
3. 创建视图
-- 创建视图
CREATE VIEW v_user_order AS
SELECT u.id, u.name, o.order_no, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id;
-- 使用视图
SELECT * FROM v_user_order WHERE id = 1;
4. 修改和删除视图
-- 修改视图
CREATE OR REPLACE VIEW v_user_order AS
SELECT u.id, u.name, o.order_no, o.amount, o.create_time
FROM users u
JOIN orders o ON u.id = o.user_id;
-- 删除视图
DROP VIEW IF EXISTS v_user_order;
5. 查看视图
-- 查看所有视图
SHOW FULL TABLES WHERE Table_type = 'VIEW';
-- 查看视图结构
DESC v_user_order;
-- 查看创建语句
SHOW CREATE VIEW v_user_order;
6. 视图的限制
1)不能用 ORDER BY 在视图里
- 外层查询可以排序
- 视图里排序意义不大
2)某些视图不能更新
- 包含 GROUP BY、DISTINCT、JOIN 等的视图
- 不能通过视图 INSERT/UPDATE/DELETE
3)视图不存数据
- 每次查询都执行 SQL
- 复杂视图查询可能慢
7. 实际使用建议
1)视图不是越多越好
- 复杂业务逻辑用视图
- 简单查询直接写 SQL
2)视图不要嵌套太多层
- 视图套视图性能差
- 最多一层就够了
3)敏感数据保护
- 密码、手机号等敏感字段
- 用视图隐藏或脱敏
8. 视图和表的区别
| 对比项 | 表 | 视图 |
|---|---|---|
| 存数据 | 是 | 否(虚拟表) |
| 占空间 | 是 | 否 |
| 查询速度 | 快 | 取决于SQL |
| 安全性 | 低 | 高 |
总结
MySQL 视图使用要点:
- 视图是虚拟表,不存数据
- 简化复杂查询
- 保护敏感数据
- 用 CREATE VIEW 创建
- 简单视图可以更新
- 复杂视图查询可能慢
- 不要过度使用视图
视图是好东西,但别滥用。
PHP 上传文件处理
PHP 上传文件处理
文件上传是 Web 开发常见功能,安全处理很重要。
1. HTML 表单
<form method="post" action="upload.php" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传</button>
</form>
注意:必须加 enctype="multipart/form-data"。
2. PHP 接收文件
// $_FILES 接收上传文件
$file = $_FILES['file'];
// 文件信息
$name = $file['name']; // 文件名
$tmp = $file['tmp_name']; // 临时路径
$size = $file['size']; // 文件大小
$error = $file['error']; // 错误码
$type = $file['type']; // MIME类型
3. 错误码说明
- UPLOAD_ERR_OK (0): 上传成功
- UPLOAD_ERR_INI_SIZE (1): 超过 php.ini 限制
- UPLOAD_ERR_FORM_SIZE (2): 超过表单限制
- UPLOAD_ERR_PARTIAL (3): 部分上传
- UPLOAD_ERR_NO_FILE (4): 没有文件
4. 完整上传代码
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$file = $_FILES['file'];
// 检查错误
if ($file['error'] !== UPLOAD_ERR_OK) {
exit('上传失败');
}
// 检查大小(限制2M)
if ($file['size'] > 2 * 1024 * 1024) {
exit('文件太大');
}
// 允许的扩展名
$allowExt = ['jpg', 'png', 'gif'];
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
if (!in_array($ext, $allowExt)) {
exit('不允许的文件类型');
}
// 生成新文件名
$newName = uniqid() . '.' . $ext;
$uploadDir = 'uploads/';
// 移动文件
if (move_uploaded_file($file['tmp_name'], $uploadDir . $newName)) {
echo '上传成功';
} else {
echo '保存失败';
}
}
5. php.ini 配置
; 上传最大文件大小
upload_max_filesize = 2M
; 表单最大提交大小
post_max_size = 8M
; 允许上传
file_uploads = On
; 临时目录
upload_tmp_dir = /tmp
6. 安全注意事项
1)不要用原文件名
- 防止覆盖文件和路径穿越
- 用 uniqid 生成新文件名
2)验证文件类型
- 不要只看扩展名
- 用 finfo 检查 MIME 类型
$finfo = new Finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
3)上传目录不可执行
- 上传目录关闭 PHP 执行
- 防止上传木马
4)重命名文件
- 不要保留用户上传的文件名
- 防止覆盖和目录穿越攻击
7. 常见问题
1)上传失败,$_FILES 为空
- 检查表单是否有 enctype
- 检查 post_max_size 够不够大
2)move_uploaded_file 失败
- 检查目录权限
- 检查目录是否存在
3)大文件上传超时
- 修改 max_execution_time
- 修改 memory_limit
总结
PHP 文件上传要点:
- 表单必须加 enctype
- 用 $_FILES 接收文件
- 检查错误码和文件大小
- 验证文件类型
- 重命名文件防止安全问题
- 上传目录关闭执行权限
- 注意 php.ini 配置
做上传功能,安全永远第一位。
Nginx 反向代理原理
Nginx 反向代理原理
反向代理是 Nginx 最常用的功能,搞懂原理很有必要。
1. 什么是反向代理
正向代理:代理客户端,比如翻墙工具。
反向代理:代理服务器,客户端不知道真实服务器。
用户请求 Nginx,Nginx 再转发给后端服务器,用户以为自己直接访问的就是 Nginx。
2. 为什么用反向代理
1)隐藏真实服务器
- 后端服务器 IP 不暴露
- 更安全
2)负载均衡
- 多个后端服务器分摊请求
- 提高并发能力
3)统一入口
- 多个服务用一个域名
- 不同路径转发到不同服务
4)SSL 卸载
- HTTPS 证书在 Nginx 配置
- 后端用 HTTP 就行
5)缓存静态内容
- Nginx 直接返回静态资源
- 减轻后端压力
3. 基本配置
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
4. 常用代理头
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;
后端通过这些头获取真实客户端信息。
5. 路径转发示例
# 根路径转发到PHP服务
location / {
proxy_pass http://127.0.0.1:8080;
}
# /api 转发到后端接口
location /api/ {
proxy_pass http://127.0.0.1:3000/;
}
# /static 直接读本地文件
location /static/ {
root /var/www;
}
6. 超时设置
# 连接超时
proxy_connect_timeout 60s;
# 读取超时
proxy_read_timeout 60s;
# 发送超时
proxy_send_timeout 60s;
7. 常见问题
1)后端获取不到真实IP
- 检查 X-Real-IP 头是否设置
- 后端代码是否读取这个头
2)WebSocket 连接失败
- 需要添加 Upgrade 头
- 配置 HTTP/1.1
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
3)大文件上传失败
- 修改 client_max_body_size
- 增加 proxy_read_timeout
总结
Nginx 反向代理要点:
- 代理服务器,隐藏真实后端
- 实现负载均衡、统一入口
- 配置 proxy_pass 转发
- 记得设置常用代理头
- 后端通过请求头获取真实客户端信息
- 注意超时和上传大小限制
- WebSocket 需要特殊配置
做 Web 开发,反向代理是必备技能。
Redis 过期策略详解
Redis 过期策略详解
Redis 设置了过期时间后,怎么删除过期的 key?这就涉及过期策略。
1. 设置过期时间
# 设置过期时间(秒)
set name "张三" ex 60
# 给已有key设置过期时间
expire name 60
# 查看剩余时间
ttl name
# 移除过期时间
persist name
2. 两种过期策略
1)惰性删除
- 等到访问这个 key 的时候才检查是否过期
- 过期了就删除,返回 nil
- 优点:对 CPU 友好
- 缺点:对内存不友好,很多过期 key 没被访问就占着内存
2)定期删除
- 每隔一段时间,随机检查一些 key
- 发现过期的就删除
- 优点:定期清理,控制内存
- 缺点:随机抽查可能漏
Redis 实际是两种策略结合用。
3. 内存淘汰策略
当内存满了,写入新数据时怎么办?
# 查看当前淘汰策略
config get maxmemory-policy
常用淘汰策略:
- noeviction: 不淘汰,写入报错(默认)
- allkeys-lru: 所有 key 中淘汰最久没用的
- allkeys-random: 所有 key 中随机淘汰
- volatile-lru: 只在设置了过期时间的 key 中淘汰最久没用的
- volatile-random: 只在设置了过期时间的 key 中随机淘汰
- volatile-ttl: 淘汰即将过期的 key
生产推荐:allkeys-lru
4. LRU 和 LFU
- LRU(最近最少使用): 淘汰最久没访问的
- LFU(最不经常使用): 淘汰访问次数最少的
Redis 4.0 开始支持 LFU。
5. 实际使用建议
-
设置合理的 maxmemory
- 不要让 Redis 把内存占满
- 一般设为服务器内存的 70%
-
选择合适的淘汰策略
- 纯缓存场景:allkeys-lru
- 有持久化数据:volatile-lru
-
key 一定要设过期时间
- 重要数据不要设过期
- 缓存数据都要设过期时间
-
监控内存使用
- 用 info memory 查看
- 关注 used_memory 指标
6. 查看内存信息
# 查看内存使用
info memory
# 查看key数量
dbsize
# 查看大key
--bigkeys
总结
Redis 过期策略要点:
- 惰性删除:访问时才删
- 定期删除:定期抽查清理
- 两种策略结合使用
- 内存满了要配置淘汰策略
- 生产推荐 allkeys-lru
- 缓存数据都要设过期时间
- 监控内存使用情况
搞懂过期策略,Redis 用起来才不踩坑。
MySQL 字符集设置
MySQL 字符集设置
中文乱码是数据库开发常见问题,字符集设置一定要搞对。
1. 常用字符集
- utf8: 最多3字节,不支持 emoji
- utf8mb4: 最多4字节,支持 emoji,推荐用这个
- gbk: 中文编码,不推荐
2. 查看当前字符集
-- 查看数据库字符集
SHOW VARIABLES LIKE 'character_set%';
-- 查看排序规则
SHOW VARIABLES LIKE 'collation%';
3. 创建数据库时指定
CREATE DATABASE mydb
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
4. 创建表时指定
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5. 修改已有表字符集
-- 修改表字符集
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4;
-- 修改字段字符集
ALTER TABLE users MODIFY name VARCHAR(50) CHARACTER SET utf8mb4;
6. 连接时设置字符集
PHP 连接数据库后一定要设置:
$conn = mysqli_connect('localhost', 'user', 'pass', 'db');
mysqli_set_charset($conn, 'utf8mb4');
或者 SQL 语句:
SET NAMES utf8mb4;
7. 中文乱码排查
出现中文乱码,按顺序检查:
- 数据库字符集是不是 utf8mb4
- 表字符集是不是 utf8mb4
- 字段字符集是不是 utf8mb4
- 连接字符集是不是 utf8mb4
- 前端页面编码是不是 utf-8
8. 常用排序规则
- utf8mb4_general_ci: 不区分大小写,常用
- utf8mb4_unicode_ci: 支持 Unicode,更准确
- utf8mb4_bin: 区分大小写,按二进制比较
总结
MySQL 字符集设置要点:
- 统一用 utf8mb4,不要用 utf8
- 数据库、表、字段都要设置
- 连接后要设置字符集
- 中文乱码逐层排查
- 推荐 utf8mb4_general_ci 排序
字符集统一,才能避免乱码问题。
PHP 超全局变量详解
PHP 超全局变量详解
PHP 超全局变量在所有作用域都能用,写接口和表单开发天天用到。
1. 什么是超全局变量
超全局变量是 PHP 预定义的数组变量,不需要 global 声明,在函数、类、文件里都能直接访问。
2. 常用超全局变量
1)$_GET
获取 URL 查询参数。
// 访问 index.php?id=1&name=张三
$id = $_GET['id']; // 1
$name = $_GET['name']; // 张三
2)$_POST
获取表单 POST 提交的数据。
// 表单 method="post"
$username = $_POST['username'];
$password = $_POST['password'];
3)$_REQUEST
同时获取 GET、POST、COOKIE 数据。
$id = $_REQUEST['id'];
// 不推荐,安全隐患大
4)$_SESSION
会话数据,跨页面共享。
session_start();
$_SESSION['user_id'] = 1;
$_SESSION['username'] = '张三';
5)$_COOKIE
获取客户端 Cookie。
$token = $_COOKIE['token'];
6)$_SERVER
服务器和执行环境信息。
// 获取请求方法
$method = $_SERVER['REQUEST_METHOD'];
// 获取当前URL
$uri = $_SERVER['REQUEST_URI'];
// 获取客户端IP
$ip = $_SERVER['REMOTE_ADDR'];
// 获取HTTP头
$host = $_SERVER['HTTP_HOST'];
3. 文件上传变量
$_FILES
处理文件上传。
// 表单 enctype="multipart/form-data"
$file = $_FILES['upload'];
$name = $file['name']; // 文件名
$tmp = $file['tmp_name']; // 临时路径
$size = $file['size']; // 文件大小
$error = $file['error']; // 错误码
4. 注意事项
- 安全问题: 所有超全局变量都不可信,必须做过滤和验证
- isset 判断: 取值前用 isset 判断是否存在,避免报错
- 类型转换: 数字参数记得转 int
- 不要用 $_REQUEST: 安全隐患大,明确用 GET 还是 POST
5. 安全取值示例
// 安全获取GET参数
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
// 安全获取POST参数
$username = isset($_POST['username']) ? trim($_POST['username']) : '';
// 判断请求方法
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 处理POST请求
}
总结
PHP 超全局变量要点:
- $_GET:获取 URL 参数
- $_POST:获取表单数据
- $_SESSION:会话数据
- $_COOKIE:客户端 Cookie
- $_SERVER:服务器信息
- $_FILES:文件上传
- 所有外部输入都要过滤验证,不能直接用
- 取值前先 isset 判断
做 PHP 开发,超全局变量是基础中的基础。