Redis面试题

Redis面试题

基础
1.什么是Redis?
	Redis 是 C 语言开发的一个开源的(遵从 BSD 协议)高性能键值对的内存数据库,可以用作数据库、缓存、消息中间件、分布式锁、支持发布订阅等。它是一种 NoSQL(非关系型数据库)数据库。性能优秀,数据在内存中,读写速度非常快,支持并发 10W QPS。单进程单线程,是线程安全的。采用 IO 多路复用机制。有丰富的数据类型。支持数据持久化。可以将内存中数据保存在磁盘中,重启时加载。主从复制,哨兵,集群。
2.为什么用Redis?
	因为传统的关系型数据库如Mysql已经不能适用所有的场景。比如秒杀的库存扣减,APP首页的访问流量高峰等等,很容易把数据库打崩,所以引入了缓存中间件,目前市面上比较常用的缓存中间件有Redis 和 Memcached 不过中和考虑了他们的优缺点,最后选择了Redis。
3.Redis 和 Memcached 的区别
1.Redis和Memcached都是将数据存放在内存中,都是内存数据库,不过memcache还用于缓存其他东西,例如:图片、视频等等
2.Redis不仅仅支持简单的k/v类型的数据,同时还提供list、set、hash等数据结构的存储
3.虚拟内存--Redis当物理内存用完时,可以将一些很久没用到的value交换到磁盘
4.过期策略--memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire设定,例如expire name 10;
5.分布式--设定memcached集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从
6.存储数据安全--memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化)
7.灾难恢复--memcached挂掉后,数据不可恢复;redis数据丢失后可以通过aof恢复
8.Redis支持数据的备份,即master-slave模式的数据备份
9.与 MC 不同的是,Redis 采用单线程模式处理请求。这样做的原因有2个:一个是因为采用了非阻塞的异步事件处理机制;另一个是缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价。
10.Redis支持持久化,所以 Redis 不仅仅可以用作缓存,也可以用作 NoSQL 数据库。
11.相比MC,Redis 还有一个非常大的优势,就是除了 K-V 之外,还支持多种数据格式,例如 list、set、sorted set、hash 等。
12.Redis 提供主从同步机制,以及Cluster集群部署能力,能够提供高可用服务。
4.Redis有哪些数据结构呀?
	常用的有五种:字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。还有几种数据结构HyperLogLog、Geo、Pub/Sub这几种是比较不常用。
	1.String 字符串类型:是redis中最基本的数据类型,一个key对应一个value。String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。使用:get、set、del、incr、decr等
	场景:
		1.缓存:经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql读写压力。
		2.计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。
		3.session:常见方案Spring session + redis实现session共享
	2.Hash:场景:缓存:能直观,相比string更节省空间的维护缓存信息,如用户信息,视频信息等。
	3.List(链表):说白了就是链表(redis使用双端链表实现的List),是有序的,value可以重复,可以通过下标取出对应的value值,左右两边都能进行插入和删除数据。
    场景:timeline:例如微博的时间轴,有人发布微博,用lpush加入时间轴,展示新的列表信息。类似粉丝列表、文章的评论列表之类的东西。
	4.Set 集合: 值是唯一的。无序。
		场景:
			1.标签(tag),给用户添加标签,或者用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
			2.点赞,或点踩,收藏等,可以放到set中实现
	5.SortedSet(有序集合):场景:排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。
	6.Bitmap :位图是支持按 bit 位来存储信息,可以用来实现 布隆过滤器(BloomFilter);
		HyperLogLog:供不精确的去重计数功能,比较适合用来做大规模数据的去重统计,例如统计 UV;
	7.Geospatial:可以用来保存地理位置,并作位置距离计算或者根据半径计算位置等。有没有想过用Redis来实现附近的人?或者计算最优地图路径?这三个其实也可以算作一种数据结构。
	8.pub/sub:功能是订阅发布功能,可以用作简单的消息队列。
	9.Pipeline:可以批量执行一组指令,一次性返回全部结果,可以减少频繁的请求应答。
	10.Lua:Redis支持提交 Lua 脚本来执行一系列的功能。
5.Redis为什么这么快?
	1.完全基于内存,绝大部分请求是纯粹的内存操作。它的数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
	2.数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
	3.采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
	4.使用多路I/O复用模型,非阻塞IO;
	5.使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM机制 ,一般系统调用系统函数的话,会浪费一定的时间去移动和请求;
6.什么是上下文切换么,为什么可能会线程不安全?
	打个比方:就好比你看一本英文书,你看到第十页发现有个单词不会读,你加了个书签,然后去查字典,过了一会你又回来继续从书签那里读,ok到目前为止没啥问题。如果是你一个人读肯定没啥问题,但是你去查的时候,别的小伙伴好奇你在看啥他就翻了一下你的书,你再看的时候就发现书不是你看的那一页了。为啥会线程不安全,就是因为你一个人怎么看都没事,但是人多了换来换去的操作一本书数据就乱了。
7.单线程的,我们现在服务器都是多核的,那不是很浪费?
他是单线程的,但是,我们可以通过在单机开多个Redis实例嘛。
8.单机会有瓶颈,那你们是怎么解决这个瓶颈的?
	我们用到了集群的部署方式也就是Redis cluster,并且是主从同步读写分离,类似Mysql的主从同步,Redis cluster支撑 Redis master node,每个master node都可以挂载多个slave node。
	这样整个Redis就可以横向扩容。如果你要支撑更大数据量的缓存,那就横向扩容更多的master节点,每个master节点就能存放更多的数据了。
9.他们之间是怎么进行数据交互的?以及Redis是怎么进行持久化的?Redis数据都在内存中,一断电或者重启不就木有了嘛?
	持久化是有两种方式:
		RDB:RDB 持久化机制,是对Redis中的数据执行周期性的持久化。
		AOF:AOF 机制对每条写入命令作为日志,以append-only的模式写入一个日志文件中,因为这个模式是只追加的方式,所以没有任何磁盘寻址的开销,所以很快,有点像Mysql中的binlog。
	两种方式都可以把Redis内存中的数据持久化到磁盘上,然后再将这些数据备份到别的地方去,RDB更适合做冷备,AOF更适合做热备,比如杭州的某电商公司有这两个数据,备份一份到杭州的节点,再备份一个到上海的,就算发生无法避免的自然灾害,也不会两个地方都一起挂吧,这灾备也就是异地容灾.
	两种机制全部开启的时候,Redis在重启的时候会默认使用AOF去重新构建数据,因为AOF的数据是比RDB更完整的。
10.RDB和AOF的优缺点
	RDB优点:
	他会生成多个数据文件,每个数据文件分别都代表了某一时刻Redis里面的数据,这种方式很适合做冷备,完整的数据运维设置定时任务,定时同步到远端的服务器,比如阿里的云服务,这样一旦线上挂了,你想恢复多少分钟之前的数据,就去远端拷贝一份之前的数据就好了。
RDB对Redis的性能影响非常小,因为在同步数据的时候他只是fork了一个子进程去做持久化的,而且数据恢复的速度比AOF快。
	RDB缺点:
	RDB都是快照文件,都是默认五分钟甚至更久的时间才会生成一次,意味着你这次同步到下次同步这中间五分钟的数据都很可能全部丢失掉。AOF则最多丢一秒的数据,数据完整性上高下立判。还有RDB在生成数据快照的时候,如果文件很大,客户端可能会暂停几毫秒甚至几秒,你公司在做秒杀的时候他刚好在这个时候fork了一个子进程去生成一个大快照,会出大问题。
	AOF优点:
	RDB五分钟一次生成快照,但是AOF是一秒一次去通过一个后台的线程fsync操作,那最多丢这一秒的数据。
	AOF在对日志文件进行操作的时候是以append-only的方式去写的,他只是追加的方式写数据,少了很多磁盘寻址的开销,写入性能惊人,文件也不容易破损。
	AOF的日志是通过一个叫非常可读的方式记录的,特性适合做灾难性数据误删除的紧急恢复,比如公司的实习生通过flushall清空了所有的数据,只要这个时候后台重写还没发生,你马上拷贝一份AOF日志文件,把最后一条flushall命令删了就完事。
	AOF缺点:一样的数据,AOF文件比RDB还要大。AOF开启后,Redis支持写的QPS会比RDB支持写的要低,每秒都要异步刷新一次日志fsync。
11.如果有大量的key需要设置同一时间过期,一般需要注意什么?
	如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。严重的话会出现缓存雪崩,我们一般需要在时间上加一个随机值,使得过期时间分散一些。
4.内存管理机制+回收机制

1:内存管理

	内容包括:过期键的懒性删除和过期删除以及内存溢出控制策略。
	Redis的过期策略,是有定期删除+惰性删除两种。
	原因:当Redis保存大量的键,对每个键都进行精准的过期删除可能会导致消耗大量的 CPU,会阻塞Redis的主线程.定期:默认100ms就随机抽一些设置了过期时间的key,去检查是否过期,过期了就删了(通过配置控制)。定时任务中删除过期键逻辑采用了自适应算法,根据键的过期比例、使用快慢两种速率模式回收键.

img

1.1最大内存限制
	Redis使用 maxmemory 参数限制最大可用内存,默认值为0,表示无限制。限制内存的目的主要有:
	用于缓存场景,当超出内存上限 maxmemory 时使用 LRU 等删除策略释放空间。防止所用内存超过服务器物理内存。因为 Redis 默认情况下是会尽可能多使用服务器的内存,可能会出现服务器内存不足,导致Redis进程被杀死。
	maxmemory 限制的是Redis实际使用的内存量,也就是 used_memory统计对应的内存。由于内存碎片率的存在,实际消耗的内存可能会比maxmemory设置的更大,实际使用时要小心这部分内存溢出。Redis默认无限使用服务器内存,为防止极端情况下导致系统内存耗尽,建议所有Redis进程都要配置maxmemory。在保证物理内存可用的情况下,系统中所有Redis实例可以调整 maxmemory参数来达到自由伸缩内存的目的。
	used_memory包括自身内存,对象内存,缓冲内存。

2:内存回收

	Redis 回收内存大致有两个机制:一是删除到达过期时间的键值对象;二是当内存达到 maxmemory 时触发内存移除控制策略,强制删除选择出来的键值对象。
3:内存溢出控制策略
	当Redis所用内存达到maxmemory上限时会触发相应的溢出控制策略。具体策略受maxmemory-policy参数控制,Redis支持6种策略:
	1)noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息(error)OOM command not allowed when used memory,此时Redis只响应读操作。
	2)volatile-lru:根据LRU算法删除设置了超时属性(expire)的键,直到腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
	3)allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性, 直到腾出足够空间为止。
	4)allkeys-random:随机删除所有键,直到腾出足够空间为止。
	5)volatile-random:随机删除过期键,直到腾出足够空间为止。
	6)volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略。
	内存溢出控制策略可以使用 config set maxmemory-policy {policy} 语句进行动态配置。
	当设置 volatile-lru 策略时,保证具有过期属性的键可以根据 LRU 剔除,而未设置超时的键可以永久保留。还可以采用allkeys-lru 策略把 Redis 变为纯缓存服务器使用。
	当Redis因为内存溢出删除键时,可以通过执行 info stats 命令查看 evicted_keys 指标找出当前 Redis 服务器已剔除的键数量。
	每次Redis执行命令时如果设置了maxmemory参数,都会尝试执行回收内存操作。当Redis一直工作在内存溢出(used_memory>maxmemory)的状态下且设置非 noeviction 策略时,会频繁地触发回收内存的操作,影响Redis服务器的性能。
4.为啥不扫描全部设置了过期时间的key呢?
	假如Redis里面所有的key都有过期时间,都扫描一遍?那太恐怖了,而且我们线上基本上也都是会设置一定的过期时间的。全扫描跟你去查数据库不带where条件不走索引全表扫描一样,100ms一次,Redis累都累死了。
5.如果一直没随机到很多key,里面不就存在大量的无效key了?
	好问题,惰性删除,见名知意,惰性嘛,我不主动删,我懒,我等你来查询了我看看你过期没,过期就删了还不给你返回,没过期该怎么样就怎么样。
官网上给到的内存淘汰机制是以下几个:
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
6.同步机制
	Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog。
7.key 失效机制
	Redis 的 key 可以设置过期时间,过期后 Redis 采用主动和被动结合的失效机制,一个是和 MC 一样在访问时触发被动删除,另一种是定期的主动删除。
	定期+惰性+内存淘汰
持久化
2.Redis是怎么持久化的?服务主从数据怎么交互的?
	RDB做镜像全量持久化,AOF做增量持久化。因为RDB会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要AOF来配合使用。在redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。
	这里很好理解,把RDB理解为一整个表全量的数据,AOF理解为每次操作的日志就好了,服务器重启的时候先把表的数据全部搞进去,但是他可能不完整,你再回放一下日志,数据就完整了。不过Redis本身的机制是 AOF持久化开启且存在AOF文件时,优先加载AOF文件;AOF关闭或者AOF文件不存在时,加载RDB文件;加载AOF/RDB文件城后,Redis启动成功;AOF/RDB文件存在错误时,Redis启动失败并打印错误信息
3.如果突然机器掉电会怎样?
	取决于AOF日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。
4.RDB的原理是什么?
fork和COW。fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
5.Pipeline有什么好处,为什么要用pipeline?
	可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。
缓存
1.缓存有哪些类型?
	缓存的类型分为:本地缓存、分布式缓存和多级缓存。
	1.本地缓存:
    本地缓存就是在进程的内存中进行缓存,比如我们的 JVM 堆中,可以用 LRUMap 来实现,也可以使用 Ehcache 这样的工具来实现。
    本地缓存是内存访问,没有远程交互开销,性能最好,但是受限于单机容量,一般缓存较小且无法扩展。
	2.分布式缓存:
    分布式缓存可以很好得解决这个问题。
    分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。缺点就是需要进行远程请求,性能不如本地缓存。
	3.多级缓存:
    为了平衡这种情况,实际业务中一般采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其他的热点数据放在分布式缓存中。
    在目前的一线大厂中,这也是最常用的缓存方案,单考单一的缓存方案往往难以撑住很多高并发的场景。
2.缓存更新方式
	这是决定在使用缓存时就该考虑的问题。
	缓存的数据在数据源发生变更时需要对缓存进行更新,数据源可能是 DB,也可能是远程服务。更新的方式可以是主动更新。数据源是 DB 时,可以在更新完 DB 后就直接更新缓存。当数据源不是 DB 而是其他远程服务,可能无法及时主动感知数据变更,这种情况下一般会选择对缓存数据设置失效期,也就是数据不一致的最大容忍时间。
	这种场景下,可以选择失效更新,key 不存在或失效时先请求数据源获取最新数据,然后再次缓存,并更新失效期。
	但这样做有个问题,如果依赖的远程服务在更新时出现异常,则会导致数据不可用。改进的办法是异步更新,就是当失效时先不清除数据,继续使用旧的数据,然后由异步线程去执行更新任务。这样就避免了失效瞬间的空窗期。另外还有一种纯异步更新方式,定时对数据进行分批更新。实际使用时可以根据业务场景选择更新方式。
3.数据不一致
	第二个问题是数据不一致的问题,可以说只要使用缓存,就要考虑如何面对这个问题。缓存不一致产生的原因一般是主动更新失败,例如更新 DB 后,更新Redis因为网络原因请求超时;或者是异步更新失败导致。 
	解决的办法是,如果服务对耗时不是特别敏感可以增加重试;如果服务对耗时敏感可以通过异步补偿任务来处理失败的更新,或者短期的数据不一致不会影响业务,那么只要下次更新时可以成功,能保证最终一致性就可以。
4.缓存穿透
	缓存穿透。产生这个问题的原因可能是外部的恶意攻击,例如,对用户信息进行了缓存,但恶意攻击者使用不存在的用户id频繁请求接口,导致查询缓存不命中,然后穿透 DB 查询依然不命中。这时会有大量请求穿透缓存访问到 DB。
	缓存穿透是指缓存和数据库中都没有的数据,而用户(黑客)不断发起请求。
	举个栗子:我们数据库的 id 都是从 1 自增的,如果发起 id=-1 的数据或者 id 特别大不存在的数据,这样的不断攻击导致数据库压力很大,严重会击垮数据库。
	解决的办法如下。
	对不存在的用户,在缓存中保存一个空对象进行标记,防止相同 ID 再次访问 DB。不过有时这个方法并不能很好解决问题,可能导致缓存中存储大量无用数据。
	使用 BloomFilter 过滤器,BloomFilter 的特点是存在性检测,如果 BloomFilter 中不存在,那么数据一定不存在;如果 BloomFilter 中存在,实际数据也有可能会不存在。非常适合解决这类的问题。
5.缓存击穿
	缓存击穿,就是某个热点数据失效时,大量针对这个数据的请求会穿透到数据源。
	缓存击穿跟缓存雪崩有点像,但是又有一点不一样,缓存雪崩是因为大面积的缓存失效,打崩了 DB。
	缓存击穿不同的是缓存击穿是指一个 Key 非常热点,在不停地扛着大量的请求,大并发集中对这一个点进行访问,当这个 Key 在失效的瞬间,持续的大并发直接落到了数据库上,就在这个 Key 的点上击穿了缓存。
	解决这个问题有如下办法:
	缓存穿透我会在接口层增加校验,比如用户鉴权,参数做校验,不合法的校验直接 return,比如 id 做基础校验,id<=0 直接拦截。
	Redis 里还有一个高级用法布隆过滤器(Bloom Filter)这个也能很好的预防缓存穿透的发生。
它的原理也很简单,就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你 return 就好了,存在你就去查 DB 刷新 KV 再 return。
	缓存击穿的话,设置热点数据永不过期,或者加上互斥锁就搞定了。
	可以使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到 DB,减小 DB 压力。
	使用随机退避方式,失效时随机 sleep 一个很短的时间,再次查询,如果失败再执行更新。
	针对多个热点 key 同时失效的问题,可以在缓存时使用固定时间加上一个小的随机数,避免大量热点 key 同一时刻失效。
6.缓存雪崩
	缓存雪崩,产生的原因是缓存挂掉,这时所有的请求都会穿透到 DB。
	目前电商首页以及热点数据都会去做缓存,一般缓存都是定时任务去刷新,或者查不到之后去更新缓存的,定时任务刷新就有一个问题。
举个栗子:如果首页所有 Key 的失效时间都是 12 小时,中午 12 点刷新的,我零点有个大促活动大量用户涌入,假设每秒 6000 个请求,本来缓存可以抗住每秒 5000 个请求,但是缓存中所有 Key 都失效了。此时 6000 个/秒的请求全部落在了数据库上,数据库必然扛不住,真实情况可能 DBA 都没反应过来直接挂了。
	解决方法:
	处理缓存雪崩简单,在批量往 Redis 存数据的时候,把每个 Key 的失效时间都加个随机值就好了,这样可以保证数据不会再同一时间大面积失效。setRedis(key, value, time+Math.random()*10000);如果 Redis 是集群部署,将热点数据均匀分布在不同的 Redis 库中也能避免全部失效。或者设置热点数据永不过期,有更新操作就更新缓存就好了(比如运维更新了首页商品,那你刷下缓存就好了,不要设置过期时间),电商首页的数据也可以用这个操作,保险。
	使用快速失败的熔断策略,减少 DB 瞬间压力;
	使用主从模式和集群模式来尽量保证缓存服务的高可用。
事务

Redis 提供的不是严格的事务,Redis 只保证串行执行命令,并且能保证全部执行,但是执行命令失败时并不会回滚,而是会继续执行下去。

哨兵
功能:
- 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。
- 消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
- 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵必须用三个实例去保证自己的健壮性的,哨兵+主从并不能保证数据不丢失,但是可以保证集群的高可用。
为啥必须要三个实例呢?
	master宕机了 s1和s2两个哨兵只要有一个认为你宕机了就切换了,并且会选举出一个哨兵去执行故障,但是这个时候也需要大多数哨兵都是运行的。
	那这样有啥问题呢?M1宕机了,S1没挂那其实是OK的,但是整个机器都挂了呢?哨兵就只剩下S2个裸屌了,没有哨兵去允许故障转移了,虽然另外一个机器上还有R1,但是故障转移就是不执行。
原理:
	当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。
哨兵机制建立了多个哨兵节点(进程),共同监控数据节点的运行状况。
同时哨兵节点之间也互相通信,交换对主从节点的监控状况。
每隔1秒每个哨兵会向整个集群:Master主服务器+Slave从服务器+其他Sentinel(哨兵)进程,发送一次ping命令做一次心跳检测。
这个就是哨兵用来判断节点是否正常的重要依据,涉及两个新的概念:主观下线和客观下线。
	主观下线:一个哨兵节点判定主节点down掉是主观下线。
	客观下线:只有半数哨兵节点都主观判定主节点down掉,此时多个哨兵节点交换主观判定结果,才会判定主节点客观下线。
	基本上哪个哨兵节点最先判断出这个主节点客观下线,就会在各个哨兵节点中发起投票机制Raft算法(选举算法),最终被投为领导者的哨兵节点完成主从自动化切换的过程。
主从复制、哨兵和集群三者区别
主从复制是为了数据备份,哨兵是为了高可用,Redis主服务器挂了哨兵可以切换,集群则是因为单实例能力有限,搞多个分散压力.
集群+主从复制
1.是否使用过Redis集群,集群的高可用怎么保证,集群的原理是什么?
  Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
  Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
	Redis 支持主从同步,提供 Cluster 集群部署模式,通过 Sentinel哨兵来监控 Redis 主服务器的状态。当主挂掉时,在从节点中根据一定策略选出新主,并调整其他从 slave到新主。 
  选主的策略简单来说有三个:
  - slave 的 priority 设置的越低,优先级越高;
  - 同等情况下,slave 复制的数据越多优先级越高;
  - 相同的条件下 runid 越小越容易被选中。
	在 Redis 集群中,sentinel 也会进行多实例部署,sentinel 之间通过 Raft 协议来保证自身的高可用。
	Redis Cluster 使用分片机制,在内部分为 16384 个slot插槽,分布在所有 master 节点上,每个 master 节点负责一部分 slot。数据操作时按 key 做 CRC16 来计算在哪个 slot,由哪个 master 进行处理。数据的冗余是通过 slave 节点来保障。
	这样的主从架构模式,前面提到了单机QPS是有上限的,而且Redis的特性就是必须支撑读高并发的,那你一台机器又读又写,这谁顶得住啊,不当人啊!但是你让这个master机器去写,数据同步给别的slave机器,他们都拿去读,分发掉大量的请求那是不是好很多,而且扩容的时候还可以轻松实现水平扩容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

分布式
1.使用过Redis分布式锁么,它是什么回事?
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
2.如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!
3.假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。
4.如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
	redis关键的一个特性:redis单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
5.使用过Redis做异步队列么,你是怎么用的?
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
6.如果对方追问可不可以不用sleep呢?
list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。
7.如果对方接着追问能不能生产一次消费多次呢?
使用pub/sub主题订阅者模式,可以实现 1:N 的消息队列。
8.pub/sub有什么缺点?
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如RocketMQ等。
9.Redis如何实现延时队列?
使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。
10.Redis分布式锁原理
	使用setnx、getset、expire、del这4个redis命令实现
	setnx 是『SET if Not eXists』(如果不存在,则 SET)的简写。 命令格式:SETNX key value;使用:只在键 key 不存在的情况下,将键 key 的值设置为 value 。若键 key 已经存在, 则 SETNX 命令不做任何动作。返回值:命令在设置成功时返回 1 ,设置失败时返回 0 。
	getset 命令格式:GETSET key value,将键 key 的值设为 value ,并返回键 key 在被设置之前的旧的value。返回值:如果键 key 没有旧值, 也即是说, 键 key 在被设置之前并不存在, 那么命令返回 nil 。当键 key 存在但不是字符串类型时,命令返回一个错误。
	expire 命令格式:EXPIRE key seconds,使用:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。返回值:设置成功返回 1 。当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0 。
	del 命令格式:DEL key [key …],使用:删除给定的一个或多个 key ,不存在的 key 会被忽略。返回值:被删除 key 的数量。

在这里插入图片描述

	过程分析:
	1.A尝试去获取锁lockkey,通过setnx(lockkey,currenttime+timeout)命令,对lockkey进行setnx,将value值设置为当前时间+锁超时时间;
	2.如果返回值为1,说明redis服务器中还没有lockkey,也就是没有其他用户拥有这个锁,A就能获取锁成功;
	3.在进行相关业务执行之前,先执行expire(lockkey),对lockkey设置有效期,防止死锁。因为如果不设置有效期的话,lockkey将一直存在于redis中,其他用户尝试获取锁时,执行到setnx(lockkey,currenttime+timeout)时,将不能成功获取到该锁;
	4.执行相关业务;
	5.释放锁,A完成相关业务之后,要释放拥有的锁,也就是删除redis中该锁的内容,del(lockkey),接下来的用户才能进行重新设置锁新值。
	缺陷:
	如果A在setnx成功后,A成功获取锁了,也就是锁已经存到Redis里面了,此时服务器异常关闭或是重启,将不会执行closeOrder,也就不会设置锁的有效期,这样的话锁就不会释放了,就会产生死锁。
	解决方法:
	关闭Tomcat有两种方式,一种通过温柔的执行shutdown关闭,一种通过kill杀死进程关闭
	或者将setnx 和expire命令合并为一条命令执行

为了解决原理1中会出现的死锁问题,提出原理2双重防死锁,可以更好解决死锁问题。

在这里插入图片描述

	过程分析:
	1.当A通过setnx(lockkey,currenttime+timeout)命令能成功设置lockkey时,即返回值为1,过程与原理1一致;
	2.当A通过setnx(lockkey,currenttime+timeout)命令不能成功设置lockkey时,这是不能直接断定获取锁失败;因为我们在设置锁时,设置了锁的超时时间timeout,当当前时间大于redis中存储键值为lockkey的value值时,可以认为上一任的拥有者对锁的使用权已经失效了,A就可以强行拥有该锁;具体判定过程如下;
	3.A通过get(lockkey),获取redis中的存储键值为lockkey的value值,即获取锁的相对时间lockvalueA
	4.lockvalueA!=null && currenttime>lockvalue,A通过当前的时间与锁设置的时间做比较,如果当前时间已经大于锁设置的时间临界,即可以进一步判断是否可以获取锁,否则说明该锁还在被占用,A就还不能获取该锁,结束,获取锁失败;
	5.步骤4返回结果为true后,通过getSet设置新的超时时间,并返回旧值lockvalueB,以作判断,因为在分布式环境,在进入这里时可能另外的进程获取到锁并对值进行了修改,只有旧值与返回的值一致才能说明中间未被其他进程获取到这个锁
	6.lockvalueB == null || lockvalueA==lockvalueB,判断:若果lockvalueB为null,说明该锁已经被释放了,此时该进程可以获取锁;旧值与返回的lockvalueB一致说明中间未被其他进程获取该锁,可以获取锁;否则不能获取锁,结束,获取锁失败。
	优化点:
	加入了超时时间判断锁是否超时了,即使A在成功设置了锁之后,服务器就立即出现宕机或是重启,也不会出现死锁问题;因为B在尝试获取锁的时候,如果不能setnx成功,会去获取redis中锁的超时时间与当前的系统时间做比较,如果当前的系统时间已经大于锁超时时间,说明A已经对锁的使用权失效,B能继续判断能否获取锁,解决了redis分布式锁的死锁问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值