redis开发与运维-阻塞、内存

Posted on By xqw

第七章 阻塞

1) 客户端最先感知阻塞等Redis超时行为, 加入日志监控报警工具可快速定位阻塞问题, 同时需要对Redis进程和机器做全面监控。
2) 阻塞的内在原因: 确认主线程是否存在阻塞, 检查慢查询等信息,发现不合理使用API或数据结构的情况, 如keys、 sort、 hgetall等。 关注CPU使用率防止单核跑满。 当硬盘IO资源紧张时, AOF追加也会阻塞主线程。
3) 阻塞的外在原因: 从CPU竞争、 内存交换、 网络问题等方面入手排查是否因为系统层面问题引起阻塞

第八章 内存

内存使用统计


需要重点关注的指标有: used_memory_rss和used_memory以及它们的比值mem_fragmentation_ratio。
mem_fragmentation_ratio > 1 : 多余内存被内存碎片所消耗,若比例大说明碎片率严重
mem_fragmentation_ratio < 1 : 多余数据通过硬盘做内存交换,若比例小可能性差

内存消耗划分

used_memory:自身内存 对象内存 缓冲内存 used_memory_rss:used_memory + 内存碎片

参数maxmemory最大可用内存,限制used_memory,config set maxmemory 6GB
实际可能会超过该值,若used_memory超出maxmemory,使用相应内存溢出控制策略

内存回收策略

1.删除过期键对象

  • 惰性删除,客户端访问时,判断该key是否过期,若过期则删除

  • 定时器删除,弥补惰性删除 随机抽查20个key,若过期的大于25%,删除掉过期的,继续抽查直到小于25%或者超过了执行时间

    ratio = 100;
    time = 0;
    overTime = 25ms;(慢模式25ms,快模式1ms)
    while (ratio > 25 || time < overTime) {
      ratio, time = delete();
    }
    

    如果删除定时器超时了,那么后面每次触发redis事件前都会以快模式运行一遍

2.内存溢出控制策略

当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。 具体策略受maxmemory-policy参数控制, Redis支持6种策略, 如下所示:
1) noeviction: 默认策略, 不会删除任何数据, 拒绝所有写入操作并返 回客户端错误信息( error) OOM command not allowed when used memory, 此 时Redis只响应读操作。
2) volatile-lru: 根据LRU算法(最近最少使用)删除设置了超时属性( expire) 的键, 直 到腾出足够空间为止。 如果没有可删除的键对象, 回退到noeviction策略。
3) volatile-ttl: 根据键值对象的ttl属性, 删除最近将要过期数据。 如果 没有, 回退到noeviction策略。
4) volatile-random: 随机删除过期键, 直到腾出足够空间为止。
5) allkeys-lru: 根据LRU算法删除键(最近最少使用), 不管数据有没有设置超时属性, 直到腾出足够空间为止。
6) allkeys-random: 随机删除所有键, 直到腾出足够空间为止。

动态配置命令:config set maxmemory-policy {policy}

内存优化

1.redisObject 对象

redisObject结构体:

typedef struct redisObject {
    //类型
    unsigned type:4;
    //编码
    unsigned encoding:4;
    //最后一次访问时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
    //引用计数
    int refcount;
    //指向底层实现数据结构的指针
    void *ptr;
} robj;
  • type : 表示当前对象使用的数据类型, Redis 主要支持5种数据类型, string,hash,list,set,zset 。 可以使用 type {key} 命令查看对象所属类型, type命令返回的是值对象的类型, 键对象都是string类型 。

  • encoding : 表示redis内部编码, encoding在redis内部使用, 表示当前对象采用哪种数据结构实现。

  • lru : 记录对象最后一次被访问的时间, 当配置了 maxmempry 和 maxmemory-policy = *-lru 时, 用于辅助 LRU 算法删除键数据。 可以使用 object idletime {key} 命令在不更新lru字段情况下查看当前键的空闲时间。 可以使用 scan + object idletime key 批量查询长时间未被访问的键, 清理以降低内存占用。

  • refcount : 记录当前对象被引用的次数, 用于通过引用次数回收内存, 当refcount=0 时,可以安全回收当前对象空间。 可以使用 object refcount {key} 获取当前对象引用。

  • *ptr : 与对象的数据内容有关, 如果是整数,直接存储数据;否则表示指向数据的指针

2.缩减键值对象

key长度: 在完整描述业务情况下, 键值越短越好。 key都是字符串类型, 建议长度控制在39字节以内。

value长度: 值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入redis。 首先应精简业务对象,去掉不必要的属性避免存储无效数据。 其次在序列化工具上,选择更高效的序列化工具来降低字节数组大小。

3.共享对象池

共享对象池是指Redis内部维护[0-9999]的整数对象池。 创建大量的整数类型redisObject存在内存开销, 每个redisObject内部至少占16字节,甚至超过了整数自身空间消耗。 所以redis内存维护一个[0-9999]的整数对象池,用于节约内存。

整数对象池在redis中通过变量 REDIS_SHARED_INTEGERS 定义,不能通过配置修改, 可以通过 object refcount 命令查看对象的引用计数来验证是否启用整数对象池。

之所以只有整数对象池, 首先因为整数对象池福永的几率最大, 其次对象共享需要判断相等性, 而整数比较算法时间复杂度为O(1),只保留一万个整数为了防止对象池浪费。

当设置了 maxmemory 并且用LRU相关淘汰策略时, Redis禁止使用共享对象池,因为对象共享意味着lru字段也会共享, LRU淘汰策略无法获取每个对象的最后访问时间。

4. 字符串优化

  • 字符串结构
    struct sdshdr {
      //记录buf数组中已使用字节的数量
      //等于SDS所保存字符串的长度
      int len;  
      //记录buf数组中未使用字节的数量
      int free;
      //字节数组,用于保存字符串
      char buf[];
    };
    //特点:
    //O(1) 时间复杂度获取字符串长度、已用长度、未用长度;
    //可用于保存字节数组,支持安全的二进制数据存储;
    //内部实现空间预分配机制,降低内存再分配次数;
    //惰性删除机制,字符串缩减后空间不释放,作为预分配空间保留。
    
  • 预分配机制

    预分配规则如下:
    1) 第一次创建len属性等于数据实际大小, free等于0, 不做预分配。
    2) 修改后如果已有free空间不够且数据小于1M, 每次预分配一倍容 量。 如原有len=60byte, free=0, 再追加60byte, 预分配120byte, 总占用空 间: 60byte+60byte+120byte+1byte。
    3) 修改后如果已有free空间不够且数据大于1MB, 每次预分配1MB数 据。 如原有len=30MB, free=0, 当再追加100byte, 预分配1MB, 总占用空 间: 1MB+100byte+1MB+1byte。

    应尽量减少字符串频繁修改操作,如append、setrange, 改为直接使用set命令修改字符串,降低预分配带来的内存浪费和内存碎片化。

  • 字符串重构

    指的是不一定把每份数据作为字符串整体存储, 像json数据可以使用hash结构(ziplist编码),使用二级结构存储也能帮我们节省内存。