《redis》之缓存

 


redis持久化,两种方式的优缺点

 

1 rdb是Redis默认的持久化方式,按照一定的时间周期策略把内存中的数据一快照的形式保存到
硬盘二进制文件中。对应产生的文件为dump.rdb。

2 AOF redis每收到一个写命令就通过write函数追加到文件的最后。当Redis重启时会重新
执行文件中的写命令在内存中重建整个数据库的内容。

 

------------------
1.RDB是一个非常紧凑 的文件,它保存了redis 在某个时间点上的数据集。适合用于进行备份和灾难恢复
2.生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,
主进程不需要进行任何磁盘IO操作。
3.RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

RDB方式数据没办法做到实时持久化/秒级持久化。
每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。
在数据集比较庞大时, fork() 可能会非常耗时。

---------------------------
1 AOF可以更好的保护数据不丢失,一般AOF会以每隔1秒进行同步
2 AOF以appen-only的模式写入,写入性能非常高。写入过程中即使出现宕机现象,也不会破坏日志文件中已经
存在的内容。
3 误删除恢复

1 对于同一份文件AOF文件比RDB数据快照要大。

AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
2 数据恢复比较慢,不适合做冷备。
3 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB

 

如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久。
AOF 将 Redis 执行的每一条命令追加到磁盘中,处理巨大的写入会降低 Redis 的性能,不知道你是否可以接受。


=======================================================


随着Redis不断接受命令,每个写命令都被添加到AOF文件,AOF文件膨胀到需要rewrite时又或者
接收到客户端的bgrewriteaof命令。

fork出一个子进程进行rewrite,而父进程继续接受命令,现在的写操作命令都会被额外添加到一个
aof_rewrite_buf_blocks缓冲中。

当子进程rewrite结束后,父进程收到子进程退出信号,把aof_rewrite_buf_blocks的缓冲添加到
rewrite后的文件中,然后切换AOF的文件fd。

rewrite任务完成,继续第二个步骤。

=====================================================

生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,
主进程不需要进行任何磁盘IO操作。RDB对redis对外提供读写服务的时候,影响很小。

在执行fork的时候操作系统(类Unix操作系统)会使用 写时复制 (copy-on-write)策略,
父子进程共享同一内存数据,当父进程要更改其中某片数据时(如执行一个写命令 ),
操作系统会将该片数据复制一份以保证子进程的数据不受影响,
所以新的RDB文件存储的是执行fork那一刻的内存数据

 

=======================================================

阿里的面试官问问我为何redis 使用跳表做索引,却不是用B+树做索引

因为B+树的原理是 叶子节点存储数据,非叶子节点存储索引,B+树的每个节点可以存储多个关键字,
它将节点大小设置为磁盘页的大小,充分利用了磁盘预读的功能。每次读取磁盘页时就会读取一整个节点,
每个叶子节点还有指向前后节点的指针,为的是最大限度的降低磁盘的IO;
因为数据在内存中读取耗费的时间是从磁盘的IO读取的百万分之一

而Redis是 内存中读取数据,不涉及IO,因此使用了跳表;

 

=======================================================

什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?

缓存穿透

一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。
一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。
这就叫做缓存穿透。

如何避免?

1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后
清理缓存。

2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过
该bitmap过滤。


=======================================================


缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大
压力。导致系统崩溃。

如何避免?

1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个
线程查询数据和写缓存,其他线程等待。

2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,
A2设置为长期

3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

 


=======================================================

14、redis热key问题?如何发现以及如何解决?


缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的
并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,
这个时候大并发的请求可能会瞬间把后端DB压垮。


解决方案:

​ 对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;
其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询

 

 

=======================================================

Redis实现分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不
存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作


解锁:使用 del key 命令就能释放锁

解决死锁:
1)通过Redis中expire()给锁设定最大持有时间,如果超过,则Redis来帮我们释放锁。
2) 使用 setnx key “当前系统时间+锁持有的时间”和getset key
“当前系统时间+锁持有的时间”组合的命令就可以实现。

 

=======================================================

1redis 的 zset 怎么实现的?


Zset数据量少的时候使用压缩链表ziplist实现,有序集合使用紧挨在一起的压缩列表节点来保存,
第一个节点保存member,第二个保存score。ziplist内的集合元素按score从小到大排序,
score较小的排在表头位置。

=======================================================
数据量大的时候使用跳跃列表skiplist和哈希表hash_map结合实现,
查找删除插入的时间复杂度都是O(longN)

跳跃表按 score 从小到大保存所有集合元素,查找时间复杂度为平均 O(logN),最坏O(N) 。
字典则保存着从 member 到 score 的映射,这样就可以用 O(1)​ 的复杂度来查找 member 对应的
score 值虽然同时使用两种结构,但它们会通过指针来共享相同元素的 member 和 score,
因此不会浪费额外的内存。

 

当有序集合对象同时满足以下两个条件时,对象使用 ziplist 编码:

1、保存的元素数量小于128;
2、保存的所有元素长度都小于64字节。

不能满足上面两个条件的使用 skiplist 编码。

 

=======================================================

如何保证缓存和数据库的数据一致性?


方式一:


读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个
请求。


方式二:


读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存。
更新的时候,先更新数据库,然后再删除缓存。

 

=========================================================================================
String int raw embstr
list ziplist linkedlist
set intset hashtable
zset ziplist skiplist
hash ziplist hashtable

sds 简单动态字符串

sds 在 len 属性中记录了 sds 本身的长度,所以获取一个 sds 长度的复杂度仅为 O(1) 。
与此同时,它还通过 alloc 属性记录了自己的总分配空间。

通过使用 sds 而不是 C 字符串,redis 将获取字符串长度所需的复杂度从 O(N) 降低到了 O(1) ,
这是一种以空间换时间的策略,确保了获取字符串长度的工作不会成为 redis 的性能瓶颈。

sds 实现了空间预分配和惰性空间释放两种优化策略。


ziplist
压缩列表 ziplist 整体占用一大块内存, ziplist 却是将链表中每一项存放在前后连续的地址空间内


intset 整数集合是集合键的底层实现方式之一。

quicklist 以ziplist为结点的, 双端链表结构. 宏观上, quicklist是一个链表,
微观上, 链表中的每个结点都是一个ziplist.


跳表(skip List)是一种随机化的数据结构,基于并联的链表,链表加多级索引的结构
由许多层结构组成。
每一层都是一个有序的链表。
最底层 (Level 1) 的链表包含所有元素。
如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。
每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

 

上一篇:【Redis】压缩列表


下一篇:Redis 数据结构