美文网首页Redis学习
第12章 Redis开发运维的陷阱

第12章 Redis开发运维的陷阱

作者: 岁月如歌2020 | 来源:发表于2020-03-22 23:32 被阅读0次
    [图片上传中...(image.png-caa867-1584891059523-0)]
    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数据源误删数据恢复步骤演练:
    1. 防止AOF重写
    config set auto_aof_rewrite_percentage 1000
    config set auto_aof_rewrite_min_size 100,000,000,000
    
    1. 去掉主从AOF文件中的flush相关内容
    *1
    $8
    flushall
    
    1. 重启Redis主节点服务器, 恢复数据
    • 最佳实践

    提前准备好恢复数据的shell脚本, 故障不等人!!!

    12.3 安全的Redis如何设计

    12.3.1 密码机制

    1. 简单的密码机制
    # 服务端
    redis-server --requirepass hello_redis_devops
    
    # 客户端使用-a
    redis-cli -a hello_redis_devops
    
    # 客户端使用auth
    redis-cli
    auth hello_redis_devops
    
    1. 最佳实践
    • 密码足够复杂(64个字节以上)
    • 主从结构,从节点配置要加上masterauth配置, 对应主节点的密码
    • auth明文并不十分可靠,需要其他方案的配合

    12.3.2 伪装危险命令

    1. 引入rename-command
    • Redis危险命令:
    keys: 键值多时易阻塞
    flushall/flushdb: 容易误删数据
    save: 键值多时易阻塞
    debug: debug reload时会重启Redis
    config: 应交给管理员
    shutdown: 关闭Redis
    
    • 重命名危险指令
    rename-command flushall afsdafadfad
    
    1. rename-command的副作用
    • 管理员需要修改客户端代码, 例如jedis.flushall()操作内部使用了flushall命令, 改名后代码也要修改
    • 不支持config set
    • 若AOF和RDB包含了rename-command前的指令,Redis将无法启动
    • Redis源码中有些命令是写死的,rename-command后可能导致Redis无法工作
    1. 最佳实践
    • 对于一些危险的指令,不管内网还是外网,一律使用rename-command配置
    • 第一次配置Redis的时候一次性设置好rename-command,以后不再修改
    • 保持主从一致

    12.3.3 防火墙

    比如Linux系统只允许80端口对外

    12.3.4 bind

    1. 简介

    bind指定了Redis和哪块网卡绑定

    1. 举例
    内网地址: 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的危害

    1. 内存分布不均衡
    2. 超时阻塞
    3. 网络拥塞

    12.4.2 如何发现

    1. redis-cli --bigkeys
    2. 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 机器

    1. 简介

    对机器上所有Redis端口的TCP数据包抓包分析

    1. 现有工具

    ELK体系下的packetbeat插件

    12.5.5 对比分析

    方案 优点 缺点
    客户端 实现简单 1. 内存泄露隐患 2. 维护成本高 3. 只能统计单个客户端
    代理 实现最方便 增加代理端的开发部署成本
    服务端 实现简单 1. monitor本身的成本和危害性,只能短时间使用 2. 只能统计单个Redis节点
    机器 对于客户端和服务端无侵入和影响 需要专业的运维团队开发,增加了机器部署成本

    12.6 本章小结

    1. Linux相关优化:
    • vm.overcommit_memory建议为1
    • Linux>3.5, vm.swappiness建议为1,否则建议为0
    • THP建议关闭掉
    • 可以为Redis进程设置oom_adj, 减少Redis被OOM killer杀掉的概率,但不要过度依赖此特性
    • 建议对Redis所有节点所在机器使用NTP服务
    • 设置合理的ulimit保证网络连接正常
    • 设置合理的tcp-backlog参数
    1. 理解Redis的持久化有助于解决flush操作之后的数据快速恢复问题
    2. Redis安全建议:
    • 根据具体网络环境决定是否设置Redis密码
    • rename-command可以伪装命令,但是要注意成本
    • 合理的防火墙是防止攻击的利器
    • bind可以将Redis的访问绑定到指定网卡上
    • 定期备份数据应该作为习惯性操作
    • 可以适当错开Redis默认端口启动
    • 使用非root用户启动Redis
    1. bigKey的危害不容忽视: 数据倾斜、超时阻塞、网络拥塞,可能是Redis生产环境的一颗定时炸弹,删除bigKey时通常使用渐进式遍历的方式,防止出现Redis阻塞的情况
    2. 通过客户端、代理、monitor、机器抓包四种方式找到热点key, 这几种方式各具优势,具体使用哪种要根据当前场景来决定。

    相关文章

      网友评论

        本文标题:第12章 Redis开发运维的陷阱

        本文链接:https://www.haomeiwen.com/subject/kjgbyhtx.html