image.png
12.1 Linux配置优化
12.1.1 内存分配控制
- overcommit_memory
值 | 含义 |
---|---|
0 | 内核将检查是否有足够的可用内存(物理内存+swap),如果有,则内存申请通过;否则申请失败,并把错误返回给应用进程 |
1 | 内核允许超量使用内存直到用完为止 |
2 | 内核决不过量的使用内存,即系统整个内存地址空间不能超过swap+50%的RAM值,50%是overcommit_ratio的默认值,可修改 |
- 获取和设置
cat /proc/sys/vm/overcommit_memory
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1
- 最佳实践
- Redis设置maxmemory保证机器有20%-30%的闲置内存
- 集中化管理AOF重写和RDB的bgsave
- 设置vm.overcommit_memory=1防止极端情况下fork失败
12.1.2 swappiness
- swappiness的值越大,表明系统使用swap的概率越大
- 重要值说明
值 | 策略 |
---|---|
0 | Linux3.5以及以上:宁愿用OOM killer也不用swap; 否则反之 |
1 | Linux3.5以及以上:宁愿用swap也不用OOM killer |
60 | 默认值 |
100 | 操作系统会主动地使用swap |
- 设置
# 临时生效
echo {bestvalue} > /proc/sys/vm/swappiness
# 永久生效
echo swappiness={bestvalue} >> /etc/sysctl.conf
# 查看swap的总体情况
free -m
# 实时查看swap的使用, 看si(swap in)和so(swap out)列
vmstat 1
# 查看指定进程的swap使用, 假设进程号为986
redis-cli -h ip -p port info server | grep process_id
cat /proc/986/smaps
- 最佳实践
对于高可用的Redis集群来说,单个Redis节点死掉比阻塞更好,所以应设置swappiness=0(当Linux>=3.5)
12.1.3 THP(Transparent Huge Pages)
- 开启后会加快fork速度, 但fork操作之后,每个内存页从4KB变为2MB,会增加写期间父进程内存消耗,由于每次写命令引起的复制内存页单位放大了512倍,写操作也会变慢,建议禁用
- 禁用方法
echo never > /sys/kernel/mm/transparent_hugepage/enabled
12.1.4 OOM killer
- 简介
OOM killer会在可用内存不足时选择性地杀掉用户进程, 杀掉进程的选择依据进程权重来进行,权重越高被杀掉的概率越高
- 设置进程权重
# 设置单个Redis进程权重
echo {value} > /proc/${process_id}/oom_adj
# 设置批量Redis进程权重
for redis_pid in $(pgrep -f "redis-server")
do
echo -17 > /proc/${redis_pid}/oom_adj
done
- 最佳实践
同THP设置一样的思路,对于Redis集群来说, 宁可让Redis节点被杀掉,也不愿意Redis节点阻塞,所以一般不要依赖此项配置
12.1.5 使用NTP(Network Time Protocol)
- 简介
用来帮Redis集群节点间统一系统时钟, 一般每天/小时同步一次
0 * * * * /usr/sbin/ntpupdate ntp.xx.com > /dev/null 2>&1
12.1.6 ulimit
- 简介
系统当前用户进程的资源数(包含文件描述符合网络连接)
- 查看和设置
# 查看系统当前用户进程的资源数, 找open files参数
ulimit -a
# 设置当前用户进程的资源数,Redis建议open files设置成至少10032
ulimit -Sn {max-open-files}
12.1.7 TCP backlog
- Redis默认511,Redis的TCP backlog不能超过Linux系统的TCP backlog
- 设置Linux的TCP backlog方法
# 查看
cat /proc/sys/net/core/somaxconn
# 设置
echo 511 > /proc/sys/net/core/somaxconn
12.2 flushall/flushdb误操作快速恢复方法
12.2.1 缓存与存储
- 缓存
误操作删除数据,一般不用恢复,遇到并发量较大的情况,为了降低后端直接负载,才需要手动恢复
- 存储
需要恢复
12.2.2 借助AOF机制恢复
- 前提条件
- AOF重写尚未发生
- 紧急措施(为了阻止AOF重写发生)
- 调大auto_aof_rewrite_percentage和auto_aof_rewrite_min_size, 阻止AOF重写的发生
- 拒绝手动bgrewriteaof
- 去掉AOF日志中的flushall/flushdb相关操作指令
- 用redis-check-aof工具检验AOF文件的合法性
12.2.3 RDB有什么变化
- 前提条件
- 没有开启RDB的自动策略, 比如
save 900 1 save 300 10 save 60 10000
- 没有手动执行过save, bgsave
- 紧急措施
- 删除RDB中flushall/flushdb相关操作指令
- 后续影响
RDB文件的数据可能有一定延迟,这是跟RDB的同步频率有关的。
12.2.4 从节点有什么变化
与主节点一致。
12.2.5 快速恢复数据
- AOF数据源误删数据恢复步骤演练:
- 防止AOF重写
config set auto_aof_rewrite_percentage 1000
config set auto_aof_rewrite_min_size 100,000,000,000
- 去掉主从AOF文件中的flush相关内容
*1
$8
flushall
- 重启Redis主节点服务器, 恢复数据
- 最佳实践
提前准备好恢复数据的shell脚本, 故障不等人!!!
12.3 安全的Redis如何设计
12.3.1 密码机制
- 简单的密码机制
# 服务端
redis-server --requirepass hello_redis_devops
# 客户端使用-a
redis-cli -a hello_redis_devops
# 客户端使用auth
redis-cli
auth hello_redis_devops
- 最佳实践
- 密码足够复杂(64个字节以上)
- 主从结构,从节点配置要加上masterauth配置, 对应主节点的密码
- auth明文并不十分可靠,需要其他方案的配合
12.3.2 伪装危险命令
- 引入rename-command
- Redis危险命令:
keys: 键值多时易阻塞
flushall/flushdb: 容易误删数据
save: 键值多时易阻塞
debug: debug reload时会重启Redis
config: 应交给管理员
shutdown: 关闭Redis
- 重命名危险指令
rename-command flushall afsdafadfad
- rename-command的副作用
- 管理员需要修改客户端代码, 例如jedis.flushall()操作内部使用了flushall命令, 改名后代码也要修改
- 不支持config set
- 若AOF和RDB包含了rename-command前的指令,Redis将无法启动
- Redis源码中有些命令是写死的,rename-command后可能导致Redis无法工作
- 最佳实践
- 对于一些危险的指令,不管内网还是外网,一律使用rename-command配置
- 第一次配置Redis的时候一次性设置好rename-command,以后不再修改
- 保持主从一致
12.3.3 防火墙
比如Linux系统只允许80端口对外
12.3.4 bind
- 简介
bind指定了Redis和哪块网卡绑定
- 举例
内网地址: 10.10.xx.192
外网地址: 220.181.xx.123
回环地址: 127.0.0.1
当前Redis配置了bind 10.10.xx.192, 那么Redis访问如果通过
redis-cli -h 220.181.xx.123 -p 6379
redis-cli -h 127.0.0.1 -p 6379
就无法正常访问Redis了
12.3.5 定期备份数据
最后一招,有备份数据才是王者
12.3.6 不使用默认端口
Redis默认端口6379, 容易被别有用心的人恶意攻击
12.3.7 使用非root用户启动
root权限太大, Redis一旦失陷, 整台机器失陷
12.4 处理bigkey的方案与最佳实践
12.4.1 bigkey的危害
- 内存分布不均衡
- 超时阻塞
- 网络拥塞
12.4.2 如何发现
redis-cli --bigkeys
-
scan
+debug object key
12.4.3 如何删除
分批删除, 不要一次性删除
public void delBigHash(String bigKey) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
String cursor = "0";
while (true) {
ScanResult<Map.Entry<String, String>> scanResult = jedis.hscan(bigKey, cursor, new ScanParams().count(100));
cursor = scanResult.getStringCursor();
List<Entry<String, String>> list = scanResult.getResult();
if (list == null || list.size() == 0) {
continue;
}
String[] fields = getFieldsFrom(list);
jedis.hdel(bigKey, fields);
if (cursor.equals("0")) {
break;
}
}
jedis.del(bigKey);
}
12.4.4 最佳实践思路
- 合理的bigKey检测机制
- 开发阶段注意防止bigKey的出现
12.5 寻找热点key
12.5.1 客户端
客户端本地计数探知热点key
12.5.2 代理端
-
示意图
image.png -
开源实现
- Twemproxy
- Codis
12.5.3 Redis服务端
- 伪代码
List<String> keyList = redis.monitor(100000);
AtomicLongMap<String> ATOMIC_LONG_MAP = AtomicLongMap.create();
for (String key : keyList) {
ATOMIC_LONG_MAP.incrementAndGet(key);
}
statHotKey(ATOMIC_LONG_MAP);
- 开源实现
- Facebook的redis-faina
12.5.4 机器
- 简介
对机器上所有Redis端口的TCP数据包抓包分析
- 现有工具
ELK体系下的packetbeat插件
12.5.5 对比分析
方案 | 优点 | 缺点 |
---|---|---|
客户端 | 实现简单 | 1. 内存泄露隐患 2. 维护成本高 3. 只能统计单个客户端 |
代理 | 实现最方便 | 增加代理端的开发部署成本 |
服务端 | 实现简单 | 1. monitor本身的成本和危害性,只能短时间使用 2. 只能统计单个Redis节点 |
机器 | 对于客户端和服务端无侵入和影响 | 需要专业的运维团队开发,增加了机器部署成本 |
12.6 本章小结
- Linux相关优化:
-
vm.overcommit_memory
建议为1 - Linux>3.5, vm.swappiness建议为1,否则建议为0
- THP建议关闭掉
- 可以为Redis进程设置oom_adj, 减少Redis被OOM killer杀掉的概率,但不要过度依赖此特性
- 建议对Redis所有节点所在机器使用NTP服务
- 设置合理的ulimit保证网络连接正常
- 设置合理的tcp-backlog参数
- 理解Redis的持久化有助于解决flush操作之后的数据快速恢复问题
- Redis安全建议:
- 根据具体网络环境决定是否设置Redis密码
- rename-command可以伪装命令,但是要注意成本
- 合理的防火墙是防止攻击的利器
- bind可以将Redis的访问绑定到指定网卡上
- 定期备份数据应该作为习惯性操作
- 可以适当错开Redis默认端口启动
- 使用非root用户启动Redis
- bigKey的危害不容忽视: 数据倾斜、超时阻塞、网络拥塞,可能是Redis生产环境的一颗定时炸弹,删除bigKey时通常使用渐进式遍历的方式,防止出现Redis阻塞的情况
- 通过客户端、代理、monitor、机器抓包四种方式找到热点key, 这几种方式各具优势,具体使用哪种要根据当前场景来决定。
网友评论