Redis系列一 - 入门篇

问:项目中为何要选用Redis?

  答:传统的关系型数据库(如MySQL)已经不适用所有的场景了,比如美云销抢单活动的库存扣减,APP首页的访问流量高峰等等,都容易把数据库打崩,所以引入了缓存中间件,目前市场上比较常用的缓存中间件有Redis 和 Memcached不过综合考虑了他们的优缺点,最后选择了Redis 

问:Redis 和 Memcached 有啥区别,你们为何选择Redis作为缓存中间件?

  答:

 Redis的好处:

  1. 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作时间的复杂度都是0(1);
  2. 支持丰富的数据类型;
  3. 支持事物,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行;
  4. 丰富的特性:可用于缓存、消息、按 key 设置过期时间、过期后将会自动删除等;

 Redis 支持复杂的数据结构

  Redis 相比 Memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作,Redis 会是不错的选择。

 Redis 原生支持集群模式

  在 redis3.x 版本中,便能支持 Cluster模式,而 Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。

 性能对比

  由于 Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上,Redis 在存储小数据时要比 Memcached 性能更高。而在 100k 以上的数据中,Memcached 性能要高于 Redis,虽然 Redis 最近也在存储大数据的性能上进行优化,但是比起 Memcached 还是稍逊一筹。

  Tip:其实面试官这么问,是想看你知道为啥要用这个技术栈吗,你为什么选这个技术栈,你是否做过技术选型的对比,优缺点你是否了解,你什么都不知道,只是知道怎么用,那你面试可能就差点了。

问:Redis的线程模型了解吗?

  答:Redis 内部使用文件事件处理器(file event handler),这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器进行处理。

  文件事件处理器的结构包含4个部分:

  1. 多个 Socket
  2. IO 多路复用程序;
  3. 文件事件分派器;
  4. 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器);

  多个 Socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 Socket,会将 Socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。

问:如果有大量的Key需要设置同一时间过期,需要注意什么?

  答:如果大量的 Key 过期时间设置得过于集中,到过期的那个时间点,Redis可能回出现短暂的卡顿现象,严重的话可能会出现雪崩。我们一般在过期时间上加一个随机值,使得过期时间分散一些,这样也能有效地预防雪崩。

  电商首页经常会使用定时任务刷新缓存,可能大量的数据的失效时间都十分集中,如果失效时间一样,又刚好在失效的时间点有大量用户涌入,就有可能造成缓存雪崩。

问:你使用过分布式锁吗?它是怎么回事?

  答:先拿 setnx( ) 来争抢锁,抢到之后,再用 xepire( ) 给锁加一个过期时间防止锁忘记释放。

   传送门:《Redis分布式锁的实现方式

问:这时候对方可能会说你回答不错,然后接着问,如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎样?

  答:(此时可以适当做出惊讶的反馈)对哦,如果遇到这种情况,这个锁就永远得不到释放了。紧接着你需要抓一抓自己的脑袋,故作思考片刻,好像接下来的结果是你主动思考出来的,然后继续回答:我记得 set 指令有非常复杂的参数,这个应该是可以同时把 setnx 和 expire 合成一条指令来用的,这样就不会出现这种情况了。

插播:秒杀场景如何对分布式锁进行高并发优化?下单时,怎么防止库存超卖?

  传送门:每秒上千订单的场景下,如何对分布式锁进行高并发优化?

问:假如Redis里面有1亿个key,其中有10W个key是以某个固定的已知的前缀开头,如何将它们全部找出来?

  答:使用keys指令可以扫出指定模式的key列表。

追问:如果这个redis正在给线上的业务提供服务,那是用keys指令会有什么问题?

  答:这个时候要回答 Redis 关键的一个特性:Redis 是单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才会恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞地提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

  不过,增量式迭代命令也不是没有缺点的:举个例子,使用 SMEMBERS 命令可以返回集合键当前包含的所有元素,但是对于 SCAN 这类增量式迭代命令来说,因为对键进行增量式迭代的过程中,键可能会被修改,所以增量式迭代命令只能对被返回的元素提供有限的保证。

问:使用Redis做异步队列吗?你是怎么用的?

  答:一般使用 List 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep 一会儿再试。

追问:可不可以不使用 sleep 呢?

  答:list 还有个指令叫 blpop,在没有消息的时候,它会阻塞住,直到消息到来。

追问:能不能生产一次,消费多次呢?

  答:使用 pub/sub 主题订阅模式,可以实现 1:N 的消息队列。

追问: pub/sub 有什么缺点?

  答:在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列,如 RocketMQ 等。

终极追问:Redis如何实现延时队列?

  答:(稳住,淡定)使用 sortedset,那时间戳作为 score,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令获取N秒之前的数据轮询进行处理。

问:Redis是怎么持久化的?服务主从数据怎么交互的?

  答:RDB做镜像全量持久化,AOF做增量持久化。因为RDB会耗费较长时间,不够实时,在停机的时候会导致大量数据丢失,所以需要AOF来配合使用。在 Redis 示例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。

  这个很好理解,把RDB理解为一整个表全量的数据,AOF理解为每次操作的日志就好了,服务器重启的时候先把表的数据全搞进去,但是他可能不完整,你再回放一下日志,数据不就完整了嘛。不过Redis本身的机制是AOF持久化开启且存在AOF文件时,优先加载AOF文件;AOF关闭或者AOF文件不存在时,加载RDB文件;加载AOF/RDB文件域后,Redis启动成功;AOF/RDB文件存在错误时,Redis启动失败病打印错误信息。

追问:那如果机器突然掉电会怎样?

  答:取决于AOF日志 sync 属性的配置,如果不要求性能,在每条写指令时都 sync 一下磁盘,就不会丢失数据。但是在高性能要求下每次都sync是不现实的,一般都是用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

追问:RDB的原理是什么?

  答:给出两个词汇就可以了,fork 和 cow 。fork 是指 redis 通过创建子进程来进行 RDB 操作,cow 是指 copy on write,子进程创建之后,父子进程共享数据段,父进程继续提供读写服务,写脏的也米恩数据会逐渐和子进程分离开来。

  注:回答这个问题的时候,如果你还能说出 AOF 和 RDB 的优缺点,面试官可能会在这个问题上给你点赞,两者的区别还是挺大的,而且涉及到Redis集群的数据同步问题等等。

问:Pipeline有什么好处,为什么要用Pipeline?

  答:可以将多次 IO 往返的时间缩减为一次,前提是 pipeline 执行的指令之间没有因果关系性。使用 redis-benchmark 进行压测的时候可以发现影响 redis 的QPS峰值的一个重要因素是 pipeline 批次指令的数目。

问:Redis的同步机制了解吗?

  答:Redis 可以使用主从同步,丛丛同步。第一次同步时,主节点做一次 bgsave,并同时将后续修改操作记录到内存 buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通过主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的 binlog。

问:是否使用过Redis集群,集群的高可用怎么保证,集群的原理是什么?

  答:Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。

    Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行分片存储。

问:你有没有考虑过,如果你多个系统同时操作(并发)Redis带来的数据问题?

  答:这个问题我以前开发的时候遇到过,其实并发过程中确实会有这样的问题,比如下面这样的情况

 

  系统A、B、C三个系统,分别取操作Redis的同一个Key,本来顺序1,2,3 是正常的,但是因为系统 A 网络抖动了一下,B、C 在它前面操作了 Redis,这样数据不就错了吗?

  就好比下单、支付、退款三个顺序变了,你先退款,再下单,再支付,那么流程就会失败,那数据不就乱了?订单还没生成就支付、退款,明显是不行的,这在线上是很恐怖的事情。

问:这种情况应该怎么解决?

  我们可以找个管家帮我们管理数据的嘛!

  某个时刻,多个系统实例都去更新某个key,可以基于 Zookeeper 实现分布式锁。每个系统通过 Zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 Key,别人不允许读和写。

  你要写入缓存的数据,都是从 MySQL 里面查出来的,都得写入 MySQL 中,写入 MySQL 中的时候,必须保存一个时间戳,从 MySQL 查出来的时候,时间戳也查出来。

  每次要写之前,先判断一下当前这个 Value 的时间戳是否比缓存里的 Value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。

问:你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?

  一般来说,如果允许缓存可以稍微地跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求“缓存+数据库”必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去。

  串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。

  把一些列的操作都放到队列里面,顺序肯定不会乱,但是并发高了,这队列会让你容易阻塞,反而会成为整个系统的弱点、瓶颈。

问:你了解最经典的KV、DB读写模式吗?

  答:最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern

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

问:为什么是删除缓存,而不是更新缓存?

  答:原因很简单,很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。

  比如可能更新了某个表的某一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值。

  另外更新缓存的代价有时候是很高的。是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份?也许有的场景是这样的,但是对于比较复杂的缓存数据计算的场景就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新,但是问题在于,这个缓存到底会不会被频繁访问到

  举个栗子:一个缓存涉及的表字段,在一分钟内就修改了30次,或者是50次,那么缓存更新30次、50次;但是这个缓存在1分钟内只被读取了 1 次,有大量的冷数据

  实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低,用到缓存才去计算缓存

  其实删除缓存,而不是更新缓存,就是一个 Lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。

  想 MyBatis、Hibernate,都有懒加载思想。查询一张订单,订单带了商品的 List,没必要说每次查询订单,都把里面的100个商品的数据也同时查出来啊。80%的情况,查这张订单,就只是要查看这张订单的价钱或其他信息就可以了。先查订单,同时要访问里面的商品,那么这个时候只有在你要访问里面商品的时候,才会去数据库里面查询 100个商品。

小结:

  在面技术的时候,不管是Redis还是什么问题,如果你能举出实际的例子,或者是直接说自己开发过程的问题和收获,会给面试官留下很好的印象,回答逻辑性要强一些,不要东一点西一点,容易把自己绕晕。

  还有一点就是,我问你为啥要用Redis,你不要一上来就直接回答问题了,你可以这样回答:

  首先,我们的项目DB遇到了瓶颈,特别是秒杀和热点数据(抢单活动)这样的场景,DB基本就扛不住了,那就需要缓存中间件的加入,目前市面上的缓存中间件有 Redis 和 Memcached,它们的优缺点…,综合这些,再结合我们项目的特点,最后我们在技术选型的时候选了Redis。

  如果这样有条不紊,有理有据地回答了我的问题,而且还说出这么多我问题外的知识点,我会觉得你不只是一个会写代码的人,你逻辑清晰,你对技术选型,对中间键、对项目都有自己的理解和思考,是个人才。

 

posted on 2019-11-19 20:51  Leung_柠檬先生  阅读(225)  评论(0编辑  收藏  举报

导航