Redis面试题

Redis相比memcached有哪些优势?

这是一道非常常见的面试题,也是大家在工作中很容易忽略掉的点,大部分场景下redis确实更适合用于我们项目,但是我们可能答不上来它们都作为键值对数据库其中的区别是什么。
从数据结构侧来说,memcached仅支持value为string类型,而我们redis支持的类型是相当丰富的,有string、hash、list、set、sort set等等,所以在功能上redis是比我们memcached支持的更好的。还有就是memcached的单value值容量只有1M,而我们的redis则最大支持至512M。
从数据持久化来说,memcached只做缓存,没有可靠性的需求,所以是不支持的,只要断电或者服务关闭之后那么就会丢失内存中的数据,而redis更倾向于内存数据库,如果我们有持久化需求的话可以优先考虑redis。
同时我们的redis还支持lua脚本,脚本提交是原子执行的,我们在面对复杂业务场景中,需要保证按照我们所需的顺序一步步执行就可以通过我们的lua脚本来解决。

Redis真的是单线程吗?

所谓的redis单线程其实指的是在网络IO和键值对读写时是通过一个线程完成的。而其他的一些模块比如说持久化存储、集群支撑模块这些都是多线程的。
那为什么网络操作模块和数据存储模块不用多线程呢?
其实非常简单,首先网络IO模块的性能瓶颈就不在CPU上,而是要提升我们的IO利用率,虽然使用多线程能带来一些提升,但是多线程也是存在一定的弊端的,首先是多线程模型下的共享资源和并发控制非常复杂,线程的上线文切换也会带来一定的性能损耗,所以Redis在这块采用的是IO多路复用。
另一方面,Redis的绝大部分操作都是在内存中完成的,内存操作本来就比硬盘读写快了百倍以上,并且在数据结构上也进行了大量的优化,比如hash表和跳表。而使用单线程还能避免多线程下的锁竞争,省去了线程的时间和性能开销也不会存在锁竞争的问题。

Redis6为何引入多线程?

redis6中引入的多线程是正对于网络IO模块进行了多线程改造,因为多路复用的IO模型本质上来说还是同步阻塞型IO模型,在调用epoll的过程是阻塞的,并发量极高的场景就成为了性能瓶颈,那么在碰到这类问题上,就可以通过多线程来解决。它通过多线程解决了网络IO等待造成的影响,还可以充分利用CPU的多核优势。对于我们读写模块依旧还是采用的单线程模型,避免了多线程环境下并发访问带来的很多问题。在简单的get/set命令性能上多线程IO模型提升了有接近一倍。

Redis是如何解决Hash冲突的?

redis是通过我们的链式hash来解决我们的hash冲突问题,哈希算法产生的哈希值的长度是固定并且是有限的,比如说我们通过MD5算法生成32位的散列值,那么它能生成出来的长度则是有限的,我们的数据如果大于32位是不是就可能存在不同数据生成同一个散列值,那么redis通过链式hash,以不扩容的前提下把有相同值的数据链接起来,但是如果链表变得很长就会导致性能下降,那么redis就采用了rehash的机制来解决,类似于hashmap里面的扩容机制,但是redis中的rehash并不是一次把hash表中的数据映射到另外一张表,而是通过了一种渐进式的方式来处理,将rehash分散到多次请求过程中,避免阻塞耗时。

Redis是如何处理过期的数据的?

我们可以通过expire来设置redis的过期时间
那么通常情况下我们有三种方式来进行处理过期数据:
定时删除,每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性删除,惰性删除其实就是当访问数据时判断,如果数据已经过期的话直接删除并返回null回去,它可以只占用很少的一部分系统资源来处理但是这种策略的也有一定的缺陷在面对冷数据会占用空间删除不及时,造成空间浪费,只能靠redis数据淘汰策略来进行处理。
另一种是随机删除,定期删除是每隔一段时间随机取出设置了过期时间的一定数量的key进行检查,然后删除其中过期的key。如果超过了百分之25%的数据过期了,就会再次执行。它通过限制删除操作的时长和频率来减少对CPU和内存空间占用的影响,但是它的删除效果并没有定时删除的效果好,同时也没有比惰性删除所占用的系统资源低。
而我们的redis则采用的是惰性删除与随机删除。

MySQL里有2000w数据Redis中只存20w的数据,如何保证 redis 中的数据都是热点数据?

首先我们可以看到Redis的空间时间上比我们MySQL少的多,那么Redis如何能够筛选出热点数据,这道题主要考察的是Redis的数据淘汰策略(这里有个误区,很多人容易混淆把数据淘汰策略当做数据过期策略),在Redis 4.0之后是为我们提供了8种淘汰策略,4.0之前则是提供的6种,主要是新增了LFU算法。其实说说是有8种,但是真正意义上是5种,针对random、lru、lfu是提供了两种不同数据范围的策略,一种是针对设置了过期时间的,一种是没有设置过期时间的。具体的五种策略分别为:
1noeviction 选择这种策略则代表不进行数据淘汰,同时它也是redis中默认的淘汰策略,当缓存写满时redis就不再提供写服务了,写请求则直接返回失败。
2random 随机策略这块则是分为两种,一种是volatile,这种是设置了过期时间得数据集,而另外一种是allkeys,这种是包含了所有的数据,当我们缓存满了的时候,选用这种策略就会在我们的数据集中进行随机删除。
3volatile-ttl 这种策略是针对设置了过期时间的数据,并且按照过期时间的先后顺序进行删除,越早过期的越先被删除
4lru 这里的lru策略和我们上面random策略一样也是提供了两种数据集进行处理,LRU算法全程为(最近最少使用)简单一句话来概括就是“如果数据最近被访问过,那么将来被访问的几率也就越高”。这种算法其实已经比较符合我们的实际业务需求了,但是还是存在一些缺陷。
5lfu 最后一种策略就是我们的LFU算法,它是在我么LRU算法基础上增加了请求数统计,这样能够更加精准的代表我们的热点数据。
我们再回看我们的这个问题,我们能很清楚的知道,我们需要的策略是LFU算法。选择volatile还是allkeys就要根据具体的业务需求了。

高并发场景下我们如何保证幂等性?

首先普及下幂等的概念“在计算机中编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。”
那么在我们的实际业务场景中幂等是一个非常高频的场景,比如:
●电商场景中用户因网络问题多次点击导致重复下单问题
●MQ消息队列的重复消费
●RPC中的超时重试机制
●等等
那么我们有那些方案可以解决我们的幂等性问题呢?
●数据库唯一主键实现幂等性
○其实现方式是使用分布式ID充当主键,不使用MySQL中的自增主键
●乐观锁实现幂等性
○在表中增加版本号标识,只有版本号标识一直才更新成功
●分布式锁
○简单来说就是分布式的排他锁,但是我们可以控制锁的粒度以提高程序的执行性能
●获取token
a服务端提供获取 Token 的接口,请求前客户端调用接口获取 Token
b然后将该串存入 Redis 数据库中,以该 Token 作为 Redis 的键(注意设置过期时间)。
c将 Token 返回到客户端,在执行业务请求带上该 Token
d服务端接收到请求后根据 Token 到 Redis 中查找该 key 是否存在(注意原子性),
e如果存在就将该 key 删除,然后正常执行业务逻辑。如果不存在就抛异常,返回重复提交的错误信息。

Redis如何保证与数据库的双写一致性 (北冥)

我们来分析一下这道面试题,这道题主要是偏实际应用的
缓存可以提升性能,减轻数据库压力,在获取这部分好处的同时,它却带来了一些新的问题,缓存和数据库之间的数据一致性问题。
想必大家在工作中只要用了咱们缓存势必就会遇到过此类问题,那这道题该如何回答呢?
首先我们来看看一致性:
●强一致性
●弱一致性
解决双写一致性方案:
●延迟双删
○延迟双删策略是分布式系统中数据库存储和缓存数据保持一致性的常用策略,但它不是强一致。
○实现思路:也是非常简单的,先删除缓存然后更新DB在最后延迟 N 秒去再去执行一次缓存删除
○弊端:小概率会出现不一致情况、耦合程度高
●通过MQ进行重试删除
○更新完DB之后进行删除,如果删除失败则向MQ发送一条消息,然后消费者不断进行删除尝试。
●binlog异步删除
○实现思路:低耦合的解决方案是使用canal。canal伪装成mysql的从机,监听主机mysql的二进制文件,当数据发生变化时发送给MQ。最终消费进行删除

谈谈缓存穿透、击穿、雪崩的区别,又如何去解决?(北冥)

面试题分析

这道题主要考察的是求职者是否具有高并发思维,它也是在面试中一道高频的考点

缓存穿透

缓存穿透代表的意思是在我们的缓存中没有找到缓存信息,那么我们在高并发场景下就会面临所有的请求都会直接打到DB,缓存则失去了它原本的意义,并且极有可能导致数据库压力过大而造成服务不可用。
●缓存空结果信息
●布隆过滤器(不存在的一定不存在,存在的可能不存在,通过bitmap实现,想深入布隆过滤器可以专门去看看这部分专题内容)
●过滤常见非法参数,拦截大部分无效请求()

缓存击穿

缓存击穿代表的意思是我们数据库中存在数据,但是缓存中不存在数据.这种场景一般是在缓存失效时发生的. 在高并发的场景下极有可能瞬间打垮数据库.
●我们可以考虑面对请求量大的热点接口直接将缓存设置永不过期.
●当然我们也可能碰到一些特殊场景不能设置永久缓存,那么我们可以在db为空时设置互斥锁,当查询完db更新至缓存时再释放锁

缓存雪崩

缓存雪崩代表是意思是我们在某一个时间段,碰到大量热点缓存数据过期导致大量请求直接打垮数据库
●我们可以考虑面对请求量大的热点接口直接将缓存设置永不过期.
●缓存过期时间可以设置一个随机的波动值,防止大量数据在同一时间过期

10分钟带你了解灰度发布

灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
全量发布
●回滚周期长
●BUG导致服务集群雪崩
●服务可用性差,影响用户体验
灰度发布
●降低发布影响面,提升用户体验
●可以做到不停机迁移
●回滚速度快
发布方案对比图

策略

零停机

生产流量测试

针对特定用户

机器资源成本

回滚时长

负面影响

实现复杂度

全量发布

×

×

×

蓝绿发布

×

×

高(双倍)

金丝雀发布

中(按需)

全链路灰度

中(按需)

金丝雀发布
据说以前有个典故,矿工开矿前,会先放一只金丝雀下去,看金丝雀是否能活下来,用来探测是否有毒气,金丝雀发布也是由此得名。
灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度,而我们平常所说的金丝雀部署也就是灰度发布的一种方式。
全链路灰度发布 全链路灰度治理策略主要专注于整个调用链,它不关心链路上经过具体哪些微服务,流量控制视角从服务转移至请求链路上,仅需要少量的治理规则即可构建出从网关到整个后端服务的多个流量隔离环境,有效保证了多个亲密关系的服务顺利安全发布以及服务多版本并行开发,进一步促进业务的快速发展。

雪花ID算法的前世今生

snowflake是Twitter开源的分布式ID生成算法,结果是64bit的Long类型的ID,有着全局唯一和有序递增的特点。
●最高位是符号位,因为生成的 ID 总是正数,始终为0,不可用。
●41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
●10位的机器标识,10位的长度最多支持部署1024个节点。
●12位的计数序列号,序列号即一系列的自增ID,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
缺点也是有的,就是强依赖机器时钟,如果机器上时钟回拨,有可能会导致主键重复的问题。

Redis 事务支持 ACID 么?

原子性(Atomicity):一个事务的多个操作必须完成,或者都不完成。
一致性(Consistency):事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后顺序都是合法数据状态。
隔离性(Isolation):事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(Durability):事务一旦提交,所有的修改将永久的保存到数据库中,即使系统崩溃重启后数据也不会丢失。

redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
单独的隔离操作
●事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念
●队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
不保证原子性
●事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
注:redis的discard只是结束本次事务,正确命令造成的影响仍然存在.
1)MULTI命令用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。
3)通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。