Redis
- 1. 谈谈Redis为什么是单线程?
- 2. Redis为什么要使用I/O多路复用?
- 3. Redis常用的数据结构和各自的应用场景?
- 3. Redis分布式锁的实现方式有哪些?和Zookeeper分布式锁的区别?
- 4. Redis的持久化方式,以及各种方式的应用场景?
- 5. Redis的过期策略有哪些?
- 6. Redis的内存淘汰策略有哪些?手写LRU策略?
- 6. 什么是缓存雪崩、缓存穿透、缓存击穿以及各种问题的解决方式?
- 7. 如何保证Redis缓存和Mysql双写的数据一致性?
- 8. Redis怎样实现高可用?详细谈谈Sentinel模式以及Redis集群模式?
- 9.Redis-Cluster 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?
- 10.Redis主从模式下业务执行时间大于锁过期时间如何解决?master节点丢失,发生主从切换后导致锁丢失如何解决?
1. 谈谈Redis为什么是单线程?
官方答案:因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的最大瓶颈最有可能的是机器内存的大小或者网络宽带。而单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章的采用单线程的方案了,除此之外,单线程还有以下优势:
- 代码更清晰,处理逻辑更简单;
- 不用考虑各种锁的问题,不存在加锁、释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 不存在“多线程或多进程导致的切换”而消耗CPU;
- Redis采用单线程的模型,保证每个操作的原子性,也减少了线程上下文切换和竞争。
2. Redis为什么要使用I/O多路复用?
首先,Redis是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞,所以I/O操作在一般情况下往往不能直接返回,这会导致某一文件的I/O阻塞导致整个进程无法对其他用户提供服务,而I/O多路复用就是为了解决这个问题而出现的。
I/O多路复用模型使用了Reactor设计模式实现了这一机制,通过Reactor的方式,可以将用户线程轮询I/O操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他工作。 而Reactor线程负责调用内核的select函数查询socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行hand_event进行数据读取、处理的工作。由于select函数阻塞的,因此多路I/O复用模型也被称为异步阻塞I/O模型。注意,这里所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。
文件事件处理器使用I/O多路复用模块同时监听多个FD,当accept、read、write和close文件事件产品时,文件事件处理器就会回调FD绑定的事件处理器。虽然整个文件事件处理器是在单线程上运行的,但是通过I/O多路复用模块的引入,实现了同时对多个FD读写的监控, 提高了网络通信模型的性能,同时也可以保证整个Redis服务实现的简单。
3. Redis常用的数据结构和各自的应用场景?
1. String的使用场景:字符串类型的使用场景:信息缓存、计数器、分布式锁等等。常用命令:get/set/del/incr/decr/incrby/decrby。
实战场景1:记录每个用户的访问次数,或者记录每一个商品的浏览次数
方案:
常见键名:userid:pageview或者pageview:userid,如果一个用户的id为123,那对应的redis key就为pageview:123,value就为用户的访问次数,增加次数可以使用命令:incr。
使用理由:每一个用户访问次数或者商品浏览次数的修改是很频繁,如果使用mysql这种文件系统频繁修改就会造成mysql压力,效率也低。而使用redis的好处有二:使用内存,很快;单线程,所有无竞争,数据不会乱改。
实战场景2:缓存频繁读取,但是不常修改的信息,如用户信息,视频信息
方案:
业务逻辑上:先从redis读取,有值就从redis读取,没有则从mysql读取,并写一份到redis中作为缓存,注意要设置过期时间。
键值设计上:直接将用户一条mysql记录做序列化(通常序列化为json)作为值,userInfo:userid作为key,键名如:userInfo:123,value存储对应用户信息的json串。如key为:“userid:name1”,value为“{“name”:“leijia”,“age”:18}”。
实战场景3:限定某个ip特定时间内的访问次数
方案:
用key记录ip,value记录访问次数,同时key的过期时间设置为60秒,如果key过期了则重新设置,否则进行判断,当一分钟访问超过100次,则禁止访问。
实战场景4:分布式session
我们知道session是以文件的形式保存在服务器上,如果你的应用做了负载均衡,将网站的项目放在多个服务器上,当用户在服务器A上进行登录,session文件会写在A服务器;当用户跳转页面时,请求被分配到B服务器上的时候,就找不到这个session文件,用户就要重新登录。如果想要多个服务器共享一个session,可以将session存放在redis中,redis可以独立于所有复杂均衡服务器,也可以放在其中一台负载均衡服务器;但是所有应用所有的服务器连接的都是同一台redis服务器。
2. List的使用场景:列表本质时一个有序的,元素可重复的队列。
实战场景:定时排行榜
list类型的lrange命令可以分页查询队列中的数据,可将每隔一段时间计算一次排行榜存储在list类型中,如QQ音乐内地排行榜,每周计算一次存储在list类型中,访问接口是通过page、size分页转化成lrange命令获取排行榜数据。但是,并不是所有的排行榜都能用list类型实现,只有定时计算的排行榜才适合使用list类型存储,于定时计算的排行榜相对应的是实时计算的排行榜,list类型不能支持实时计算的排行榜,下面介绍有序集合sorted set的应用场景时会详细介绍实时计算的排行榜的实现。
3. Set的使用场景:集合的特点时无序性和确定性(不重复)。
实战场景:收藏夹
例如QQ音乐中如果你喜欢一首歌,点个【喜欢】就会将歌曲放到个人收藏夹中,每一个用户做一个收藏的集合,每个收藏的集合存放用户收藏过的歌曲id。key为用户id,value为歌曲id的集合。
4. ZSet的使用场景:有序集合的特点时有序,无重复值。于set不同的时zset每个元素都会关联一个score属性,redis正式通过score来为集合中的成员进行从小到大的排序。
实战场景:实时排行榜
QQ音乐中有多种实时榜单,比如飙升榜、热歌榜、新歌榜,可以用redis key存储榜单类型,score为点击量,value为歌曲id,用户每点击一首歌曲会更新redis数据,sorted set会依据score,即点击量将歌曲id排序。
5. Hash的使用场景:以购物车为例,用户id设置为key,那么购物车里所有的商品就是用户key对应的值了,每个商品有id和购买数量,对应hash的结构就是商品id为field,商品数量为value。如图所示:
如果将商品id和商品数量序列化成json字符串,那么也可以用上面讲的string类型存储,下面对比一下这两种数据结构:
对比项 | string | hash |
---|---|---|
效率 | 很高 | 高 |
容量 | 低 | 低 |
灵活性 | 低 | 高 |
序列号 | 简单 | 复杂 |
总结一下:当对象的某个属性需要频繁修改时,不适合string + json,因为它不够灵活,每次修改都需要重新将整个对象序列化并赋值;如果使用hash类型,则可以这对某个属性单独修改,没有序列化,也不需要修改整个对象,比如,商品的价格、销量、关注数、评价数等可能经常发生变化的属性,就适合存储在hash类型里。
3. Redis分布式锁的实现方式有哪些?和Zookeeper分布式锁的区别?
- Zookeeper分布式锁:Znode的节点只能被一个线程创建,且同一个Znode节点只能被创建一次的特性实现,即使创建的Znode节点线程挂掉了,那该Znode也会被删除掉(Zookeeper监控),不会产生死锁,解决惊群效应(临时顺序节点)
4. Redis的持久化方式,以及各种方式的应用场景?
https://blog.csdn.net/weixin_44776993/article/details/107479417
https://blog.csdn.net/qq_38261137/article/details/106920868
Redis持久化的意义,在于故障恢复,也属于高可用的一环,例如当存放在内容的数据,会因为Redis的突然挂掉,而导致数据丢失,Redis的持久化,就是将Redis内存中的数据,持久化到磁盘中,然后将磁盘数据进行云备份。Redis的持久化方式有两种:
- RDB持久化方式:是对Redis中的数据执行周期性的持久化,逻辑模型如下:
1.1 RDB优缺点:
1.1.1 RDB会生成多个数据文件,每个文件都代表了某一时刻Redis中的数据,这种多数据文件的方式,非常适合做冷备份,可以将这种完整的数据文件发送到一些远程的安全存储上去,以预定好的备份策略来定期备份redis中的数据。
1.1.2 RDB对Redis对外提供的读写服务影响非常小,可以让Redis保持高性能,因为Redis主进程只需要Fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可。
1.1.3 相对于AOF持久化机制来说,直接基于RDB数据文件来启动和恢复Redis进程,更加快速。
1.1.4 如果想让在Redis故障中,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦Redis进程宕机,那么会丢失最近5分钟的数据。
1.1.5 RDB每次在Fork子进程在执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致客户端提供的服务暂停数毫秒,甚至数秒。
- AOF持久化方式:AOF机制对每条写入命令作为日志,以append-only的模式写入一个文件日志中,在Redis重启的时候,可以通过回访AOF日志中的写入指令来重新构建整个数据集,逻辑模型如下:
2.1 AOF优缺点:
2.1.1 AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多执行1秒钟的数据;
2.1.2 AOF日志文件以append-only模式写入,所有没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,及时文件尾部破,也很容易修复;
2.1.3 AOF文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite log的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志。在创建日志文件的时候,老的日志文件还是照常写入,当新的merge后的日志文件ready的时候,在交换新老日志文件即可。
2.1.4 AOF日志文件的命令通过可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人执行了flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可立即拷贝AOF文件,将最后一条flushall命令删除,然后再将AOF文件放回去,就可通过恢复机制,自动恢复所有数据。
2.1.5 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大。
2.1.6 AOF开启后,支持写QPS会比RDB支持的写QPS低,因为AOF一般会配置成fsync一次日志文件,如果实时写入,那么QPS会大降,Redis性能会大大降低。
2.1.7 以前AOF发生过bug,就通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来,所以说,类似AOF这种较为复杂的给予命令日志/merge/回放的方式,比基于RDB每次持久化一份完整的数据快照文件的方式,更加脆弱,容易出现bug。- RDB和AOF到底应该如何选择
3.1 不要仅仅使用RDB,因为那样会导致丢失更多的数据;
3.2 也不要仅仅使用AOF,第一:通过AOF做冷备份,没有RDB做冷备份来的恢复速度更快;第二:AOF这种复杂的备份和恢复机制容易产生bug;
3.3 Redis支持同时开启两种持久化方式,可以综合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复第一选择,用RDB来做不同成都的冷备份,在AOF文件都丢失或损坏不可用的时候,还可以进行快速的数据恢复。
5. Redis的过期策略有哪些?
Redis的过期策略是指当Redis中缓存的Key过期了,Redis如何处理,过期策略通常有一下三种:
- 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除,该策略可以立即清除过期的key,对内存友好,但是会占用大量的CPU资源去处理过期数据,从而影响缓存的响应时间和吞吐量。
- 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则删除,该策略可以最大化的节省CPU资源,却对内存不友好,极端情况可能出现大量的过期key没有再次被访问,从而不会清除,占用大量内存。
- 定期过期:每隔一定的时间,会扫描一定数量的Redis的expires字典中一定数量的key,并清除其中已过期的key,该策略是前两者的一个折衷方案,通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下时间使得CPU和内存资源达到最优的平衡效果。(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间,键空间是指该Redis集群中保存的所有键。)。
- Redis中同时使用了惰性过期和定期过期两种过期策略。
6. Redis的内存淘汰策略有哪些?手写LRU策略?
Redis的内存淘汰策略是指Redis的用于缓存的内存不足时,如何处理新写入且需要申请额外空间的数据。
1.noeviction:当内存不足以容纳写入数据时,新写入操作会报错;
2. allkeys-lru:当内存不足以容纳写入数据时,在键空间中,移除最近最少使用的key;
3. allkeys-random:当内存不足以容纳写入数据时,在键空间中,随机移除某个key;
4. volatile-lru:当内存不足以容纳写入数据时,在设置了过期空间的键空间中,移除最近最少使用的key;
5. valotile-random:当内存不足以容纳写入数据时,在设置了过期空间的键空间中,随机移除某个key;
6. valotile-ttl:当内存不足以容纳写入数据时,在设置了过期空间的键空间中,又更早过期时间的key优先移除。
6. 什么是缓存雪崩、缓存穿透、缓存击穿以及各种问题的解决方式?
- 缓存雪崩:对于系统A,假设每天高峰期每秒5000个请求,本来缓存在高峰期可以抗住4000个请求,但是缓存服务器以外发生全盘宕机,缓存挂了,此时1秒5000个请求全部走数据库,数据库必然扛不住,它会报警,然后就挂了。此时如果没有采用容灾方案来处理这个故障,只能重启数据库,但是数据库立马又被新来的请求打死,这就是缓存雪崩。
1.1 缓存雪崩的解决方案如下:
1.1.1 事前:Redis高可用,主从+哨兵,redis cluster,避免全盘崩溃;
1.1.2 事中:本地ehcache缓存 + hystrix限流和降级,避免Mysql被打死;
1.1.3 事后:redis持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。- 缓存穿透:对于系统A,假设一秒可以处理5000个请求,结果其中4000个请求是黑客发出的恶意攻击,缓存中查不到,每次都去数据库里查,而且也查不到,请求每次都”视缓存于无物“,直接查询数据库,这种恶意攻击的场景就是缓存穿透。
2.1 解决方案:每次系统A从数据库中没有查到数据,就写一个空值到缓存里去,然后设置一个过期时间,这样的话,下次有相同的key来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。- 缓存击穿:某个key非常热点,访问非常频繁,处于高并发访问的情况,当这个Key在失效的瞬间,大量的请求就击穿的缓存,直接请求数据库,就像在一道屏障上凿开一个洞。
3.1 解决方式:可以将热点数据设置永远不过期;或者基于redis实现互斥锁,等待第一个请求构建完缓存后,再释放锁,进而其他请求才能通过该key访问数据。
7. 如何保证Redis缓存和Mysql双写的数据一致性?
使用Cache Aside Pattern,读取的时候,先读取缓存中是否有数据,缓存中没有数据,再去数据库中进行查询,查询出来以后,在存入缓存中;更新的时候,先删除缓存,然后再更新数据库。
8. Redis怎样实现高可用?详细谈谈Sentinel模式以及Redis集群模式?
- Redis主从:(单点故障,主从主备):弱一致性,备机收到多少个成功主机才会返回成功,主机写,异步备份备机;
1.1.全量复制,主节点通过bgsave命令fork线程进行RDB持久化,主节点通过网络IO将dump.rdb发送到从节点,从节点清除老数据,载入新dump.rdb,此时从节点无法响应请求。
1.2 增量复制:执行复制的双方会维护一个复制偏移量offset;主节点内部维护一个固定长度的、先进先出队列最为复制积压缓冲区(当主从节点offset的差距过大查过缓冲区长度,将执行全量复制);每个Redis节点都会生成一个运行ID,主节点会将自己的ID发送给从节点,从节点会将主节点的运行ID存起来,执行流程如下:
1.2.1 从服务器收到slaveof命令;
1.2.2 判断是否是第一次执行复制,如果是则想主节点发送psync命令,主节点返回fullresync{runId}{offset}执行全量复制;
1.2.3 如果不是第一次复制,想主节点发送psync{runId}{offset}命令,如果主节点runId和参数中的runId一致则执行增量同步,否则执行全量同步。- Redis Sentinel(哨兵):集群监控:负责监控master和slave进程是否工作正常;消息通知:如果某个实例故障,那么负责发送消息作为报警通知管理员;故障转移:如果master节点故障,会自动转移到slave节点上;配置中心:如果故障转移发生了,通知其他slave节点新的master地址;至少需要三个master节点;
2.1 多个哨兵判断master节点是是否宕机,需要超过半数节点同意;
2.2 分布式选举? 1. 如果断开时间大于cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成master;2. 每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举;3. 所有的master节点开始进行选举的slave进行投票,如果大部分master节点(N/2 + 1)都投给了某个从节点,那么选举通过,那个从节点可以切换为master。
什么情况下数据会丢失?1. 异步复制数据: 因为master -> slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机了,此时这些部分数据就丢失了;2. 脑裂
在redis的主节点的配置文件中进行配置
min-slaves-to-write 1
min-slaves-max-lag 10
要求至少有1个slave,数据复制和同步的延迟不能超过10秒;如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了;上面两个配置可以减少异步复制和脑裂导致的数据丢失- Redis Cluster:分片集群
9.Redis-Cluster 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?
方案说明:
- 在.Redis-Cluster架构下,每个redis要开发两个端口,一般一个是6379,另一个就是加1w的端口号,比如16379,16379端口用于进行节点之间通讯,由于进行故障检测、配置更新、故障转移授权,采用gossip协议,用于节点之间进行高效的数据交换,占用更少的网络带宽和处理时间。
- Redis-Cluster使用了hashslot算法实现key的分片与寻址:Redis-Cluster固定的16384个hashslot,Redis-Cluster中每个master除了会持有自身的一部分hashslot还会持有下一个节点(一致性Hash算法)的hashslot;hashslot让节点的增加和伤处都很简单,增加一个几点,就将其他节点的hashslot移动部分过去,减少一个节点,就将它的hashslot移动到其他节点上,移动hashslot的成本非常低。任何一台及其宕机,另外的节点不都影响,以为key找的是hashslot,不是机器。
- 当进行写入时,对写入的key计算CRC16值,然后对16384进行取模,可以获取对应的hashslot后,将key存储对应的master的hashslot中;读取数据时,只需要找到对应节点的hashslot即可;且如果没有分配在该节点时,redis 会返回转向指令,指向正确的节点。
10.Redis主从模式下业务执行时间大于锁过期时间如何解决?master节点丢失,发生主从切换后导致锁丢失如何解决?
- redission:监控业务执行时间是否大于锁过期时间,如果是则会对key进行续期;
- redlock:从多个节点获取锁,当一半以上节点获取锁成功了,锁才算获取成功。