Redis笔记II | 青训营笔记

51 阅读7分钟
  1. Redis线程模型?

    • 多个socket套接字
    • IO多路复用程序
    • 文件事件分派器
    • 事件处理器(命令请求、命令回复、连接应答处理器)

    img

    多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。

    多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,然后程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。
    
    这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个客户端的网络IO连接请求(尽量减少网络 IO 的时间消耗)
    

Redis底层数据结构

redisObject:

  • redisObject就是Redis对外暴露的第一层面的数据结构:string, list, hash, set, sorted set,而每一种数据结构的底层实现所对应的是哪些第二层面的数据结构(dict, sds, ziplist, quicklist, skiplist等),则通过不同的encoding来区分。可以说,robj是联结两个层面的数据结构的桥梁。
  • 为多种数据类型提供一种统一的表示方式。
  • 允许同一类型的数据采用不同的内部表示,从而在某些情况下尽量节省内存。
  • 支持对象共享和引用计数。当对象被共享的时候,只占用一份内存拷贝,进一步节省内存。

String: 动态字符串sds long

Hash: 压缩列表ziplist 字典dict

List: 压缩列表ziplist 双向循环链表linkedlist quicklist【3.2后】

Set: 整数集合intset 字典dict

Sorted Set: 压缩列表zipllist 跳表skiplist


简单动态字符串sds:

结构:len + free(未使用字节的数量) + buf[]

  • 可以动态扩展内存
  • 二进制安全,可以存储任意二进制数据
  • 采用预分配冗余空间的方式来减少内存的频繁分配
  • 兼容部分C字符串函数

字典dict:

使用哈希表作为底层实现【一个哈希表用于存储数据,一个用于rehash】

  • 哈希算法:使用MurmurHash算法计算法哈希
  • 哈希冲突:利用拉链法解决哈希冲突的问题
  • 重新哈希:扩容和缩容的时候会引发rehash。为了避免rehash时单个请求的响应时间剧烈增加,使用渐进式rehash的方法,将重哈希的操作分散到对于dict的各个增删改查的操作中去。

压缩列表ziplist:

ziplist使用一块连续的内存空间来存储数据,并采用可变长的编码方式,支持不同类型和大小的数据的存储,更加节省内存,而且数据存储在一片连续的内存空间,读取的效率也非常高。

  • 节约内存而开发的顺序形数据结构
  • 包含多个节点,每个节点可以保存一个字节数组或者整数值
  • 添加和删除节点时,会发生连锁更新的操作

整数集合intset:【整数,且元素数量不多】

  • 整数集合底层采用数组实现,有序、无重复保存元素。
  • 有序,可以利用二分查找快速地判断一个元素是否属于这个集合。
  • 升级操作为整数集合带来了操作上的灵活性,还可以尽可能的节约内存。
  • 只能升级,不能降级。

跳表skiplist:

跳表支持平均O(logN)、最坏O(N)复杂度的节点查找。层高是1-32的随机数。

  • 跳表结合了链表和二分查找的思想
  • 由原始链表和一些通过“跳跃”生成的链表组成
  • 第0层是原始链表,越上层“跳跃”的越高,元素越少
  • 上层链表是下层链表的子序列
  • 查找时从顶层向下,不断缩小搜索范围

Redis过期键的删除策略

  1. 键的过期删除策略?

    常见的过期删除策略是惰性删除定期删除定时删除

    • 惰性删除:只有访问这个键时才会检查它是否过期,如果过期则清除。**优点:**最大化地节约CPU资源。**缺点:**如果大量过期键没有被访问,会一直占用大量内存。
    • 定时删除:为每个设置过期时间的key都创造一个定时器,到了过期时间就清除。**优点:**该策略可以立即清除过期的键。**缺点:**会占用大量的CPU资源去处理过期的数据。
    • 定期删除:每隔一段时间就对一些键进行检查,删除其中过期的键。该策略是惰性删除和定时删除的一个折中,既避免了占用大量CPU资源又避免了出现大量过期键不被清除占用内存的情况。

    Redis使用的策略:惰性删除和定期删除相结合。

    • 惰性删除:所有读写数据库的命令在执行前都要调用expireIfNeeded函数对输入键进行过期检查,如果过期则删除否则不做任何动作。
    • 定期删除:redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果有过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载。
  2. Redis的内存淘汰机制是什么样的?

    Redis是基于内存的,所以容量肯定是有限的,有效的内存淘汰机制对Redis是非常重要的。

    当存入的数据超过Redis最大允许内存后,会触发Redis的内存淘汰策略。在Redis4.0前一共有6种淘汰策略。

    • **volatile-lru:**当Redis内存不足时,会在设置了过期时间的键中使用LRU算法移除那些最少使用的键。

    • **volatile-ttl:**从设置了过期时间的键中移除将要过期的。

    • **volatile-random:**从设置了过期时间的键中随机淘汰一些键。

    • **allkeys-lru:**当内存空间不足时,根据LRU算法移除一些键。

    • **allkeys-random:**当内存空间不足时,随机移除某些键。

    • **noeviction:**不淘汰策略,当内存空间不足时,新的写入操作会直接报错。

      前三个是在设置了过期时间的键的空间进行移除,后三个是在全局的空间进行移除。

    在Redis4.0后可以增加两个

    • **volatile-lfu:**从设置过期时间的键中移除一些最不经常使用的键(LFU算法:Least Frequently Used))
    • **allkeys-lfu:**当内存不足时,从所有的键中移除一些最不经常使用的键。

    LFU (Least Frequently Used) :最近最不频繁使用,跟使用的次数有关,淘汰使用次数最少的。

    LRU (Least Recently Used):最近最少使用,跟使用的最后一次时间有关,淘汰最近使用时间离现在最久的。

    如果业务数据中有明显的冷热数据区分,建议使用 allkeys-lru 策略。

    如果业务应用中的数据访问频率相差不大,没有明显的冷热数据区分,建议使用 allkeys-random 策略,随机选择淘汰的数据就行。

  3. 为什么删除数据后,内存占用依然很高?

    1、Redis删除数据内存仍然占用高是因为存在内存碎片

    2、Redis内存碎片的由自身的 内存分配策略 和 键值对的大幅度修改、删除造成的。

    3、可以使用INFO memory命令查看内存的碎片率。

    4、可以通过memory purge主动的方式或者开启activedefrag被动的方式清理内存碎片。