前言
在一个灯光阑珊,透着五彩斑斓白光的小黑屋中,我眺望着远方。这时一个大腹便便,穿着花格子寸衫、戴着厚厚眼睛的中年男子走进来坐在了我的对面,上下打量着我。看着我整整10页纸的简历,还未等我自我介绍一波,挠了挠头皮就问道:你熟悉Redis吗,我反手就是一个精通。中年男子推了推镜框丢下一句我劝你耗子尾汁后走了出去…
是什么?
Redis是一个包含多种数据结构、基于内存、可选持久性key-value存储系统,并提供多种语言的 API的非关系型数据库。
应用场景
- 热点数据的缓存
- 分布式session
- 计数器、标记相关问题
- 排行榜相关问题
- 分布式锁
- 其他业务不可变或者变动几率小的数据
数据类型(必问)
- String 字符串
- Hash 哈希
- List 列表
- Set 集合
- Zset/Sorted set:有序集合
- 高阶:HyperLogLog、Geo、Pub/Sub、BloomFilter,RedisSearch,Redis-ML等
原理解析
- redis服务器启动时,会把 AE_READABLE 向eventLoop(IO多路复用)注册。
- 客户端请求与服务器建立连接,服务器生成Scoket(s1)通道并绑定AE_READABLE事件。
- 客户端(s1)请求执行set key value(写命令),s1触发AE_READABLE 由命令请求器将key和value读取到内存并对数据修改
- 设值结束后将s1与AE_WRITABLE事件关联绑定,从而触发写操作,成功后由命令回复器输出结果“OK”
- 返回结果将s1的AE_WRITABLE事件与命令回复处理器解除绑定。
IO多路复用
IO多路复用:I/O是指网络I/O,多路指多个TCP连接(即socket或者channel),复用指复用一个或几个线程。也就是说一个或一组线程处理多个TCP连接。最大优势是减少系统开销小,不必创建过多的进程/线程,也不必维护这些进程/线程。
主要机制:select、poll、epollselect机制:
基本原理:
客户端操作服务器时就会产生这三种文件描述符(简称fd):writefds(写)、readfds(读)、和exceptfds(异常)。select会阻塞住监视3类文件描述符,等有数据、可读、可写、出异常 或超时、就会返回;返回后通过遍历fdset整个数组来找到就绪的描述符fd,然后进行对应的IO操作。
优点:
几乎在所有的平台上支持,跨平台支持性好
缺点:
由于是采用轮询方式全盘扫描,会随着文件描述符FD数量增多而性能下降。
每次调用 select(),需要把 fd 集合从用户态拷贝到内核态,并进行遍历(消息传递都是从内核到用 户空间)
默认单个进程打开的FD有限制是1024个,可修改宏定义,但是效率仍然慢。poll机制:
基本原理:
基本原理与select一致,也是轮询+遍历;唯一的区别就是poll没有最大文件描述符限制(使用链表的方式存储fd)。epoll机制:
基本原理:
没有fd个数限制,用户态拷贝到内核态只需要一次,使用时间通知机制来触发。通过epoll_ctl注册fd,一旦fd就绪就会通过callback回调机制来激活对应fd,进行相关的io操作。
epoll之所以高性能是得益于它的三个函数:
1. epoll_create()系统启动时,在Linux内核里面申请一个B+树结构文件系统,返回epoll对象,也是一个fd
2. epoll_ctl() 每新建一个连接,都通过该函数操作epoll对象,在这个对象里面修改添加删除对应的链接fd, 绑定一个callback函数
3. epoll_wait() 轮训所有的callback集合,并完成对应的IO操作
优点:
没fd这个限制,所支持的FD上限是操作系统的最大文件句柄数,1G内存大概支持10万个句柄
效率提高,使用回调通知而不是轮询的方式,不会随着FD数目的增加效率下降
内核和用户空间mmap同一块内存实现(mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间)
例子:
100万个连接,里面有1万个连接是活跃,我们可以对比 select、poll、epoll 的性能表现
select:不修改宏定义默认是1024,l则需要100w/1024=977个进程才可以支持 100万连接,会使得CPU性能特别的差。
poll:没有最大文件描述符限制,100万个链接则需要100w个fd,遍历都响应不过来了,还有空间的拷贝消耗大量的资源。
epoll:请求进来时就创建fd并绑定一个callback,主需要遍历1w个活跃连接的callback即可,即高效又不用内存拷贝
持久化
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
Redis提供了两种持久化方式:RDB(默认)和AOFRDB:
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
默认配置:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
优势:
1. RDB文件紧凑,全量备份,非常适合用于进行备份(冷备)和灾难恢复。
2. 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
3. RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。AOF:
全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。
默认配置:
appendfsync always #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no #从不同步。高效但是数据不会被持久化。
优势:
1. AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。
2. AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。
3. AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
4. AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据RDB和AOF到底该如何选择
小孩子才做选择,我全部都要,一般都是两种方式配合使用
集群模式
Redis 一共提供了三种集群模式
主从复制/同步、哨兵模式、Cluster 集群主从复制/同步
为了避免单点故障,通常的做法是将数据库复制多个副本以部署在不同的服务器上,这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。为此, Redis 提供了复制(replication)功能,可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。
在复制的概念中,数据库分为两类,一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
原理
1. 从数据库连接主数据库,发送SYNC命令;
2. 主数据库接收到SYNC命令后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
3. 主数据库BGSAVE执行完后,向所有从数据库发送快照文件,并在发送期间继续记录被执行的写命令;
4. 从数据库收到快照文件后丢弃所有旧数据,载入收到的快照;
5. 主数据库快照发送完毕后开始向从数据库发送缓冲区中的写命令;
6. 从数据库完成对快照的载入,开始接收命令请求,并执行来自主数据库缓冲区的写命令;(从数据库初始化完成)
7. 主数据库每执行一个写命令就会向从数据库发送相同的写命令,从数据库接收并执行收到的写命令(从数据库初始化完成后的操作)
8. 出现断开重连后,2.8之后的版本会将断线期间的命令传给重数据库,增量复制。
9. 主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。Redis 的策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
优点:
1.支持主从复制,主机会自动将数据同步到从机,可以进行读写分离;
2.为了分载Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务;
3.Slave同样可以接受其它Slaves的连接和同步请求,这样可以有效的分载Master的同步压力;
4.Master Server是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求;
5.Slave Server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据;
缺点:
1.Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复;
2.主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性;
3.如果多个Slave断线了,需要重启的时候,尽量不要在同一时间段进行重启。因为只要Slave启动,就会发送sync请求和主机全量同步,当多个Slave重启的时候,可能会导致 Master IO剧增从而宕机。
4.Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂;
5.因为所有从服务器都拥有一份主服务器全量数据备份,所有支持的数据总量有限。哨兵模式
在主从同步/复制的模式下,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
作用:
1.通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器;
2.当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机;
原理:
1.每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。
2.如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态
3.当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN)
4.在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。
5.当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
6.若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除
优点:
1. 哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。
2. 主从可以自动切换,系统更健壮,可用性更高。Cluster 集群
Redis 的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的内容。
原理:
1. 在 Redis 的每一个节点上,都有这么两个东西,一个是插槽(slot),它的的取值范围是:0-16383。还有一个就是cluster,可以理解为是一个集群管理的插件。当我们的存取的 Key到达的时候,Redis 会根据 crc16的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动路由到这个对应的节点上进行存取操作。
2.为了保证高可用,redis-cluster集群引入了主从模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点A1都宕机了,那么该集群就无法再提供服务了。
优点:
1. 集群完全去中心化,采用多主多从;所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
2.客户端与 Redis 节点直连,不需要中间代理层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
3.每一个分区都是由一个Redis主机和多个从机组成,分片和分片之间是相互平行的。
缓存雪崩、击穿、穿透
缓存雪崩
缓存雪崩是指缓存服务器重启或者大量缓存集中在某一个时间点失效,从而导致大量请求全部达到数据库上,从而导致数据库的不可用
解决方案:
1. 在批量存入缓存数据时,将缓存失效的时间加个随机值
2. 热点数据永远不过期,有更新操作就更新缓存(注意双写一致)就好了缓存击穿
缓存击穿是指与雪崩有点类似,指的是在某个时间点内一个key(非常热点key)失效,从而导致所有请求打到数据库中
解决方案:
1. 使用互斥锁,在key失效的瞬间只允许一个请求将数据库最新的数据重新加载到redis中,其余线程还是冲redis中获取
2. 也可以将热点数据设置永远不过期,有更新操作就更新缓存(注意双写一致)就好了缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求一般的缓存系统,一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。
解决方案:
1.在接口层面添加参数校验 ,过滤不合法请求
2.将缓存取不到的数据,在数据库中也没有取到的数据也存入redis中并设置过期时间
3.使用布隆过滤器,可以看这篇布隆过滤器解析,通过布隆过滤器判断是否存在该数据,不存在则返回。
过期策略
Redis的过期策略,是有定期删除+惰性删除两种。
定期删除:
默认100ms就随机抽一些设置了过期时间的key,去检查是否过期,过期了就删了。
惰性删除:
当客户端查询时再检查是否过期,过期就删了不返回,没过期该怎么样就怎么样。
内存淘汰机制
1.noeviction: 返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
2.allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
3.volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
4.allkeys-random: 回收随机的键使得新添加的数据有空间存放。
5.volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
6.volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
未完待续…
本期到这里啦,写的不对的地方巨佬们多多指点,喜欢的话来一个一键三连吧