网站做网页,教育类手机网站模板下载,网络认证网站,90设计网站兼职怎么样1、redis为什么快
1#xff09;Redis是单线程执行#xff0c;在执行时顺序执行
redis单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的#xff0c;Redis在处理客户端的请求时包括获取(socket 读)、解析、执行、内容返回 (socket 写)等都由一个顺序串行的主线…1、redis为什么快
1Redis是单线程执行在执行时顺序执行
redis单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的Redis在处理客户端的请求时包括获取(socket 读)、解析、执行、内容返回 (socket 写)等都由一个顺序串行的主线程处理这就是所谓的“单线程”。这也是Redis对外提供键值存储服务的主要流程。
2redis的多路复用
从redis6.x开始采用io多路复用让单个线程高效的处理多个连接请求尽量减少网络IO的时间消耗将最耗时的Socket的读取、请求解析、写入单独外包出去剩下的命令执行仍然由主线程串行执行并和内存的数据交互。 2、redis高效的数据结构
Redis支持五种主要数据结构字符串Strings、列表Lists、哈希表Hashes、集合Sets和有序集合SortedSets。这些数据结构为开发者提供了灵活的数据操作方式满足了不同场景下的数据存储需求。
• 字符串Strings最基本的数据类型可以包含任何数据如数字、字符串、二进制数据等。在Redis中字符串是二进制安全的这意味着它们可以有任何长度并且不会因为包含空字符而被截断。
• 列表Lists简单的字符串列表按照插入顺序排序。你可以添加一个元素到头部左边或者尾部右边。
• 哈希表Hashes是键值对的集合是字符串类型的字段和值的映射表。适合存储对象。
• 集合Sets是字符串类型的无序集合。它是通过哈希表实现的可以做到添加、删除、查找的时间复杂度都是O(1)。
• 有序集合Sorted Sets和Sets相似但每个字符串元素都会关联一个浮点数类型的分数。元素的分数用来排序如果两个成员有相同的分数那么他们的排名按照字典序计算。 1字符串的底层实现简单动态字符串SDS 优势
预分配SDS会为buf分配额外的未使用空间通过free字段记录这意味着当你向一个SDS字符串追加内容时如果未使用空间足够Redis就不需要重新分配内存。这减少了内存分配次数从而提高了性能。
常数时间复杂度获取字符串长度由于SDS结构内部维护了一个len字段来记录字符串的当前长度获取字符串长度的操作可以在常数时间复杂度O(1)内完成而不需要像C语言的原生字符串那样遍历整个字符串。
二进制安全SDS可以存储任意二进制数据包括空字符\0。C语言的原生字符串以空字符作为结束标志这限制了它们不能包含空字符。而SDS则通过len字段来明确字符串的长度因此不受此限制。
兼容C语言字符串函数尽管SDS提供了自己的一套API来进行字符串操作但它的buf字段实际上就是一个普通的C字符串以\0结尾这意味着在必要时可以直接使用标准的C语言字符串处理函数来操作buf字段尽管通常不推荐这样做因为可能会破坏SDS结构的完整性。
2列表的底层实现双向链表与压缩列表
双向链表
当列表的元素数量较多或者元素较大时Redis会选择使用双向链表作为底层实现。双向链表中的每个节点都保存了前一个节点和后一个节点的指针这使得在列表的任何位置插入或删除元素都变得相对容易。 优势
可以在O(1)时间复杂度内完成在列表头部或尾部的元素插入和删除。 当需要遍历列表时可以从头部或尾部开始沿着节点的指针依次访问。
压缩列表
当列表的元素数量较少且元素较小时Redis会使用压缩列表ziplist作为底层实现来节省内存。压缩列表是一个紧凑的、连续的内存块它按顺序存储了列表中的元素。
ZLBYTE 压缩列表的头部信息包含了特殊编码和压缩列表的长度信息。
LEN 每个元素前的长度字段用于记录该元素的长度或前一个元素到当前元素的偏移量。
‘one’, ‘two’ 实际的列表元素它们被连续地存储在压缩列表中。
优势
内存利用率高因为元素是连续存储的没有额外的指针开销。
对于小列表操作速度可以很快因为所有数据都在一个连续的内存块中。
3哈希的底层实现Redis中的字典与压缩列表
Redis的哈希Hashes类型允许用户在单个键中存储多个字段和对应的值。为了高效地支持这种数据结构Redis在底层使用了两种主要的数据结构来实现哈希字典也称为哈希表和压缩列表。
字典HASH表
当哈希中的字段和值较多或者较大时Redis会选择使用字典作为底层实现。字典是一种通过键在Redis哈希中是字段来直接访问值的数据结构它能够在平均情况下提供O(1)时间复杂度的查找、插入和删除操作。 优势
提供了快速的字段查找、插入和删除操作。 哈希表的扩容机制可以保持较低的哈希冲突率从而保证操作的效率。
压缩列表
当哈希中的字段和值较少且较小时Redis会使用压缩列表作为底层实现来节省内存。压缩列表是一种紧凑的、连续的内存块它按顺序存储了哈希中的字段和值对 优势
• 内存利用率高因为字段和值是连续存储的没有额外的指针和元数据开销。
• 对于小哈希操作速度可以很快因为所有数据都在一个连续的内存块中。
4集合的底层实现整数集合和字典
Redis的集合Sets是一个无序的、元素不重复的集合。为了高效地支持这种数据结构及其操作Redis在底层使用了两种主要的数据结构整数集合intset和字典hashtable。
整数集合int set
当集合中的元素都是整数并且元素数量较少时Redis会选择使用整数集合作为底层实现。整数集合是一个紧凑的数组数组中的每个元素都是集合中的一个整数。 优势
• 内存利用率高整数集合将整数紧密地存储在一个连续的内存块中没有额外的指针或元数据开销。
• 操作速度快对于整数集合中的元素Redis可以直接通过数组索引访问这使得查找、添加和删除整数的操作非常快速。 然而整数集合也有其局限性。由于它要求集合中的元素必须是整数并且元素数量较少因此在处理非整数元素或大量元素时整数集合可能不是最优的选择
5有序集合的底层实现跳表和压缩列表
Redis的有序集合SortedSets是一个有序的、元素不重复的集合其中每个元素都关联了一个分数score。为了实现这种数据结构及其相关操作的高效性Redis在底层主要使用了两种数据结构压缩列表ziplist和跳表skiplist。
跳表skiplist
当有序集合的元素数量较多或元素的大小较大时Redis会使用跳表作为底层实现。跳表是一种多层的有序链表它通过维护多个层次的指针来加快查找、插入和删除操作的速度。 优势
• 查找效率高通过维护多个层次的指针跳表可以在平均情况下提供O(log N)时间复杂度的查找操作其中N是元素的数量。
• 插入和删除操作快速跳表的插入和删除操作只需要局部地调整指针而不需要移动大量的数据。
• 支持范围查询跳表可以方便地支持按照分数范围查询元素的操作
3、redis的持久化机制
Redis是一个基于内存的数据库它的数据是存放在内存中内存有个问题就是关闭服务或者断电会丢失。Redis的数据也支持写到硬盘中这个过程就叫做持久化。
Redis提供如下2中持久化方式
RDBRedis DataBase 在指定的时间间隔内定时的将 redis 存储的数据生成Snapshot快照并存储到磁盘等介质上Redis默认开启了rdb存储。
AOFAppend Of File 将 redis 执行过的所有写指令记录下来在下次 redis 重新启动时只要把这些写指令从前到后再重复执行一遍就可以实现数据恢复了。
AOFRDB RDB 和 AOF 两种方式也可以同时使用在这种情况下如果 redis 重启的话则会优先采用 AOF 方式来进行数据恢复这是因为 AOF 方式的数据恢复完整度更高
3.1 RDB
RDB概述
RedisDatabase Backup file(Redis数据备份文件)也被叫作Redis数据快照。简单的来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后从磁盘读取快照文件二进制文件恢复数据。快照文件称为RDB文件。
我们知道 Redis 是单线程程序这个线程要同时负责多个客户端套接字的并发读写操作和内存数据结构的逻辑读写。
在服务线上请求的同时Redis 还需要进行内存快照内存快照要求 Redis 必须进行文件 IO 操作这意味着单线程同时在服务线上的请求还要进行文件 IO 操作文件 IO 操作会严重拖垮服务器请求的性能。还有个重要的问题是为了不阻塞线上的业务就需要边持久化边响应客户端请求。 - 相关命令 - save命令 - 作用该命令将在redis安装目录中创建dump.rdb文件。 - 特点该命令将立即执行持久化操作同时会造成线程阻塞。 - 示例 - bgsave命令 - 作用将内存快照写入dump.rdb文件完成持久化。 - 特点该命令则会在台后异步执行持久化操作不会造成线程阻塞但不保证立即执行持久化。 - 策略执行时机 - 当前已知的执行时机有执行flushall命令后、正常关闭redis时执行save、bgsave命令。 - 使用该策略里恢复数据 - redis启动时会读取dump.rdb。当我们使用RDB模式时恢复数据只需要将dump.rdb备份文件拷贝到redis启动目录启动redis即可。 如果当redis子线程持久化的数据正常被父线程再修改的时候怎么办
在Redis中当子进程正在执行RDB持久化即将内存中的数据写入磁盘的临时文件时如果父进程即主线程同时修改了内存中的数据Linux操作系统的写时复制Copy-On-Write, COW机制会确保数据的一致性和完整性。
具体来说当父进程修改内存中的某个页面时由于子进程和父进程共享这些内存页面Linux内核会为这个页面创建一个副本并将修改应用到这个副本上而不是直接修改原始的共享页面。这样子进程在遍历和写入RDB文件时它读取的是修改前的数据快照而父进程可以继续处理客户端的请求并修改内存中的数据两者互不影响。
一旦子进程完成了RDB文件的写入它会用这个临时文件替换旧的RDB文件从而完成了整个持久化过程。在这个过程中由于写时复制机制的存在父进程的修改不会影响到子进程正在写入的RDB文件的数据一致性。
需要注意的是虽然RDB持久化可以确保数据的可靠性和持久性但在Redis宕机或故障恢复时可能会丢失最后一次RDB持久化之后的数据修改。因此对于需要更高数据可靠性和一致性的应用Redis还提供了另一种持久化方式——AOFAppend Only File它可以将每一个写命令都追加到AOF文件中从而在Redis重启时可以通过重新执行这些命令来恢复数据。
COPY-ON-WRITE写时复制cow
Linux操作系统中fork()机制的一个重要特性——写时复制
概括Redis在使用快照RDB持久化时如何通过fork()系统调用来创建一个子进程并且这个子进程与父进程共享内存中的代码段和数据段
Redis在持久化时会调用 glibc 的函数 fork产生一个子进程快照持久化完全交给子进程来处理父进程继续处理客户端请求。子进程刚刚产生时它和父进程共享内存里面的代码段和数据段。这时你可以将父子进程想像成一个连体婴儿共享身体。这是Linux 操作系统的机制为了节约内存资源所以尽可能让它们共享起来。在进程分离的一瞬间内存的增长几乎没有明显变化。 RDB优缺点
RDB 的优点
体积更小相同的数据量 RDB 数据比 AOF 的小因为 RDB 是紧凑型文件。
恢复更快因为 RDB 是数据的快照基本上就是数据的复制不用重新读取再写入内存。
性能更高父进程在保存 RDB 时候只需要fork一个子进程无需父进程的进行其他io操作也保证了服务器的性能。
RDB 的缺点
故障丢失因为 RDB是全量的我们一般是使用shell脚本实现30分钟或者1小时或者每天对 Redis 进行 RDB 备份注也可以是用自带的策略但是最少也要5分钟进行一次的备份所以当服务死掉后最少也要丢失5分钟的数据。
耐久性差相对 AOF 的异步策略来说因为 RDB 的复制是全量的即使是 fork的子进程来进行备份当数据量很大的时候对磁盘的消耗也是不可忽视的尤其在访问量很高的时候主线程 fork的时间也会延长导致 cpu 吃紧耐久性相对较差。
3.2 AOF
AOF概述
以独立日志的方式记录每次写命令重启时再重新执行AOF文件中的命令达到回复数据的目的。AOF的主要作用是解决了数据持久化的实时性。 系统调用write和fsync说明
Write操作会触发延迟写机制。Linux在内核提供页缓冲区用来提高硬盘IO性能。wirte操作在写入系统缓冲区后直接返回。同步硬盘操作依赖于系统调度机制。例如缓冲区页空间写满或达到特定时间周期。同步文件之前如果此时系统故障宕机缓冲区内数据将丢失。
fync针对单个文件操作比如AOF文件做强制硬盘同步fsync将阻塞直到写入硬盘完成后返回保证了数据持久化
AOF追加阻塞
当开启AOF持久化时常用的同步硬盘的策略是everysec用于平衡性能和数据安全性。对于这种方式Redis使用另一条线程每秒执行fsync同步硬盘。当系统硬盘资源繁忙时会造成Redis主线程阻塞
阻塞流程分析
1主线程负责写入AOF缓冲区
2AOF线程负责每秒执行一次同步磁盘操作并记录最近一次同步时间
3主线程负责对比上次AOF同步时间
如果距上次同步成功时间在2秒内主线程直接返回。
如果距上次同步成功时间超过2秒主线程将会阻塞直到同步操作完成 开启AOF功能需要设置配置 appendonly yes默认不开启。AOF文件名通过appendfilename配置设置默认文件名是appendonly.aof。保存路径同RDB持久化方式一致通过dir配置指定。 - 使用该策略恢复redis - 只需要将持久化文件copy到redis的启动目录重启redis即可。
AOF重写
随着命令不断写入AOF文件会越来越大为了解决这个问题redis引入了AOF重写机制压缩文件体积。
AOF重写是基于什么进行重写的
AOFAppend Only File重写是基于Redis进程内的当前数据状态进行重写的。AOF重写的目的是压缩AOF文件的大小提高Redis的性能并清除AOF文件中的无用命令减少冗余。
AOF重写的原理大致如下
开启子进程Redis会启动一个子进程来执行AOF重写操作。这个子进程会遍历Redis的所有键值对并将其转化为一条条写命令。写入临时文件子进程将这些写命令写入到一个新的临时文件中而不是直接修改原来的AOF文件。这个临时文件只包含当前有效和存在的数据的写入命令而不是历史上所有的写入命令。处理新写操作在子进程进行AOF重写的过程中主进程父进程会继续接收和处理客户端的请求。如果有新的写操作发生主进程会将这些写操作追加到一个缓冲区中并通过管道通知子进程。合并新写操作子进程在完成AOF重写后会将缓冲区中的新写操作也追加到临时文件中。这一步是为了确保在AOF重写过程中发生的所有写操作都不会被遗漏。替换AOF文件子进程向主进程发送信号通知主进程可以切换到新的AOF文件了。主进程在收到子进程的信号后会用临时文件替换旧的AOF文件并关闭旧的AOF文件。此时AOF重写操作完成Redis会开始使用新的AOF文件进行持久化。
AOF重写流程 AOF优缺点
AOF 的优点
数据保证我们可以设置fsync策略一般默认是 Everysec也可以设置每次写入追加所以即使服务死掉了也最多丢失一秒数据
自动缩小当 AOF 文件大小到达一定程度的时候后台会自动的去执行 AOF 重写此过程不会影响主进程重写完成后新的写入将会写到新的 AOF 中旧的就会被删除掉。但是此条如果拿出来对比 RDB 的话还是没有必要算成优点只是官网显示成优点而已。
AOF 的缺点
性能相对较差它的操作模式决定了它会对Redis 的性能有所损耗。主线程写文档
体积相对更大尽管是将 AOF 文件重写了但是毕竟是操作过程和操作结果仍然有很大的差别体积也毋庸置疑的更大。
恢复速度更慢因为要重新加载每条命令的执行恢复速度比较慢
3.3 持久化模式选择
根据业务需求选择。 需求一允许少量数据丢失则首选RDB模式。需求二不允许数据丢失则首选AOF模式。企业的选择实现redis主从模式 主机采用RDB模式从机采用AOF模式
3.4 问题总结
①RDB和AOF分别存储的是什么
1.RDBRedis DataBase - RDB存储的是Redis在某个时间点的数据快照。具体来说它保存了Redis在持久化时内存中的数据库状态。 - RDB文件是一个经过压缩的二进制文件它包含了Redis中所有键值对的数据并且是以一种紧凑的方式存储的。 - RDB的持久化过程是通过fork一个子进程来完成的子进程会遍历Redis的内存数据并将其写入到磁盘上的RDB文件中。这个过程中父进程即主进程可以继续处理客户端的请求。 - RDB的优点是生成的文件体积较小恢复速度快。但缺点是可能会丢失最后一次RDB持久化之后的数据修改。 2.AOFAppend Only File - AOF存储的是Redis服务器所执行的每一个写操作命令如SET、DEL等这些命令以文本的形式追加到AOF文件中。 - 与RDB不同AOF文件是一个持续增长的文本文件它记录了Redis从启动到当前时刻的所有写操作。 - AOF的持久化过程是通过将每一个写命令追加到AOF文件末尾来完成的。Redis在接收到写命令后会先将命令写入到AOF缓冲区然后再根据配置的AOF同步策略将缓冲区的内容同步到磁盘上的AOF文件中。 - AOF的优点是提供了更高的数据可靠性因为它记录了所有的写操作。在Redis重启时可以通过重新执行AOF文件中的命令来恢复数据。但缺点是AOF文件通常比RDB文件大并且恢复速度相对较慢。
②COW写时复制还在哪些场景用到了
1.文件系统 - 一些文件系统如Btrfs和ZFS使用了COW技术来优化数据修改。当文件被修改时文件系统不会立即覆盖原始数据而是创建一个数据的副本并在副本上进行修改。这样多个进程或用户可以同时读取原始数据而不会受到写操作的影响。 2.虚拟内存管理 - 在操作系统中虚拟内存管理经常使用COW技术来处理进程的内存页。当父进程创建子进程时子进程会继承父进程的内存页但这些内存页最初是共享的即使用COW。只有当某个进程尝试修改其内存页时操作系统才会为该进程创建一个该页面的副本并在副本上进行修改。 3.编程语言 - 在一些编程语言中COW技术被用于优化字符串和集合等数据结构。例如在Java的String类中由于字符串是不可变的immutable每次对字符串进行修改如拼接、替换等时都会创建一个新的字符串对象。然而为了优化性能Java的某些实现可能会使用COW技术来延迟或避免不必要的字符串复制。 4.数据库 - 在数据库系统中COW技术可以用于实现多版本并发控制MVCC。MVCC允许多个事务同时读取同一份数据并通过为每个事务提供数据的快照来实现并发控制。当某个事务尝试修改数据时数据库系统可以使用COW技术为该事务创建一个数据的副本并在副本上进行修改从而避免与其他事务发生冲突。 5.容器技术
- 在容器技术如Docker中COW技术被用于实现容器的轻量级和快速启动。当创建一个新的容器时容器管理器会基于一个已有的镜像即基础文件系统来创建容器的文件系统。由于容器和镜像之间使用了COW技术因此容器在启动时只需要复制和修改与镜像不同的部分从而实现了快速启动和轻量级的特性。 6.分布式系统 - 在分布式系统中COW技术可以用于实现数据复制和一致性。当多个节点需要共享数据时可以使用COW技术来确保每个节点在读取数据时都看到相同的数据版本。当某个节点需要修改数据时可以使用COW技术为该节点创建一个数据的副本并在副本上进行修改。这样其他节点可以继续读取原始数据而不会受到写操作的影响。同时通过一定的同步机制如分布式锁、Raft等可以确保所有节点最终都能看到一致的数据版本。
③哪种持久化方式丢失数据的风险最小
在Redis的持久化机制中AOFAppend Only File通常被认为丢失数据的风险最小原因如下
记录所有写操作AOF持久化方式会记录Redis服务器所执行的每一个写操作命令并将其追加到AOF文件中。这意味着即使发生宕机或异常也可以通过重新执行AOF文件中的命令来恢复数据。灵活性AOF文件是一个文本文件易于理解和处理。此外Redis提供了多种AOF重写策略如always、everysec和no允许用户根据实际需求选择数据持久化的频率和性能之间的平衡。数据完整性由于AOF记录了所有的写操作因此在恢复数据时可以保证数据的完整性和一致性。即使在写操作过程中出现错误或异常也可以通过AOF文件恢复到正确的状态。持久性保证AOF持久化方式提供了较高的持久性保证。在配置为always模式时Redis会在每个写操作后都同步AOF文件确保数据的实时持久化。而在everysec模式下Redis会每秒同步一次AOF文件提供了较好的性能和持久性之间的平衡。
然而需要注意的是虽然AOF丢失数据的风险较小但AOF文件通常比RDB文件大且恢复速度相对较慢。因此在选择持久化方式时需要根据实际需求和场景进行权衡和选择。如果对数据持久性的要求非常高且可以接受较慢的恢复速度则可以选择AOF持久化方式如果对数据持久性的要求相对较低且需要较快的恢复速度则可以选择RDB持久化方式。
4、redis的内存优化策略
1.redis使用中可能存在的隐患
缓存穿透 产生原因在高并发环境下访问数据库和缓存中都不存在的数据的现象。解决方案 禁用IP。限流。布隆过滤器。缓存击穿 产生原因某热点数据在缓存中突然失效导致大量的用户直接访问数据库而数据库无法抵抗并发压力而造成数据库服务器宕机或其它异常。解决方案 尽快可能将热点数据的超时时间设定的长一点。设定多级缓存并选择合适的内存优化策略。缓存雪崩 产生原因在缓存服务器中由于大量的缓存数据失效导致用户访问的命中率过低导致大量用户直接访问数据库。能够产生雪崩的操作 flushall命令解决方案 设定超时时间时应该采用随机算法采用多级缓存。
2.内存优化策略作用
由于内存资源相对较少而redis中的数据中保存在内存中如果数据库的数据庞大时则无法全部存在内存中。因此需要确保redis缓冲中的数据是使用次数最多的数据。redis内存优化策略即是通过数据载入算法使redis中的数据是使用最多数据从而减少对数据库的访问。
3.redis内存优化策略
LRU算法最近最少使用算法 算法策略将最近最久未使用的页面予以淘汰。实现方式 该算法赋予每个页面一个访问字段用来记录一个页面上次被访问以来所经历的时间t。当须淘汰一个页面时选择现有页面中t值最大的即最近最少使用的页面予以淘汰。淘汰标准时间tLFU算法最不经常使用算法。 算法策略将使用次数最少的页面予以淘汰。实现方式 该算赋于每个页面一个访问字段用来记录一个页面被访问的次数在引用计数器定时右移一位形成指数衰减后的平均次数。将平均次数最小的予以淘汰。淘汰标准平均使用次数。Random算法随机淘汰算法。TTL算法根据剩余的存活时间排序根据生存时间多少来淘汰数据。
4.redis的内存优化策略的设置
设置方式修改redis.conf配置文件。 参数 前缀 volatile-在设定超时时间的数据中。allkeys-在所有数据中。参数实体 lruLRU算法。lfuLFU算法。randomRANDOM算法。ttl:TTL算法。此参数只能使用于volatile-前缀中。无前缀参数 noeviction默认规则。如果内存满了则不做任何操作直接报错返回。
5、redis的架构模式
Redis支持三种集群模式分别为主从模式、哨兵模式和集群Cluster模式。
最初Redis采用主从模式构建集群。在这种模式下如果主节点(master)出现故障需要手动将从节点(slave)转换为主节点。然而这种模式在故障恢复方面效率不高。
为了提高系统的可用性Redis引入了哨兵模式。在哨兵模式中一个哨兵集群负责监控主节点和从节点。如果检测到主节点故障系统可以自动将从节点晋升为新的主节点。这提高了故障恢复的自动化程度。
尽管如此哨兵模式仍然面临内存容量和写入性能的限制因为这种模式的写入能力仍然局限于单个节点。为了解决这一问题Redis在3.x版本之后推出了Cluster集群模式。Cluster模式通过数据分片和节点的水平扩展实现了更高效的内存利用和写入性能。 5.1 redis的主从架构模式
1redis的主从架构
在Redis的主从复制架构中系统通过定义主库master和从库slave的角色实现数据的高效同步和备份。这一架构具体包含以下特点
•master的读写能力master是系统中的数据中心它不仅承担全部的写操作还能处理读请求。当在master上执行任何改变数据的操作时这些更改会自动且实时地同步到所有slave。
•单向数据流数据同步流是单向的意味着数据只从master流向slave确保了数据同步的一致性和可靠性。
•slave的只读特性slave通常被配置为只读模式它们接收并存储从master传来的数据。这样设计主要是为了分散读取压力从而提高系统的整体读取性能。
•主slave的对应关系一个master可以对应多个slave形成一对多的关系。这种结构利于数据的冗余备份和读取负载的分散。相反一个slave只能对应一个master以保持数据同步的一致性。
•slave的容错性如果某个slave出现故障它对系统其他部分的影响是最小的。即便在slave宕机的情况下其它slave仍能继续提供读服务master也能保持正常的读写操作。当故障的slave恢复后它会自动从master同步缺失的数据。
•master故障的影响master的故障会导致Redis暂时无法处理新的写请求但已连接的slave可以继续提供读服务。一旦master恢复Redis会重新提供完整的读写服务。
•master故障的应对机制在当前的master发生故障时系统不会自动在slave中选择一个新的master。这需要通过额外的高可用性解决方案来实现例如使用Redis
Sentinel或Redis Cluster来管理master的选举和故障转移。 2redis的主从同步原理 Redis的主从同步Replication原理主要包括以下几个关键步骤和机制 1.建立连接 - 当配置一个Redis实例Slave为另一个实例Master的从服务器时Slave会尝试与Master建立连接。 - 一旦连接建立成功Slave会成为Master的一个客户端并且这个连接是持久的即使因为网络问题断开Slave也会尝试重新连接。 2.数据同步 - 当Slave第一次连接Master或者与Master的连接断开后重新连接时Slave会向Master发送一个SYNC命令在Redis 2.8及以后的版本中这个命令被替换为PSYNC。 - 对于第一次同步或全量同步Full SynchronizationMaster会执行一个BGSAVE命令来在后台异步生成一个RDBRedis DataBase持久化快照文件同时收集所有在BGSAVE执行期间接收到的写命令并缓存这些命令。 - 当BGSAVE完成后Master会发送RDB文件给SlaveSlave会加载这个RDB文件到内存中从而完成数据的全量同步。 - 在发送RDB文件的同时或之后Master会把缓存的写命令发送给SlaveSlave会执行这些命令完成增量同步Partial Synchronization使得Slave的数据状态与Master保持一致。 3.命令传播 - 在完成初始的数据同步后Master会将其接收到的写命令发送给SlaveSlave会执行这些命令从而保持与Master的数据同步。 - 这种机制基于长连接Master和Slave之间的连接在数据同步完成后会保持打开状态以便进行后续的命令传播。 4.心跳检测 - Slave会定期向Master发送心跳Ping命令以检查Master是否仍然在线和可访问。 - 如果Slave在一定时间内没有收到Master的响应它会认为Master可能已经宕机然后尝试重新连接Master或进行故障转移Failover。 5.故障转移 - 在Redis的主从架构中通常还会配置一个或多个Sentinel哨兵实例来监控Master和Slave的状态。 - 如果Sentinel检测到Master宕机它会从剩余的Slave中选择一个作为新的Master并通知其他Slave和客户端进行更新。
①全量同步 从节点通过配置文件中的replicaof {masterip} {port} 获得主节点ip和port然后向主节点发送psync {repID} {offset} 指令其中repID表示主节点唯一标识offset为复制偏移量因为当前从节点与主节点尚未连接且尚未开始复制所以repID为 ?offset为-1
主节点收到psync {repID} {offset} 指令后会响应从节点并发送fullresync {repID} {offset} 指令从节点会将主节点的repID和offset保存下来
主节点收到psync {repID} {offset} 指令后会执行bgsave异步的生成RDB文件然后主节点将RDB文件发送给从节点从节点接收到RDB文件后会清空内存数据然后加载RDB文件的数据到内存中
由于主节点生成RDB文件时是异步生成的此时主节点是非阻塞的可以继续处理业务所以在生成RDB文件期间发送RDB文件期间和从节点加载RDB文件期间主节点执行的写指令均会存放到缓冲区replication_buffer中所以当从节点加载完RDB文件后主节点会将replication_buffer中的内容发送给从节点从节点会执行replication_buffer中的指令从而达到和主节点一致的状态。
②增量同步
主节点和从节点如果因为某些原因断开了连接而断开连接这段时间里主节点又处理了一些写指令那么从节点重新连接后应该怎么将断开连接那段时间里的写指令同步给重连的从节点通常的想法就是再执行一次全量同步在2.8之前的版本确实是这么实现的但从2.8版本开始引入了增量同步
具体的实现如下
主节点维护着一份repl_backlog_buffer缓冲区域叫做复制积压缓冲区主节点在任何时候执行写指令时都会将写指令记录在repl_backlog_buffer中repl_backlog_buffer是一个环形数组所以当数组满时后续再添加的写指令会覆盖旧的写指令因此主节点还使用了一个叫做master_repl_offset的偏移量来记录主节点的存到repl_backlog_buffer中的最新写指令的位置master_repl_offset就是上面提到的offset只不过在主节点中叫做master_repl_offset
从节点也有一个偏移量叫做slave_repl_offset用来记录从节点已经从主节点的repl_backlog_buffer中同步到的最新写指令的位置
主节点收到写指令后master_repl_offset增加从节点从主节点的repl_backlog_buffer同步了写指令后slave_repl_offset增加
从节点断开重连后会向主节点发送psync {repID} {slave_repl_offset} 指令此时slave_repl_offset通常会小于master_repl_offset所以主节点仅需要将slave_repl_offset到master_repl_offset之间的写指令同步给从节点这就是增量同步。
特别注意如果repl_backlog_buffer中记录的从节点断开连接期间的写指令已经被后续的写指令覆盖那么此时不能执行增量同步而是需要执行全量同步所以需要将repl_backlog_buffer的大小设置一个合理的值来尽可能的保证不出现重连后需要全量同步的情况。
5.2 哨兵架构模式
哨兵模式是主从复制模式的一种进阶形式继承了主从复制的所有优势如数据一致性和读写分离。它的核心优点在于能够自动实现主从切换和故障转移从而提升了系统的可用性和鲁棒性。在哨兵模式下系统能够从手动切换转变为自动切换极大地增强了系统的自动化程度和稳定性。然而哨兵模式也存在一定的局限性特别是在在线扩容方面。当集群容量接近或达到上限时进行扩容操作相对较为复杂和困难。 哨兵机制原理
哨兵通过发送命令(ping命令)等待Redis服务器响应如果在指定时间内主机Redis无响应从机则判断主机宕机选举从机上位从而监控运行的多个Redis实例。 第一步心跳机制
每个Sentinel 会每秒钟 一次的频率向它所知的 主服务器、从服务器 以及其他Sentinel 实例 发送一个 PING 命令获取其拓扑结构和状态信息。
第二步判断master节点是否下线
每个sentinel 哨兵节点每隔1s 向所有的节点发送一个PING命令作用是通过心跳检测检测主从服务器的网络连接状态。
如果master 节点回复 PING命令的时间超过down-after-milliseconds 设定的阈值默认30s则这个 master 会被sentinel 标记为主观下线。
第三步基于Raft算法选举领头sentinel
master客观下线那就需要一个sentinel来负责故障转移所以需要通过选举一个sentinel的领头羊来解决。
第四步故障转移
故障转移的一个主要问题和选择领头sentinel问题差不多就是要选择一个slaver节点来作为master。
选择主Maseter过程大致如下
① 选择优先级最高的节点通过sentinel配置文件中的replica-priority配置项这个参数越小表示优先级越高
② 如果第一步中的优先级相同选择offset最大的offset表示主节点向从节点同步数据的偏移量越大表示同步的数据越多
③ 如果第二步offset也相同选择run id较小的
这样通过以上四大步骤实现由Redis Sentinel自动完成故障发现和转移实现自动高可用。
第五步通知
通知所有子节点新的master后边从新的master上边同步数据广播通知所有客户端新的master。
5.3Cluster模式
由于数据量过大单个Master复制集难以承担因此需要对多个复制集进行集群形成水平扩展每个复制集只负责存储整个数据集的一部分这就是Redis的集群其作用是提供在多个Redis节点间共享数据的程序集。 Redis hash槽
Redis集群没有使用一致性hash而是引入了哈希槽的概念。
Redis集群有16384个哈希槽每个key通过CRC16校验后对16384取模来决定放置哪个槽集群的每个节点负责一部分hash槽。
举个例子比如当前集群有3个节点那么:集群的整个数据库被分为 16384 个槽slot数据库中的每个键都属于这 16384 个槽的其中一个集群中的每个节点可以处理 0 个或最多 16384 个槽。
Key 与哈希槽映射过程可以分为两大步骤
根据键值对的 key使用 CRC16 算法计算出一个 16 bit 的值
将 16 bit 的值对 16384 执行取模得到 0 16383 的数表示 key 对应的哈希槽。 6、redis和mysql一致性解决同步问题 双写模式
在双写模式即同时写入Redis和MySQL中先写Redis还是先写MySQL的顺序取决于特定的业务场景和需求。以下是两种顺序各自适合的情况
①先写MySQL再写Redis 适用场景
对数据一致性要求高由于MySQL是持久化存储先写入MySQL可以确保数据在磁盘上的安全性。即使Redis因为某些原因丢失数据也可以从MySQL中恢复。Redis主要用于缓存如果Redis主要用于缓存热点数据以加速读取而不是作为数据源那么先写入MySQL可以确保即使Redis中的数据丢失或不一致也不会影响核心业务的正常运行。可以容忍一定的延迟由于网络延迟、Redis写入性能等因素先写MySQL再写Redis可能会导致Redis中的数据相对MySQL有一定的延迟。如果这种延迟对业务来说是可接受的那么这种顺序就是合适的。
注意事项
需要确保Redis的写入操作在MySQL写入操作成功之后执行以避免出现数据不一致的情况。在Redis写入失败时需要有重试机制以确保数据的最终一致性。
②先写Redis再写MySQL 适用场景
对性能要求极高Redis作为内存数据库写入性能远高于MySQL。在某些对性能要求极高的场景下如实时计算、在线分析等可能需要先写入Redis以满足性能需求。Redis作为数据源在某些特殊场景下Redis可能作为数据源使用而MySQL只是用于备份或离线分析。在这种情况下需要先写入Redis以确保数据的实时性。
注意事项
由于Redis是内存数据库存在数据丢失的风险。因此在写入Redis之后必须确保MySQL的写入操作也成功执行以确保数据的持久化存储。同样需要处理Redis写入失败的情况确保数据的最终一致性。
总结
选择先写Redis还是先写MySQL的顺序应该根据具体的业务需求和系统架构来决定。在大多数情况下先写MySQL再写Redis是一个更稳妥的选择因为它可以确保数据的一致性和安全性。但在某些对性能要求极高的场景下可能需要考虑先写Redis再写MySQL的顺序。无论选择哪种顺序都需要确保在写入过程中处理可能出现的失败情况以确保数据的最终一致性。
异步更新
RabbitMQ的使用
RabbitMQ是一个实现了高级消息队列协议AMQP的开源消息代理软件亦称面向消息的中间件。RabbitMQ服务器是用Erlang语言编写的而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。
RabbitMQ的主要特点包括
可靠性RabbitMQ基于AMQP协议提供了持久化、可靠的消息传递机制。它确保消息能够在发送和接收之间进行可靠地传输即使在出现故障的情况下也能保证消息的安全性。灵活性RabbitMQ支持多种消息传递模式包括点对点、发布/订阅、请求/响应等。它允许开发人员根据应用程序的需求来选择合适的消息模式实现灵活的消息传递。同时它也支持水平扩展可以在需要时添加更多的节点来处理更多的消息。
RabbitMQ的主要用途包括
限流消峰在系统处理高并发请求时可以通过RabbitMQ将请求放入消息队列中进行排队限制同时访问系统的请求数量防止系统因过载而崩溃。应用解耦RabbitMQ可以作为消息队列将不同的系统或应用进行解耦降低系统间的耦合度提高系统的可维护性和可扩展性。异步处理RabbitMQ支持异步处理使得系统可以更加高效地处理请求和响应。在传统的同步通信方式中请求方需要等待响应方返回结果后才能继续执行后续操作。而在异步通信方式中请求方将请求发送到消息队列后立即返回无需等待响应方返回结果。响应方可以在处理完请求后将结果发送到另一个消息队列中供请求方或其他服务或组件异步获取。这种方式可以显著提高系统的吞吐量和响应速度降低系统的延迟和阻塞。
导入依赖
dependenciesdependencygroupIdcom.rabbitmq/groupIdartifactIdamqp-client/artifactIdversion3.3.4/version/dependency
/dependencies
RabbitMqConnection连接类
package rabbitMq;import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;import java.io.IOException;public class RabbitMqConnection {private static final ConnectionFactory connectionFactory;static {//定义一个链接工厂connectionFactory new ConnectionFactory();//设置服务地址connectionFactory.setHost(localhost);//设定端口connectionFactory.setPort(5672);//设定用户名connectionFactory.setUsername(guest);//设定密码connectionFactory.setPassword(guest);//设定虚拟机connectionFactory.setVirtualHost(/);}public static Connection getConnection() throws IOException {try{return connectionFactory.newConnection();}catch (IOException e){System.out.println(获取链接失败);return null;}}
}RabbitSender发送端
package rabbitMq;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;import java.io.IOException;public class RabbitSender {private final static String QUEUE_NAME hello;//交换机private final static String exchange_name test;public static void main(String[] argv) throws IOException {Connection connection RabbitMqConnection.getConnection();if (connection ! null){//创建通道Channel channel connection.createChannel();//创建一个队列 QUEUE_NAME自定义队列名称channel.queueDeclare(QUEUE_NAME,false,false,false,null);String message hello lala;channel.basicPublish(,QUEUE_NAME,null,message.getBytes());System.out.println(发送消息 message);//关闭通道和连接channel.close();connection.close();}}
}RabbitConsumer接收端
package rabbitMq;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.*;import java.io.IOException;public class RabbitConsumer {private final static String QUEUE_NAME hello;public static void main(String[] argv) throws IOException {Connection connection RabbitMqConnection.getConnection();if (connection ! null) {Channel channel connection.createChannel();channel.queueDeclare(QUEUE_NAME, false, false, false, null);DefaultConsumer defaultConsumer new DefaultConsumer(channel) {Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String message new String(body);System.out.println(接收消息内容 message);}};// 监听队列参数二是否自动进行消息确认false代表手动确认channel.basicConsume(QUEUE_NAME, true, defaultConsumer);}}
}基于binlog的同步 延迟双删
- 当更新MySQL时首先删除Redis中的相关缓存项。然后在更新MySQL后等待一段时间根据业务逻辑的耗时来确定再次删除Redis中的同一缓存项。 - 这种方法可以减少在高并发场景下由于缓存覆盖导致的数据不一致问题。但是它并不能完全解决所有的一致性问题。 1.延时双删有等待环节如果系统要求低延时这种场景就不合适了。
2.延时双删不适合“秒杀”这种频繁修改数据和要求数据强一致的场景。
3.延时双删延时时间是一个预估值不能确保 mysql 和 redis 数据在这个时间段内都实时同步或持久化成功了 分布式锁
为什么要使用分布式锁
本地锁只能锁住单台机器多线程请求的资源
分布式锁是多台机器共同访问redis中的一个数据只有获取到锁的线程才能访问
如何设置分布式锁 在分布式锁中如果程序突然中断怎么释放锁
在分布式锁中如果程序突然中断解决锁的释放问题主要有以下几种策略
设置锁的超时时间为锁设置一个合理的超时时间即使程序突然中断锁也会在超时后自动释放。这可以通过Redis的EXPIRE命令或SET命令结合EX选项来实现。这样可以避免因为程序中断而导致的死锁问题。使用try-finally语句在程序中使用try-finally语句块来确保无论程序是否发生异常finally块中的代码都会被执行。这样你可以在finally块中添加释放锁的代码以确保在程序中断时锁能够被正确释放。但是这种方法无法解决服务器宕机或网络中断等极端情况。利用Redis的键空间通知Redis提供了键空间通知功能可以在键被删除、过期等事件发生时发送通知。你可以利用这个功能来监听锁的键当锁过期或被删除时收到通知的客户端可以释放自己持有的锁。但是这种方法需要额外的监听和处理逻辑可能会增加系统的复杂性。使用Redis的RedLock算法RedLock算法是Redis官方推荐的一种分布式锁实现方式它通过多个Redis节点来确保锁的可靠性和安全性。在RedLock算法中如果客户端因为某种原因无法释放锁其他客户端可以通过轮询或等待一段时间后尝试获取锁从而避免死锁问题。设计良好的锁释放策略除了上述技术手段外还需要设计良好的锁释放策略来避免死锁问题。例如可以定期检测并清理过期的锁或者为锁设置唯一的标识符并在释放时进行验证等。
如果锁的时间片到了但是程序还没有执行完这种情况怎么解决
在分布式锁中如果时间片即锁的超时时间到了但程序还没有执行完这可能会导致数据不一致或并发问题。针对这种情况有几种常见的解决方案
1.锁续命Lock Renewal
如果锁支持续命操作如Redisson中的看门狗机制你可以尝试在锁到期之前重新设置或延长其超时时间。这通常通过一个定时任务或后台线程来实现定期检查锁的状态并在需要时续命。但请注意这需要在锁被释放之前完成否则可能会导致竞争条件。 2.增加锁的超时时间
如果任务确实需要较长时间来完成并且你可以接受更长的等待时间那么你可以考虑增加锁的超时时间。但请注意过长的超时时间可能会增加死锁的风险因为如果一个进程持有锁并因为某种原因崩溃或进入长时间阻塞状态那么其他等待该锁的进程可能会被阻塞很长时间。
读写分离
- 将读操作从MySQL转移到Redis中只在MySQL中执行写操作。这样可以提高系统的性能但会导致读操作的数据与写操作的数据存在一定的延迟。 - 这种方法适用于对实时性要求不高的场景。
使用Redis的事务功能
- Redis支持MULTI/EXEC命令来实现事务功能可以确保多个命令的原子性执行。在更新Redis数据时可以将多个命令放在一个事务中执行以确保数据的一致性。