软件架构设计-大型网站技术架构于业务架构融合之道——部分知识点总结【未完】

0. 引言

解决高并发高可用和由此带来的数据一致性问题

解决思路:

  1. 利用分布式系统的特性,不断分拆,把大系统拆小,降低风险,各个击破;
  2. 小步快跑,快速迭代,不断重构

隐形问题:可重用性,可扩展性,可维护性。

1. 架构师分类

1.1 架构分类

  1. 第一层:基础架构

    一般指:云平台、操作系统、网络、存储、数据库和编译器等。现在都是云平台。

  2. 第二层:中间件与大数据平台

    1. 中间件架构:

      分布式服务中间件、消息中间件、数据库中间件、缓存中间件、监控系统、工作流引擎和规则引擎等。

    2. 大数据架构:

      例如开源的Hadoop生态体系,Hive、Spark、Storm、Flink等。

  3. 第三层:业务系统架构

    1. 通用软件系统:

      最常用的办公软件、浏览器、播放器等。

    2. 离线业务系统:

      基于大数据的BI分析、数据挖掘、报表与可视化等。

    3. 大型在线业务系统:

      搜索、推荐、IM、电商、游戏、广告、企业ERP或CRM。

    这只是通用的划分,有些可能划分不会这么细致

2. 道与术

3. 语言

精通一门语言,触类旁通,举一反三。

4. 操作系统

4.1 缓冲IO和直接IO

缓冲I/O:C语言提供的库函数,均以f大头;

直接I/O:linux的系统API(底层也是C编写的)

应用程序内存:通常写代码用malloc/free、new/delete等分配出来的内存。

用户缓冲区:C语言的FILE结构体里面的buffer。

内核缓冲区:Linux操作系统的Page Cache。一个Page一般是4K(没说是Byte还是bit)

对于缓冲IO,一次读操作有3次数据拷贝,写操作有反向的3次数据拷贝

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

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

对于直接IO,一次读操作有2次数据拷贝,写操作有反向的2次数据拷贝

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

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

所以,所谓的“直接IO”其中直接的意思是指没有用户级的缓冲,但操作系统本身的缓冲还是有的。

4.2 内存映射文件与零拷贝

4.2.1 内存映射文件

内存映射文件在直接IO又取消了一次拷贝,将 ”内核缓冲区->应用程序内存“ 这一步取消。

读:磁盘->内核缓冲区

写:内核缓冲区->磁盘

本质上,是使用一个逻辑地址将应用程序内存直接指向内核缓冲区。

4.2.2 零拷贝

原来的数据发送需要将数据搬运到socket缓冲区,但现在直接在socket缓冲区做一层映射,将内核缓冲区的数据映射到socket缓冲区。

4.3 网络IO模型

4.3.1 实现层面的网路IO模型

一般有四种:

  1. 同步阻塞IO

  2. 同步非阻塞IO

  3. IO多路复用

    在linux中,有三种IO复用方法:select、epoll、poll,epoll效率最高。

  4. 异步IO

    读写都由操作系统完成。

4.3.2 Reactor和Proactor

Reactor:主动模式。由程序不断轮询OS或框架IO是否就绪。

Proactor:被动模式。read、write操作都由操作系统或框架完成,之后再回调给应用程序。

4.3.3 IO多路复用、select poll epoll和LT与ET

epoll的过程分成三个步骤:

  1. 事件注册
  2. 轮询事件是否已就绪
  3. 事件就绪,执行实际的IO操作——通过accept/read/write操作。

关于LT和ET:

  1. LT:水平触发,只要这个文件描述符还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作;
  2. ET:边缘触发,在它检测到有 I/O 事件时,通过 epoll_wait 调用会得到有事件通知的文件描述符,对于每一个被通知的文件描述符,如可读,则必须将该文件描述符一直读到空,让 errno 返回 EAGAIN 为止,否则下次的 epoll_wait 不会返回余下的数据,会丢掉事件。如果ET模式不是非阻塞的,那这个一直读或一直写势必会在最后一次阻塞。

换句话说,ET只会通知一次,一次必须把数据都读完;但LT没有这种限制。一般i倾向于使用LT,相对安全可控。

epoll、select、poll详解:https://www.itqiankun.com/article/select-poll-epoll

I/O多路复用(multiplexing)的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作

select、poll 和 epoll 都是 Linux API 提供的 IO 复用方式。

select、poll、epoll之间的区别:

\ select poll epoll
操作方式 遍历 遍历 回调
底层实现 数组 链表 哈希表
IO效率 每次调用都进行线性遍历,时间复杂度为O(n) 每次调用都进行线性遍历,时间复杂度为O(n) 事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到rdllist里面。时间复杂度O(1)
最大连接数 1024(x86)或 2048(x64) 无上限 无上限
fd拷贝 每次调用select,都需要把fd集合从用户态拷贝到内核态 每次调用poll,都需要把fd集合从用户态拷贝到内核态 调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝

链接:https://www.jianshu.com/p/722819425dbd

epoll可以理解为event poll(基于事件的轮询)。

epoll本质上是在内核中建立了一个小型的文件系统,用于管理不同的套接字io,来一个就去epoll注册一下,然后一旦就绪会触发内部的callback函数,用于提示fd已就绪,

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

epoll详解:epoll原理详解及epoll反应堆模型 - 知乎 (zhihu.com)

同步IO 同步阻塞IO
同步IO 同步非阻塞IO
同步IO IO多路复用(同步阻塞) select
epoll
poll
java的nio
异步IO 异步IO Linux aio
windows上的IOCP
C++上的asio库

总结:

  1. 阻塞和非阻塞是从函数调用角度来说的(自己),而同步和异步是从“读写是谁完成的”角度来说的(双方),

阻塞:如果读写没有就绪或完成,将一直等待;

非阻塞:函数立即返回,然后让程序执行轮询策略;

同步:读写由应用程序完成;

异步:读写由OS完成,完成之后,回调或者事件通知应用程序。

  1. IO多路复用都是同步IO,因为读写都是由程序完成的,也是阻塞IO,因为select read write都是阻塞的。

一般有三类:同步阻塞、同步非阻塞、异步IO。IO多路复用也是同步IO,因为读写都是文件完成的。

4.3.4 服务器编程的1+N+M模式

一个监听线程,N个IO线程,M个worker线程,一般N=CPU核数,M看需要。

  1. 监听线程:

    负责accept事件的注册和处理。每一个socket连接请求都由它处理,并移交给IO线程。

  2. IO线程:

    负责每个socket连接上read write事件的注册和实际的socket读写,把读到的request放入Request队列,交由worker处理。

    负责:1. 实际的socket读写;2. 将request交给Worker线程。

  3. Worker线程:

    纯粹业务线程,没有socket读写操作。

4.4 进程线程协程

多线程的好处:

  1. 提高CPU利用率
  2. 提高IO吞吐

缺点:

  1. 内存共享,需要加锁;
  2. 过多的上下文切换。

因此诞生了并发编程的设计原则:“不要通过共享内存来实现通信,而应通过通信实现共享内存。”即“尽可能通过消息通信,而不是共享内存来实现进程或者线程之间的同步。”

多进程的好处:

  1. 减少了切换开销
  2. 各自独立,其它崩了也不影响。

nginx是多进程单线程模型。

Redis是单进程单线程(单线程处理请求),利用多核的时候开多个redis实例就可以了

多进程不适合用于IO密集型应用,对于IO密集型的应用,提高IO效率的方法:

  1. 异步IO
  2. 多线程
  3. 多协程

4.5 无锁(内存屏障与CAS)

内存屏障的意义是:在内存屏障后的语句不会在内存屏障之前的语句执行。

CAS:对比与交换,一般是用版本号。

5. 网络

5.1 HTTP1.0

使用keep-alive字段解决TCP连接复用的问题,使用content-length解决何时关闭TCP连接的问题。

keep-alive和content-Length一起使用。

5.2 HTTP1.1

默认是可复用TCP连接的,所以即使没有keep-alive,也能用。

Chunk机制(content-length的替代):

字段:Transfer-Encoding: chunk。

响应的body分成了一块块的,块之间由间隔符,所有块的结尾由特殊tag。

5.2.2 流水线与Head-of-line Blocking问题

流水线(pipeline)虽然能提升速度,但也有不小的副作用,所以一般默认关闭。

流水线的问题在于一个包阻塞,会导致后面的包也没接触。

5.2.3 HTTP/2出现之前的性能提升方法

5.2.4 “一来多回”问题

5.2.5 断点续传

下载时下载到一半连接中断了,客户端可以从上次断的地方继续下载。

5.3 HTTP/2(SDPY)

Google的SPDY协议。

5.3.1 对HTTP1.1的兼容

HTTP/2和HTTP1.1并不是处于平级的位置,而是处在HTTP1.1和TCP之间。现在相当于在HTTP1.1和TCP之间多了一个转换层,这个转换层就是SPDY(HTTP/2)。

5.3.2 二进制分帧

为了解决HTTP1.1的队头阻塞问题所设计的核心属性。

二进制分帧:在把这个字符格式的报文给TCP之前转换成二进制,并且分成多个帧(多个数据块)来发送。

5.3.3 头部压缩

5.4 SSL/TLS

SSL——Secure Socket Layer。

TLS——Transport Layer Security,传输层安全协议。

SSL/TLS处于TCP的上面,支撑各种应用层协议。

对称加密->双向非对称加密->单向非对称加密->数字证书与证书认证中心->根证书与CA信任链

截止到单向非对称加密,都可能遭受中间人攻击。

SSL的四次握手:

5.5 HTTPS

HTTPS = HTTP + SSL/TLS

所以在HTTP和TCP之间,还有HTTP/2和SSL/TLS可以选

增加了SSL的传输过程的阶段:

  1. TCP连接的建立;
  2. SSL/TLS四次握手协商出对称加密的密钥
  3. 基于密钥,在TCP连接上对所有的HTTP Request/Response进行加密和解密

因为1和2只在连接建立时做1次,之后只要连接不关闭,每个请求都是直接进入3,所以相比HTTP,性能的增加很小,是常数级别。

5.6 TCP和UDP

TCP解决三个问题:

  1. 丢包(ACK+超时重传)
  2. 乱序(滑动窗口)
  3. 包乱码(检错)

网络2将军:不管发多少次,永远都不知道最后的那个包是否被对方收到。如果要知道,只能让对方回复ACK,但是对方又不知道这ACK是否收到,只能让对方为这个ACK再回一个ACK。

5.7 QUIC

Quick UDP Internet Connect,基于UDP的多路并发传输协议。

只要使用TCP,就无法解决队头阻塞问题。

QUIC取代了TCP的部分功能(不丢包),实现了SSL的所有功能,取代了HTTP/2的部分功能(多路复用)

5.7.1 不丢包

冗余(raid5和raid6)

Raid5:

​ 每发送5个数据包,就发送一个冗余包,(R = A+B+C+D+E),所以丢了一个包也能恢复(E = R-A-B-C-D),如果每发送10个数据包就发送一个冗余包,那么十个丢1个包就能恢复。

Raid6:

​ 在上面的基础上,变成两个冗余块。所以5个包可以丢两个包。

A+B+C+D+E=R1

A-B+C-D+E=R2

5.7.2 更少的RTT

UDP不需要RTT

5.7.3 连接迁移

与TCP(客户端IP+PORT以及服务端IP+port)不同,使用一个64位的标识标记一个连接。

6. 数据库

6.1 范式与反范式

一般要求达到第三范式。

范式 描述 反例
第一范式 每个字段都是原子的,不能再分解
第二范式 1. 表必须有主键,主键可以是单个属性或者几个属性的组合
2. 非主属性必须完全依赖,而不能部分依赖主键
在好友关系表中,主键是关注人ID+被关注人ID,但该表中还存储了名字、头像等字段,
这些字段只依赖与组合主键中的某个字段,而不完全依赖主键
第三范式 没有传递依赖:非主属性必须直接依赖主键

6.2 分库分表

分库的问题:

  1. 为什么要分
  2. 什么时候分
  3. 怎么分

分完之后的问题:

  1. Join(联表查询)
  2. 分布式事务

6.2.1 为什么要分

不同的出发点会导致不同的分库方式,应对高并发可以是设置多个从库,业务拆分可以是

  1. 业务拆分——方便以后扩展,便于团队职责分工
  2. 应对高并发,区分高并发读还是高并发写。高并发读可以通过增加缓存、从库解决,高并发写要考虑分库分表
  3. 数据隔离

6.2.2 分布式ID生成服务

必须提供全局唯一的主键生成服务

6.2.3 拆分维度的选择

以电商为例,用用户ID去分还是用订单ID去分。

对于拆分之后其它维度的查询,一般有以下几个方法:

  1. 建立一个映射表

    建立订单ID和用户ID之间的映射关系。但是很麻烦,要维护一张很大的表

  2. 业务双写

    同一份数据,两套分库分表。一套用户ID,一套订单ID

  3. 异步双写

    还是两套表,只是业务单写,然后通过binLog,同步到另外一套表上。

  4. 两个维度统一到一个维度:

    把订单ID和用户ID统一成一个维度,这样可以按照用户ID分库,然后按订单ID查询的时候取出用户ID,用用户ID去查询。

6.2.4 Join查询问题

分库分表之后,如何进行Join操作:

  1. 把Join拆成多个单表查询,不让数据库做Join,在软件层面对结果进行拼装;

  2. 做宽表,重写轻读

    对于不得不用Join的情况,可以另外做一个join表,提前把结果Join好,重写轻读,也是空间换时间

  3. 利用搜索引擎

6.2.5 事务

分布式事务很麻烦。最大的问题在于保证ACID。

A——atomicity,原子性

C——consistency,一致性

I——isolation,隔离性

D——durability,持久性

6.3 B+树

相比K-V结构,B+树可以做到:

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

6.3.1 逻辑结构

6.3.2 物理结构

磁盘的最小单位是块,InnoDB一块(也叫page)默认是16KB,每个page的id是32位,所以InnoDB的存储上限是64TB(2^32 * 16KB),

一个Page大概装1000个Key(1个Key大概是16Byter,其中8B的key,8B其它字段),意味这B+树有1000个分叉,装叶子节点的话,一个page大概可以装200条记录,一个三层的B+树大概可以存储16GB数据,另外需16MB的内存存储非叶子节点。

所以一般是装16MB的非叶节点数据在内存中,可以支撑2E条记录的查找。

所以建议按主键的自增顺序插入记录,防止引起page的大量迁移。

6.3.3 非主键索引

新建索引B+树,但是叶子节点记录的是数据的主键,可以随意组合新的索引组合。

6.4 事务的四个隔离级别

6.4.1 事务的四个隔离级别

事务并发导致的几类问题

编号 问题 描述
1 脏读 事务A读取了一条记录的值,在做业务逻辑的时候,事务B回滚了该记录,所以A读了一个脏数据
2 不可重复读 在同一个事务里,两次读取同一行记录,但结果不一样,因为另一个事务在对这个数据进行Update操作(没有加锁)
3 幻读 在同一个事务里,两次select数据,但结果数目不一样,因为另一个事务在对这个数据进行insert/delete操作(没有加锁)
4 丢失更新 两个事务同时修改同一条记录,A的修改被B的修改覆盖了。

2-3本质是一样的,区别是2是对数据做update,3是增/删了数据,

InnoDB事务隔离级别

级别 名称 解决问题
1 RU,Read Uncommited,读未提交 什么都没解决,相当于没有
2 RC,Read Commited,读已提交 解决脏读
3 RR,可重复读 解决了1-3,InnoDB的默认等级
4 Serialization,串行 串行化,完全解决1-4

丢失更新是在软件层面保障,比如悲观锁和乐观锁。

6.4.2 悲观锁和乐观锁

几种防丢失更新的方式:

  1. 利用单条语句的原子性

  2. 悲观锁

    很重的锁

  3. 乐观锁

    CAS机制,保存版本号。

  4. 分布式锁

    解决分库分表状况下的锁问题

6.4.3 死锁检测

死锁四个条件:

  1. 互斥
  2. 循环等待
  3. 不可剥夺
  4. 请求和保持

6.5 事务的实现原理

A——atomicity,原子性。

C——consistency,一致性,各种约束条件,比如参照完整性、主键不为空等。C通过上层规则约束,相对简单。

I——isolation,隔离性。

D——durability,持久性。D容易,写到磁盘。

A和I很难,牵扯到并发和崩溃恢复的问题。

6.5.1 D的实现——Write-Ahead

内存操作数据 + Write-Ahead Log实现快速操作数据和把数据写入磁盘。

具体到InnoDB中,Write-Ahead Log是Redo Log。在InnoDB中,不光事务修改的数据库表数据是异步刷盘的,连Redo Log的写入本身也是异步的(RedoLog也是先在内存操作,然后再写到磁盘的)。

6.5.2 Redo Log的逻辑与物理结构

RedoLog是一个循环使用的块(新的会覆盖旧的)。

6.5.3 Physiological Logging

6.5.4 IO写入的原子性

跟底层系统息息相关。

最后的解决方法:

  1. 让硬件支持16KB写入的原子性
  2. Double write

6.5.5 Redo Log Block结构

6.5.6 事务、LSN与Log Block的关系

6.5.7 事务Rollback与崩溃恢复(ARIES算法)

6.6 事务实现的原理之2:Undo Log

6.6.1 Undo Log存在的必要性

6.6.2 Undo Log(MVCC)

读写的并发问题有三种策略

策略 解释
互斥锁
读写锁
CopyOnWrite 写的时候,把该数据对象拷贝一份,等写完之后,再把数据对象的指针(引用)一次性赋值回去,读的时候读取原始数据。
1. 读和读可以并发
2. 读和写可以并发
3. 写和写理论上可以并发

歪楼,MVCC解析

https://zhuanlan.zhihu.com/p/66791480

https://www.php.cn/mysql-tutorials-460111.html

MVCC是“维持一个数据的多个版本,使读写操作没有冲突”的一个抽象概念。换句话说,可能存在一个数据的多个版本

这个概念需要具体功能去实现,这个具体实现就是快照读。(具体实现下面讲)

  1. MVCC解决并发哪些问题?

    1. mvcc用来解决读—写冲突的无锁并发控制,就是为事务分配单向增长的时间戳。为每个数据修改保存一个版本,版本与事务时间戳相关联。
    2. 读操作只读取该事务开始前的数据库快照。
  2. 解决问题如下:

    1. 并发读-写时:可以做到读操作不阻塞写操作,同时写操作也不会阻塞读操作。
    2. 解决脏读、幻读、不可重复读等事务隔离问题,但不能解决上面的写-写 更新丢失问题。
  3. 因此有了下面提高并发性能的组合拳:

    1. MVCC + 悲观锁:MVCC解决读写冲突,悲观锁解决写写冲突
    2. MVCC + 乐观锁:MVCC解决读写冲突,乐观锁解决写写冲突
  4. MVCC的实现原理
    它的实现原理主要是:

    1. 版本链,
    2. undo日志 ,
    3. Read View 来实现的

从以上的描述中我们可以看出来,所谓的MVCC指的就是在使用READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。

6.6.3 Undo Log不是Log

6.6.4 Undo Log与Redo Log的关联

6.6.5 各种锁

MVCC解决了快照读和写直接的并发问题,但对于写和写,当前读和写直接的并发,MVCC就无能为力了。

  1. 共享锁(S锁)和排他锁(X锁);
  2. 意向锁
  3. 记录锁
  4. 间隙锁
  5. 临键锁
  6. 插入意向锁
  7. 自增锁

这些锁可以按粒度分(表、行、gap),也可以按模式分(共享、排他、意向)

锁表 锁行 锁范围
共享S 表共享锁 行共享锁
排他X 表排他锁 行排他锁
意向共享IS 表意向共享锁
意向排他IX 表意向排他锁
AI 自增锁
  1. 表(S锁、X锁)、行(S锁、X锁)

  2. 意向锁(IS锁,IX锁)

    意向锁是一种不与行级锁冲突表级锁。它的作用是提前向其它人宣布自己即将要对表中某一行上锁,提升判断效率。所以,一个事务要给某张表的某一行加S锁,就必须先获得整张表的IS锁;要给某一张加X锁,就必须先获得整张表的IX锁。

    换句话说,IX IS锁只是声明,与其它锁并不互斥。

6.7 binlog和主从赋值

6.7.1 Binlog与Redo Log的主要差异

6.7.2 内部XA-Binlog与Redo Log一致性问题

6.7.3 三种主从复制模式

6.7.4 并行复制

7.框架、软件与中间件

8. 高并发问题

8.1 问题分类

高并发读、高并发写、高并发读写

8.1.1 侧重于 高并发读 的系统

  1. 搜索引擎
  2. 电商产品
  3. 电商系统的商品描述和图片

8.1.2 侧重于 高并发写 的系统

  1. 广告扣费系统

8.1.3 侧重于 高并发读 和 高并发写 的系统

  1. 电商的库存系统和秒杀系统
  2. 支付系统和微信红包
  3. 微博和朋友圈

8.2 高并发读

策略有:

  1. 加缓存
  2. 并发读
  3. 重写轻读
  4. 读写分离

8.2.1 策略1:加缓存

方法有:

  1. 本地缓存或Memcached/Redis集中式缓存

    缓存可能遇到的问题:

    1. 缓存雪崩
    2. 缓存穿透
    3. 大量的热key过期
  2. Mysql的Master/Slave(主从)

  3. CDN静态文件加速(动静分离)

8.2.2 策略2:并发读

方法有:

  1. 异步RPC
  2. Google的“冗余请求”

8.2.3 策略3:重写轻读

重写轻读 vs 重读轻写

重写轻读,本质就是“空间换时间“。你不是计算起来耗时,延迟高吗,那我可以提前计算,然后存储起来。取的时候,直接去取。

我们通常对Mysql的用法,都是重读轻写,写的时候,简单;查的时候,做复杂的join计算,返回结果。这样做的好处是容易做到数据的强一致性,不会因为字段冗余,造成数据的不一致。但是性能可能就是问题。
而如果采用重写轻读,怎么做呢?你不是要看Feeds吗,那就为每个人准备一个Feeds,或者说收件箱。某个人发了微博之后,把他的微博扩散到所有人的收件箱,这个扩散是异步的,在后台扩散。这样每个人看自己的Feeds的时候,直接去自己的收件箱取就可以了。

方法有:

  1. 微博Feeds流
  2. 多表的关联查询:宽表与搜索引擎

8.2.4 总结:读写分离(CQRS结构)

本质都是读写分离,即CQRS(Command Query Reponsibility Separation)

CQRS的特征:

  1. 分别为读和写设计不同的数据结构
  2. 写通常通过分库分表抵抗写的压力
  3. 读和写的串联
  4. 读比写有延迟

8.3 高并发写

  1. 数据分片
  2. 任务分片
  3. 异步化
  4. 批量
  5. 串行化+多进程单线程+异步IO

8.3.1 策略1:数据分片

本质:对要处理的数据或请求分成多份并行处理

包括有:

  1. 数据库的分库分表
  2. Kafka的partition
  3. ES的分布式索引

8.3.2 策略2:任务分片

数据分片是对要处理的数据进行分片,任务分片是对处理程序本身进行分片,例如流水线

包括有:

  1. CPU的指令流水线
  2. MapReduce
  3. Tomcat的1+N+M

8.3.3 策略3:异步化

  1. 短信验证码注册登录
  2. 电商的订单系统
  3. 广告计费系统
  4. LSM树(写内存+Write-Ahead日志)
  5. Kafka的pipeline

8.3.4 策略4:批量

写一条记录,可以等数量多了,合并写入。

  1. Kafka的百万QPS写入
  2. 广告计费系统的合并扣费
  3. MySQL的小事务合并机制

8.3.5 串行化+多进程单线程+异步IO

多进程单线程是为了解决:锁竞争;线程切换开销大,导致线程数无法开很多。

8.4 容量规划

8.4.1 吞吐量、响应时间和并发数

8.4.2 压力测试与容量评估

9. 高可用与稳定性

9.1 多副本

9.2 隔离、熔断、限流和降级

  1. 隔离

    隔离是指系统或资源分隔开,在系统发生故障时能限定传播范围和影响范围,即发生故障后不会出现滚雪球效应,从而把故障的影响限定在一个范围内。

    1. 数据隔离

    2. 机器隔离

    3. 线程池隔离

    4. 信号量隔离

      比线程池隔离要更轻量。当信号量达到阈值,线程获取不到该信号量会丢弃请求,而不是阻塞在那等待信号量。

  2. 限流

    1. 技术层面的限流

      1. 限制并发数,比如限制连接池、线程池、Nginx的limit_conn模块
      2. 限制QPS
    2. 业务层面的限流

      比如100件产品的秒杀,有2W人抢购,只需要处理前500个请求就可以了。

    3. 限流算法

      1. 漏桶算法(输出恒定,可适合削峰、应付突发流量)

      2. 令牌算法(输入恒定,限制平均速率)

      3. 对比

        漏桶算法是流出速率保持恒定,令牌算法是流入速率保持恒定。

  3. 熔断

    一种自我保护,当某个服务已经接近极限的时候,会强制断开,不再提供服务,防止崩溃

    1. 根据请求失败率做熔断
    2. 根据请求响应时间做熔断
  4. 降级

    兜底方案,尽可能提供最基础的核心业务,其它业务可以不提供。

9.3 灰度发布于回滚

9.4 监控体系于日志报警

10. 事务一致性

余春龙《软件架构设计-大型网站技术架构于业务架构融合之道》

你可能感兴趣的:(架构,数据库,中间件)