费曼学习法(Redis总结)

Posted 每天告诉自己要努力

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了费曼学习法(Redis总结)相关的知识,希望对你有一定的参考价值。

5种数据类型

①string,如果value是整型,那么会用int记录,否则会用SDS动态字符串记录。redis是用C语言编写的,C语言的字符串是用/0作为结束标记,并且字符串叠加的时候可能会发生缓冲区溢出。SDS动态字符串封装了一个字符串结构体,结构体内有一个属性记录字符串的长度,用长度去记录字符串的实际长度,不再以/0作为结束。并且还有扩容机制,避免缓冲区溢出。
②list,有一个ListNode结构体,有3个指针,一个指向下一个节点,一个指向前一个节点,一个是指向value,是一个双向链表。redis还把这个ListNode再封装成一个list结构体,里面包含这个双向链表的头节点和尾节点,还有一个属性记录着双向链表中节点的个数。List链表这个数据类型在数据量较少的情况下是用会用压缩链表去存储,在最新版本用压缩链表被替换成listpack。
③hash,跟list一样在数据量小的时候会用压缩链表(最新版本已经改为listpack)。另外一个数据结构是哈希表,在不扩容的基础下,redis的哈希表是用链式法去解决哈希冲突,定义了哈希表结构体,里面有一个指针指向key,一个union联合体指向value(联合体内部多个变量共享同一个内存地址),还有一个指针指向下一个哈希表(拉链)。
redis的哈希表最巧妙的地方在于扩容机制,rehash被触发的条件是基于负载因子,这个数值是可以通过参数调整的,默认是已经有的节点数量跟哈希表容量是1比1的时候会触发扩容。redis的哈希表其实是有两个数组组成的,未发生扩容时是往数组1去进行存储数据,发生扩容时,需要把数据1的数据渐进式的迁移到数据2中。由于redis是内存数据库,假如哈希表里的数据量很大,假如一次性拷贝迁移可能会造成短时间的卡顿,影响业务的正常执行。渐进式rehash就是把这种大数据分为多次,逐渐迁移。未发生扩容时,rehashIndex = -1,发生扩容时,rehashIndex = 0,表示进入单步rehash状态。每操作一次哈希表的增删改查,都会迁移rehashIndex当前下标的数据到数组2中,并且index+1,如果遇到增操作,直接增加到数组2中,如果遇到删、改、查,都是直接先在数组1去查找,再去查找数组2。当index增长到数组1的长度相等时,表示迁移完成,数组2会代替数组1,然后原来的数组1会清空。
④set,无序不重复集合,适合用来做算交集,比如共同关注的公众号。如果数据个数小于512个的时候会使用整数集合作为存储数据的数据结构。整数集合有一个升级规则,如果新加入一个数据是比之前所有的数据都要大,那么就要按最大的这个数据在原数据的基础上进行扩容,然后把每个数据格式都改为更大的那个数据格式。
当数据量超过512的时候会采用哈希表。
⑤zset,有序集合,做排行榜。数据量较小时也是用压缩列表,当数据量大的时候会使用跳表。跳表跟B+树的思想一样,都是用空间去换去时间。跳表的底层是一个双向链表,由于链表的增加删除操作都是O(1),但是查找需要遍历,因此通过在链表的上层去建立索引,以此来优化查找的时间复杂度。跳表的设计思路是这样的,每一个节点,他的相邻上层都有二分之一的几率可以建立节点,运用随机概率去达到整体的一个平衡,让下一层是上一层的两倍节点数量,最终使得查找时间复杂度为O(logn),这种随机数的思想在很多的算法上也会采用,优点是不需要严格维护另外的限制条件参数,节省了内存消耗,并且在总体上能得到一样的预期效果。

持久化AOF、RDB

RDB是redis默认的持久化手段,RDB是二进制的全量数据文件,因此优点是恢复数据时会比较快,并且占用的空间较小,而且是紧凑形文件,不易造成内存碎片。缺点就是容易丢失数据。
rdb的持久化手段有两种,一种是主动通过save命令进行同步写RDB文件,这样会阻塞redis主线程的读写。另一种是通过fork一个子线程去异步进行rdb文件的写入。当redis执行一条写命令时,会修改内存中的数据,然后fork一个子线程,父子进程共享资源,子进程复杂把内存的所有数据进行一个全量的二进制保存,通过系统调用write写入page cache,然后进行刷盘。在子进程进行RDB文件写入的过程中,如果主线程执行了其他的写命令,那只能等下一次的RDB快照来保存了,因此如果在这个期间redis服务器宕机的话,数据就会丢失。如果rdb快照的保存执行得太频繁,会导致频繁fork和频繁的磁盘IO和上下文切换,开销比较大,但是如果不够频繁,造成数据丢失的风险又会加大。

AOF日志也是redis持久化的手段。当redis主线程进行一个写命令后,会把这个命令同时写入aof缓冲区中,调用系统调用write写入page cache,然后通过参数去控制刷盘时机。always、每秒、no,其实这个刷盘时机是redis去主动调用fsync这个系统调用函数去实现的。
AOF日志有自己独特的格式,*a $b c,a行,b个字母,单词。AOF日志记录的是执行的命令,用一种文本格式存储,因此占用的空间会比较大,并且在恢复数据的时候需要重新执行命令,比较慢。
当AOF日志的大小超过设定阈值时,会发生AOF重写。也就是把AOF日志里面的多个记录,根据key-value的形式,去重,只保留唯一的key对应的最新那条key-value记录。redis官网上把这个特点说成是优点,说可以减少aof日志的大小,但是我个人觉得这相比于RDB的二进制文件不能算得上是优点。AOF重写的流程是这样的,当主线程执行一条命令后,会同时写入AOF缓冲区和AOF重写缓冲区,并且fork一个子线程,由子线程去完成AOF日志的去重,并且把筛选过滤后的命令保存到新的AOF日志文件中,然后通过信号机制通知redis主进程。在子进程进行AOF重写的过程中,如果redis主线程还执行了其他命令,会写入aof重写缓冲区中。因此当主进程捕捉到子进程完成aof重写的通知后,会把aof重写缓冲区中的数据追加到新的aof日志文件中,并且替换掉原来的AOF日志文件。

结合二者的优缺点,redis4.0版本推出了混合持久化机制,作用于AOF重写的过程中,把子进程进行重写的这个步骤,改为子进程对内存数据进行一个RDB快照的捕捉并且保存到AOF日志中。最后的追加操作跟原来的方法一致。最终的AOF日志前半部分是RDB格式的数据,后半部分是追加的AOF格式的命令。

缓存雪崩、缓存击穿、缓存穿透

对于缓存雪崩,redis故障或者是大量数据同时过期,导致数据库的请求骤增,引起系统崩溃。
如果是因为redis宕,可采取服务熔断,后续请求直接响应错误即可。或者搭建高可用redis集群,主机故障时,从机变为主机,继续保证业务的正常运行。
如果是因为大量数据同时过期的话:①可以在设置超时时间的时候给一个随机数,避免同时过期。②可以设置默认值或者是空值,访问不到数据的时候直接返回。③双key策略,主key会超时,备key不超时,更新数据的时候一起更新。④不设置过期时间,由后台线程更新缓存。⑤互斥锁

对于缓存击穿,是因为热点数据过期导致的数据库请求骤增,可以采用互斥锁和后台线程更新机制。

对于缓存穿透,是因为缓存和数据库都没有数据,造成大量的无用请求。可以用布隆过滤器解决,布隆过滤器是用N个哈希函数和一个位图数组两部分组成的。当写入一条数据时,先跟N个哈希函数生成N个哈希值,然后N个哈希值跟位图数组的长度取模,对应的位置为1,表示数据的记录。当缓存中请求不到数据时,不着急去请求数据库,先通过布隆过滤器查找,如果布隆过滤器查找到有一个位置为0,那么就说明不在数据库中。否则就要去请求数据库查找是否存在。因为有哈希冲突的存在,所以布隆过滤器只是一个过滤的机制,并不能百分百的保证通过过滤的数据一定会存在于数据库中。

以上是关于费曼学习法(Redis总结)的主要内容,如果未能解决你的问题,请参考以下文章

费曼学习法(Redis总结)

费曼学习法

实用的费曼学习法 | 一些思考

世界上最好的学习法:费曼学习法

费曼学习法

费曼学习法——如何制定自己的规划,计划和日程