项目中常见的缓存问题实战

1.redis的线程模型是什么?为什么单线程的redis比多线程的memcached效率要高得多(为什么redis是单线程的但是还可以支撑高并发)?

[redis线程模型]

redis的server端会有server socket来接收客户端的请求,当准备完毕会放入io多路复用程序,然后通过io多路复用程序压入一个对列中,然后通过文件事件分派器给到链接应答处理器。这时候发送set命令接着放到io多路复用程序,也会压入队列中,同时会有一个wating状态来去监听io多路复用程序,然后上面发送命令的请求从队列中进入文件事件分派器中,这时候连接的是命令请求处理器,需要执行命令,并且将刚才创建的wating请求与命令回复处理器进行关联起来。随后命令回复处理器执行完成后,会回复到客户端。

2.为啥redis单线程模型也能效率这么高?

1)纯内存操作

2)核心是基于非阻塞的IO多路复用机制

3)单线程反而避免了多线程的频繁上下文切换问题

3.redis都有哪些数据类型?分别在哪些场景下使用比较合适?

string:做简单的kv缓存

hash:value放对象信息,可以直接操作这个对象的某一字端。例如试题章节试题可以这样存储,不用取出来直接修改某道题的试题信息。

list:微博,某个大v的粉丝,就可以以list的格式放在redis里去缓存

set:可以基于set玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁;也可以存放订单信息,用redis的zset队列,放进去的时候把订单号以及加一个时间戳。

sorted set:用户排行榜

4.redis的过期策略都有哪些?内存淘汰机制都有哪些?手写一下LRU代码实现?

redis使用的过期策略,定期删除+惰性删除;指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除,惰性删除指的是在查询的时候发现key过期了,就要删除。

【引申问题】如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了,咋整?

这时候走内存淘汰策略,如果redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略:

1个key,最近1分钟被查询了100次

1个key,最近10分钟被查询了50次

1个key,最近1个小时倍查询了1次

noeviction:内存不足以写入的话,直接报错

allkeys-lru:当内存不足以容纳写入内存的时候,在健空间中,移除最少使用的

allkeys-random:和上边的区别是随机移除

volatile-random:当内存不足以容纳写入内存的时候,在设置了过期时间的健空间中,随机移除某个key

volatile-ttl:和上边的区别,是有更早过期时间的key进行移除

实现手写lru算法的思路:定义一个方法,定义初始内存大小,用来存放缓存数据,用linkedhashmap来存储,最近的放在开头,最老的放在结尾。再定义一个移除的方法,当map的数据量大于指定的缓存的数据量的时候,就删除最老的数据。

5.如何保证Redis的高并发和高可用?redis的主从复制原理能介绍一下么?redis的哨兵原理能介绍一下么?

【redis replication核心机制】

     1)redis采用异步的方式复制数据到slave节点。不过redis2.8开始,slave node会周期性的确认每次复制的数量。

     2)  一个master可以连接多个slave,slave也可以连接其他的slave,slave做复制的时候,是不会阻塞master的。

     3)slave在做复制的时候,也不会block自己的查询,会用旧的数据提供服务;但是复制完成之后,需要删除旧的数据,加载新的数据集,这时候会暂停对外服务。slave节点主要是做横向扩容,进行读写分离。提高吞吐量。

【主从架构的核心原理】

      当启动一个slave的时候,会给master发送一个psync的命令,如果是slave重新连接master,那么master仅仅会复制给slave部分缺少的数据,如果是第一次连接的话,就触发一次full resynchronization,在进行full resynchronization的时候,master会启动一个后台线程,生成rdb快照文件,同时还将客户端写的命令存储到内存中,rdb文件生成之后,mster会将rdb发送给slave,slave先写入本地磁盘,然后再从本地磁盘加载到内存中。然后master会将内存中缓存的写命令发送给slave,slave也会同步数据。

【主从复制的断点续传】

redis 2.8之后才能实现。master会在内存中常见backlog;master和slave都会保存一个replica offset 还有master id,offset就是保存在backlog中的。如果断开之后重新连接,slave会让master从上次的replica offset开始继续复制,如果没有对应的offset就需要resynchronization。

【无磁盘化复制】

   master在内存中直接创建rdb,然后发送给slave,不会在自己本地落地磁盘了

【过期key处理】

    slave不会过期key,只会等待master过期key。如果master过期了一个key,或者通过LRU淘汰了一个key,那么会模拟一条del命令发送给slave。

【全量复制】

1)master执行bgsave,,会在本地生成一份rdb快照文件。

2)master会将快照文件传给slave,如果rdb复制时间超过60秒,那么slave node认为复制失败。

3)对于千兆网络,一般每秒传输100M,6g文件,很有可能超过60s。

4)master在生成rdb的时候,会将写命令缓存在内存中,在slave保存了rdb之后,再将新的写命令复制给slave。

5)client-output-buffer-limit slave 256MB 64MB 60,如果在复制期间,内存缓冲区持续消耗超过64MB,或者一次性超过256MB,那么停止复制,复制失败

6)slave接收到rdb之后,清空自己的旧数据,然后重新加载rdb到自己的内存中,同时基于旧的数据版本提供服务。

7)如果slave node开启了AOF,那么会立即执行BGREWRITEAOF,重写AOF

【增量复制】

1)在进行全量复制的过程中,连接中断,那么slave重新连接master时,触发增量复制

2)master直接从自己的backlog中获取部分丢失的数据,发送给slave,默认backlog是1m

3)master是根据slave发送的psync中的offset来从backlog中获取数据

【heartbeat】

主从节点互相都会发送heartbeat信息

master默认每隔10秒发送一次heartbeat,salve node每隔1秒发送一个heartbeat

【redis数据丢失的问题】

1)异步复制导致的数据丢失问题

【原因】master到slave是异步的,所以可能有部分数据还没复制到slave,master就宕机了,数据会丢失。

2)脑裂导致的丢失数据问题

【原因】发生了脑裂,,但是客户端依旧向旧的redis里面写数据。当数据重新挂到新的redis下面,会把自己的数据清空,就会导致数据丢失的问题。

【上面两种问题的解决方案】

min-slaves-to-write 1s
min-slaves-max-lag 10

要求至少有1个slave,数据复制和同步的延迟不能超过10秒

如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了

6.哨兵模式的算法

【sdown和odown两种失败状态】

sdown是主观宕机,就一个哨兵自己觉得master宕机了。

odown是客观宕机,如果quorum数量的哨兵都觉得一个master宕机了,那么就是客观宕机

sdown达成的条件很简单,如果一个哨兵ping一个master,超过了is-master-down-after-milliseconds指定的毫秒数之后,就主观认为master宕机

sdown到odown转换的条件很简单,如果一个哨兵在指定时间内,收到了quorum指定数量的其他哨兵也认为那个master是sdown了,那么就认为是odown了,客观认为master宕机

【哨兵集群的自动发现机制】

订阅模式,__sentinel__:hello,每隔2秒钟监听

每个哨兵,监控自己的通道,其它哨兵订阅。

【slave->master选举算法】
(1)跟master断开连接的时长
(2)slave优先级
(3)复制offset
(4)run id

如果一个slave跟master断开连接已经超过了down-after-milliseconds的10倍,外加master宕机的时长,那么slave就被认为不适合选举为master

接下来会对slave进行排序

(1)按照slave优先级进行排序,slave priority越低,优先级就越高
(2)如果slave priority相同,那么看replica offset,哪个slave复制了越多的数据,offset越靠后,优先级就越高
(3)如果上面两个条件都相同,那么选择一个run id比较小的那个slave

6.redis的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?

【rdb持久化的优缺点】

rdb优点

1)适合做冷备份,同步到阿里云等云服务器做备份。

2)对外提供的读写服务,性能影响非常小。可以让redis保持高性能,因为redis主线程只需要fork一个子线程。

3)相对于aof持久化机制来说,直接基于RDB数据文件来重启和恢复redis进程,更加快速

缺点

1.每隔5分钟生成一次快照文件,可能会导致部分数据丢失。

2.在fork子线程来去同步数据给slave时,如果rdb文件过大,可能会导致对客户端的服务暂停几毫秒或者甚至数秒。

aof优点

1.每隔1秒同步一次数据,最多丢失1秒的数据

2.直接拼接的方式写入,写入性能非常高,而且文件不容易破损,容易修复。

3.文件过大进行压缩,但是压缩是搞出来一份最小的恢复数据的日志,再创建日志文件的过程中,老的日志文件还是照常写入。

4.适合做灾难性误删除的紧急恢复。因为只要没有进行rewrite操作,命令就可以拿来直接直接执行。

缺点

1.文件更大

2.因为要每秒执行fsync一次日志执行,会导致支持写的qps会低一些。

3.数据恢复可能无法恢复成一摸一样的数据。因为有rewrite,merge等一系列复杂的操作的影响。

7.redis cluster

【redis cluster的hashslot算法】

     redis cluster有固定的16384个hash slot,对每个key计算crc16,然后对16384进行取模

     对于增加或者移除,只需要把部分数据进行迁移即可。

8.了解什么是redis的雪崩和穿透?redis雪崩之后会怎么样?系统该如何应对这种情况?如何处理redis的穿透?

【缓存雪崩的解决方案】

事前:高可用

事中:本地缓存ehcathe+ hystrix限流&降级,避免mysql被打死

事后:redis持久化,快速恢复缓存数据

【缓存穿透】

只要数据库中没有查到,就写个空值到数据库中。

9.如何保证缓存与数据库的双写一致性?

【简单的解决方案】

(1)读的时候,先读缓存,缓存没有的话,那么就读数据库,然后取出数据后放入缓存,同时返回响应

(2)更新的时候,先删除缓存,然后再更新数据库

【复杂的解决方案】

当你先删除了缓存,还没来的及更新数据库的时候,另一个请求去数据库读取了旧的数据,这时候就会造成复杂的缓存不一致问题。这时候可以用jvm队列来解决,将更新的请求放到队列中,另一个读请求也放到队列中。在读请求时,可以设置请求时间,在设置的请求时间内不断轮训,如果获取到了就反回,如果没获取到就去数据库中查数据。

读请求长时间阻塞

如果一个内存队列里居然会挤压100个商品的库存修改操作,每隔库存修改操作要耗费10ms区完成,那么最后一个商品的读请求,可能等待10 * 100 = 1000ms = 1s后,才能得到数据

这个时候就导致读请求的长时阻塞

加机器,加队列

读请求并发量过高?

多服务实例部署的请求路由?

热点商品的路由问题,导致请求的倾斜?

10.redis的并发竞争问题是什么?如何解决这个问题?了解Redis事务的CAS方案吗?

某个时刻,多个系统实例都去更新redis某个key,首先呢从redis取出数据的时候带上时间戳,然后利用分布式锁加锁执行,如果当前执行的时间戳大于已经执行的时间戳就执行,如果小于的话就不执行了。

11.生产环境中的redis是怎么部署的?

你可能感兴趣的:(——Redis,ζ框架技术)