极客时间《Redis 核心技术与实战》学习笔记
概览Redis篇一:单线程模型 概览Redis篇二:AOF日志 概览Redis篇三:RDB快照 概览Redis篇四:主从 概览Redis篇五:哨兵 概览Redis篇六:切片集群 什么是哨兵,哨兵有什么用 哨兵其实就是一个运行在特殊模式下的 Redis 进程,主从库实例运行的同时,它也在运行。哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。
有了哨兵,我们就可以在主库故障的情况下快速实现主从库自动切换。
哨兵机制的基本流程 监控 哨兵在运行时,会周期性地给所有的主从库发送 PING 命令,检测它们是否仍然在线运行。如果从库没有在规定时间内响应哨兵的 PING 命令,哨兵就会把它标记为“下线状态”;同样,如果主库也没有在规定时间内响应哨兵的 PING 命令,哨兵就会判定主库下线,然后开始自动切换主库的流程。
选主 主库挂了以后,哨兵就需要从很多个从库里,按照一定的规则选择一个从库实例,把它作为新的主库。
通知 在选出新的主库后,哨兵会把新主库的连接信息发给其他从库,让它们执行 replicaof 命令,和新主库建立连接,并进行数据复制。同时,哨兵会把新主库的连接信息通知给客户端,让它们把请求操作发到新主库上。
哨兵如何判断主库是否下线 主观下线 哨兵进程会使用 PING 命令检测它自己和主、从库的网络连接情况,用来判断实例的状态。如果哨兵发现主库或从库对 PING 命令的响应超时了,那么,哨兵就会先把它标记为“主观下线”
如果检测的是从库,那么,哨兵简单地把它标记为“主观下线”就行了,因为从库的下线影响一般不太大,集群的对外服务不会间断。
但是,如果检测的是主库,那么,哨兵还不能简单地把它标记为“主观下线”,开启主从切换。因为很有可能存在这么一个情况:那就是哨兵误判了,其实主库并没有故障。可是,一旦启动了主从切换,后续的选主和通知操作都会带来额外的计算和通信开销。
因此我们要特别注意误判的情况。
为了减少误判,可以采用多实例组成的集群模式进行部署,这也被称为哨兵集群。引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。
客观下线 当大多数的哨兵实例,都判断主库已经“主观下线”了,主库才会被标记为“客观下线”,这个叫法也是表明主库下线成为一个客观事实了。
如何选择新主库 简单来说,哨兵选择新主库的过程可以描述为“筛选+打分”。筛选是说,我们在多个从库中,先按照一定的筛选条件,把不符合条件的从库去掉。而打分意思是按照一定的规则,给剩下的从库逐个打分,将得分最高的从库选为新主库,
那么筛选和打分的标准是怎样的?
在筛选时,除了要检查从库的当前在线状态,还要判断它之前的网络连接状态。使用配置项 down-after-milliseconds * 10。其中,down-after-milliseconds 是我们认定主从库断连的最大连接超时时间。如果在 down-after-milliseconds 毫秒内,主从节点都没有通过网络联系上,我们就可以认为主从节点断连了。如果发生断连的次数超过了 10 次,就说明这个从库的网络状况不好,不适合作为新主库。
筛掉了不适合做主库的从库后,需要对剩下的从库进行打分。分别按照一下三个规则进行三轮:
优先级最高的从库得分高。 用户可以通过 slave-priority 配置项,给不同的从库设置不同优先级。...
极客时间《Redis 核心技术与实战》学习笔记
概览Redis篇一:单线程模型 概览Redis篇二:AOF日志 概览Redis篇三:RDB快照 概览Redis篇四:主从 概览Redis篇五:哨兵 概览Redis篇六:切片集群 什么是切片集群,有什么用 切片集群,也叫分片集群,就是指启动多个 Redis 实例组成一个集群,然后按照一定的规则,把收到的数据划分成多份,每一份用一个实例来保存。
当Redis保存大量数据时,其在进行RDB持久化时需要fork一个子进程,而fork子进程的用时和Redis的数据量是正相关的,而 fork 在执行时会阻塞主线程。数据量越大,fork 操作造成的主线程阻塞的时间越长。因此如果Redis保存了大量数据,会导致Redis响应变慢。
数据切片后,在多个实例之间如何分布 从 3.0 开始,官方提供了一个名为 Redis Cluster 的方案,用于实现切片集群。Redis Cluster 方案中就规定了数据和实例的对应规则。
Redis Cluster 方案采用哈希槽Hash Slot,来处理数据和实例之间的映射关系。在 Redis Cluster 方案中,一个切片集群共有 16384 个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中。
具体的映射过程分为两大步:首先根据键值对的 key,按照CRC16 算法计算一个 16 bit 的值;然后,再用这个 16bit 值对 16384 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽。
在部署 Redis Cluster 方案时,可以使用 cluster create 命令创建集群,此时,Redis 会自动把这些槽平均分布在集群实例上。例如,如果集群中有 N 个实例,那么,每个实例上的槽个数为 16384/N 个。当然,也可以使用cluster meet命令手动建立实例间的连接,形成集群,再使用cluster addslots 命令,指定每个实例上的哈希槽个数。在手动分配哈希槽时,需要把 16384 个槽都分配完,否则 Redis 集群无法正常工作。...
极客时间《Redis 核心技术与实战》学习笔记
概览Redis篇一:单线程模型 概览Redis篇二:AOF日志 概览Redis篇三:RDB快照 概览Redis篇四:主从 概览Redis篇五:哨兵 概览Redis篇六:切片集群 Redis的高可靠性 Redis 具有高可靠性,又是什么意思呢?其实,这里有两层含义:一是数据尽量少丢失,二是服务尽量少中断。
AOF 和 RDB 保证了前者,而对于后者,Redis 的做法就是增加副本冗余量,将一份数据同时保存在多个实例上。即使有一个实例出现了故障,需要过一段时间才能恢复,其他实例也可以对外提供服务,不会影响业务使用。
Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。
读操作:主库、从库都可以接收; 写操作:首先到主库执行,然后,主库将写操作同步给从库。 为什么要读写分离
如果在上图中,不管是主库还是从库,都能接收客户端的写操作,那么,一个直接的问题就是:如果客户端对同一个数据(例如 k1)前后修改了三次,每一次的修改请求都发送到不同的实例上,在不同的实例上执行,那么,这个数据在这三个实例上的副本就不一致了(分别是 v1、v2 和 v3)。在读取这个数据的时候,就可能读取到旧的值。
当然我们可以使这个数据在三个实例上保持一致,但这涉及到加锁,实例间协商等一系列操作,会带来巨额的开销。
而主从库模式一旦采用了读写分离,所有数据的修改只会在主库上进行,不用协调三个实例。主库有了最新的数据后,会同步给从库,这样,主从库的数据就是一致的。
主从库如何进行第一次同步 当我们启动多个 Redis 实例的时候,它们相互之间就可以通过 replicaof(Redis 5.0 之前使用 slaveof)命令形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步。
例如,现在有实例 1(ip:172.16.19.3)和实例 2(ip:172.16.19.5),我们在实例 2 上执行以下这个命令后,实例 2 就变成了实例 1 的从库,并从实例 1 上复制数据:
replicaof 172.16.19.3 6379 主从库间数据第一次同步有三个阶段: 主从库间建立连接、协商同步。主要是为全量复制做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了。
具体来说,从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数.
runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。...
8086结构 x86架构中经典的处理器8086的大体结构如下:
其寻址范围为1M
为了暂存数据,8086 处理器内部有 8 个 16 位的通用寄存器,也就是刚才说的 CPU 内部的数据单元,分别是 AX、BX、CX、DX、SP、BP、SI、DI。这些寄存器主要用于在计算过程中暂存数据。这些寄存器比较灵活,其中 AX、BX、CX、DX 可以分成两个 8 位的寄存器来使用,分别是 AH、AL、BH、BL、CH、CL、DH、DL,其中 H 就是 High(高位),L 就是 Low(低位)的意思。
控制单元:
IP 寄存器就是指令指针寄存器(Instruction Pointer Register),指向代码段中下一条指令的位置。CPU 会根据它来不断地将指令从内存的代码段中,加载到 CPU 的指令队列中,然后交给运算单元去执行。
如果需要切换进程,每个进程都分代码段和数据段,为了指向不同进程的地址空间,有四个 16 位的段寄存器,分别是 CS、DS、SS、ES。
其中,CS 就是代码段寄存器(Code Segment Register),通过它可以找到代码在内存中的位置;DS 是数据段的寄存器,通过它可以找到数据在内存中的位置。SS 是栈寄存器(Stack Register)。栈是程序运行中一个特殊的数据结构,数据的存取只能从一端进行,秉承后进先出的原则,push 就是入栈,pop 就是出栈。
存储起始地址的CS和DS都是16位的;存储偏移量的IP寄存器和通用寄存器都是16位的;但8086地址总线是20位的。如何从16位的寄存器寻址到20位的地址?
方法是:起始地址×16+偏移量,也就是把 CS 和 DS 中的值左移 4 位,变成 20 位的,加上 16 位的偏移量,这样就可以得到最终 20 位(1M)的数据地址。
32位处理器 32位处理器必须保持和原来处理器的兼容。
首先,通用寄存器有扩展,可以将 8 个 16 位的扩展到 8 个 32 位的,但是依然可以保留 16 位的和 8 位的使用方式。其中,指向下一条指令的指令指针寄存器 IP,就会扩展成 32 位的,同样也兼容 16 位的。...
一个UDP客户可以创建一个套接口并发送一个数据报給一个服务器,然后立即用同一个套接口发送另一个数据报給另一个服务器。同样,一个UDP服务器可以用同一个UDP套接口从5个不同的客户一连串接收5个数据报。
UDP可以是全双工的
TCP连接断开时,主动要求断开的一方会存在TIME_WAIT状态,此状态需要持续60s时间(Linux),在此状态期间,连接未完全断开,依然占用一个socket连接。
存在TIME_WAIT状态的理由:
实现终止TCP全双工连接的可靠性 如果最后一个ACK丢失,服务器将重发最终的FIN,因此客户必须维护状态信息以允许它重发最终的ACK。
允许老的重复分节在网络中消逝。 如果某个连接被关闭后,在以后的某个时刻又重新建立起相同的IP地址可端口之间的TCP连接。后一个连接称为前一个连接的化身(incarnation),因为它们的IP地址和端口号都相同,TCP必须防止来自某个连接的老重复分组在连接终止后再现,从而被误解成属于同一连接的化身。 也就是说,某一个Socket对失效后,在网络中还有针对这个Socket对的分组的情况下又建立了一个一样的Socket对,为了防止网络中针对上一个socket对的分组被误认为是当前连接的分组,必须存在一个时间间隔,使得网络中针对上一个socket对的分组失效。
TCP连接耗尽: 对于客户端来说,其端口耗尽后,就不能再建立连接。 对于服务端来说,其使用socket对server_ip.server_port:client_ip.client_port来标识一个socket连接,对于某个特定服务来说,其server_ip和server_port是不变的,每次有一个连接进来,服务端都会fork出一个自身的子进程来对连接进行服务。这样,可服务的连接数就由client_ip和client_port两个共同决定,client_ip最多有$2^32$个,client_port最多有$2^16$个,因此理论上最多可服务连接数为$2^48$个。但每一个socket连接都需要消耗一个文件描述符,因此最大连接数还收到文件描述符数目的限制。除此之外,socket连接还会占用内存等资源,这些资源也限制了最大连接数。
Unix系统有保留端口的概念,它是小于1024的任何端口。这些端口只能分配給超级用户进程的套接口,所有众所周知的端口(0-1023)都为保留端口,因此分配这些端口的服务器启动时必须具有超级用户的特权。
TCP套接口编程 socket函数 为了执行网络I/O,一个进程必须做的第一件事就是调用socket函数,指定期望的通信协议类型。
#include <sys/socket.h> int socket(int family, int type, int protocol); 其中family指明协议族,type是某个常值,参数protocol一般设为0,除非用在原始套接口上。
对于family,其是以下常值之一:
族 解释 AF_INET IPv4协议 AF_INET6 IPv6协议 AF_LOCAL Unix域协议 AF_ROUTE 路由套接口 AF_KEY 密钥套接口 对于type,其是以下常值之一:...
极客时间《Redis 核心技术与实战》学习笔记
概览Redis篇一:单线程模型 概览Redis篇二:AOF日志 概览Redis篇三:RDB快照 概览Redis篇四:主从 概览Redis篇五:哨兵 概览Redis篇六:切片集群 AOF: Append Only File
AOF日志的WAL 数据库中的WAL是Write-Ahead Logging, 为先写日志后执行。而Redis中的WAL为Write Ahead Log,先执行后写日志。
为了避免额外的开销,Redis在向AOF里面记录日志时,不回去对这些命令进行语法检查,因此,如果先记录日志再执行命令的话,日志中有可能记录了错误的命令,Redis在使用日志恢复数据时,就可能会出错。
AOF日志中记录了什么内容 有如下命令:
set testkey testvalue 则AOF日志内容为:
*3 $3 set $7 testkey $9 testvalue 其中“*3”表示当前命令有3个部分,每部分都由“$+数字”开头,后面紧跟具体的命令、键或值。
AOF的潜在风险 AOF使用先执行,再写日志的方式,这样可以避免记录错误的命令,同时不会阻塞当前的写操作。
但这样也带来了一些潜在风险:
刚执行完一个命令,没来得及记日志就宕机了,这个命令和相应的数据就有丢失的风险。 AOF避免了对当前命令的阻塞,但AOF写日志是在主线程中执行的,可能会给下一个操作带来阻塞风险。 这就需要我们对AOF写日志的实际进行把控。
AOF的三种写回策略 Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
针对避免主线程阻塞和减少数据丢失问题,这三种写回策略都无法做到两全其美。原因如下:
“同步写回”可以做到基本不丢数据,但是它在每一个写命令后都有一个慢速的落盘操作,不可避免地会影响主线程性能; 虽然“操作系统控制的写回”在写完缓冲区后,就可以继续执行后续的命令,但是落盘的时机已经不在 Redis 手中了,只要 AOF 记录没有写回磁盘,一旦宕机对应的数据就丢失了; “每秒写回”采用一秒写回一次的频率,避免了“同步写回”的性能开销,虽然减少了对系统性能的影响,但是如果发生宕机,上一秒内未落盘的命令操作仍然会丢失。所以,这只能算是,在避免影响主线程性能和避免数据丢失两者间取了个折中。 总结一下:...
1. 分层的镜像 在我们启动一个容器之前,通常需要下载这个容器对应的镜像,以这个镜像为基础启动容器。镜像中包含了对应的程序的二进制文件与其所依赖的文件,程序在启动后看到的rootfs只是这个镜像中存在的文件。这样,我们就可以为容器中的进程提供一个干净的文件系统。
创建一个镜像(image)的最简单方法是使用Dockerfile。
FROMscratchCOPY hello /CMD ["/hello"]scratch是docker为我们提供的一个空镜像,我们可以在此基础上构建任何我们想要的镜像。
在书写Dockerfile时,想必你听说过这么一句话,不要在Dockerfile中创建太多层.
在Dockerfile中,每一个指令都会创建一个新的“层”,这里的层,指的是UnionFS中的一个文件目录。当我们创建了过多的层,会导致镜像体积变大,除此之外,Union FS 也会有最大层数限制。
因此对于如下的Dockerfile文件写法,应尽量避免:
FROMdebian:stretchRUN apt-get updateRUN apt-get install -y gcc libc6-dev make wgetRUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"RUN mkdir -p /usr/src/redisRUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1RUN make -C /usr/src/redisRUN make -C /usr/src/redis install可优化为如下写法
FROMdebian:stretchRUN set -x; buildDeps='gcc libc6-dev make wget' \ && apt-get update \ && apt-get install -y $buildDeps \ && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \ && mkdir -p /usr/src/redis \ && tar -xzf redis....
极客时间《Redis 核心技术与实战》学习笔记
概览Redis篇一:单线程模型 概览Redis篇二:AOF日志 概览Redis篇三:RDB快照 概览Redis篇四:主从 概览Redis篇五:哨兵 概览Redis篇六:切片集群 Redis单线程模型 Redis 是单线程,主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
为什么Redis采用单线程 通常的程序设计都采用多线程来提高性能,获得更快的响应速度。
但采用多线程模型将不可避免地面对以下问题:
多个线程对同一资源的数据竞争 因解决数据竞争而导致的性能损耗 增加系统复杂度,降低系统代码的易调试性和可维护性 为啥Redis单线程还这么快 主要是两点:
Redis大部分操作在内存上完成,并采用了高效的数据结构 Redis采用了多路复用机制
1. 什么是inotify Inotify API提供了一种监视文件系统的机制事件。 Inotify可用于监视单个文件或监视目录。当监视目录时,Inotify将返回目录本身的事件,以及内部的文件目录。
简单的说,就是inotify可以为你监控文件系统的变化,在发生一些事件时通知你。
2. 实验 需要安装inotify-tools包
# arch系统 yay -S inotify-tools inotify-tools提供了两个命令:inotifywait和inotifywatch
2.1 inotifywait 先来看inotifywait,它被用来"Wait for a particular event on a file or set of files.", 也就是说等待在文件上的某些事件发生。使用inotifywait -h查看其支持的参数:
-h show this help info --exclude <pattern> 排除匹配所给正则的所有事件 --excludei <pattern> 类似前一个命令但非敏感 --include <pattern> 排除除匹配正则之外的所有事件 --includei <pattern> 类似前一个命令但非敏感 -m|--monitor 在timeout之前保持监听,若不设置此标志,inotifywait将在一个事件后退出。 -d|--daemon 类似前一个命令但在后台运行,将日志输出到`--outfile`所指定的文件 -P|--no-dereference 不跟踪符号链接 -r|--recursive 递归的监听、 --fromfile <file> Read files to watch from <file> or `-' for stdin. -o|--outfile <file> 输出到<file>而不是标准输出 -s|--syslog 向syslog发送错误而不是Stderr -q|--quiet 只输出事件 -qq 啥也不输出 --format <fmt> 以特定格式输出 --no-newline 在格式化输出后不打印换行符 --timefmt <fmt> strftime-compatible format string for use with %T in --format string....
极客时间《MySQL实战45讲》笔记
MySQL的基本结构 大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。
Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。
现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。
从一条查询语句看各个模块的执行过程 对于如下语句:
mysql> select * from T where ID=10; 连接器 第一步,你会先连接到这个数据库上,这时候接待你的就是连接器。连接器负责跟客户端建立连接、获取权限、维持和管理连接。连接命令一般是这么写的:
mysql -h$ip -P$port -u$user -p 这里要注意的一个问题是:一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。
因为一个连接的权限实在用户名密码认证通过后,连接器到权限表中查出的,之后这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。
查询缓存 连接建立完成后,你就可以执行 select 语句了。执行逻辑就会来到第二步:查询缓存。
之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果在缓存中可以找到查询结果,可以直接返回结果,如果找不到,就会继续后面的执行阶段。
但大多数情况下缓存往往弊大于利,因为查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。
可以将query_cache_type参数设置为DEMAND,这样对于默认的SQL查询语句都不使用缓存。
需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。
分析器 如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。
分析器先会做“词法分析”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。
做完了这些识别以后,就要做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。
优化器 经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。...