Redis集群高频问答,连夜肝出来了
Redis 集群方案
Redis集群方案应该怎么做?都有哪些方案?
使用codis方案:目前用的多的集群方案,基本和twemproxy一致的效果,但它支持在节点数量改变情况下,旧节点数据可恢复到新hash节点。
Redis cluster3.0自带的集群:特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。
在业务代码层实现:起几个毫无关联的Redis实例,在代码层对key进行hash计算,然后去对应的Redis实例操作数据。这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控等等。
Redis集群方案什么情况下会导致整个集群不可用?
如有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少 5501-11000这个范围的槽而不可用。
Redis集群的主从复制模型是怎样的?
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型, 每个节点都会有N-1个复制品。
Redis集群会有写操作丢失吗?为什么?
Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
Redis集群之间是如何复制的?
Redis集群之间是通过异步复制。
Redis集群最大节点个数是多少?
16384个。
Redis集群如何选择数据库?
Redis集群目前无法做数据库选择,默认在0数据库。
Redis 持久化方案
Redis的持久化是什么?
RDB持久化:该机制可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。
AOF持久化:记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。
AOF文件中的命令全部以Redis协议的格式来保存,新命令会被追加到文件的末尾。Redis还可以 在后台对AOF文件进行重写(rewrite),使得AOF文件的体积不会超出保存数据集状态所需的实际大小。
AOF和RDB的同时应用:当Redis重启时,它会优先使用AOF文件来还原数据集,因为AOF文件保存的数 据集通常比RDB文件所保存的数据集更完整。
RDB的优缺点?
优点:RDB是一个非常紧凑(compact)的文件,它保存了Redis在某个时间点上的数据集。这种文件非常适合用于进行备份:比如说你可以在近的24小时内,每小时备份一次RDB文件,并且在每个月的每一天,也备份一个RDB文件。这样的话,即使遇上问题也可以随时将数据集还原到不同的版本。
RDB非常适用于灾难恢复(disaster recovery):它只有一个文件,并且内容都非常紧凑,可以(在加密后)将它传送到别的数据中心,或者亚马逊S3中。
RDB可以最大化Redis的性能:父进程在保存RDB文件时唯一要做的就是fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘I/O操作。RDB在恢复大数据集时的速度比AOF的恢复速度要快。
缺点:如果你需要尽量避免在服务器故障时丢失数据,那么RDB不适合你。虽然Redis允许你设置不同的保存点(save point)来控制保存RDB文件的频率,但是,因为RDB文件需要保存整个数据集的状态,所以它并不是一个轻松的操作。因此你可能会至少5分钟才保存一次RDB文件。
在这种情况下,一旦发生故障停机,你就可能会丢失好几分钟的数据。每次保存RDB的时候,Redis都要 fork()出一个子进程,并由子进程来进行实际的持久化工作。
在数据集比较庞大时,fork()可能会非常耗时,造成服务器在某某毫秒内停止处理客户端;如果数据集非常巨大,并且CPU时间非常紧张 的话,那么这种停止时间甚至可能会长达整整一秒。
AOF的优缺点?
优点:使用AOF持久化会让Redis变得非常耐久(much more durable):你可以设置不同的fsync策略,比如无fsync,每秒钟一次fsync,或者每次执行写入命令时fsync。
AOF的默认策略为每秒钟fsync一次,在这种配置下,Redis仍然可以保持良好的性能,并且就算发生故障停机,也多只会丢失一秒钟的数据(fsync会在后台线程执行,所以主线程可以继续努力地处理命令请求)。
AOF文件是一个只进行追加 操作的日志文件(append onlylog),因此对AOF文件的写入不需要进行seek,即使日志因为某些原因 而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机等等),redis-check-aof工具也可以轻易地修复这种问题。
Redis可以在AOF文件体积变得过大时,自动地在后台对AOF进行重写:重写后的新AOF文件包含了恢复当前数据集所需的小命令集合。
整个重写操作是绝对安全的,因为Redis在创建新AOF文件的过程 中,会继续将命令追加到现有的AOF文件里面,即使重写过程中发生停机,现有的AOF文件也不会丢 失。
而一旦新AOF文件创建完毕,Redis就会从旧AOF文件切换到新AOF文件,并开始对新AOF文件进行追加操作。
缺点:对于相同的数据集来说,AOF文件的体积通常要大于RDB文件的体积。根据所使用的fsync策略,AOF的速度可能会慢于RDB。
在一般情况下,每秒fsync的性能依然非常高,而关闭fsync可以让AOF的速度和RDB一样快,即使在高负荷之下也是如此。不过在处理巨大的写入载入时,RDB可以提供更有保证的最大延迟时间(latency)。
AOF在过去曾经发生过这样的bug:因为个别命令的原因,导致AOF文件在重新载入时,无法将数据集 恢复成保存时的原样。
如何选择合适的持久化方式?
(1)一般来说,如果想达到足以媲美PostgreSQL的数据安全性,你应该同时使用两种持久化功能。
在这种情况下,当 Redis 重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保 存的数据集要比RDB文件保存的数据集要完整。
(2)如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB持久化。
(3)有很多用户都只使用AOF持久化,但并不推荐这种方式,因为定时生成RDB快照(snapshot)非常 便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比AOF恢复的速度要快,除此之外,使用RDB 还可以避免AOF程序的bug。
(4)如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。
Redis持久化数据和缓存怎么做扩容?
(1)如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。
(2)如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。
否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。
Redis的内存淘汰策略有哪些?
Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间 的数据。
全局的键空间选择性移除 noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。 allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近少使用的key。 allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。 设置过期时间的键空间选择性移除 volatile-lru:当内存不足以容纳新写入数据时,且设置了过期时间的键空间中,移除最近少使用 的key。 volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某 个key。 volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key优先移除。
简单描述下Redis线程模型
Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。
它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。
因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。
文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目 前执行的任务来为套接字关联不同的事件处理器。
当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭 (close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与Redis服务器中其他同样以单线程方式运行的模块进行对接,这保持了Redis内部单线程设计的简单性。
Redis事务其他实现方式?
(1)基于Lua脚本,Redis可以保证脚本内的命令一次性、按顺序地执行, 其同时也不提供事务运行错误的回滚,执行过程中如果部分命令运行错误,剩下的命令还是会继续运行完。
(2)基于中间标记变量,通过另外的标记变量来标识事务是否执行完成,读取数据时先读取该标记变量 判断是否事务执行完成。但这样会需要额外写代码实现,比较繁琐 。
Redis 缓存雪崩与缓存击穿
简单说说缓存雪崩及解决方法
缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间 (例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访 问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。)
解决办法:大多数系统设计者考虑用加锁( 使用多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就是设置缓存失效时间分散开。
缓存穿透怎么导致的?
在高并发下查询key不存在的数据,会穿过缓去存查询数据库。导致数据库压力过大而宕机。
解决方法:
1.对查询结果为空的情况也进行缓存,缓存时间(ttl)设置短一点,或者该key对应的数据insert了之后清理缓存。缺点:缓存太多空值占用了更多的空间。
2.使用布隆过滤器。在缓存之前在加一层布隆过滤器,在查询的时候先去布隆过滤器查询 key 是否存在,如果不存在就直接返回,存在再查缓存和DB。
布隆过滤器原理:当一个元素被加入集合时,将这个元素通过n次Hash函数结果映射成一个数组中的n个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了,如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。总之布隆过滤器是一个很大二进制的位数组,数组里面只存0和1。
项目中有出现过缓存击穿,简单说说怎么回事?
缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般会从数据库中加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案:
1.用分布式锁控制访问的线程,使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数据库。
2. 不设超时时间,采用volatile-lru淘汰策略 缺点:会造成写一致问题,当数据库数据发生更新时,缓存中的数据不会及时更新,这样会造成数据库中的数据与缓存中的数据的不一致,应用会从缓存中读取到脏数据。可采用延时双删策略处理。
Redis 缓存一致性与竞争
遇到缓存一致性问题,你怎么解决的?
由于缓存和数据库不属于同一个数据源,本质上非原子操作,所以是无法保证强一致性的,只能去实现终一致性。
解决方案:
延时双删:先更新数据库同时删除缓存,等2秒后再删除一次缓存,等到读的时候再回写到缓存。
利用工具(canal)将数据库的binlog日志采集发送到MQ中,然后通过ACK机制确认处理删除缓存 。
为什么要用 Redis 而不用 map/guava 做缓存?
缓存分为本地缓存和分布式缓存:以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存, 主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
使用 Redis 或 MemoryCache之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。
缺点是需要保持 redis 或memcached服务的高可用,整个程序架构上较为复杂。
如何解决 Redis 的并发竞争Key问题?
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同。
推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。如果不存在 Redis 的并发竞 争 Key 问题,不要使用分布式锁,这样会影响性能。
基于zookeeper临时有序节点可以实现的分布式锁。
大致思想为:每个客户端对某个方法加锁时,在 zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。
判断是否获取锁的方式很简单,只需要判断有序节点中序号小的一个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
在实践中,当然是从以可靠性为主,所以首推Zookeeper。
什么是 RedLock?
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节 点的方法更安全。
它可以保证以下特性:
安全特性:互斥访问,即永远只有一个 client 能拿到锁
避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
容错性:只要大部分 Redis 节点存活就可以正常提供服务
什么时候需要缓存降级?
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然 需要保证服务还是可用的,即使是有损服务。
系统可以根据一些关键数据进行自动降级,也可以配置开 关实现人工降级。
缓存降级的终目的是保证核心服务可用,即使是有损的。
而且有些服务是无法降级的(如加入购物 车、结算)。
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:
一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级; 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级, 并发送告警; 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的 大阀值,此时可以根据情况自动降级或者人工降级; 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。 因此,对于不重要 的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查 询,而是直接返回默认值给用户。
如何保证缓存与数据库双写时的数据一致性?
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况。
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
还有一种方式就是可能会暂时产生不一致的情况,但是发生的几率特别小,就是先更新数据库,然后再 删除缓存。
肝完啦,感谢友友们的支持!