0. 引言
解决高并发高可用和由此带来的数据一致性问题
解决思路:
- 利用分布式系统的特性,不断分拆,把大系统拆小,降低风险,各个击破;
- 小步快跑,快速迭代,不断重构
隐形问题:可重用性,可扩展性,可维护性。
1. 架构师分类
1.1 架构分类
-
第一层:基础架构
一般指:云平台、操作系统、网络、存储、数据库和编译器等。现在都是云平台。
-
第二层:中间件与大数据平台
-
中间件架构:
分布式服务中间件、消息中间件、数据库中间件、缓存中间件、监控系统、工作流引擎和规则引擎等。
-
大数据架构:
例如开源的Hadoop生态体系,Hive、Spark、Storm、Flink等。
-
-
第三层:业务系统架构
-
通用软件系统:
最常用的办公软件、浏览器、播放器等。
-
离线业务系统:
基于大数据的BI分析、数据挖掘、报表与可视化等。
-
大型在线业务系统:
搜索、推荐、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模型
一般有四种:
-
同步阻塞IO
-
同步非阻塞IO
-
IO多路复用
在linux中,有三种IO复用方法:select、epoll、poll,epoll效率最高。
-
异步IO
读写都由操作系统完成。
4.3.2 Reactor和Proactor
Reactor:主动模式。由程序不断轮询OS或框架IO是否就绪。
Proactor:被动模式。read、write操作都由操作系统或框架完成,之后再回调给应用程序。
4.3.3 IO多路复用、select poll epoll和LT与ET
epoll的过程分成三个步骤:
- 事件注册
- 轮询事件是否已就绪
- 事件就绪,执行实际的IO操作——通过accept/read/write操作。
关于LT和ET:
- LT:水平触发,只要这个文件描述符还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作;
- 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_c |