Blog

主从库的第一次同步

Content #

现在有实例 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 两个参数。

  1. runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。
  2. offset,此时设为 -1,表示第一次复制。

主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。

FULLRESYNC 响应表示第一次复制采用的全量复制。

主库将所有数据同步给从库 #

主库执行 bgsave 命令,生成 RDB 文件,接着将文件发给从库。从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。

在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。

主库会把第二阶段执行过程中新收到的写命令,再发送给从库 #

当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作。

...

bgsave避免阻塞主进程的原理

Content #

Redis 就会借助操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。

bgsave 子线程是由主线程 fork 生成的,可以共享主线程的所有内存数据。 bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。

如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和 bgsave 子进程相互不影响。

如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本(键值对 C’)。然后,主线程在这个数据副本上进行修改。同时,bgsave 子进程可以继续把原来的数据(键值对 C)写入 RDB 文件。

这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。

Viewpoints #

From #

05 | 内存快照:宕机后,Redis如何实现快速恢复?

AOF非阻塞重写

Content #

AOF 日志由主线程写回,重写过程是由后台子进程 bgrewriteaof 来完成的,这也是为了避免阻塞主线程,导致数据库性能下降。

我把重写的过程总结为“一个拷贝,两处日志”。

“一个拷贝”就是指,每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。

“两处日志”又是什么呢?

因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有写操作,第一处日志就是指正在使用的 AOF 日志,Redis 会把这个操作写到它的缓冲区。这样一来,即使宕机了,这个 AOF 日志的操作仍然是齐全的,可以用于恢复。

而第二处日志,就是指新的 AOF 重写日志。这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,我们就可以用新的 AOF 文件替代旧文件了。

总结来说,每次 AOF 重写时,Redis 会先执行一个内存拷贝,用于重写;然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。而且,因为 Redis 采用额外的线程进行数据重写,所以,这个过程并不会阻塞主线程。

Viewpoints #

From #

04 | AOF日志:宕机了,Redis如何避免数据丢失?

哨兵的三个任务

Content #

哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。

  1. 监控。

监控是指哨兵进程在运行时,周期性地给所有的主从库发送 PING 命令,检测它们是否仍然在线运行。如果从库没有在规定时间内响应哨兵的 PING 命令,哨兵就会把它标记为“下线状态”;同样,如果主库也没有在规定时间内响应哨兵的 PING 命令,哨兵就会判定主库下线,然后开始自动切换主库的流程。

  1. 选主。

主库挂了以后,哨兵就需要从很多个从库里,按照一定的规则选择一个从库实例,把它作为新的主库。这一步完成后,现在的集群里就有了新主库。

  1. 通知。

在执行通知任务时,哨兵会把新主库的连接信息发给其他从库,让它们执行 replicaof 命令,和新主库建立连接,并进行数据复制。同时,哨兵会把新主库的连接信息通知给客户端,让它们把请求操作发到新主库上。

Viewpoints #

From #

07 | 哨兵机制:主库挂了,如何不间断服务?

Kafka的零拷贝

Content #

从磁盘读数据发送到网络上去。如果我们自己写一个简单的程序,最直观的办法,自然是用一个文件读操作,从磁盘上把数据读到内存里面来,然后再用一个 Socket,把这些数据发送到网络上去。

File.read(fileDesc, buf, len);
Socket.send(socket, buf, len);

数据一共发生了四次传输的过程。

  1. 从硬盘上读到操作系统内核的缓冲区里。这个传输是通过 DMA 搬运的。
  2. 从内核缓冲区里面的数据,复制到应用分配的内存里面。这个传输是通过 CPU 搬运的。
  3. 从应用的内存里面,再写到操作系统的 Socket 的缓冲区里面去。这个传输,还是由 CPU 搬运的。
  4. 从 Socket 的缓冲区里面,写到网卡的缓冲区里面去。这个传输又是通过 DMA 搬运的。

Kafka 把这个数据搬运的次数,从四次,变成了两次,并且只有 DMA 来进行数据搬运,不需要 CPU。

@Override
public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
    return fileChannel.transferTo(position, count, socketChannel);
}

Kafka 的代码调用了 Java NIO 库,具体是 FileChannel 里面的 transferTo 方法。数据并没有读到中间的应用内存里面,而是直接通过 Channel,写入到对应的网络设备里。并且,对于 Socket 的操作,也不是写入到 Socket 的 Buffer 里面,而是直接根据描述符(Descriptor)写入到网卡的缓冲区里面。于是,在这个过程之中,只进行了两次数据传输。

第一次,是通过 DMA,从硬盘直接读到操作系统内核的读缓冲区里面。第二次,则是根据 Socket 的描述符信息,直接从读缓冲区里面,写入到网卡的缓冲区里面。

在这个方法里面,我们没有在内存层面去“复制(Copy)”数据,所以这个方法,也被称之为零拷贝(Zero-Copy)。

Viewpoints #

From #

48 | DMA:为什么Kafka这么快?

...

DMAC数据传输原理

Content #

DMA 控制器(DMA Controller,简称 DMAC)

总线上的设备有两种类型。主设备(Master)和从设备(Slave)。

想要主动发起数据传输,必须要是一个主设备才可以,CPU 就是主设备。而从设备(比如硬盘)只能接受数据传输。所以,如果通过 CPU 来传输数据,要么是 CPU 从 I/O 设备读数据,要么是 CPU 向 I/O 设备写数据。

那 I/O 设备不能向主设备发起请求么?可以是可以,不过这个发送的不是数据内容,而是控制信号。I/O 设备可以告诉 CPU,我这里有数据要传输给你,但是实际数据是 CPU 拉走的,而不是 I/O 设备推给 CPU 的。

DMAC对于 CPU 来说,它是一个从设备;对于硬盘这样的 IO 设备来说呢,它又变成了一个主设备。

用 DMAC 进行数据传输的过程:

  1. CPU 向 DMAC 设备发起请求。这个请求,其实就是修改 DMAC 的配置寄存器,告诉 DMAC 如下信息:
    • 源地址的初始值以及传输时候的地址增减方式。
    • 目标地址初始值和传输时候的地址增减方式。
    • 要传输的数据长度。
  2. 设置完这些信息之后,DMAC 就会变成一个空闲的状态(Idle)。
  3. 硬盘向 DMAC 发起数据传输请求。这个请求是通过一个额外的连线。
  4. DMAC 需要通过额外的连线响应这个申请。
  5. DMAC 向硬盘的接口发起要总线读的传输请求。数据就从硬盘里面,读到了 DMAC 的控制器里面。
  6. DMAC 再向内存发起总线写的数据传输请求,把数据写入到内存里面。
  7. 反复进行第 6、7 步的操作,直到 DMAC 的寄存器里面设置的数据长度传输完成。
  8. DMAC 重新回到第 3 步的空闲状态。

整个数据传输的过程中,不是通过 CPU 来搬运数据,而是由 DMAC 这个芯片来搬运数据。但是 CPU 在这个过程中也是必不可少的。因为传输什么数据,从哪里传输到哪里,其实还是由 CPU 来设置的。这也是为什么,DMAC 被叫作“协处理器”。

...

behave like a sunbeam

Content #

Talking to Francis gave me the sensation of settling slowly to the bottom of the ocean. He was the most boring child I ever met. As he lived in Mobile, he could not inform on me to school authorities, but he managed to tell everything he knew to Aunt Alexandra, who in turn unburdened herself to Atticus, who either forgot it or gave me hell, whichever struck his fancy. But the only time I ever heard Atticus speak sharply to anyone was when I once heard him say, “Sister, I do the best I can with them!” It had something to do with my going around in overalls.

...

泻水著地,纵横流漫的现代批评

Content #

善人与恶人殷中军问:“自然无心于禀受,何以正善人少,恶人多?”诸人莫有言者。刘尹答曰:“譬如写水著地,正自纵横流漫,略无正方圆者。”一时绝叹,以为名通。——《世说新语·文学》

殷中军就是东晋清谈名家殷浩,刘尹就是辩才无碍的刘惔,因他曾官至丹阳尹常被称为刘尹。“略无”和“正自”是当时的口语,分别是“全无”和“只是”的意思。殷浩遇上了刘惔可以说是“棋逢对手”,他们时常在一起相互戏谑调侃,在舌战中逞雄辩斗机锋。

“性本善”还是“性本恶”这个问题,在中国历代文人学者中打了几千年官司。有的认为人性本善,有的宣称人性本恶,谁也不能说服谁。“性善”或“性恶”隐含着另一个同样复杂的问题:万物的存在形态是自然而然的,还是受其他意志支配的?

素有辩才之称的殷浩又向朋友挑起了这个难题:宇宙的万事万物的发展变化都是自然而然,无心接受其他外力的影响,为什么正直的善人少,奸邪的恶人多?大家一时都被问傻了眼,没有一个能对得上来。刘惔应声回答说:“譬如泻水着地,只是纵横四处流淌,绝对没有正方形或正圆形的。”人之生于世也像水之泄于地,难得形成正而且直的人。在座的人听他这么一说无不称叹,都认为这是至理名言。

不过,这句“名言”未必道出了“至理”,语言的俏皮未必能保证内容的正确。水是一种自然存在物,人则首先是一种“社会动物”,因而,水泻于地不同于人生于世,水泻于地没有正方形或正圆形,是自然属性决定的,世上的善人少恶人多,根源在于人生活的时代风气与社会环境。不仅社会环境和教育造就了善人或恶人,而且善恶本身也只有放在特定的社会中才能做出评价,在这个社会环境中的善,可能是另一个社会中的恶,这个人眼中的善人,可能是另一个人眼中的恶人。抽象地谈论性善性恶,既不会被证实也不会被证伪,既没有社会意义也没有理论价值。这些容易被现代人接受的东西,一千多年前的古人也许很难理解。

From #

郗公选婿的写作之妙

Content #

东床袒腹郗太傅在京口,遣门生与王丞相书,求女婿。丞相语郗信:“君往东厢,任意选之。”门生归,白郗曰:“王家诸郎,亦皆可嘉,闻来觅婿,咸自矜持,唯有一郎在东床上袒腹卧,如不闻。”郗公云:“正此好。”访之,乃是逸少,因嫁女与焉。——《世说新语·雅量》

作者笔致空灵跳脱,“君往东厢任意选之”一句后,径直便写“门生归”。从禀报郗太傅的话中见出门生选婿观察之仔细,如果一五一十详写他如何选婿,那真是佛头着粪,死板乏味。以“诸郎”的“咸自矜持”,衬托逸少的袒腹真率,反衬手法的运用也恰到好处。郗公“正此好”三字让人忍俊不禁,文字有一种不动声色的冷幽默。文中郗公写信求婿,王丞相表态同意,门生东厢挑选,然后回去禀报,接着郗公拍板,每一个人都忙得不亦乐乎,而真正的主角逸少始终没有露面。逸少人无须露面,其形象却活灵活现,这种“背面傅粉”技巧之高明令人叫绝。只用一百来个文字,居然写了那么多人物,那么曲折的情节,那么多转折顿宕,如此高明的艺术手腕你能不服吗?

From #