Redis必知必会系列
1.常用命令
https://www.cnblogs.com/huozhonghun/p/11636053.html
2.Redis是什么
Redis 是 C 语言开发的一个开源的(遵从 BSD 协议)高性能键值对(key-value)的内存数据库,是一种 NoSQL(not-only sql,泛指非关系型数据库)的数据库,常用于做数据库、缓存,消息中间件,分布式锁等,丰富的数据类型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets);支持数据持久化;支持主从;支持分片。
3.数据类型
- String 是 Redis 最基本的类型,可以理解成与 Memcached一模一样的类型,一个 Key 对应一个 Value,键值分别最大能存储 512M。String类型二进制安全的,意思是Redis的String包含任何数据,比如图片或者序列化的对象。
- Hash是一个键值(key-value)的集合。Redis 的 Hash 是一个 String 的 Key 和 Value 的映射表,Hash 特别适合存储对象。
- List 列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)。List 就是链表(双向链接),可以用来当消息队列。
- Set 是 String 类型的无序集合。集合是通过 hashtable 实现的。Set 中的元素是没有顺序的,而且是没有重复的。
- Zset(Sorted Set)和 Set 一样是 String 类型元素的集合,且不允许重复的元素。当你需要一个有序的并且不重复的集合列表,那么可以选择 Sorted Set 结构。和 Set 相比,Sorted Set关联了一个 Double 类型权重的参数 Score,使得集合中的元素能够按照 Score 进行有序排列,Redis 正是通过分数来为集合中的成员进行从小到大的排序。实现方式:Redis Sorted Set 的内部使用 HashMap 和跳跃表(skipList)来保证数据的存储和有序,HashMap 里放的是成员到 Score 的映射。而跳跃表里存放的是所有的成员,排序依据是 HashMap 里存的 Score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
4.缓存中间件,Memcache和Redis的区别
- Memcache:代码层次类似Hash,非常简单易用;支持简单的数据类型,但是不支持数据的持久化存储,如果服务器宕机了,数据是无法保存的;不支持主从同步;不支持分片机制,Value 的大小只有 1MB
- Redis:数据类型丰富;支持数据磁盘持久化存储;支持主从;支持分片,Value 的大小可以达到 1GB
5.Redis为什么可以这么快
Redis官方提供的10W+QPS,(QPS即query per second,每秒内查询次数)。
- 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高(Redis是采用单进程单线程模型的K-V数据库,由c语言编写,将数据存储到内存中,读写数据的时候都不会受到硬盘IO速度的限制)
- 数据结构简单,对数据操作也简单(Redis不使用表,它的数据库不会预定义或者强制要求用户对redis存储的不同数据进行关联,因此性能相比关系型数据库要高出不止一个量级,其存储结构就是键值对,类似于hashMap,hashMap的优势就是查询,查询的时间复杂度是O(1))
- 采用单线程,单线程也能处理高并发请求,想多核也可启动多实例(在面对高并发的请求的时候,首先想要的是多线程来进行处理,将IO线程和业务线程分开,业务线程使用线程池来避免频繁创建线程和销毁线程,即便是一次请求,阻塞了也不会影响到其它请求。Redis单线程结构是指主线程是单线程的,主线程包含IO事件的处理,以及IO对应的相关请求的业务处理,此外,主线程还负责过期键的处理,复制协调,集群协调等等,这些除了IO事件之外的逻辑会被封装成周期性的任务,由主线程周期性的处理,正因为采用单线程的设计,对于客户端的所有读写请求,都由一个主线程串行的处理,因此多个客户端同时对一个键进行写操作的时候,就不会有并发的问题,避免了频繁的上下文切换和锁竞争,使得redis执行起来效率更高。单线程是可以处理高并发的请求的,并发并不是并行,并行性意外着服务器能够同时执行几个事情,具有多个计算的单元,而并发性IO流,意味着能够让一个计算单元来处理来自多个客户端的流请求。Redis使用单线程配合上IO多路复用,将可以大幅度的提升性能,在多核CPU流行的今天,只要一个线程,只用一个核很浪费,CPU不是制约redis的性能瓶颈,瓶颈可能是机器内存的大小或者网络带宽。此外,可以在多核的服务器中启动多个实例来利用多核的特性。注意,这里的单线程只是在处理我们的网络请求的时候,只有一个单线程来处理,一个正式的Redis server,在运行的时候,肯定不止一个线程的,例如Redis在进行持久化的时候,会根据实际情况,以子进程子线程的方式执行)
- 使用多路I/O复用模型,非阻塞IO(Redis是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或者输出都是阻塞的,所以IO操作在一般情况下,往往不能直接返回,就会导致某一文件的IO阻塞,进而导致整个进程无法对其它客户端提供服务。而IO多路复用就是为了解决这个问题而出现的)
6.FD
File Descriptor文件描述符。在操作系统中,一个打开的文件通过唯一的描述符进行引用,该描述符是打开文件的元素据到文件本身的映射,在linux中,该描述符称为文件描述符即File Descriptor,文件描述符用一个整数来表示。
7.传统的阻塞I/O模型
当使用read或者write的对某一个文件描述符FD进行读写的时候,如果当前的FD不可读或者不可写,整个Redis服务就不会对其它的操作做出相应,导致整个服务不可用,这也就是传统意义上的阻塞IO模型,阻塞模型会影响其它FD对应的服务,所以在需要处理多个客户端任务的时候,往往都不会使用阻塞模型。此时,需要一种更高效的IO模型来支持Redis的高并发处理,就是IO复用多路模型。
8.多路I/O复用模型
最重要的函数调用就是Select系统调用。Select可以同时监控多个文件描述符的可读,可写情况,当其中的某些文件描述符可读或者可写的时候,select方法就会返回可读以及可写的文件描述符个数,也就是说,Selector是负责监听我们的文件是否可读或者可写的,监听的任务交给Selector之后呢,程序就可以做其它的事情了,而不被阻塞了。
与此同时,也有其它的多路IO复用函数,如:epoll、kqueue,evport。它们相比select的性能是更加优秀的,都使用了内核内部的结构,能够服务几十万的文件描述符。
这么多的函数,redis采用哪个?
- 因地制宜的,Redis需要在多个平台下运行,为了最大化的提高执行效率和性能,会根据编译平台的不同选择不同的IO多路复用函数作为子模块,提高给上层统一的接口
- 优先选择时间复杂度为O(1)的IO多路复用函数作为底层实现
- 一般以时间复杂度为O(n)的select作为保底(如果编译环境没有epoll、kqueue、evport、就会使用select作为备选方案,使用时会扫描全部的文件描述符,性能较差,时间复杂度是O(n))
- 基于react设计模式监听I/O事件(Redis采用react设计模式来实现文件处理器的,文件事件处理器使用IO多路复用模块,同时监听多个FD,当accept、read,write等事件产生的时候,事件处理器就会回调FD绑定的事件处理器,虽然整个事件处理器是在单线程运行的,但是通过IO多路复用模块的引用,实现了同时对多个FD读写的监控,提高了网络通信模型的性能,同时来保证了整个Redis服务实现的简单)
9.Redis内部内存管理
Redis 内部使用一个 redisObject 对象来表示所有的 key 和 value。
- type :代表一个 value 对象具体是何种数据类型。
- encoding :是不同数据类型在 redis 内部的存储方式,比如:type=string 代表 value 存储的是一个普通字符串,那么对应的 encoding 可以是 raw 或者是 int,如果是 int 则代表实际 redis 内部是按数值型类存储和表示这个字符串的,当然前提是这个字符串本身可以用数值表示,比如:"123"这样的字符串。
- vm 字段:只有打开了 Redis 的虚拟内存功能,此字段才会真正的分配内存,该功能默认是关闭状态的。 Redis 使用 redisObject 来表示所有的 key/value 数据是比较浪费内存的,当然这些内存管理成本的付出主要也是为了给 Redis 不同数据类型提供一个统一的管理接口,实际作者也提供了多种方法帮助我们尽量节省内存使用。
10.缓存穿透
是什么?缓存和数据库中都不存在的数据,用户不断请求,造成数据库压力过大
原因?
- 业务自身代码或数据出现问题
- 一些恶意攻击、爬虫造成大量空的命中
解决?
- 设置布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,拦截不存在的数据
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)
11.缓存击穿
是什么?缓存中不存在但数据库中存在的数据,用户并发请求特别多,造成数据库压力过大
解决?
- 设置热点数据永远不过期
- 加互斥锁
12.缓存雪崩
是什么?大量缓存集中在某一个时间段失效,大量数据会直接去访问数据库。
解决?没有最佳方案,看情况而定。
- 设置redis集群和DB集群的高可用,如果redis出现宕机情况,可以立即由别的机器顶替上来。这样可以防止一部分的风险
- 使用互斥锁,在缓存失效后,通过加锁或者队列来控制读和写数据库的线程数量。比如:对某个key只允许一个线程查询数据和写缓存,其他线程等待。单机的话,可以使用synchronized或者lock来解决,如果是分布式环境,可以是用redis的setnx命令来解决
- 不同的key,可以设置不同的过期时间,让缓存失效的时间点不一致,尽量达到平均分布
- 永远不过期,redis中设置永久不过期,这样就保证了,不会出现热点问题,也就是物理上不过期
- 可以利用ehcache等本地缓存来暂时支持,但主要还是要对源服务访问采取限流、资源隔离(熔断),降级措施等
13.如何从海量Key里面查询出某一固定前缀的Key
先确定数据量,再从以下方式选择
(一)使用keys pattern,查找所有符合给定模式pattern的key,keys一次性返回所有匹配的key,键的数量过大会使服务卡顿。
(二)使用非阻塞scan指令,但获取的数据是随机的,可能会获取到重复key,无法统计。
Scan指令:Scan cursor [MATCH pattern] [COUNT count],其中cursor是游标,MATCH pattern是需要查找的模式,COUNT count是查找的数量。
(1)Scan指令是一个基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程。意味着命令每次被调用都需要使用上一次调用返回的游标作为该次调用的游标参数,依次来延续之前的迭代过 程。
(2)以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历。当scan指令的游标参数即cursor被置为0的时候,服务器将开始一次新的迭代,而当服务器向用户返回值为0的游标的时候,就表示迭代完成,以0作为游标开始新一次的迭代,一直调用scan指令直到命令返回游标0,称这个过程为一次完整的遍历。
(3)不保证每次执行都返回某个给定数量的元素,支持模糊查询。Scan增量式迭代命令并不保证每次执行都会返回某个给定数量的元素,甚至可能返回0个元素,但只要命令返回的游标不是0,应用程序就不应该将迭代视作结束,命令返回的元素数量总是符合一定的规则的。对于一个大数据集来说,增量式迭代命令每次最多可能会返回数十个元素,而对于一个足够小的数据集来说,可能会一次迭代返回所有的key,类似于keys指令,scan可以通过给定match参数的方式传入要查找键位的模糊匹配方式,让命令只返回和给定模式下向匹配的元素。
(4)一次返回的数量不可控,只能是大概率符合count参数。此外,对于增量式迭代命令是没有办法保证每次迭代所返回的元素数量的,我们可以使用count选项对命令的行为进行一定程度的调整,count选项的作用就是让用户告知迭代命令,在每次迭代中,应该从数据集里返回多少元素,使用count选项对于增量式迭代命令相当于是一种提示,大多数情况下,这种提示都是比较有效的控制返回的数量的。值得注意的是,count数量并不能严格的控制返回的key的数量,只能说是一个大致的约束,并非每次迭代都会返回count数量的约束,用户可以根据自己的需求在每次迭代中随意改变count的值,只要记得将上次迭代返回的游标用到下次迭代的游标里面就可以了。
14.分布式锁需要解决的问题
分布式锁是控制分布式系统或者不同系统之间共同访问共享资源的一种锁的实现,如果不同的系统或者同一个系统不同主机之间共享了某个资源的时候,往往需要互斥来防止彼此干扰,进而保证一致性。
- 互斥性(任意时刻只能有一个客户端获取锁,不能同时有两个客户端获取到锁)
- 安全性(锁只能被持有该锁的客户端删除,不能由其它客户端删除掉)
- 死锁(获取锁的客户端因为某些原因而宕机,而未能释放锁,其它客户端再也无法获取到该锁,而导致的死锁,此时需要有机制避免这种问题的发生)
- 容错(部分redis节点宕机,客户端要保证可以获取锁和释放锁)
15.Redis实现分布式锁
SETNX key value,如果key不存在,则创建并赋值。SETNX即set if not exist,时间复杂度是O(1),返回值:设置成功,返回1;设置失败,返回0。
正因为SETNX的操作是原子性的,因此初期便被用在实现分布式锁,在执行某段代码逻辑的时候,先尝试使用SETNX对某个key设值,如果设值成功,则证明此时没有别的线程在执行该段代码,或者说占用该独占资源,这个时候线程就可以顺利的去执行该段代码逻辑了,如果设值失败,则证明此时有别的程序或者线程占用该资源,那么当前线程就需要等待直至设值SETNX成功,如果设值SETNX的key,这个key就会长久有效了,后续线程如何能再次获得到锁,此时需要给该key设值一个过期时间。
(一)使用EXPIRE key seconds设值过期时间:
- 设值key的生存时间,当key过期的时候(生存时间为0),会被自动删除
- 缺点:原子性得不到满足(如果SETNX与EXPIRE结合使用,它们分别都是原子性的,但是组合到一起却不是原子性的)
(二)完美方案
Redis2.6.12之后,通过set操作,将SETNX和EXPIRE柔和到一起去执行,此时就满足了分布式锁的原子性需求。
命令格式:SET key value [EX seconds] [PX milliseconds] [NX][XX],操作成功完成时候,返回OK,否则返回nil。
- EX seconds,设值键的过期时间为second秒。
- PX milliseconds,设值键的过期时间为millisecond毫秒。
- NX,只在键不存在的时候,才对键进行设值操作。效果等同于SETNX。
- XX,只在键已经存在的时候,才对键进行设置操作。
16.大量的key同时过期的注意事项,如何避免系统卡断现象
在设值key的过期时间的时候,给每个key加上随机值就行了,使得过期时间分散一些,很大程度上避免卡顿现象的发生
17.实现异步队列
- 使用List作为队列,rpush生产消息,lpop消费消息,实现先进先出的效果,缺点:没有等待队列里有值就直接消费,可以在应用层引入sleep机制去调用lpop重试来弥补
- BLPOP key [key...] timeout:阻塞直到队列有消息或者超时。BLPOP可以替代sleep做更精准的阻塞控制,缺点:只能供一个消费者消费
- pub/sub,主题订阅者模式,发送者(pub)发送消息,订阅者(sub)接收消息,订阅者可以订阅任意数量的频道,可以实现一对多的消费队列,缺点:消息的发布是无状态的,无法保证消息是否送达达,解决这个问题,可以使用专业的消息队列:kafka等
18.Redis持久化
Redis提供了三种持久化的方案,将内存中的数据保存到磁盘中。
(一)RDB(快照)持久化:保存某个时间点的全量数据快照,为一个二进制文件
(1)缺点
- 内存数据的全量同步,太多数据可能会由于大量I/O而严重影响性能的。每次快照持久化都是将快照数据完整的写入到磁盘一次,并不是增量的只同步脏数据,如果数据量大的话,并且写操作比较多的时候必然会引起大量的磁盘IO操作,可能会影响性能。
- 可能会因为Redis挂掉而丢失从当前至最近一次快照期间的数据,由于快照方式是在一定间隔时间做一次的快照后的所有修改,如果应用要求不能丢失任何修改的话,可以采用AOF。
(2)Redis参数设置说明
#持久化的时间策略,可以根据自身redis的写入情况合理配置
save 900 1 # 表示900秒之内如果有一条是写入指令就触发产生一次快照。产生一次快照就可以理解为是一次备份了。
save 300 10 # 表示300秒以内如果有10条写入就会产生快照。如果变动数是大于0但是还没有到10条的话,就会等到900秒过后才去做备份。
save 60 10000 # 表示60秒内如果有一万条写入就进行一次备份。
save "" # 禁用RDB配置。
# yes表示当备份进程出错的时候,主进程就停止接收新的写入操作了,这样做是为了保护持久化的数据一致性的问题。如果自己的业务有完善的监控系统,可以禁止此项配置,否则开启。
stop-writes-on-bgsave-error yes
# rdb压缩,yes表示在备份的时候需要将rdb文件进行压缩后才保存。建议设置为no,因为redis本身是cpu密集型服务器,再开启压缩,会带来更多的cpu的消耗,相比硬盘成本,cpu更值钱。
rdbcompression yes
(3)命令生成RDB文件
①SAVE,阻塞Redis的服务器进程,直到RDB文件被创建完毕(很少被使用,因为save操作是在主线程中保存快照的,由于Redis是用一个主线程来处理所有的请求的,这种方式会阻塞所有的客户端请求)
②BGSAVE,Fork出一个子进程来创建RDB文件,不阻塞服务器进程(记录接收BGSAVE当时的数据库状态,父进程继续处理接收到的命令,子进程完成文件的创建之后会发送信号给父进程即Redis的主进程,而于此同时,父进程处理命令的同时,通过轮询来接收子进程的信号,不阻塞服务器进程。BGSAVE指令是使用后台方式保存RDB文件的,调用此命令后会立刻返回OK返回码,Redis会产生一个子进程进行处理,并立刻恢复对客户端的服务,在客户端可以使用last save这个指令,查看操作是否成功,last save记录了上一次成功执行save或者bgsave命令的时间)
(4)自动化触发RDB持久化的方式
①根据redis.conf配置里面的save m n规则定时触发(用的BGSAVE)
②主从复制时,主节点自动触发(从节点全量复制的时候,主节点发送RDB文件给从节点完成复制操作后,主节点就会触发BGSAVE)
③执行Debug Reload
④执行shutdown且没有开启AOF持久化
(5)BGSAVE原理
①在执行了GBSAVE指令之后,首先回去检查当前子进程有没有正在执行的AOF/RDB子进程,有的话返回错误。这样做是为了防止子进程之间的竞争,也就意味着在执行GBSAVE期间呢,客户端发送的SAVE/GBSAVE命令会被服务器j拒绝执行,如果此时没有发生相关子进程,则会触发持久化
②触发了持久化,就会调用redis源码里面的rdbSaveBackground方法执行fork系统调用。其实执行BGSAVE指令来生成RDB文件的时候,本质就是调用了操作系统的系统调用fork指令
③系统调用fork(),就是用来创建进程的,而Linux下fork系统调用实现了Copy-on-Write写时复制。传统方式下fork函数在创建子进程的时候直接把所有的资源复制给子进程,这种实现方式简单,但是效率低下,而且复制的资源可能对子进程毫无用处,linux为了降低创建子进程的成本,改进fork实现方式,当父进程创建子进程的时候,内核只为子进程创建虚拟空间,父子两个进程使用的是相同的物理空间,只有父子进程发生更改的时候才会为子进程分配独立的物理空间
④改进的实现方式称为写时复制,Copy-on-Write(简称COW)是计算机程序设计领域的优化策略,核心思想如果有多个调用者同时要求相同资源(如内存或者磁盘上的数据存储),它们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容的时候,系统才会真正复制一份专用副本给该调用者,而其它调用者所见到的最初的资源仍然保持不变。
(二)AOF(append-only-file)持久化,通过保存Redis服务器所执行的写状态来记录数据库的。
(1)记录下除了查询以外的所有变更数据库状态的指令(以redis协议格式来保存的)
(2)以append的形式追加保存到AOF文件中,以增量的形式(数据库会记录下所有变更数据库状态的指令,除了指定数据库的查询命令,其它的命令都是来自client)
(3)refis.conf配置说明
①aof持久化默认是关闭的,可以通过修改refis.conf配置,appendonly no修改为appendonly yes,生成的文件名称是appendonly.aof,修改配置后需要重启redis服务器
②appendfsync everysec该配置主要用来配置aof文件的写入方式的,可以接收三个不同的参数分别是,always、everysec、no。
- always表示一旦缓存区的内容发生变化,就总是及时的将缓存区的内容写入到aof中
- everysec是将缓存区的内容每隔一秒去写入到aof中
- no是将写入aof的操作交由操作系统来决定
- 一般而言,为了提高效率,操作系统会将缓存区被填满才会开始同步数据到磁盘中。一般推荐everysec默认的方式,速度比较快,安全性比较高
(4)日志重写解决AOF文件大小不断增大的问题原理
- 首先调用fork(),创建一个子进程
- 子进程把新的AOF写到一个临时文件里面,不依赖原来的AOF文件(新的AOF的重写是直接把当前内存的数据生成对应的命令,并不需要读取老的AOF文件进行分析或者合并)
- 主进程持续将新的变动同时写到内存和原来的AOF里(这样即使写入失败,也能保证数据的安全)
- 主进程获取子进程重写AOF的完成信号,往新AOF同步增量变动
- 使用新的AOF文件替换掉旧的AOF文件。
(三)AOF和RDB的混合模式
Redis4.0之后,推出了结合AOF和RDB的混合模式,并且作为默认的方式来使用,推荐使用。(RDB作为全量备份,AOF作为增量备份,来提升备份的效率)
- BGSAVE做镜像全量持久化,AOF做增量持久化,因为BGSAVE会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据的问题,需要AOF配合使用,在Redis重启的时候会使用BGSAVE持久化文件,重新构建内容,再使用AOF重放近期的操作指令,来实现完整恢复之前的状态
- AOF重写机制,它其实也是先写一份全量数据到AOF文件中,再追加增量,只不过全量数据是以redis命令格式写入的,那么是否可以先以RDB格式写入全量数据,再追加增量数据呢,这样既可以提高重新和恢复速度,也可以减少文件的大小,还同时可以保证数据的完整性,能够结合RDB和AOF的优点,AOF和RDB的混合模式正是在这种需求下诞生的。在此种方式下,子进程在做AOF重写的时候,会通过管道从父进程读取增量数据并缓存下来,那么在以RDB格式保存全量数据的时候,也会从管道读取数据,同时不会造成管道的阻塞,也就是说,AOF文件前半段是RDB格式的全量数据,而后半段是Redis命令格式的增量数据。
19.RDB和AOF文件共存情况下的恢复流程
20.RDB和AOF的优缺点
- RDB优点,RDB本质上是一个内存快照,保存了创建RDB文件那个时间点的Redis全量数据,全量数据快照,文件小,创建恢复快
- RDB缺点,无法保存最近一次快照之后的数据
- AOF优点,AOF本质上是一份执行日志,保存所有被Redis更改的指令,可读性高,适合保存增量数据,数据不易丢失
- AOF缺点,文件体积大,恢复时间长
21.Pipeline的好处
Pipeline和Linux的管道类似,它可以让Redis批量执行指令。
Redis基于请求/响应模型,单个请求处理需要一一应答。如果需要同时执行大量命令,则每条命令都需要等待上一条命令执行完毕后才能继续执行,这中间不仅仅多了RTT(来回交互时间),还频繁调用系统IO,发送网络请求。为了提升效率,就可以使用Pipeline,批量执行指令,可以节省多次IO和请求响应往返的时间。但是如果指令之间存在依赖关系,则建议分批发送指令。
22.主从同步原理
Redis一般是使用一个Master节点来进行写操作,而若干个Slave节点进行读操作,Master和Slave分别代表了一个个不同的RedisServer实例,另外,定期的数据备份操作也是单独选择一个Slave去完成,这样可以最大程度发挥Redis的性能,为的是保证数据的弱一致性和最终一致性。我们不需要保证Master和Slave的数据是实时同步的,但是在一段时间后Master和Slave的数据是趋于同步的,这就是最终一致性。
第一次同步时,主节点做一次BGSAVE,并同时将后续修改操作记录到内存Buffer,待完成后,将RDB文件全量同步到从节点里面,从节点接收完成后,将RDB镜像加载到内存中,加载完成后,再通知主节点,将期间修改的操作记录,即增量数据同步到从节点进行存放,到某个时间点之前的全量数据同步完成后,把该时间点后的增量数据进行同步,这样就完成了整个同步的过程。
(一)全同步过程
- Slave发送sync命令到Master。
- Master启动一个后台进程,将Redis中的数据快照保存到文件中(BGSAVE)
- Master将保存数据快照期间接收到的写命令缓存起来
- Master完成写文件操作后,将该文件发送给Slave
- 使用新的AOF文件替换掉旧的AOF文件(Slave将接收到的文件保存到磁盘中,加载文件到内存中去恢复数据快照)
- Slave完成数据快照恢复后,Master将这期间收集的增量写命令发送给Slave端,进行回放
(二)增量同步过程
- Master接收到用户的操作指令,执行相应的操作函数,判断是否需要传播到Slave
- 将操作记录追加到AOF文件,首先将操作转换成Redis内部的协议格式,并以字符串的形式存储,并追加到AOF文件
- 将操作传播到其它Slave,主要有两步处理,首先对齐主从库(确保是该操作的从数据库),然后,往响应缓存写入指令
- 将缓存中的数据发送给Slave
23.Redis Sentinel工作原理
主从模式的弊端,就是不具备高可用性,当master挂掉后,redis将不能对外提供写操作,它可以解决主从同步Master宕机后的主从切换问题。
官方提供的集群管理工具,本身是独立运行的进程,主要功能有:
- 监控:检查主从服务器是否运行正常
- 提醒:可以通过API向管理员或者其它应用程序发送故障通知
- 自动故障迁移:主从切换(在Master宕机后,将其中一个Slave升级为Master,将其他的Slave从该节点同步数据)
主从切换过程:PING命令时间超时》master被 Sentinel 标记为主观下线》足够数量的 Sentinel同意后,master标记为客观下线》Sentinel投票选出新master,将剩余slave指向新的master进行数据复制
24.流言协议Gossip(在杂乱无章中寻求一致)
- 每个节点都随机的与对方通信,最终所有节点的状态达成一致
- 种子节点定期随机向其他节点发送节点列表以及需要传播的消息
- 不保证信息一定会传递给所有节点,但是最终会趋于一致
25.Redis集群原理
(一)如何从海量数据里快速找到所需
分片:按照某种规则去划分数据,分散存储在多个节点上。
通过分片实现数据分片来减轻单节点服务器的压力。
(二)原理
Redis集群采用无中心结构,每个节点保存数据和整个集群的状态,每个节点都和其他所有节点连接,节点之间使用Gossip协议去传播信息以及发现新的节点。
(三)目的
将不同的key分散放置到不同的redis节点
(四)一致性哈希算法
通常的做法是获取key的hash值,根据节点数来求模,这样的方法有弊端,当需要动态增减节点时,会造成大量的key无法被命中,所以引入了一致性哈希算法
(1)服务器对 2^32 取模,将哈希值空间组成虚拟的圆环,整个圆环按顺时针方向组织,每个节点依次为 0、1,2…2^32-1(即哈希值是一个32位无符号整形)
(2)对数据使用同样的 Hash 算法,并映射到相同的圆上
(3)然后从数据映射到的位置开始顺时针寻找,将数据保存到找到的第一个服务器上
无论增减服务器,受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响,一致性哈希算法具有较好的容错性和可扩展性。
一致性哈希算法在节点较少时,可能因为节点分布不均匀,造成数据倾斜
解决:引入虚拟节点解决数据倾斜问题,即对每个服务器节点计算多个Hash,计算位置都放置一个虚拟服务器节点
26.Redis怎么用
结合 Spring Boot 使用,一般有两种方式。一种是直接通过 RedisTemplate 来使用,另一种是使用 Spring Cache 集成 Redis(也就是注解的方式)。
27.缓存和数据库数据一致性问题
分布式环境下非常容易出现缓存和数据库间数据一致性问题。
如果项目对缓存要求强一致性,那么就不要使用缓存。
如果项目对缓存不要求强一致性,我们只要开辟一个异步任务去保证最终一致性即可。
方案:
1.延时双删策略(仅在休眠时间内数据存在不一致,数据库已经更新,缓存还是旧值)
- 先删除缓存
- 再写数据库
- 休眠指定毫秒
- 再次删除缓存
2.通过消息队列来更新缓存
- 实现了异步更新缓存,降低了系统的耦合性
- 但是破坏了数据变化的时序性
- 成本相对比较高
3.通过binlog来同步mysql数据库到redis中
Mysql数据库任何时间对数据库的修改都会记录在binglog中;当数据发生增删改,创建数据库对象都会记录到binlog中,数据库的复制也是基于binlog进行同步数据
- 在mysql压力不大情况下,延迟较低
- 和业务完全解耦
- 解决了时序性问题
- 成本相对而言比较大
28.淘汰策略
- volatile-lru(Least Recently Used):从已设置过期时间的数据集中优先对最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集中优先剩余时间短的数据淘汰
- volatile-random:从已设置过期时间的数据集中随机选择数据淘汰
- allkeys-lru:从所有数据集中优先对最近最少使用的数据淘汰
- allkeys-random:从数据集随机选择数据淘汰
- no-enviction:不淘汰策略,若超过最大内存,返回错误信息
Redis 4.0开始 加入了 LFU(Least Frequency Use)淘汰策略,包括 volatile-lfu 和 allkeys-lfu,通过统计访问频率,优先对访问频率最少的数据淘汰。