- 基础类型
- Redis是典型的Key-Value类型数据库,单线程内存型,用于做缓存(redis的计数器生成分布式唯一主键,实现分布式锁,队列和会话缓存)
- Key为字符类型,Value的类型常用的为五种类型:String、Hash 、List 、 Set 、 Ordered Set
- Redis的优点与缺点
- 优点:
- 1 读写性能优异
- 2 支持数据持久化,支持AOF和RDB两种持久化方式
- 3 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
- 4 数据结构丰富:除了支持string类型的value外还支持string、hash、set、sortedset、list等数据结构。
- 缺点:
- 1 Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
- 2 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
- 3 redis的主从复制采用全量复制,复制过程中主机会fork出一个子进程对内存做一份快照,并将子进程的内存快照保存为文件发送给从机,这一过程需要确保主机有足够多的空余内存。若快照文件较大,对集群的服务能力会产生较大的影响,而且复制过程是在从机新加入集群或者从机和主机网络断开重连时都会进行,也就是网络波动都会造成主机和从机间的一次全量的数据复制,这对实际的系统运营造成了不小的麻烦。
- 4 Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
- 优点:
- 持久化
- RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是调用系统的fork()函数创建一个与当前进程一模一样的一个子进程(所有数据,变量、环境变量、程序计数器等)进行持久化操作,先将数据集写入临时文件,待持久化结束,再用临时文件替换上次的持久化文件,用二进制压缩存储。主进程不需要任何io操作,提高性能(dump文件)
- 什么时候出发fork子进程触发持久化:
- 在shoutdown,如果没有开其aof,会触发
- 配置文件中默认的快照配置
- save 900 1: 900秒内,出现一次增删改操作会触发
- save 300 10:300秒内,出现10次增删改
- save 60 10000:60秒内,10000次增删改
- save“”:关闭
- 手动执行sava:只是保存,主进程进行持久化,其他请求阻塞;bgsave:在后台进行常规fork进行持久化
- 什么时候出发fork子进程触发持久化:
- AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作命令,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。在重新启动之后,会照着日志文件重新运行一遍命令完成持久化
- 持久化文件 :apppendonly.aof
- 触发机制:
- no:操作系统数据缓存同步到磁盘(快,持久化没保证)
- always:同步持久化,每次发生数据变更,都会记录日志(慢。安全)
- everysec:每秒同步一次(默认,很快,但可能会丢失1秒钟以内的数据)
- aof重写机制:如果日志过大,Redis可以自动启用rewrite机制。Aof重写不需要对原有的aof文件进行任何操作,而是通过当前数据库的状态,直接替换掉多余或者重复操作。
- 1、fork出一个子进程进行AOF重写,主进程可以继续处理命令请求;子进程带有主进程的数据副本,使用子进程而不是线程,可以避免在锁的情况下,保证数据的安全性。
- 2、Redis增加了一个AOF重写缓存,这个缓存在fork出子进程之后开始启用,Redis服务器主进程在执行完写命令之后,会同时将这个写命令追加到AOF缓冲区和AOF重写缓冲区;AOF缓冲区的内容会定期被写入和同步到AOF文件中,对现有的AOF文件的处理工作会正常进行从创建子进程开始,服务器执行的所有写操作都会被记录到AOF重写缓冲区中;
- 3、将AOF重写缓存中的内容全部写入到新的AOF文件中;这个时候新的AOF文件所保存的数据库状态和服务器当前的数据库状态一致;对新的AOF文件进行改名,原子的覆盖原有的AOF文件;完成新旧两个AOF文件的替换
- 触发机制(配置):最大AOF文件达到xmb就触发重写机制,如果正在率为100%,那么下一次从写就是2xmb
- redis4.0后的混合持久化机制:对重复多余操作进行改写合并,然后存储到dump.rdb中
- RDB和AOF:RDB由于AOF,如果两种机制共存,以Aof为主;一般需要同时开启,优先使用aof持久化机制。
- RDB存在哪些优势呢?
- 1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
- 2). 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
- 3). 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
- 4). 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。
- RDB又存在哪些劣势呢?
- 1). 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
- 2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
- AOF的优势有哪些呢?
- 1). 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。
- 2). 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。
- 3). 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。
- 4). AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。
- AOF的劣势有哪些呢?
- 1). 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
- 2). 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。
- 二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。
- RDB存在哪些优势呢?
- 常用配置
- RDB持久化配置
- Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息:
- save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。
- save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
- save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
- Redis会将数据集的快照dump到dump.rdb文件中。此外,我们也可以通过配置文件来修改Redis服务器dump快照的频率,在打开6379.conf文件之后,我们搜索save,可以看到下面的配置信息:
- AOF持久化配置
- 在Redis的配置文件中存在三种同步方式,它们分别是:
- appendfsync always #每次有数据修改发生时都会写入AOF文件。
- appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
- appendfsync no #从不同步。高效但是数据不会被持久化。
- 在Redis的配置文件中存在三种同步方式,它们分别是:
- RDB持久化配置
- RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是调用系统的fork()函数创建一个与当前进程一模一样的一个子进程(所有数据,变量、环境变量、程序计数器等)进行持久化操作,先将数据集写入临时文件,待持久化结束,再用临时文件替换上次的持久化文件,用二进制压缩存储。主进程不需要任何io操作,提高性能(dump文件)
- Redis为什么这么快
- 1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
- 2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
- 3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 4、使用多路I/O复用模型,非阻塞IO;
- 5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
- 以上几点都比较好理解,下边我们针对多路 I/O 复用模型进行简单的探讨:多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。
- 那么为什么Redis是单线程的
- 我们首先要明白,上边的种种分析,都是为了营造一个Redis很快的氛围!官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)。
- 谈谈Redis的过期策略以及内存淘汰机制
- 过期策略:
- 我们set key的时候,都可以给一个expire time,就是过期时间,指定这个key比如说只能存活1个小时,我们自己可以指定缓存到期就失效。如果假设你设置一个一批key只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的?答案是:定期删除+惰性删除;所谓定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意,这里可不是每隔100ms就遍历所有的设置过期时间的key,那样就是一场性能上的灾难。实际上redis是每隔100ms随机抽取一些key来检查和删除的。但是,定期删除可能会导致很多过期key到了时间并没有被删除掉,所以就得靠惰性删除了。
- 这就是说,在你get某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。并不是key到时间就被删除掉,而是你查询这个key的时候,redis再懒惰的检查一下
- 如果大量过期key堆积在内存里,导致redis内存块耗尽了,怎么办?答案是:内存淘汰机制。
- 内存淘汰机制
- 如果redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略:
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
- volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
- 过期策略:
- 如何解决Redis和和数据库双写一致性的问题?读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。写的时候出现双写怎么办:更新数据库的时候,更新的时候,先更新数据库,然后再删除缓存。等下一次查询的时候再将数据库数据更新至内存;
- 方案一:先更新数据库,再更新缓存
- 同时有请求A和请求B进行更新操作,那么会出现
- (1)线程A更新了数据库
- (2)线程B更新了数据库
- (3)线程B更新了缓存
- (4)线程A更新了缓存
- 这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。
- 同时有请求A和请求B进行更新操作,那么会出现
- 方案二:先删除缓存,在更新数据库:A有数据发生了变更,先删除了缓存,然后准备要去修改数据库,此时还没修改,这时候一个请求过来B,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中 ,A之后数据变更的程序完成了数据库的修改。数据库和缓存中的数据不一样了;
- 解决:采用延时双删策略:但还是有可能再第二次删除缓存失败----方案三
- (1)先淘汰缓存
- (2)再写数据库(这两步和原来一样)
- (3)休眠1秒,再次淘汰缓存
- 解决:采用延时双删策略:但还是有可能再第二次删除缓存失败----方案三
- 方案三:先更新数据库,再删缓存:(1)缓存刚好失效(2)请求A查询数据库,得一个旧值(3)请求B将新值写入数据库(4)请求B删除缓存(5)请求A将查到的旧值写入缓存
- 这个方案发生不一致的前提是,(3)中B的写操作耗时比(2)中的读时间更短,才有可能出现(4)早于(5);
- 方案一:先更新数据库,再更新缓存
- 如何应对缓存穿透(直接对存储层操作,失去了缓存层的意义)与缓存雪崩的问题(缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。)?
- 缓存穿透:查询一个数据库中不存在的数据,比如商品详情,查询一个不存在的ID,每次都会访问DB,如果有人恶意破坏,很可能直接对DB造成过大地压力。
- 解决方案:
- 1.当通过某一个key去查询数据的时候,如果对应在数据库中的数据都不存在,我们将此key对应的value设置为一个默认的值,比如“NULL”,并设置一个缓存的失效时间,这时在缓存失效之前,所有通过此key的访问都被缓存挡住了。后面如果此key对应的数据在DB中存在时,缓存失效之后,通过此key再去访问数据,就能拿到新的value了。
- 2.常见的则是采用布隆过滤器(可以用很小的内存保留很多的数据),将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。(布隆过滤器:实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)
- 解决方案:
- 缓存雪崩:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
- 解决方案:
- 1.将系统中key的缓存失效时间均匀地错开,防止统一时间点有大量的key对应的缓存失效;
- 2.在原有的失效时间上加一个随机值,这样每一个缓存的失效时间的重复率降低,很难引起集体失效
- 解决方案:
- 缓存穿透:查询一个数据库中不存在的数据,比如商品详情,查询一个不存在的ID,每次都会访问DB,如果有人恶意破坏,很可能直接对DB造成过大地压力。
- 如何解决Redis的并发竞争Key问题(同时有多个子系统去set一个key。这个时候要注意什么呢?)
- 缓存击穿:缓存的热点key失效,导致大量的sql访问db,
- 解决:使用redis数据库的分布式锁,解局mysql的压力问题
- 1、redis自带的分布式锁,set ex nx
- set nx :当key不存在的时候,才能成功。当多个请求同时请求redis的时候,只会有1个请求set成功,其他请求返回nil。等待过期时间结束,其他请求才能set
- set nx :当key不存在的时候,才能成功。当多个请求同时请求redis的时候,只会有1个请求set成功,其他请求返回nil。等待过期时间结束,其他请求才能set
- 2、redisson框架(NIO):一个redis的juc的实现,既有jedis的功能,又有juc的lock功能的客户端实现
- 1、redis自带的分布式锁,set ex nx
- 解决:使用redis数据库的分布式锁,解局mysql的压力问题
- 缓存击穿:缓存的热点key失效,导致大量的sql访问db,
reids数据库面试总结
最新推荐文章于 2020-12-03 15:10:48 发布