Redis知识总结

redis的槽点:

在redis官方给出的集群方案中,数据的分配是按照槽位来进行分配的,每一个数据的键被哈希函数映射到一个槽位,redis中规定一共有16384个槽位,当用户put或者是get一个数据的时候,首先会查找这个数据对应的槽位是多少,然后查找对应的节点,然后才把数据放入这个节点。这样就做到了把数据均匀的分配到集群中的每一个节点上,从而做到了每一个节点的负载均衡,充分发挥了集群的威力。

Redis为什么这么快?

1、完全基于内存,数据放在内存的特定空间,CPU对内存的访问速度非常的快

2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路I/O复用模型,非阻塞IO;

五种IO模型

为了避免用户应用导致冲突甚至内核崩溃,用户应用与内核是分离的:

将进程的寻址空间会划分为两部分:内核空间、用户空间

**用户空间只能执行受限的命令(Ring3):**而且不能直接调用系统资源,必须通过内核提供的接口来访问

内核空间可以执行特权命令(Ring0):调用一切系统资源

Linux系统为了提高IO效率,会在用户空间和内核空间都加入缓冲区:

写数据时,要把用户缓冲数据拷贝到内核缓冲区,然后写入设备

读数据时,要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区前四个是同步IO模型

1.阻塞IO

用户读取数据,发现用户缓冲区没有,首先需要将数据从设备读取到内核缓冲区,这个过程需要用户等处于阻塞状态,读取之后,需要将内核缓冲区的数据拷贝一份到用户缓冲区,这个过程也是阻塞状态。拷贝完成之后,用户进程解锁阻塞,处理数据。

2.非阻塞IO

用户读取数据,发现用户缓冲区没有,首先需要将数据从读取到内核缓冲区,客户不需进入阻塞状态,而是客户轮询内核是否有数据准备好,当数据已经写入内核缓冲区时,就进行拷贝数据报的操作。这是用户线程处于阻塞状态,等待拷贝完成,解锁阻塞处理数据。

3.IO多路复用

阻塞IO和非阻塞IO,差别在于无数据时的处理方案:如果调用recvfrom时,恰好没有数据,阻塞IO会使CPU阻塞,非阻塞IO使CPU空转,都不能充分发挥CPU的作用。

而在单线程情况下,只能依次处理IO事件,如果正在处理的IO事件恰好未就绪(数据不可读或不可写),线程就会被阻塞,所有IO事件都必须等待,性能自然会很差。IO多路复用是利用单个线程来同时监听多个文件描述符FD,并在某个FD可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。监听文件描述符有三种方式

select

对于监听的多个文件描述符,它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),用户需要无差别轮询FD数组看是哪一个数据已经就绪,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。

select缺点

select本质上是通过设置或者检查存放fd(文件描述符)标志位的数据结构来进行下一步处理。

  • 单个进程所打开的FD是有限制的默认1024 ;
  • 每次调用 select,都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大;
  • 需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大对 socket 扫描时是线性扫描,采用轮询的方法,效率较低(高并发)

Poll

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd(文件描述符)对应的设备状态,数据就绪或超时后,拷贝pollfd数组到用户空间,返回就绪fd数量n 。但是它没有最大连接数的限制,原因是它是基于链表来存储的

.poll缺点

每次调用 poll ,都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大;对 socket 扫描是线性扫描,采用轮询的方法,效率较低(高并发时)

epoll函数接口

epoll是事件驱动的,epoll和select不同的地方就是, 将文件描述符放到内核空间的红黑树上 ,并采用回调机制, 内核在检测到某文件描述符可读/可写时会调用回调函数, 将文件描述符放在就绪链表中。 只观察就绪链表中有无数据即可,最后将链表的数据返回用户。

epoll的优点

  • 没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);

  • 效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数

  • 文件描述符是通过mmap让内核和用户空间共享同一块内存实现传递的

    IO多路转接是利用select函数对这些文件描述符进行循环监听,当某个文件描述符就绪时,就对这个文件描述符进行处理,不需要等待其他的io就绪,这样可以提高同时处理io的能力。

4.信号驱动IO

进程在IO访问的时候,会先通过sigaction系统调用,提交一个信号处理函数,当内核准备好数据以后,会产生一个SIGIO电平触发信号给信号处理函数,在信号处理函数中把数据从内核空间复制到用户空间就可以了。用户进程处理数据,期间用户可以执行其他的任务,无需阻塞。

5.异步IO

异步IO的整个过程都是非阻塞的,用户进程调用完异步API后就可以去做其它事情,内核等待数据就绪并拷贝到用户空间后才会递交信号,通知用户进程。应用程序调用aio_read创建回调函数,之后内核一方面去取数据将内容返回,另一方面将程序控制权还给应用进程,应用进程继续处理其他事情,是一种非阻塞的状态。当内核中数据就绪时,由内核将数据报拷贝到用户缓冲区,拷贝完成之后,内核递交信号触发aio_read中的回调函数。用户进行处理数据(就比如点外卖,对一阶段是做好饭,第二阶段是送到家里来,都是内核帮我完成,这期间我可以干别的,而同步的的时候就是我必须自己去拿我的饭,不会有人送来)

阻塞程度:阻塞IO>非塞IO>多路转接IO>信号驱动IO>异步IO,效率是由低到高的。

Inset

Inset是一个特殊的整形数组,头部保存了元素个数,编码方式等字段。内存为连续分配的,会将整数按照升序依次保存,且没有重复,在contents数组中,每一个元素所占空间大小是一样的,随着放入数据的大小也会进行编码方式的转变更改元素的占用空间。由于是有序的可以通过二分查找方式查询。

Dict

Dict由三部分组成,分别是:哈希表(DictHashTable)、哈希节点(DictEntry)、字典(Dict)三部分构成,哈希表和哈希节点用来保存键值对,而在Dict中保存了哈希表的大小,元素个数等信息。并且保存了两个哈希表,一个用来保存数据,一个用来进行扩容。

ZipList

ZipList 是一种特殊的“双端链表” ,由一系列特殊编码的连续内存块组成。在头部保存了位偏移量和节点个数,可以在任意一端进行压入/弹出操作, 并且该操作的时间复杂度为 O(1)。ZipList 中的Entry并不像普通链表那样记录前后节点的指针,在遍历时,是通过每一个节点中保存的前一节点的长度和本节点的长度来进行寻址的,(例如首先得到第一个节点的地址,而在第二个节点中第一个节点的长度,那么第二个节点的内存地址就是第一个节点的内存地址加上上一节点的长度)

QuickList

QuickList,它是一个双端链表,只不过链表中的每个节点都是一个ZipList。解决了传统链表的内存占用问题,且控制了ZipList大小,解决连续内存空间申请效率问题。

SkipList

跳跃表是一个双向链表,每个节点都包含score和ele值,并且节点按照score值排序,如果score值一样则按照ele字典排序,每个节点都可以包含多层指针,指针的跨度不同。且层级越高,指针的跨度越大

Redis字符串(String)

数据结构:String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。可以修改的字符串内容,动态字符串内部维护了字符串的长度和实际分配空间大小,获取字符串长度时间复杂度为o(1),并采用预分配冗余空间的方式来减少内存的频繁分配,、。

在修改字符串扩容时,当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为 512M。以处理二进制的方式来处理SDS存放在buf数组里的数据,为二进制安全的。

优点:

获取字符串长度的时间复杂度为O(1)

支持动态扩容

减少内存分配次数

二进制安全

Redis列表(List)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SmrBMyCY-1657959162702)(C:\Users\Hao\Desktop\面试\img\image-20220714193114261.png)]

Redis采用ZipList和LinkedList来实现List,当元素数量小于512并且元素大小小于64字节时采用ZipList编码,若超过512个元素超过则采用LinkedList编码。
ZipList可以看做一种连续内存空间的"双向链表" ,列表的节点之间不是通过指针连接,而是记录上一节点长度本节点的长度来寻址,(例如首先得到第一个节点的地址,而在第二个节点中保存了第一个节点的长度,那么第二个节点的内存地址就是第一个节点的内存地址加上上一节点的长度)内存占用较低 如果列表数据过多,导致链表过长,可能影响查询性能,增删较大数据时有可能发生连续更新问题。
在3.2版本之后,Redis统一采用QuickList来实现List
quicklist就是将多个ziplist使用双向指针串起来使用,这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

Redis集合(Set)

数据结构:当保存的所有对象都是数字的时候并且数量不超过512的时候是使用的Intset结构,Inset是一个特殊的整形数组,内存为连续分配的,会将整数按照升序依次保存在contents数组中,且没有重复,由于是有序的可以通过二分查找方式查询。当不满足这两个条件的时候使用的是dict字典,key为元素,字典是用是一个value为null的哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

Redis哈希(Hash) --value是一个map

Redis hash是一个field和value的映射表,hash特别适合用于存储对象。

数据结构:Hash结构默认采用ZipList编码,用以节省内存。 ZipList中相邻的两个entry 分别保存field和value

当数据量较大时Hash结构会转为Dict字典的结构。触发条件有两个: ZipList中的元素数量超过了hash-max-ziplist-entries(默认512) ZipList中的任意entry大小超过了hash-max-ziplist-value(默认64字节

Redis有序集合Zset(sorted set)

Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。

不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。

数据结构: zset底层使用了两个数据结构

1、Dict,Dict的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。

2、跳跃表,跳跃表是一个双向链表,每个节点都包含score和ele值,并且节点按照score值排序,如果score值一样则按照ele字典排序,每个节点都可以包含多层指针,指针的跨度不同。且层级越高,指针的跨度越大。跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

**生活案例:**2辆公交车起点和终点一样,第一辆8个站,第二辆3个站,就是跳表增加层级可以提高查找效率,理想调表每间隔一个节点插入一级,查询的时间复杂度为o(logn),但是增删时会改变跳表结构改进:添加每一层的概率为1/2(第一层为1/2,第二层为1/4,1/8…,最高32层)

Bitmaps

  1. Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value)也就是 byte 数组,但是它可以对字符串的位进行操作。
  2. Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zBTYdomL-1657959162704)(C:\Users\Hao\Desktop\面试\img\7e43570b5375b2908d8edc087b7b72fc-9898)]

Bitmaps与set对比 假设网站有1亿用户, 每天独立访问的用户有5千万, 如果每天用集合类型和Bitmaps分别存储活跃用户可以得到表

HyperLogLog(去重)

像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?

这种求集合中不重复元素个数的问题称为基数问题。

解决基数问题有很多种方案:

  1. 数据存储在MySQL表中,使用distinct count计算不重复个数使
  2. 用Redis提供的hash、set、bitmaps等数据结构来处理

以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

给定一系列的随机整数,我们记录下低位连续零位的最大长度 k,通过这个 k 值可以估算出随机数的数量。

redis过期策略

Redis本身是一个典型的key-value内存存储数据库,因此所有的key、value都保存在Dict结构中。不过在其database结构体中,有两个Dict:一个用来记录key-value;另一个用来记录key-TTL。从而判断是否过期。

  1. 惰性删除 : 客户端访问某个key时,Redis会检测这个key是不是已经过期了,若过期了就会删除
  2. 定期删除: 就是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。

淘汰策略

就是当Redis内存使用达到设置的上限时,主动挑选部分key删除以释放更多内存的流程

noeviction: 不淘汰任何key,但是内存满时不允许写入新数据,写入数据发生错误默认就是这种策略。

volatile-ttl: 对设置了TTL的key,比较key的剩余TTL值,TTL越小越先被淘汰

allkeys-random:对全体key ,随机进行淘汰。也就是直接从db->dict中随机挑选

volatile-random:对设置了TTL的key ,随机进行淘汰。也就是从db->expires中随机挑选。

allkeys-lru: 对全体key,基于LRU算法进行淘汰,最少最近使用。用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。volatile-lru: 对设置了TTL的key,基于LRU算法进行淘汰

allkeys-lfu: 对全体key,基于LFU算法进行淘汰,最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高

volatile-lfu: 对设置了TTL的key,基于LFI算法进行淘汰

lru算法思想:

hahsmap+双向链表实现( HashMap 存储 key, Value 指向双向链表实现的 LRU 的 Node 节点)

save(key, value),首先在 HashMap 找到 Key 对应的节点,如果节点存在,更新节点的值,并把这个节点移动队头。

如果不存在,需要构造新的节点,并且尝试把节点塞到队头,

如果LRU空间不足,则通过 tail 淘汰掉队尾的节点,同时在 HashMap 中移除 Key。

get(key),通过 HashMap 找到 LRU 链表节点,把节点插入到队头,返回缓存的值。

Redis事务

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。Redis事务的主要作用就是串联多个命令防止别的命令插队。
事务错误处理

  1. 入队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
  2. 如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚
    乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

redis事务的特性:

  1. 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。
  2. 事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  3. 没有隔离级别的概念:队列中的命令没有提交之前都不会实际被执行, 被放入队列缓存,只有等到提交的时候才能够真正的执行。
  4. 不保证原子性: Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

redis持久化

**RDB:**在指定的时间间隔内将内存中的数据集快照写入磁盘,它恢复时是将快照文件直接读到内存里。

备份是如何执行的?
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
RDB优势:

适合大规模的数据恢复,对数据完整性和一致性要求不高更适合使用

节省磁盘空间恢复速度快,直接读取数据进行恢复

劣势

复刻(Fork)的时候,内存中的数据被克隆了一份,导致空间浪费

虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。

在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

**AOF:**将Redis执行过的所有写指令记录下来(读操作不记录), 追加Aof文件中,但不可以改写文件,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

AOF持久化流程

客户端的请求写命令会被append追加到AOF缓冲区内;

AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;

Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;

AOF优势:

备份机制更稳健,丢失数据概率更低。

可读的日志文本,通过操作AOF稳健,可以处理误操作。

劣势

比起RDB文件占用更多的磁盘空间。因为将所有操作指令保存下来了

恢复备份速度要慢。需要将操作指令重新执行一遍。

每次读写都同步的话,有一定的性能压力。

由于采用敕令追加的方式会导致AOF文件不断增大,当文件大小超过阈值是,为启动AOF文件压缩的的功能,保存可以回复数据的最小指令集。如果AOF文件持续增大,会采用重写的机制,将 会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件
用哪个好?

官方推荐两个都启用。如果对数据不敏感,可以选单独用RDB。不建议单独用 AOF,因为可能会出现Bug。如果只是做纯内存缓存,可以都不用。
AOF和RDB同时开启,redis听谁的?

AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)

缓存穿透

问题描述

key对应的数据在数据源并不存在, 用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

解决方案:

  1. 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟
  2. 设置可访问的名单(白名单):使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
  3. 采用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有误差(hash冲突)

将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。

缓存击穿

问题描述

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库。导致数据库瞬间压力过大。

解决方案解决问题:

  1. 预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
  2. 热点数据永不过期。
  3. 使用锁:使用互斥锁,只让一个请求去load DB,成功之后重新写缓存,其余请求没有获取到互斥锁,可以尝试重新获取缓存中的数据。

缓存雪崩

问题描述

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

比如在双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

解决方案:

  1. 构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)
  2. 使用锁或队列:用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
  3. 将缓存失效时间分散开:比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率 就会降低,就很难引发集体失效的事件。

Redis 为什么是单线程的

因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。并且,单线程容易实现,那就顺理成章地采用单线程的方案了,Redis利用队列技术将并发访问变为串行访问

1)绝大部分请求是纯粹的内存操作(非常快速)

2)采用单线程,避免了不必要的上下文切换和竞争条件

3)非阻塞IO优点:

1.速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

2支持丰富数据类型,支持string,list,set,sorted set,hash

3.支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

4.丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除。
^Redis是纯内存操作,执行速度非常快,它的性能瓶颈是网络延迟而不是执行速度,因此多线程并不会带来巨大的性能提升。

^多线程会导致过多的上下文切换,带来不必要的开销

^引入多线程会面临线程安全问题,必然要引入线程锁这样的安全手段,实现复杂度增高,而且性能也会大打折扣

讲解下Redis线程模型

文件事件处理器包括分别是套接字、 I/O 多路复用程序、 文件事件分派器、 以及事件处理器

使用 I/O 多路复用程序来同时监听多个套接字,

并根据套接字目前执行的任务来为套接字关联不同的事件处理器。

当被监听的套接字准备好执行连接应答、读取、写入、关闭等操作时, 与操作相对应的文件事件就会产生,

文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

I/O 多路复用程序负责监听多个套接字, 并向文件事件分派器传送那些产生了事件的套接字。

redis主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主
1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

2、故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。

3、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。

4、高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础redis主从复制可以有多种形式:

一主二仆

在这种模式下。一个主节点挂在两个从节点。

如果从服务器挂掉再重启,变成主服务器,需要手动配置才能变成从服务器,并且请求同步主服务器数据。

从机是否可以写?set可否? 不能

主机shutdown后情况如何?从机是上位还是原地待命? 原地待命

主机又回来了后,主机新增记录,从机还能否顺利复制? 能
薪火相传

上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,去中心化降低风险。

风险是一旦某个slave宕机,后面的slave都没法备份

主机挂了,从机还是从机,无法写数据了
反客为主

当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改。(手动)用 slaveof no one 将从机变为主机。
当Redis进行主从复制

复制原理

当从服务器连接到主服务器后,从服务器向主服务器发送进行数据同步消息

主服务器接到从服务器发送过来的同步消息,把主服务器数据进行持久化,生成rdb文件,把rdb文件发送从服务器,

从服务器拿到rdb进行读取并进行同步。之后采用增量同步的方式,每次主服务器进行写操作之后,和从服务器进行数据同步

但是当直接宕机之后就不能提供相关的服务。

哨兵模式

当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。 Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题 。能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

哨兵作用:

1、哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例是否正常运行。

2、当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

多哨兵模式

一个哨兵进程对Redis服务器进行监控,当哨兵挂掉时,就无法提供服务,或者发送命令由于网络原因主节点并不能返回确认信息。可能会出现问题,可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行故常转移过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
优点:

  1. 哨兵集群模式是基于主从模式的,所有主从的优点,哨兵模式同样具有。

  2. 主从可以切换,故障可以转移,系统可用性更好。

  3. 哨兵模式是主从模式的升级,系统更健壮,可用性更高。

    缺点:

    1. Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
      1. 实现哨兵模式的配置也不简单,甚至可以说有些繁琐

redis集群

Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。

之前通过代理主机来解决,但是redis3.0中提供了解决方案。就是无中心化集群配置。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pmZ0IrWp-1657959162704)(C:\Users\Hao\Desktop\面试\img\image-20220714220152517.png)]

一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个,集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。

集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点,

其中:

节点 A 负责处理 0 号至 5460 号插槽。

节点 B 负责处理 5461 号至 10922 号插槽。

节点 C 负责处理 10923 号至 16383 号插槽。
Redis 集群提供了以下好处

实现扩容

分摊压力

无中心配置相对简单

Redis 集群的不足

多键操作是不被支持的

多键的Redis事务是不被支持的。lua脚本不被支持

由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

redis的Multi和Pipleline的区别

Redis中的Multi和Pipleline都可以一次性执行多个命令,但是Pipeline只是把多个redis指令一起发出去,redis并没有保证这些指令执行的顺序,且减少了多次网络传递的开销,因而其执行效率很高;

Multi相当于一个redis的transaction,保证整个操作的有序性,通过watch这些key,可以避免这些key在事务的执行过程中被其它的命令修改,从而导致得的到结果不是所期望的

redis的watch机制:

6383 号插槽。
Redis 集群提供了以下好处

实现扩容

分摊压力

无中心配置相对简单

Redis 集群的不足

多键操作是不被支持的

多键的Redis事务是不被支持的。lua脚本不被支持

由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

redis的Multi和Pipleline的区别

Redis中的Multi和Pipleline都可以一次性执行多个命令,但是Pipeline只是把多个redis指令一起发出去,redis并没有保证这些指令执行的顺序,且减少了多次网络传递的开销,因而其执行效率很高;

Multi相当于一个redis的transaction,保证整个操作的有序性,通过watch这些key,可以避免这些key在事务的执行过程中被其它的命令修改,从而导致得的到结果不是所期望的

redis的watch机制:

增加watch命令,可以确保被watch的key在事务的执行期间,如果被其它连接修改了,则当前事务则会在执行exec时报错(报nil),事务被中断,事务中所有的命令都不会执行(但是被其它线程修改的那个会修改成功)。

你可能感兴趣的:(redis,java,数据库)