软件架构设计-读书笔记上

1,008 阅读27分钟

欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈

总篇章

image.png

这本书总共分为 个部分,共计 17 章 ,总体来说内容还是挺多的。内容相对全面,但并没有面面俱到,还是比较推荐阅读的一本书,话不多说,进入正文!

第一部分:什么是架构

第一部分由两个章节组成,简单的介绍了下什么是架构

第一章:五花八门的架构师职业

1.1 架构师职业分类

现在随便找一个招聘网站或猎头发布的招聘广告,我们都能看到各式各样的架构师头衔,比如有:Java 架构师,前端架构师,后端架构师,数据架构师,中间件架构师... 等等,而且年限的要求也各不一,3-5年,8-10年。

但是从这些岗位的需求我们可以看出,“架构师”中的架构是一个很虚的词,不同领域和行业对员工要求的能力和工作经验差异很大。

现在问起很多开发者的发展路线都不约而同的是要成为一名架构师,那么对架构师的定义是怎么样的?架构师在项目体系和团队结构中应当着一个怎么样的角色?如何成为一名架构师?这些你是否都有一个明确的答案,是否也为之目标而努力前行着!

1.2 架构的分类

单纯以技术的角度来看,软件系统自底向上可以分为三层

第一层:基础架构

基础架构是指云平台、操作系统、网络、存储这些构成,一些中小公司大多会选择使用大公司研发的云计算平台,研发成本低,稳定有保障

第二层:中间件与大数据层

中间件属于公司中必有的,类似消息中间件,数据库中间件,缓存中间件,而大数据层对于中小公司来说比较少有沉淀,类似开源的 Hadoop 生态体系,Hive、Spark、Storm、Fink等

第三层:业务系统架构

对于第三层的划分并不是绝对,图中体现了三种架构类型:通用软件架构离线业务系统架构在线业务系统架构,但由于现实中软件的种类过多,比如还存在嵌入式系统。这里简单描述下图中第三种具备的类种:

  • 通用软件架构:常用的办公软件、浏览器、播放器等
  • 离线业务系统:  基于大数据的 BI(商业智能) 分析、数据挖掘、报表与可视化等
  • 在线业务系统架构:  搜索、推荐、即时通信、电商、游戏、广告、企业ERP或CRM等

第二章:架构的道与术

2.1 何为道,何为术

不禁感叹这年头聊架构,这可以上道与术的层面了。

这张图是大多数项目的基本架构图,可以将每层映射到你们的项目中,是不是不会觉得很陌生。

那么实际中这张图能够反映出架构抉择吗,架构师的任务是否就是简单的划分层级结构,然后就可以埋头进行开发了?

我们依赖这张图将问题进行扩展:

  1. 如何拆分微服务?
  2. 如何组织服务与服务之间的层级关系?
  3. 如何设计接口?
  4. 如何保证高可用?如何分库分表?如何保证数据一致性?...

想要表达的问题实在是太多了,由此可见架构师的任务并不简单。

2.2 道与术的辩证关系

问题那么复杂,我们就以道与术来理解。假如你要成为一名武林高手,那么花里胡哨的招式对于某些人来说很重要,因为要追求好看,所谓的花架子,而招式我们便可理解为术,那么追求高手的层面,我们是否要修炼内功心法,底子扎实,才能成为顶级高手。

那么重要还是重要,这是个公说公有理婆说婆有理的问题,段誉的内功厉害,但使不出招式可能也有些徒然,招式好看,却没有内功支撑,也只能成为花架子的笑谈,而道术兼备,方能顶级

image.png

第二部分主要讲术,第三、四部分主要讲道

第二部分:计算机功底

这部分的内容颇多,重在道的修炼

第三章:语言

语言是在是太多了,忍不住吐槽~ 尽管语言如此之多,市面上还是不断地推陈出新,我们面对语言的不断迭代要追求潮流还是岿然不动?在我看来,我们要追求道,底层掌握结实,管它日转星移,我亦坦然相对。

语言再多再繁杂,都具备共同的典型特性,无外乎一些语法糖使用熟练与否

第四章:操作系统

I/O是绕不过去的一个基本问题。从文件I/O到网络I/O,存在着各式各样的概念和I/O模型

4.1 缓存I/O 和 直接I/O

在了解两个原理之前,我们先清楚几个概念:

  • 应用程序内存:  通常写代码用 malloc/free、new/delete 等分配出来的内存
  • 用户缓冲区:  位于用户空间中缓冲区,如 C语言FILE 结构体里面的 Buffer
  • 内核缓冲区:  Linux 操作系统的 Page Cache。一个 Page 的大小一般为 4K

以上三个概念了解后,我们继续看 I/O 操作

  • 缓冲I/O

:磁盘 -> 内核缓冲区 -> 用户缓冲区 -> 应用程序

写:  应用程序 -> 用户缓冲区 -> 内核缓冲区 -> 磁盘

对于缓冲I/O,一个读操作会有3次数据拷贝,一个写操作会有反向的3次数据拷贝

  • 直接I/O

读:  磁盘 -> 内核缓冲区 -> 应用程序

写:  应用程序 -> 内核缓冲区 -> 磁盘

对于直接I/O,一个读操作会有2次数据拷贝,一个写操作会有返现的2次数据拷贝

总结:直接I/O 并不是没有缓冲,而是没有用户级的缓冲,对于操作系统本身的缓冲还是有的

4.2 内存映射文件与零拷贝

1)内存映射文件

从缓冲I/O到直接I/O,读写操作从3次的数据拷贝缩减到2次数据拷贝。而到了内存映射文件,读写操作再次缩减到了1次数据拷贝,也就是:

  • 读: 磁盘 -> 内核缓冲区
  • 写:  内核缓冲区 -> 磁盘

应用程序虽然读写的是自己的内存,但这个内存只是一个 "逻辑地址",实际读写的是 内核缓冲区

2)零拷贝

零拷贝(Zero Copy)又是提升 I/O 效率的一大利器,在平时有问到 Kafka 是如何做到读写那么快的时候,其中一个很大的原因便是 Kafka 用到了零拷贝技术。

  1. 这是一个利用直接I/O进行收发文件的过程
image.png
  1. 这是一个利用内存映射文件进行收发文件的过程

整个过程从4次的数据拷贝降低到了3次,不再经过应用程序内存,直接在内核空间中从内核缓冲区拷贝到 Socket 缓冲区

  1. 这是一个利用零拷贝进行收发文件的过程

利用零拷贝的话,连内存缓冲区到Socket缓冲区的数据拷贝步骤都可以省略。在内核缓冲区和 Socket 缓冲区之间并没有做数据拷贝,只是一个地址的映射,底层的网卡驱动程序要读取数据并发送到网络的时候,看似读的是Socket缓冲区中的数据,实际上读得是内核缓冲区的数据

总结:为什么称之为零拷贝呢,因为从内存的角度上看,数据在内存中没有发生数据拷贝,只在内存与I/O之间传输。利用的还是 内核缓冲区 与 Socket缓冲区之间的映射,数据本身只有一份

4.3 网络 I/O 模型

网络 I/O 模型也是一个极易混淆的概念,至今为止我们听过几种网络I/O模型呢

  1. 网络阻塞 I/O
  2. 网络非阻塞 I/O
  3. I/O 多路复用
  4. 异步 I/O

很多时候我们容易混淆的概念是 非阻塞 和 异步

1)网络模型

1. 网络阻塞 I/O

这种模型很好理解,就是调用的时候会被阻塞,直到数据读取完成或写入成功

2. 网络非阻塞 I/O

和上述相反,但没有数据的时候会立即返回,不会阻塞,然后通过轮询的方式不断查询直到获取到数据

如果只有几十乃至上百个连接的时候,上面两种 I/O 模型处理的方式问题都不大,当连接数达到几十万乃至上百万时,那问题就很严重了

3. I/O 多路复用

该方式也是阻塞调用,一次性将所有的连接请求传进来,当某个连接请求具备条件后,会立即将结果放回,告知应用程序有哪些连接可读或可写。常用的 I/O 多路复用的方法有:select、poll、epoll、Java的NIO,其中 epoll 的效率最高,也是目前最主流的。

epoll

整个 epoll 分为三个步骤

  1. 事件注册
  2. 轮询查询是否就绪
  3. 事件就绪后进行读写

其中又可分为两种模式:LT(水平触发/条件触发) 和 ET(边缘触发/状态触发)

  1. LT 水平触发:只要读缓冲区不空就会一直触发读事件;写缓冲区不满,就会一直触发写事件
  2. ET 边缘触发:读缓冲区的状态从空转为非空的时候触发一次;写缓冲区的状态从满转为非满的时候触发一次

总结:实际的开发中,大家一般倾向于 LT(默认模式)。但使用的时候需要避免 "写死循环"的问题,因为写缓冲区为满的概率很小,会一直触发写事件

4. 异步 I/O

异步 I/O 是指所有的读写操作都由操作系统完成,当处理结束后,将结果通过指定的回调函数或其他机制告知应用程序

总结:  阻塞和非阻塞是从函数调用的角度来说,而同步和非同步是从 "读写是由谁来完成" 的角度来说

2)设计模式

除了上面几种网络I/O模型,我们还经常听到 Reactor 模式与 Proactor 模型,这两种并不是网络I/O模型。而是网络框架中的两种设计模式,无论操作系统的网络 I/O 模型的设计,还是上层网络框架的网络 I/O 模型的设计,用的都是这两中设计模型之一

1. Reactor 模式

这是一种主动模式。应用程序会不断地轮询,询问操作系统或网络框架、I/O 是否就绪。select、poll、epoll、Java中的NIO 就属于这种主动模式。

2. Proactor 模式

这是一种被动模式。应用程序会将所有的读写操作都交给操作系统完成,完成后再将结果通过一定的通知机制告知应用程序

详见IO系列文章

4.4 进程、线程和协程

不同语言有不同的使用习惯。如 Java 一般是写 单进程多线程 ,C++ 一般是 单进程多线程 或 多进程单线程

1)为什么要使用多线程?

  1. 提高 CPU 使用率
  2. 提高 I/O 吞吐

2)多线程会带来的问题?

  1. 锁(悲观锁、乐观锁、互斥锁、读写锁、自旋锁、公平/非公平锁等)
  2. Wait 与 Signal 操作
  3. Condition

3)为什么需要多进程?

  1. 线程间锁的存在,会导致并发效率下降,同时增加编码难度
  2. 线程上下文切换需要时间,过多的切换会导致效率低下
  3. 多进程相互独立,其中一个崩溃后,其他进程可以继续运行,提高可靠性

不要通过共享内存来实现通信,而应通过通信实现共享内存

通俗理解:尽可能通过消息通信, 而不是共享内存来实现进程或线程之间的同步

4)为什么需要协程?

  1. 更好地利用CPU,协程可以由应用程序自己调度,而线程不行
  2. 更好地利用内存,协程的堆栈大小不是固定的,用多少申请多少

image.png image.png

4.5 无锁(内存屏障与CAS)

1)内存屏障

读可以多线程、写必须单线程,如果多线程写,则做不到无锁

基于内存屏障(防止代码重排序),有了Java中的volatile关键字,再加上单线程写的原则,就有了Java中的无锁并发框架

2)CAS

如果是多线程写,内存屏障并不适用,这是就需要用到 CAS。CAS是在CPU层面提供的一个硬件原子指令,实现对同一个值的Compare和Set 两个操作的原子化。

第五章:网络

网络的具体认知可以空降:掌握《网络》,见微才能知著

第六章:数据库

6.1 范式与反范式

  • 第一范式:每个字段都是原子的,不能再分解。(反例:某个字段是 JSON 串,或数组)
  • 第二范式:表必须有主键,主键可以是单个属性或几个属性的组合,非主属性必须完全依赖,而不能部分依赖(反例:有张好友关系表,主键是 关注人ID+被关注人ID,但该表中还存储了名字、头像等字段,这些字段只依赖组合主键中其中一个字段(关注人ID),而不是完全依赖主键)
  • 第三范式:没有传递依赖,非主属性必须直接依赖主键,而不能间接依赖主键(反例:有张员工表,有个字段是部门ID,还有其他部门字段,比如部门名称,部门描述等,这些字段直接依赖部门ID,而不是员工ID,不应该在员工表中存在)

看了三大范式不禁有些汗颜,实际开发中为了性能或便于开发,违背范式的设计比比皆是,但也无可厚非,虽然范式不一定要遵守,但还是需要仔细权衡。

6.2 分库分表

分库分表使分布式系统设计中一个非常普遍的问题。

1)分库分表的目的

  1. 业务拆分。通过业务拆分我们可以把一个大的复杂系统拆成多个业务子系统,系统与系统之间可以通过 RPC 或消息中间件的方式通信。
  2. 应对高并发。高并发我们可以具体分为是读多写少还是读少写多的并发场景。读多我们可以利用缓存中间件减少压力,而写多我们就需要考虑是否要进行分库分表
  3. 数据隔离。核心业务区分开来,区别对待,投入的开发和运维成本也可以侧重点偏移

2)拆分维度的选择

  1. 按照 Id 维度拆分:根据 Id % 64 取模拆成 0~63 的64张表
  2. 固定位拆分:取 Id 指定二位,例如倒数 2 ,3位组成 00~99 张表
  3. hash值拆分:将 Id 取 hash 值,然后 % 表数
  4. range 拆分:按照 userId 指定范围拆分,0 - 1千万一张表,这种用的比较少,容易产生热点数据问题
  5. 业务域拆分:把不同业务域的表拆分到不同库中,例如订单相关的表,用户信息相关的表,营销相关的表分开在不同库
  6. 把不常用的字段单独拿出来存储到一张表中

3)面对 JOIN 问题

  1. 拆分成多个单表查询,在代码层做逻辑拼装
  2. 做宽表(JOIN 好的表),重写轻读
  3. 利用搜索引擎,例如 Elasticsearch

6.3 B+树

B+ 树具备了哪些查询特性:

  1. 范围查询
  2. 前缀匹配模糊查询
  3. 排序和分页

1)逻辑结构

B+ 树结构

这个是一个 B+ 树结构,相对来说比较抽象,我们提取下其中的几个关键特征:

  1. 叶子节点之间所有记录的主键,并按照从小到大的顺序排列,形成一个双向链表。叶子节点的每一个key都指向一条记录
  2. 非叶子节点取的是叶子节点里面key的最小值。同层的非叶子节点也相互串联,形成一个双向链表

为什么只支持前缀匹配模糊查询 - like abc%

前缀匹配模糊查询可以转换为范围查询,例如 abc% 可以转换为 key in [abc, abcz],而如果是全模糊查询是没有办法转换的

2)物理结构

上面描述的树只是一个逻辑结构,而不是实际上的物理结构,因为数据最终都是要存储到磁盘上的。

磁盘都是以  为单位

在 InnoDB 引擎中默认的块大小是 16 KB(可通过 innodb_page_size参数指定),这里的块,指的是逻辑单位,而不是磁盘扇区的物理块,块是 InnoDB 读写磁盘的基本单位,InnoDB 每次进行磁盘I/O读取的都是 16 KB的整数倍,无论是叶子节点还是非叶子节点都是装在 Page 里面

三层的磁盘B+树结构

一个Page大概可以装1000个key(意味着B+树有1000个分叉),每个Page大概可以装200条记录(叶子节点),那么三层结构可以装多少?1000 *1000* 200 = 2亿条,约16GB的数据,这就是 B+ 数的强大之处

3)非主键索引

每一个非主键索引都对应一个 B+ 树,与主键索引不同的是非主键索引每个叶子节点存储的是主键的值而不是记录的指针。也就是说对于非主键索引的查询,会先查到主键的值,再拿主键的值去查询主键的B+数,这就需要两次 B+ 树的查询操作,也就我们常说的 回表查询

6.4 事务与锁

1)事务的隔离级别

什么事务?事务就是一个"代码块"(一条船的蚂蚱),要么都不执行,要么都执行。多个事务之间,为了想完成任务,那么它们之间就很容易发生冲突,冲突产生就容易带来问题,比如:有两个事务分别是 小王 和 小李

  • 脏读:小王 读取了 小李 不想要的东西,给 小王 带来了脏数据
  • 不可重复读:  小王两次读取同一个记录,发现两次都不一样,原来是小李在搞鬼,一直在更新数据
  • 幻读:  小王两次读取数据,发现读出来的条数都不一样,原来是小李在搞鬼,一直在增加/删除数据
  • 丢失更新:小王正在将一条数据的值修改为 5,没想到小李也在修改这条数据修改为4,在小李修改结束之前,小王先修改完成了,小李才结束修改,这是小王发现数据怎么变成了 4

看了上面的 4 个问题,我们都觉得小李实在是太坏了,那有没有什么方法可以帮助到小王?

  1. RU(Read Uncommited):徒有其名,什么都没做,什么问题都没解决
  2. RC(Read Commited):可以解决 脏读 问题
  3. RR(Repeatable Read):可以解决 脏读、不可重复读、幻读 问题
  4. Serialization(串行化):解决所有问题

尽管串行化可以解决所有问题,但所有操作都是串行的,性能无法结局,所以常用的隔离级别是 RC 和 RR(默认)。RR 可以解决 脏读、不可重复读、幻读 问题,那最后一个 丢失更新 我们就要另外想方法解决了

2)悲观锁

悲观心态,认为数据发生冲突的概率很大,在读之前就直接上锁,可以利用 select xxx for update 语句。但会存在拿到锁之后一直没释放的问题,在高并发场景下会造成大量请求阻塞

3)乐观锁

乐观心态,认为数据发生冲突的概率很小,读之前不上锁,写的时候才判断原有的数据是否被其他事务修改了,也就是常说的 CAS

CAS的核心思想是:数据读出来的时候有一个版本v1,然后在内存里面修改,当再写回去的时候,如果发现数据库中的版本不是v1(比v1大),说明在修改的期间内别的事务也在修改,则放弃更新,把数据重新读出来,重新计算逻辑,再重新写回去,如此不断地重试。

6.5 事务实现原理之1:Redo Log

事务的四大核心属性:

  1. 原子性:  事务要么不执行,要么完全执行。如果执行一半,宕机重启,已执行的一半要回滚回去
  2. 一致性:事务的执行使得数据库从一种正确状态转换成另外一种正确状态
  3. 隔离性:在事务正确提交之前,不允许把该事务对数据的任何改变提供给其他事务
  4. 持久性:一旦事务提交,数据就不能丢

1)Write-Ahead

一个事务存在修改多张表的多条记录,而多条记录又可分布在不同的 Page 里面,对应着磁盘的不同位置。如果每个事务都直接写磁盘,性能势必达不到要求。

解决的方式就是在内存中进行事务提交,然后通过后台线程异步地把内存中的数据写入到磁盘中。但这个时候又会有个问题,那就是如果发生宕机,内存中的数据没来得及刷盘就丢失了。

而这个时候 Redo Log 就是用来解决这种问题

一样是先在内存中提交事务,然后写日志(Redo Log),然后后台任务把内存中的数据异步刷到磁盘中。日志是顺序的记录在尾部,这样就可以避免一个事务发生多次磁盘随机I/O 问题。

从图中我们可以看到,在事务提交之后,Redo Log先写入到内存中的 Redo Log Buffer 中,然后异步地刷到磁盘的 Redo Log。因此不光光事务修改的操作是异步刷盘的,Redo Log 的写入也是异步刷盘的。

既然都是先写到内存中,那么发生宕机还是会出现丢失数据的问题,因此 InnoDB 有个参数 innodb_flush_log_at_trx_commit 可以控制刷盘策略:

  • 0:  每秒刷一次,默认的策略
  • 1:  每提交一个事务,就刷一次(最安全)
  • 2:  不刷盘。然后根据参数innodb_flush_log_at_timeout设置的值决定刷盘频率。

总结:0 和 2 都可能丢失数据,1 是最安全的,但是性能是最差的

2)日志结构

从物理结构上来看,日志是一个永不结束的字节流,但从逻辑结构上看,日志不可能是一个永不结束的字节流

物理结构

逻辑结构

因此在 Redo Log 中存在一个 LSN(Log Sequence Number)的编号(按照时间顺序),在一定时间后之前的历史日志就会归档,并从头开始循环使用

在 Redo Log 中会采用逻辑和物理的方式总和记录,先以 Page 为单位记录日志,然后每个 Page 中在采用逻辑记法(记录 Page 里面的哪一行被修改了),这种记法也称为 Physiological Logging

3)崩溃后恢复

不同事务的日志在Redo Log 中是交叉存在的,也就意味着未提交的事务也在 Redo Log 中。而崩溃后恢复就会用到一个名为 ARIES 算法,不管事务有没有提交,日志都会记录到 Redo Log 中,当崩溃再恢复的时候就会把 Redo Log 全部重放一遍,提交和未提交的事务都会重放,从而让数据库回到宕机之前的状态,称之为 Repeating History 。重放结束后再把宕机之前未完成的事务找出来,然后逐一利用 Undo Log 进行回滚。

4)总结

  1. 一个事务对应多条 Redo Log,并且是不连续存储的
  2. Redo Log 只保证事务的持久性,而无关原子性
  3. 未提交的事务回滚是通过 Checkpoint 记录的 “活跃事务表” + 每个事务日志的开始/结束标识 + Undo Log实现的
  4. Redo Log 具有幂等性,通过 LSN 实现
  5. 无论是提交的、还是未提交的事务,其对应的 Page 数据都可能被刷到了磁盘中。未提交的事务对应的Page数据,在宕机重启后会回滚。

6.6 事务实现原理值2: Undo Log

上面说到进行 Redo Log 宕机回滚的时候,如果 Redo Log 中存在未提交的事务,那么就需要借助 Undo Log进行辅助,换言之,如果 Redo Log 里面记录的都是已经提交的事务,那么回滚的时候也就不需要 Undo Log 的帮助

那么 Undo Log 除了在宕机恢复时对未提交的事务进行回滚,还具备以下两个核心作用:

  • 实现 隔离性
  • 高并发

在多线程的场景中应对并发问题的策略通常有三种:

  1. 互斥锁:  一个数据对象上面只有一个锁,先到先得。(写写互斥,读写互斥,读读互斥)
  2. 读写锁:  一个数据对象一个锁,两个视图。(写写互斥,读写互斥,读读并发)
  3. CopyOnWrite:  写时复制,写完之后再把数据对象的指针一次性赋值回去(写写并发,读写并发,读读并发)

Undo Log 的作用就是在 CopyOnWrite 部分。每个事务修改记录之前,都会先把记录拷贝一份出来,拷贝出来的那个备份就是存在Undo Log 里面。每个事务都有唯一的编号,ID从小到大递增,每一次修改就是一个版本,因此Undo Log负责的就是维护数据从旧到新的每个版本,各个版本之间的记录通过链表串联

为了不能让事务读取到正在修改的数据,只能读取历史版本,这就实现了隔离性

Undo Log 不是 log 而是数据,因为 Undo Log 只是临时记录,当事务提交之后,对应的 Undo Log 文件就可以删除了,因此 Undo Log 成为记录的备份数据更为准确

正是有了 MVCC 这种特性,通常的 select 语句都是不加锁的,读取的全部是数据的历史版本,从而支撑高并发的查询,也就是所谓的 快照读,与之相对应的是 当前读

快照读/当前读

读取历史数据的方式就叫做快照读,而读取数据库最新版本数据的方式叫做 当前读

  • 快照读

当执行 select 操作时,InnoDB 默认会执行快照读,会记录下这次 select 后的结果,之后 select 的时候就会返回这次快照的数据,即使其他事务提交了不会影响当前 select 的数据,这就实现了可重复读

快照的生成当在第一次执行 select 的时候,也就是说假设当 A 开启了事务,然后没有执行任何操作,这时候 B insert 了一条数据然后 commit,这时 A 执行 select,那么返回的数据中就会有 B 添加的那条数据。之后无论再有其他事务 commit 都没有关系,因为快照已经生成了,后面的 select 都是根据快照来的。

  • 当前读

对于会对数据修改的操作(update、insert、delete)都是采用 当前读 的模式。在执行这几个操作时会读取最新的版本号记录,写操作后会把版本号改为当前事务的版本号,所以即使是别的事务提交的数据也可以查询到。

假设要 update 一条记录,但是在另一个事务中已经 delete 掉这条数据并且 commit 了,如果 update 就会产生冲突,也正是因为这样所以会产生幻读,所以在 update 的时候需要知道最新的记录。

6.7 Binlog 与主从复制

Binlog 称之为记录日志,它与 Redo Log 和 Undo Log 不同之处在于,后两者是 InnoDB 引擎层面的,而 Binlog 是Mysql 层面的,它的主要作用是用来做主从复制,它同样具有刷盘机制:

  • 0:  事务提交之后不主动刷盘,依靠操作系统自身的刷盘机制
  • 1:  每提交一个事务,刷一次磁盘
  • n:  每提交 n 个事务,刷一次磁盘

总结:0 和 n 都是不安全的,为了不丢失数据,一般都是建议双 1 保证,即 sync_binlog 和 innodb_flush_log_at_trx_commit 的值都是 1

1)Binlog 与 Redo Log的区别
  1. Redo Log 和 Binlog 的产生方式不同。redo log是在物理存储引擎产生,而Binlog是在 mysql 数据库的 server 层产生。并且 Binlog不仅针对 InnDB 存储引擎,MySQL 数据库中的任何存储引擎对数据库的更改都会产生 Binlog
  2. Redo Log 和 binlog 记录的方式不同。Binlog 记录的是一种逻辑日志,即通过 sql 语句的方式来记录数据库的修改;而 InnoDB层产生的Redo Log 是一种物理格式的日志,记录磁盘中每一个数据页的修改
  3. Redo Log 和 Binlog 记录的时间点不同。Binlog只是在事务提交完成后进行一次写入,而 Redo Log 则是在事务进行中不断写入,Redo Log 并不是随着事务提交的顺序进行写入的,这也就是说在 Redo Log 中针对一个事务会有多个不连续的记录日志
2)主从复制

Mysql 有三种主从复制的方式

  • 同步复制:  所有的 Slave 都接受完 Binlog 才认为事务提交成功,便返回成功的结果
  • 异步复制:  只要 Master 事务提交成功,就对客户端返回成功,然后通过后台线程的方式把 Binlog 同步给 Slave(可能会丢数据)
  • 半同步复制:  Master 事务提交,同时把 Binlog 同步给 Slave,只要部分 Slave 接收到了 Binlog(数量可设置),就认为事务提交成功,返回成功结果

总结:无论异步复制,还是半异步复制(可能退化为异步复制),都可能在主从切换的时候丢数据。业务一般的做法是牺牲一致性来换取高可用性,即在Master宕机后切换到Slave,忍受少量的数据丢失,后续再人工修复

3)并行复制

原生的 MySQL 主从复制都是单线程的,将 Master 的 Binlog 发送到 Slave 上后生成 RelayLog 文件,Slave 再对 RelayLog 文件进行重放

串行复制

而所谓的并行复制实际上是并行回放,传输还是单线程,但是回放是使多线程

并行复制

详见:数据库系列文章

第七章:框架、软件与中间件

开源运行的兴起,最不缺的便是开发框架,现市面上有各种各样的轮子

常用软件与中间件