《极客时间-Redis核心技术与实战》学习笔记

 

数据结构

  1. Redis数据类型和底层数据结构的对应关系:String -> 简单动态字符串,List -> 双向链表、压缩列表,Hash -> 压缩列表、哈希表,Sorted Set -> 压缩列表、跳表,Set -> 哈希表、整数数组。
  2. 哈希表rehash高效操作:默认使用两个全局哈希表,默认使用哈希表1,rehash时,给哈希表2分配更大的空间,把哈希表1的数据重新映射到哈希表2,释放哈希表1的空间。重新映射时,无法处理客户端请求,会造成线程阻塞,采用“渐进式rehash”解决:重新映射数据时,仍然正常处理客户端请求,每处理一个请求,从哈希表1的第一个索引位置开始,将这个索引位置上所有entries拷贝到哈希表2中,等处理下一个请求时,再顺带拷贝哈希表1的下一个索引位置的数据。在rehash被触发后,即使没有收到新请求,Redis也会定时执行一次rehash操作(定时任务)。

     

  3. rehash的条件:Redis会使用装载因子(load factor)来判断是否需要做rehash。装载因子的计算方式是,哈希表中所有entry的个数除以哈希表的哈希桶个数。Redis会根据装载因子的两种情况,来触发rehash操作:装载因子≥1,同时,哈希表被允许进行rehash;装载因子≥5。装载因子等于1说明所有键值对平均分配在哈希桶中无需rehash。在进行RDB生成和AOF重写时,哈希表的rehash是被禁止的。装载因子大于等于5时,就表明当前保存的数据量已经远远大于哈希桶的个数,哈希桶里会有大量的链式哈希存在,性能会受到严重影响,此时,就立马开始做rehash。
  4. 压缩列表:实际上类似于一个数组,表头有三个字段zlbytes、zltail、zllen,分别表示列表长度、列表尾的偏移量、列表中entry个数;表尾还有zlend,表示列表结束。

     

  5. 跳表:增加多级索引,通过索引位置的几个挑战,实现数据的快速定位。

  6. 数据结构的时间复杂度:

  7. 单线程快的原因:高效的数据结构、IO多路复用。

AOF

  1. WAL:Write Ahead Log,数据库写前日志,AOF:写后日志,先执行命令,再写日志。写日志时无需校验命令是否正确。
  2. AOF日志格式:*3:命令有3个部分,$+数字:命令或键值有多少个字节。

    《极客时间-Redis核心技术与实战》学习笔记_第1张图片

  3. AOF三种写回策略:Always:同步写回,每个写命令执行完,立马同步地将日志写回磁盘;Everysec:每秒写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区的内容写入磁盘;No:操作系统控制的写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
  4. AOF重写:一个拷贝,两处日志。“一个拷贝”就是指,每次执行重写时,主线程fork出后台的bgrewriteaof子进程。此时,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。第一处日志就是指正在使用的AOF日志,Redis会把这个操作写到它的缓冲区。这样一来,即使宕机了,这个AOF日志的操作仍然是齐全的,可以用于恢复。第二处日志,就是指新的AOF重写日志。这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的AOF文件,以保证数据库最新状态的记录。此时,我们就可以用新的AOF文件替代旧文件了。说是重写,其实就是遍历数据库的所有数据,变成写命令写入AOF文件,对于value较多的key,会变成多条命令写入AOF文件。

《极客时间-Redis核心技术与实战》学习笔记_第2张图片

RDB

  1. 全量快照,两个命令生成RDB文件,save:在主线程执行,会导致阻塞,bgsave:创建一个子进程,专门用于写入RDB文件,避免主线程的阻塞。
  2. 写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。bgsave子进程由主线程fork生成的,可以共享主线程的素有内存数据。bgsave子进程运行后,开始读取主线程的内存数据,并写入RDB文件。主线程的读操作,正常执行。写操作时,主线程要修改的数据会被复制一份,生成该数据的副本,bgsave子进程会把这个副本数据写入RDB文件,主线程直接修改原来的数据。

    《极客时间-Redis核心技术与实战》学习笔记_第3张图片

  3. 增量快照:做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。我们需要记住哪些数据被修改了,它需要我们使用额外的元数据信息去记录哪些数据被修改了,这会带来额外的空间开销问题。

  4. Redis 4.0中提出了一个混合使用AOF日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用AOF日志记录这期间的所有命令操作。快照不用很频繁地执行,避免了频繁fork对主线程的影响。AOF日志也只用记录两次快照间的操作,不需要记录所有操作了,就不会出现文件过大的情况了,也可以避免重写开销。

主从同步

  1. replica of(redis5.0之前是slave of)命令形成主从库关系。
  2. 第一次同步为全量同步,分为三个阶段:
    1. 主从库建立连接,协商同步。从库给主库发送psync命令,包含主库runID和复制进度offset两个参数。(runID,是每个Redis实例启动时都会自动生成的一个随机ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的runID,所以将runID设为“?”;offset,此时设为-1,表示第一次复制。)主库收到psync命令后,会用FULLRESYNC响应命令带上两个参数:主库runID和主库目前的复制进度offset,返回给从库。从库收到响应后,会记录下这两个参数。
    2. 主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。主库执行bgsave命令,生成RDB文件,接着将文件发给从库。从库接收到RDB文件后,会先清空当前数据库,然后加载RDB文件。
    3. 主库会把第二阶段执行过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成RDB文件发送后,就会把此时replication buffer中的修改操作发给从库,从库再重新执行这些操作。

       

  3. 从库数量过多会导致主库忙于fork子进程生成RDB文件,进行数据全量同步。fork这个操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求速度变慢。此外,传输RDB文件也会占用主库的网络带宽,同样会给主库的资源使用带来压力。采用“主-从-从”模式,在部署主从集群的时候,可以手动选择一个从库(比如选择内存资源配置较高的从库),用于级联其他的从库。然后,我们可以再选择一些从库(例如三分之一的从库),在这些从库上执行如下命令,让它们和刚才所选的从库,建立起主从关系。
  4. 在Redis 2.8之前,如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量复制,开销非常大。从Redis 2.8开始,网络断了之后,主从库会采用增量复制的方式继续同步。
  5. 当主从库断连后,主库会把断连期间收到的写操作命令,写入replication buffer,同时也会把这些操作命令也写入repl_backlog_buffer这个缓冲区。repl_backlog_buffer是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置。主从库的连接恢复之后,从库首先会给主库发送psync命令,并把自己当前的slave_repl_offset发给主库,主库会判断自己的master_repl_offset和slave_repl_offset之间的差距。主库只用把master_repl_offset和slave_repl_offset之间的命令操作同步给从库就行。因为repl_backlog_buffer是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。避免的办法就是根据实际情况增大缓冲区的大小。

哨兵机制

  1. 主从模式中主库挂了,集群无法执行写入操作,哨兵机制实现主从库自动切换。
  2. 哨兵的三个任务:监控、选主、通知。
    1. 监控:周期性给所有主从库发送PING命令,检测是否在线。从库的线下影响不大,只要发现从库没有响应,直接标记为客观下线。主库挂需要重新选主,为了避免对集群的影响,需要考虑误判的情况。分为主观下线和客观下线两种状态,主观下线:当有一个哨兵发出的PING命令主库没有响应,此时标记为主观下线,因为有可能是哨兵自己的原因。当N/2+1个哨兵都标记主库为主观下线,此时主库标记为客观下线,认为主库已经挂了。
    2. 选主:按照一定规则给从库打分,分高的作为主库。
      1. 首先判断从库的网络质量:down-after-milliseconds是我们认定主从库断连的最大连接超时时间。如果在down-after-milliseconds毫秒内,主从节点都没有通过网络联系上,我们就可以认为主从节点断连了。如果发生断连的次数超过了10次,就说明这个从库的网络状况不好,不适合作为新主库。
      2. slave-priority配置的优先级高的得分高。
      3. 和旧主库同步程度最接近的从库得分高。(slave_repl_offset需要最接近master_repl_offset)
      4. 实例ID小的从库得分高。
    3. 通知:在执行通知任务时,哨兵会把新主库的连接信息发给其他从库,让它们执行replicaof命令,和新主库建立连接,并进行数据复制。同时,哨兵会把新主库的连接信息通知给客户端,让它们把请求操作发到新主库上。
  3. 通过pub/sub机制,哨兵在主库“__sentinel__:hello”频道进行通信,哨兵之间可以组成集群,同时,哨兵又通过INFO命令,从主库获得了从库连接信息,也能和从库建立连接,并进行监控了。客户端也通过pub/sub机制监听不同频道的数据实现客户端主从地址的切换。

  4. 任何一个哨兵实例只要自身判断主库“主观下线”后,就会给其他实例发送is-master-down-by-addr命令。接着,其他实例会根据自己和主库的连接情况,做出Y或N的响应,Y相当于赞成票,N相当于反对票。一个哨兵获得了仲裁所需的赞成票数(quorum配置项)后,就可以标记主库为“客观下线”。此时,这个哨兵就可以再给其他哨兵发送命令,表明希望由自己来执行主从切换,并让所有其他哨兵进行投票。这个投票过程称为“Leader选举”。任何一个想成为Leader的哨兵,要满足两个条件:第一,拿到半数以上的赞成票;第二,拿到的票数同时还需要大于等于哨兵配置文件中的quorum值。

Redis Cluster集群

  1. Redis数据量大了之后,单纯靠增加内存已经不行,需要把数据切片存储。Redis Cluster采用哈希槽管理数据切片和实例的关系。一个集群有16384个哈希槽,根据key按照CRC16计算hash值,再对16384取模得到哈希槽。默认情况下使用cluster create命令会均匀的在集群实例上分配哈希槽,也可以通过cluster meet命令手动建立实例间的连接,形成集群,再使用cluster addslots命令,指定每个实例上的哈希槽个数。在手动分配哈希槽时,需要把16384个槽都分配完,否则Redis集群无法正常工作
  2. 客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端。但是,在集群刚刚创建的时候,每个实例只知道自己被分配了哪些哈希槽,是不知道其他实例拥有的哈希槽信息的。Redis实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键所对应的哈希槽,然后就可以给相应的实例发送请求了。
  3. 集群增加/删除示例时会造成哈希槽的调整,Redis Cluster提供“重定向”机制,客户端给一个实例发送数据读写操作时,这个实例上并没有相应的数据,此时会返回MOVED命令响应(MOVED 13320 172.16.19.5:6379),包含新实例的地址,此时客户端要再给一个新实例发送操作命令,同时也会更新客户端本地缓存的哈希槽和实例的关系。
  4. 如果数据还没有迁移完成,此时客户端就会收到一条ASK报错信息((error) ASK 13320 172.16.19.5:6379),表示,客户端请求的键值对所在的哈希槽13320,在172.16.19.5这个实例上,但是这个哈希槽正在迁移。此时,客户端需要先给172.16.19.5这个实例发送一个ASKING命令。这个命令的意思是,让这个实例允许执行客户端接下来发送的命令。然后,客户端再向这个实例发送GET命令,以读取数据。ASK命令并不会更新客户端缓存的哈希槽分配信息。

    《极客时间-Redis核心技术与实战》学习笔记_第4张图片

字符串存储

《极客时间-Redis核心技术与实战》学习笔记_第5张图片

Redis性能

  1. Redis阻塞操作:集合全量查询和聚合操作、bigkey删除、清空数据库、AOF日志同步写、从库加载RDB文件。除了“集合全量查询和聚合操作”和“从库加载RDB文件”,其他三个阻塞点涉及的操作都不在关键路径上,可以使用Redis的异步子线程机制来实现。
  2. Redis主线程启动后,会使用操作系统提供的pthread_create函数创建子线程。主线程通过一个链表形式的任务队列和子线程进行交互。主线程会把需要异步执行的操作封装成一个任务,放入到任务队列中,然后给客户端返回一个完成信息,表明操作已经完成,后台子线程从任务队列中读取任务后,才开始实际操作。如惰性删除。键值对删除UNLINK命令、清空数据库FLUSHDB ASYNC/FLUSHALL AYSNC。
  3. 在CPU多核的场景下,用taskset命令把Redis实例和一个核绑定,可以减少Redis实例在不同核上被来回调度执行的开销,避免较高的尾延迟;在多CPU的NUMA架构下,如果你对网络中断程序做了绑核操作,建议你同时把Redis实例和网络中断程序绑在同一个CPU Socket的不同核上,这样可以避免Redis跨Socket访问内存中的网络数据的时间开销。
  4. 当把Redis实例绑到一个CPU逻辑核上时,就会导致子进程、后台线程和Redis主线程竞争CPU资源,一旦子进程或后台线程占用CPU时,主线程就会被阻塞,导致Redis请求延迟增加。在给Redis实例绑核时,不要把一个实例和一个逻辑核绑定,而要和一个物理核绑定,也就是说,把一个物理核的2个逻辑核都用上。
  5. Redis变慢:
    1. redis-cli命令提供了–intrinsic-latency选项,可以用来监测和统计测试期间内的最大延迟,这个延迟可以作为Redis的基线性能。超过极限性能可以认为是变慢了。
    2. 通过Redis日志,或者是latency monitor工具,查询是否存在变慢的请求。
    3. 过期key:是否存在大量过期时间一致的key,同时失效。
    4. fsync后台子线程和AOF重写子进程的存在,主IO线程一般不会被阻塞。但是,如果在重写日志时,AOF重写子进程的写入量比较大,fsync线程也会被阻塞,进而阻塞主线程,导致延迟增加。
    5. 如果没有控制好内存的使用量,或者和其他内存需求大的应用一起运行了,就可能受到swap的影响,而导致性能变慢。
    6. 内存大页机制(Transparent Huge Page, THP),也会影响Redis性能。写时复制机制需要拷贝一份数据(一个大页)。
  6. 缓冲区溢出的三个原因:命令数据发送过快过大;命令数据处理较慢;缓冲区空间过小。
  7. Redis缓存淘汰策略:noevction、volatile-lru、volatile-lfu、volatile-ttl、volatile-random、allkeys-lru、allkeys-lfu、allkeys-random八种。
    1. noevction不进行数据淘汰。
    2. volatile-ttl在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
    3. volatile-random就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
    4. volatile-lru会使用LRU算法筛选设置了过期时间的键值对。
    5. volatile-lfu会使用LFU算法选择设置了过期时间的键值对。
    6. allkeys-random策略,从所有键值对中随机选择并删除数据;
    7. allkeys-lru策略,使用LRU算法在所有数据中进行筛选。
    8. allkeys-lfu策略,使用LFU算法在所有数据中进行筛选。
  8. Redis中的LRU策略,会在每个数据对应的RedisObject结构体中设置一个lru字段,用来记录数据的访问时间戳。在进行数据淘汰时,LRU策略会在候选数据集中淘汰掉lru字段值最小的数据(也就是访问时间最久的数据)。
  9. LRU策略,Redis只是把原来24bit大小的lru字段,又进一步拆分成了16bit的ldt和8bit的counter,分别用来表示数据的访问时间戳和访问次数。为了避开8bit最大只能记录255的限制,LFU策略设计使用非线性增长的计数器来表示数据的访问次数。
  10.  

Redis6.0新特性

《极客时间-Redis核心技术与实战》学习笔记_第6张图片

 

 

你可能感兴趣的:(读书笔记,Redis)