IT技术架构之《从0开始学架构》 |
书名:《从0开始学架构》
作者:李云华
脑图绘制者:WilliamLi
脑图绘制者微信号:kk375044771
GitHub:https://github.com/WilliamLizk/FastReading
什么是架构
框架与架构的区别
单纯从定义的角度来看,框架和架构的区别还是比较明显的,框架关注的是“规范”,架构关注的是“结构”
架构定义:软件架构指软件系统的顶层结构。
架构设计的历史背景
软件架构是一种对软件的“组织”方法论。一分一合,其目的是为了软件研发过程中的成本、进度、质量得到有效控制。
机器语言(1940 年之前)
汇编语言(20 世纪 40 年代)
高级语言(20 世纪 50 年代)
第一次软件危机与结构化程序设计(20 世纪 60 年代~20 世纪 70 年代)
第二次软件危机与面向对象(20 世纪 80 年代)
软件架构的历史背景
我们可以通过诸多方式去接近“银弹”,但很遗憾,软件活动中没有“银弹”。
较大的软件系统才会面临软件架构相关的问题,例如:
架构设计的目的
误区
因为架构很重要,所以要做架构设计
不是每个系统都要做架构设计吗
公司流程要求系统开发过程中必须有架构设计
为了高性能、高可用、可扩展,所以要做架构设计
架构设计的主要目的是为了解决软件系统复杂度带来的问题
复杂度来源
高性能
硬件存储从纸带→磁带→磁盘→SSD,并没有显著带来系统复杂度的增加。
软件系统中高性能带来的复杂度主要体现在两方面,一方面是单台计算机内部为了高性能带来的复杂度;另一方面是多台计算机集群为了高性能带来的复杂度。
单机复杂度
进程
多进程让多任务能够并行处理任务,但本身还有缺点,单个进程内部只能串行处理,而实际上很多进程内部的子任务并不要求是严格按照时间顺序来执行的,也需要并行处理。
多个 CPU 能够同时执行计算任务的解决方案有 3 种:SMP(Symmetric Multi-Processor,对称多处理器结构)、NUMA(Non-Uniform Memory Access,非一致存储访问结构)、MPP(Massive Parallel Processing,海量并行处理结构)。其中 SMP 是我们最常见的,目前流行的多核处理器就是 SMP 方案。
操作系统发展到现在,如果我们要完成一个高性能的软件系统,需要考虑如多进程、多线程、进程间通信、多线程并发等技术点,而且这些技术并不是最新的就是最好的,也不是非此即彼的选择。
Nginx 可以用多进程也可以用多线程,JBoss 采用的是多线程;Redis 采用的是单进程,Memcache 采用的是多线程,这些系统都实现了高性能,但内部实现差异却很大。
线程
线程是进程内部的子任务,但这些子任务都共享同一份进程数据。为了保证数据的正确性,又发明了互斥锁机制。有了多线程后,操作系统调度的最小单位就变成了线程,而进程变成了操作系统分配资源的最小单位。
集群的复杂度
1. 任务分配
需要增加一个任务分配器
这个分配器可能是硬件网络设备(例如,F5、交换机等),可能是软件网络设备(例如,LVS),也可能是负载均衡软件(例如,Nginx、HAProxy),还可能是自己开发的系统。选择合适的任务分配器也是一件复杂的事情,需要综合考虑性能、成本、可维护性、可用性等各方面的因素。
任务分配器和真正的业务服务器之间有连接和交互
需要选择合适的连接方式,并且对连接进行管理
任务分配器需要增加分配算法
是采用轮询算法,还是按权重分配,又或者按照负载进行分配。如果按照服务器的负载进行分配,则业务服务器还要能够上报自己的状态给任务分配器。
2.任务分解
通过任务分配的方式,我们能够突破单台机器处理性能的瓶颈,通过增加更多的机器来满足业务的性能需求,但如果业务本身也越来越复杂,单纯只通过任务分配的方式来扩展性能,收益会越来越低。
业务越来越复杂,单台机器处理的性能会越来越低。为了能够继续提升性能,我们需要采取第二种方式:任务分解。
如果系统拆分得太细,为了完成某个业务,系统间的调用次数会呈指数级别上升,而系统间的调用通道目前都是通过网络传输的方式,性能远比系统内的函数调用要低得多。
简单的系统更加容易做到高性能
可以针对单个任务进行扩展
高可用
无论是单个硬件还是单个软件,都不可能做到无中断,硬件会出故障,软件会有 bug;硬件会逐渐老化,软件会越来越复杂和庞大……
高性能增加机器目的在于“扩展”处理性能;高可用增加机器目的在于“冗余”处理单元。
计算高可用
计算有一个特点就是无论在哪台机器上进行计算,同样的算法和输入数据,产出的结果都是一样的。
需要增加一个任务分配器
任务分配器和真正的业务服务器之间有连接和交互
任务分配器需要增加分配算法
存储高可用
存储与计算相比,有一个本质上的区别:将数据从一台机器搬到到另一台机器,需要经过线路进行传输。
存储高可用的难点不在于如何备份数据,而在于如何减少或者规避数据不一致对业务造成的影响。
数据不一致
高可用状态决策
在具体实践的过程中,恰好存在一个本质的矛盾:通过冗余来实现的高可用系统,状态决策本质上就不可能做到完全正确。
论采取什么样的方案,状态决策都不可能做到任何场景下都没有问题,但完全不做高可用方案又会产生更大的问题,如何选取适合系统的高可用方案,也是一个复杂的分析、判断和选择的过程。
独裁式
当决策者本身故障时,整个系统就无法实现准确的状态决策。
协商式
协商式决策指的是两个独立的个体通过交流信息,然后根据规则进行决策,最常用的协商式决策就是主备决策。
民主式
民主式决策指的是多个独立的个体通过投票的方式来进行状态决策。
算法复杂
脑裂
可扩展性
设计具备良好可扩展性的系统,有两个基本条件:正确预测变化、完美封装变化。
一个具备良好可扩展性的架构设计应当符合开闭原则:对扩展开放,对修改关闭。
设计具备良好可扩展性的系统,有两个思考角度:(1)从业务维度。对业务深入理解,对可预计的业务变化进行预测。
(2)从技术维度。利用扩展性好的技术,实现对变化的封装。
实现良好的可扩展性:
(1)使用分布式服务(框架)构建可复用的业务平台。
(2)使用分布式消息队列降低业务模块间的耦合性。
预测变化
不能每个设计点都考虑可扩展性。
不能完全不考虑可扩展性。
所有的预测都存在出错的可能性。
应对变化
系统需要拆分出变化层和稳定层
需要设计变化层和稳定层之间的接口
低成本
低成本本质上是与高性能和高可用冲突的,所以低成本很多时候不会是架构设计的首要目标,而是架构设计的附加约束。
低成本给架构设计带来的主要复杂度体现在,往往只有“创新”才能达到低成本目标。
类似的新技术例子很多,我来举几个。
引入新技术
创造新技术
安全
安全本身是一个庞大而又复杂的技术领域,并且一旦出问题,对业务和企业形象影响非常大。
功能安全
本质上是因为系统实现有漏洞,黑客有了可乘之机。黑客会利用各种漏洞潜入系统,这种行为就像小偷一样,黑客和小偷的手法都是利用系统或家中不完善的地方潜入,并进行破坏或者盗取。因此形象地说,功能安全其实就是“防小偷”。
架构安全
如果说功能安全是“防小偷”,那么架构安全就是“防强盗”。
传统的架构安全主要依靠防火墙,防火墙最基本的功能就是隔离网络,通过将网络划分成不同的区域,制定出不同区域之间的访问控制策略来控制不同信任程度区域间传送的数据流。
互联网系统的架构安全目前并没有太好的设计手段来实现,更多地是依靠运营商或者云服务商强大的带宽和流量清洗的能力,较少自己来设计和实现。
规模
规模带来复杂度的主要原因就是“量变引起质变”
功能越来越多,导致系统复杂度指数级上升
系统的复杂度 = 功能数量 + 功能之间的连接数量
数据越来越多,系统复杂度发生质变
MySQL 单表的数据因不同的业务和应用场景会有不同的最优值,但不管怎样都肯定是有一定的限度的,一般推荐在 5000 万行左右。
可伸缩性
伸缩性度量指标包括
(1)处理更高并发;
(2)处理更多数据;
(3)处理更高频次的用户交互。
伸
增强系统在伸缩性度量指标三个方面的处理能力
缩
缩减系统处理能力
低成本和快速
架构设计三原则
合适优于先进>演化优于一步到位>简单优于复杂
面对多种可能性时进行选择。
可是一旦涉及“选择”,就很容易让架构师陷入两难的境地,例如:
淘宝技术发展主要经历了“个人网站”→“Oracle/ 支付宝 / 旺旺”→“Java 时代 1.0”→“Java 时代 2.0”→“Java 时代 3.0”→“分布式时代”。
手机 QQ 的发展历程按照用户规模可以粗略划分为 4 个阶段:十万级、百万级、千万级、亿级,不同的用户规模,IM 后台的架构也不同,而且基本上都是用户规模先上去,然后产生各种问题,倒逼技术架构升级。
合适原则
合适原则宣言:“合适优于业界领先”。
优秀的技术人员都有很强的技术情结,当他们做方案或者架构时,总想不断地挑战自己,想达到甚至优于业界领先水平是其中一个典型表现,因为这样才能够展现自己的优秀。但现实是,大部分这样想和这样做的架构,最后可能都以失败告终!
简单原则
简单原则宣言:“简单优于复杂”。
软件领域的复杂性体现在两个方面:
1. 结构的复杂性
2. 逻辑的复杂性
演化原则
演化原则宣言:“演化优于一步到位”
对于建筑来说,永恒是主题;而对于软件来说,变化才是主题。
软件架构设计其实更加类似于大自然“设计”一个生物,通过演化让生物适应环境,逐步变得更加强大:
架构设计流程
识别复杂度
如果运气真的不好,接手了一个每个复杂度都存在问题的系统,那应该怎么办呢?答案是一个个来解决问题,不要幻想一次架构重构解决所有问题。
正确的做法是将主要的复杂度问题列出来,然后根据业务、技术、团队等综合情况进行排序,优先解决当前面临的最主要的复杂度问题。
峰值一般取平均值的 3 倍
设计备选方案
做事情永远都要有B方案
常见错误
设计最优秀的方案
很多架构师在设计架构方案时,心里会默认有一种技术情结:我要设计一个优秀的架构,才能体现我的技术能力!
只做一个方案
很多架构师在做方案设计时,可能心里会简单地对几个方案进行初步的设想,再简单地判断哪个最好,然后就基于这个判断开始进行详细的架构设计了。
这样做有很多弊端:
备选方案过于详细
有的架构师或者设计师在写备选方案时,错误地将备选方案等同于最终的方案,每个备选方案都写得很细。这样做的弊端显而易见:
正确方向
根据3个原则设计方案
设计多个备选方案
备选方案的数量以 3 ~ 5 个为最佳
备选方案的差异要比较明显
备选方案的技术不要只局限于已经熟悉的技术。
备选阶段关注技术选型而不是技术细节
评估和选择备选方案
360 度环评
操作方式为:列出我们需要关注的质量属性点,然后分别从这些质量属性的维度去评估每个方案,再综合挑选适合当时情况的最优方案。
360 度环评表也只能帮助我们分析各个备选方案,还是没有告诉我们具体选哪个方案,原因就在于没有哪个方案是完美的,极少出现某个方案在所有对比维度上都是最优的。
举例:RocketMQ 和 Kafka 区别
RocketMQ 和 Kafka 有什么区别?
(1) 适用场景
Kafka适合日志处理;RocketMQ适合业务处理。
(2) 性能
Kafka单机写入TPS号称在百万条/秒;RocketMQ大约在10万条/秒。Kafka单机性能更高。
(3) 可靠性
RocketMQ支持异步/同步刷盘;异步/同步Replication;Kafka使用异步刷盘方式,异步Replication。RocketMQ所支持的同步方式提升了数据的可靠性。
(4) 实时性
均支持pull长轮询,RocketMQ消息实时性更好
(5) 支持的队列数
Kafka单机超过64个队列/分区,消息发送性能降低严重;RocketMQ单机支持最高5万个队列,性能稳定(这也是适合业务处理的原因之一)
详细方案设计
详细设计方案阶段可能遇到的一种极端情况就是在详细设计阶段发现备选方案不可行,一般情况下主要的原因是备选方案设计时遗漏了某个关键技术点或者关键的质量属性。例如,我曾经参与过一个项目,在备选方案阶段确定是可行的,但在详细方案设计阶段,发现由于细节点太多,方案非常庞大,整个项目可能要开发长达 1 年时间,最后只得废弃原来的备选方案,重新调整项目目标、计划和方案。这个项目的主要失误就是在备选方案评估时忽略了开发周期这个质量属性。
架构师不但要进行备选方案设计和选型,还需要对备选方案的关键细节有较深入的理解。
通过分步骤、分阶段、分系统等方式,尽量降低方案复杂度
如果方案本身就很复杂,那就采取设计团队的方式来进行设计
CAP理论
CAP关注的粒度是数据,而不是整个系统;
CAP是忽略网络延时的
CAP关注的是对数据的读写操作,而不是分布式系统的所有功能;
虽然CAP理论定义是三个要素中只能取两个,但放到分布式环境下来思考,我们会发现必须选择P(分区容忍)要素,因为网络本身无法做到100%可靠,有可能出故障,所以分区是一个必然的现象。如果我们选择了CA而放弃了P,那么当发生分区现象时,为了保证C,系统需要禁止写入,当有写入请求时,系统返回eror(例如,当前系统不允许写入),这又和A冲突了,因为A要求返回 no error和 no timeout因此,分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构。
1.CP-Consistency/Partition Tolerance
为了保证一致性,当发生分区现象后,N1节点上的数据已经更新到y,但由于N1和N2之间的复制通道中断,数据y无法同步到N2,N2节点上的数据还是X。这时客户端C访问N2时,N2需要返回Eror,提示客户端C“系统现在发生了错误”,这种处理方式违背了可用性( Availability)的要求,因此CAP三者只能满足CP.
2.AP- Availability/Partition Tolerance
为了保证可用性,当发生分区现象后,N1节点上的数据已经更新到y,但由于N1和N2之间的复制通道中断,数据y无法同步到N2,N2节点上的数据还是这时客户端C访问N2时,N2将当前自己拥有的数据ⅹ返回给客户端C了,而实际上前最新的数据已经是y了,这就不满足一致性( Consistency)的要求了,因此CAP三者只能满足AP。注意:这里N2节点返回ⅹ,虽然不是一个“正确”的结果,但是一个“合理”的结果,因为ⅹ是旧的数据,并不是一个错乱的值,只是不是最新的数据而已.
一致性(Consistency)
对于某个指定的客户端来说,读操作保证能够返回最新的写操作结果;
可用性(Availability)
非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。
分区容错性(Partition Tolerance)
出现网络分区后,系统能够继续“履行职责”
关键细节
CAP关注的粒度是数据,而不是整个系统
CAP 定理关注的是对数据的读写操作,而不是分布式系统的所有功能,它要求分布式系统节点间是互相连接且有数据共享的,例如 Memcache 的集群中节点相互间没有连接和数据共享,因此不是 CAP 定理讨论的对象,同理 ZooKeeper 的选举机制也不是 CAP 探讨的对象。
CAP是忽略网络延迟的
正常运行情况下,不存在CP和AP的选择,可以同时满足CA
放弃不等于什么都不做,需要为分区恢复后做准备
BASE
BASE理论本质上是对CAP的延伸和补充;
Basically Available(基本可用)
分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
Soft State(软状态)
允许系统存在中间状态,而该中间状态不会影响系统整体可用性。
Eventual Consisitency(最终一致性)
系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
ACID
ACID是数据库管理系统为了保证事务的正确性而提出来的一个理论
Actomicity(原子性)
一个事务中的所有操作,要么全部完成,要么全部不完成,不会在中间某个环节结束。
Consistency(一致性)
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
Isolation(隔离性)
隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted)、读提交( read committed)、可重复读( repeatable read)和串行化( Serializable)
Durability(持久性)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丟失。
架构师成长之路
总的指导原则是:积累经验,拓宽视野,深度思考
内功
判断力
执行力
创新力
经验
视野
典型的阶段
工程师
成为一个合格的工程师需要 1 ~ 3 年时间,其典型特征是“在别人的指导下完成开发”,这里的“别人”主要是“高级工程师”或者“技术专家”,通常情况下,高级工程师或者技术专家负责需求分析和讨论、方案设计,工程师负责编码实现,高级工程师或者技术专家会指导工程师进行编码实现。
Java 的语法、基本数据结构的使用
Eclipse、IDEA、Maven、Linux 命令行等各种工具。
数据库 CRUD 操作、缓存的基本使用等。
业务系统的基本流程。
高级工程师
成长为高级工程师需要 2 ~ 5 年时间,其典型特征是“独立完成开发”,包括需求分析、方案设计、编码实现,其中需求分析和方案设计已经包含了“判断”和“选择”,只是范围相对来说小一些,更多是在已有架构下进行设计。
MySQL 数据库表如何设计,是设计成两个表还是三个表?
是否要用缓存,缓存的 Key 和 Value 如何设计,缓存的更新策略是什么?
产品提出的需求是否合理?是否有更好的方式来满足?
与工程师的差异
深度
如果说工程师是要求知道 How,那高级工程师就要求知道 Why 了。
理论
理论就是前人总结出来的成熟的设计经验,例如数据库表设计的 3 个范式、面向对象的设计模式、SOLID 设计原则、缓存设计理论(缓存穿透、缓存雪崩、缓存热点)等。
技术专家
成长为技术专家需要 4 ~ 8 年时间,其典型的特征是“某个领域的专家”,通俗地讲,只要是这个领域的问题,技术专家都可以解决。
与高级工程师的差异
高级工程师主要是在已有的架构框架下完成设计,而技术专家会根据需要修改、扩展、优化架构。
拓展技术宽度
学习业界成熟的开源方案
研究业界的经验分享
初级架构师
成长为初级架构师需要 5 ~ 10 年时间,其典型特征就是能够“独立完成一个系统的架构设计”,可以是从 0 到 1 设计一个新系统,也可以是将架构从 1.0 重构到 2.0。
与专家的典型区别
架构师是基于完善的架构设计方法论的指导来进行架构设计,而技术专家更多的是基于经验进行架构设计。
形成自己的架构设计方法论
系统学习架构设计方法论,包括订阅专栏或者阅读书籍等。
深入研究成熟开源系统的架构设计。
这个手段在技术专家阶段也会用到,但关注点不一样,同样是研究开源系统,技术专家阶段聚焦于如何更好地应用开源项目;初级架构师阶段聚焦于学习其架构设计原理和思想,例如 Kafka 的文档中就有关于消息队列架构设计的分析和取舍。
结合架构设计方法论,分析和总结自己团队甚至公司的各种系统的架构设计优缺点,尝试思考架构重构方案。
中级架构师
成长为中级架构师需要 8 年以上时间,其典型特征是“能够完成复杂系统的架构设计”,包含高性能、高可用、可扩展、海量存储等复杂系统
与初级架构师的典型区别
系统复杂度的不同,中级架构师面对的系统复杂度要高于初级架构师。
技术深度和技术理论的积累
技术理论
技术深度
高级架构师
成长为高级架构师需要 10 年以上时间,其典型特征是“创造新的架构模式”
与中级架构师的典型区别
创造性
高级架构师能够创造新的架构模式,开创新的技术潮流
可能诞生创造性架构的背景条件
足够复杂的业务场景
足够强大的技术团队
不满足于现状的态度
尊重技术价值的文化
架构设计文档模板
备选方案模板
需求介绍
性能问题
耦合问题
效率问题
...
需求分析
5W
1H
8C
复杂度分析
高可用
高性能
可扩展
备选方案
备选方案 1
备选方案 2
备选方案 3
备选方案评估
架构设计模板
总体方案
架构总览
核心流程
详细设计
高可用设计
高性能设计
可扩展设计
安全设计
其他设计
部署方案
架构演进规划
成熟的架构模式
高性能架构模式
高性能架构设计主要集中在两方面:
·尽量提升单服务器的性能,将单服务器的性能发挥到极致。
·如果单服务器无法支撑性能,设计服务器集群方案
架构设计决定了系统性能的上限,实现细节决定了系统性能的下限。
高性能数据库集群
读写分离
并不是说一有性能问题就上读写分离,而是应该先优化,例如优化慢查询,调整不合理的业务逻辑,引入缓存等,只有确定系统没有优化空间后,才考虑读写分离或者集群
复杂度
复制延迟
分配机制
一般有两种方式:程序代码封装和中间件封装。
程序代码封装
程序代码封装的方式具备几个特点:
中间件封装
中间件封装指的是独立一套系统出来,实现读写操作分离和数据库服务器连接的管理。中间件对业务服务器提供 SQL 兼容的协议,业务服务器无须自己进行读写分离。
数据库中间件的方式具备的特点是:
分库分表
当数据量达到千万甚至上亿条的时候,单台数据库服务器的存储能力会成为系统的瓶颈,主要体现在这几个方面:
基于上述原因,单个数据库服务器存储的数据量不能太大,需要控制在一定的范围内。为了满足业务数据存储的需求,就需要将存储分散到多台数据库服务器上。
业务分库
问题
join 操作问题
业务分库后,原本在同一个数据库中的表分散到不同数据库中,导致无法使用 SQL 的 join 查询。
事务问题
原本在同一个数据库中不同的表可以在同一个事务中修改,业务分库后,表分散到不同的数据库中,无法通过事务统一修改。虽然数据库厂商提供了一些分布式事务的解决方案(例如,MySQL 的 XA),但性能实在太低,与高性能存储的目标是相违背的。
成本问题
业务分库同时也带来了成本的代价,本来 1 台服务器搞定的事情,现在要 3 台,如果考虑备份,那就是 2 台变成了 6 台。
小公司初创业务建议
对于小公司初创业务,并不建议一开始就这样拆分,主要有几个原因:
分表
单表数据拆分有两种方式:垂直分表和水平分表。示意图如下:
为了形象地理解垂直拆分和水平拆分的区别,可以想象你手里拿着一把刀,面对一个蛋糕切一刀:
垂直分表
复杂性
增加表操作数量
水平分表
复杂性
路由
水平分表后,某条数据具体属于哪个切分后的子表,需要增加路由算法进行计算,这个算法会引入一定的复杂性。
范围路由
Hash 路由
配置路由
配置路由就是路由表,用一张独立的表来记录路由信息。
配置路由的缺点就是必须多查询一次,会影响整体性能;而且路由表本身如果太大(例如,几亿条数据),性能同样可能成为瓶颈,如果我们再次将路由表分库分表,则又面临一个死循环式的路由算法选择问题。
join 操作
count() 操作
记录数表
这种方式获取表记录数的性能要大大优于 count() 相加的方式,因为只需要一次简单查询就可以获取数据。缺点是复杂度增加不少,对子表的操作要同步操作“记录数表”,如果有一个业务逻辑遗漏了,数据就会不一致;且针对“记录数表”的操作和针对子表的操作无法放在同一事务中进行处理,异常的情况下会出现操作子表成功了而操作记录数表失败,同样会导致数据不一致。
order by 操作
什么时候引入分库分表
应该是这些操作依次尝试:
1.做硬件优化,例如从机械硬盘改成使用固态硬盘,当然固态硬盘不适合服务器使用,只是举个例子
2.先做数据库服务器的调优操作,例如增加索引,oracle有很多的参数调整;
3.引入缓存技术,例如Redis,减少数据库压力
4.程序与数据库表优化,重构,例如根据业务逻辑对程序逻辑做优化,减少不必要的查询;
5.在这些操作都不能大幅度优化性能的情况下,不能满足将来的发展,再考虑分库分表,也要有预估性
高性能NoSQL
关系数据库缺点
关系数据库存储的是行记录,无法存储数据结构
关系数据库的schema扩展很不方便
关系数据库在大数据场景下I/O较高
关系数据库的全文搜索功能比较弱
NoSQL方案分类
K-V存储
以Redis为代表
优点
高性能
存储支持数据结构
缺点
不支持完整的ACID事务
Redis的事务只能保证隔离性和一致性(I和C),无法保证原子性和持久性(A和D)
文档数据库
以MongoDB为代表
优点
新增字段简单
历史数据不会出错
可以很容易存储复杂数据
缺点
不支持事务
无法实现关系数据库的join操作
列式数据库
以HBase为代表
优点
业务同时读取多个列时效率高
能够一次性完成对一行中的多个列的写操作
存储压缩比更高
缺点
特定业务场景下才能体现优势
全文搜索引擎
以Elasticsearch为代表
特点
搜索条件随意排列组合
倒排索引
图数据库
以Neo4j为代表
特点
高性能缓存架构
缓存就是为了弥补存储系统在这些复杂业务场景下的不足,其基本原理是将可重复使用的数据放到内存中,一次生成,多次使用,避免每次使用都去访问存储系统。
缓存处理流程
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
存储数据不存在
缓存数据生成耗费大量时间或者资源
具体的场景有:
分页缓存的有效期设置为1天,因为设置太长时间的话,缓存不能反应真实的数据.
通常情况下,用户不会从第1页到最后1页全部看完,一般用户访问集中在前10页,因此第10页以后的缓存过期失效的可能性很大.
竟争对手每周来爬取数据,爬虫会将所有分类的所有数据全部遍历,从第1页到最后1页全部都会读取,此时很多分页缓存可能都失效了.
由于很多分页都没有缓存数据,从数据库中生成缓存数据又非常耗费性能(order by limit操作),因此爬虫会将整个数据库全部拖慢.
缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决方案:
1、设置热点数据永远不过期。
2、更新锁机制
对缓存更新操作进行加锁保护,保证只有一个线程能够进行缓存更新,未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或者默认值.
加互斥锁。
3、后台更新机制
由后台线程来更新缓存,而不是由业务线程来更新缓存,缓存本身的有效期设置为永久,后台线程定时更新缓存.
1.后台线程除了定时更新缓存,还要频繁地去读取缓存(例如,1秒或者100毫秒读取一次),如果发现缓存被“踢了”就立刻更新缓存,这种方式实现简单,但读取时间间隔不能设置太长,因为如果缓存被踢了,缓存读取间隔时间又太长,这段时间内业务访问都拿不到真正的数据而是一个空的缓存值,用户体验一般。
2.业务线程发现缓存失效后,通过消息队列发送一条消息通知后台线程更新缓存。可能会出现多个业务线程都发送了缓存更新消息,但其实对后台线程没有影响,后台线程收到消息后更新缓存前可以判断缓存是否存在,存在就不执行更新操作。这种方式实现依赖消息队列,复杂度会高一些,但缓存更新更及时,用户体验更好.
后台线程除了定时更新缓存,还要频繁地去读取缓存(例如,1秒或者100毫秒读取一次),如果发现缓存被“踢了”就立刻更新缓存
业务线程发现缓存失效后,通过消息队列发送一条消息通知后台线程更新缓存。
缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2、如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
3、设置热点数据永远不过期。
单服务器高性能模式
单服务器高性能的关键之一就是服务器采取的并发模型,并发模型有如下两个关键设计点:
服务器如何管理连接.
服务器如何处理请求.
以上两个设计点最终都和操作系统的ⅣO模型及进程模型相关。
I/O模型:阻塞、非阻塞、同步、异步。
进程模型:单进程、多进程、多线程。
PPC与TPC
优点:
实现简单;
缺点:
无法支撑高并发的场景
PPC
一般情况下,PPC 方案能处理的并发连接数量最大也就几百。
PPC是 Process per connection的缩写,其含义是指毎次有新的连接就新建一个进程去专门处理这个连接的请求,这是传统的∪NⅨX网络服务器所采用的模型。基本的流程图是:
prefork 模式
PPC 模式中,当连接进来时才 fork 新进程来处理连接请求,由于 fork 进程代价高,用户访问时可能感觉比较慢,prefork 模式的出现就是为了解决这个问题。
顾名思义,prefork 就是提前创建进程(pre-fork)。系统在启动的时候就预先创建好进程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去 fork 进程的操作,让用户访问更快、体验更好。prefork 的基本示意图是:
“惊群”现象( Linux 2.6 版本后已解决)
“惊群”现象,就是指虽然只有一个子进程能 accept 成功,但所有阻塞在 accept 上的子进程都会被唤醒,这样就导致了不必要的进程调度和上下文切换了。
TPC
TPC是 Thread per Connection的缩写,其含义是指每次有新的连接就新建一个线程去专门处理这个连接的请求。
Apache服务器MPM worker模式默认支持16*25=400个并发处理线程。
性能问题
创建线程虽然比创建进程代价低,但并不是没有代价,高并发时(例如每秒上万连接)还是有性能问题。
死锁问题
线程间的互斥和共享又引入了复杂度,可能一不小心就导致了死锁问题。
整个进程退出
多线程会出现互相影响的情况,某个线程出现异常时,可能导致整个进程退出(例如内存越界)。
Reactor与Proactor
Reactor
Reactor其实就是I/O多路复用技术;
I/O多路复用技术归纳起来有两个关键实现点:
1.当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接,常见的实现方式有 select、epol! kqueue等。
2.当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理。
Reactor模式的核心组成部分包括 Reactor和处理资源池(进程池或线程池),其中Reactor负责监听和分配事件,处理资源池负责处理事件。
1.Reactor的数量可以变化:可以是一个 Reactor,也可以是多个 Reactor。
2.资源池的数量可以变化:以进程为例,可以是单个进程,也可以是多个进程(线程类似)。
最终 Reactor模式有这三种典型的实现方案:
·单Reactor单进程/线程。
·单Reactor多线程。
·多Reactor多进程/线程。
Proactor
Proactor是异步网络模型;
Reactor可以理解为“来了事件我通知你,你来处理″,而 Proactor可以理解为“来了事件我来处理,处理完了我通知你”。这里的“我”就是操作系统内核,“事件”就是有新连接、有数据可读、有数据可写的这些I/O事件,“你”就是我们的程序代码。
Reactor与Proactor可以这样打个比方:
1、假如我们去饭店点餐,饭店人很多,如果我们付了钱后站在收银台等着饭端上来我们才离开,这就成了同步阻塞了。
2、如果我们付了钱后给你一个号就可以离开,饭好了老板会叫号,你过来取。这就是Reactor模型。
3、如果我们付了钱后给我一个号就可以坐到坐位上该干啥干啥,饭好了老板会把饭端上来送给你。这就是 Proactor模型了。
高性能负载均衡
分类及架构
组合的基本原则为:
1.DNS负载均衡用于实现地理级别的负载均衡;
2.硬件负载均衡用于实现集群级别的负载均衡;
3.软件负载均衡用于实现机器级别的负载均衡。
DNS负载均衡
DNS负载均衡是最简单也是最常见的负载均衡方式,一般用来实现地理级别的均衡。
DNS负载均衡实现简单、成本低,但也存在粒度太粗、负载均衡算法少等缺点。仔细分析一下优缺点,其优点有:
简单、成本低 : 负载均衡工作交给DNS服务器处理,无须自己开发或者维护负载均衡设备
就近访问,提升访问速度 : DNS解析时可以根据请求来源I,解析成距离用户最近的服务器地址,可以加快访问速度,改善性能。
缺点有:
更新不及时 : DNS缓存的时间比较长,修改DNS配置后,由于缓存的原因,还是有很多用户会继续访问修改前的IP,这样的访问会失败,达不到负载均衡的目的,并且也影响用户正常使用业务
扩展性差 : DNS负载均衡的控制权在域名商那里,无法根据业务特点针对其做更多的定制化功能和扩展特性。
分配策略比较简单 : DNS负载均衡支持的算法少;不能区分服务器的差异(不能根据系统与服务的状态来判断负载);也无法感知后端服务器的状态。
硬件负载均衡
硬件负载均衡是通过单独的硬件设备来实现负载均衡功能,这类设备和路由器、交换机类似,可以理解为一个用于负载均衡的基础网络设备。目前业界典型的硬件负载均衡设备有两款:F5和A10。
硬件负载均衡的优点是:
1.功能强大:全面支持各层级的负载均衡,支持全面的负载均衡算法,支持全局负载均。
2.性能强大:对比一下,软件负载均衡攴持到10万级并发已经很厉害了,硬件负载均衡可以支持100万以上的并发。
3.稳定性高∶商用硬件负载均衡,经过了良好的严格测试,经过大规模使用,稳定性高。
4.支持安全防护∶硬件均衡设备除具备负载均衡功能外,还具备防火墙、防DDoS攻击等安全功能。
硬件负载均衡的缺点是:
1.价格昂贵:最普通的一台F5就是一台“马6”,好一点的就是"Q7”了
2.扩展能力差:硬件设备,可以根据业务进行配置,但无法进行扩展和定制。
软件负载均衡
软件负载均衡通过负载均衡软件来实现负载均衡功能,常见的有 Nginx和LVs,其中Nginx是软件的7层负载均衡,LVS是 Linux内核的4层负载均衡。4层和7层的区别就在于协议和灵活性。
软件负载均衡的优点:
1.简单:无论是部署还是维护都比较简单。
2.便宜:只要买个Lnux服务器,装上软件即可。
3.灵活:4层和7层负载均衡可以根据业务进行选择;也可以根据业务进行比较方便的扩展,例如,可以通过 Nginx的插件来实现业务的定制化功能。
缺点(和硬件负载均衡相比的,并不是说软件负载均衡没法用):
1.性能一般:一个 Nginx大约能支撑5万并发。
2.功能没有硬件负载均衡那么强大
3.一般不具备防火墙和防DDoS攻击等安全功能。
算法
参考微信红包架构
任务平分类
负载均衡系统将收到的任务平均分配给服务器进行处理,这里的“平均”可以是绝对数量的平均,也可以是比例或者权重上的平均。
轮询
负载均衡系统收到请求后,按照顺序轮流分配到服务器上;
“简单”是轮询算法的优点,也是它的缺点。
(无法根据服务器的状态差异进行任务分配的问题)
加权轮询
主要目的是为了解决不同服务器处理能力有差异的问题
(无法根据服务器的状态差异进行任务分配的问题)
负载均衡类
负载均衡系统根据服务器的负载来进行分配,这里的负载并不一定是通常意义上我们说的“CPU负载”,而是系统当前的压力,可以用CPU负载来衡量,也可以用连接数、I/O使用率、网卡吞吐量等来衡量系统的压力。
最低优先
负载均衡系统将任务分配给当前负载最低的服务器,负载根据不同的任务类型和业务场景,可以用不同的指标来衡量。
最少连接数优先
最少连接数优先的算法要求负载均衡系统统计毎个服务器当前建立的连接,其应用场景仅限于负载均衡接收的任何连接请求都会转发给服务器进行处理,否则如果负载均衡系统和服务器之间是固定的连接池方式,就不适合采取这种算法。例如,LVS可以采取这种算法进行负载均衡,而一个通过连接池的方式连接 MySQL集群的负载均衡系统就不适合采取这种算法进行负载均衡。
CPU负载最低优先
CPU负载最低优先的算法要求负载均衡系统以某种方式收集每个服务器的CPU负载而且要确定是以1分钟的负载为标准,还是以15分钟的负载为标准,不存在1分钟肯定比15分钟要好或者差。不同业务最优的时间间隔是不一样的,时间间隔太短容易造成频繁波动,时间间隔太长又可能造成峰值来临时响应缓慢。
性能最优类
性能最优优先类算法则是站在客户端的角度来进行分配的,优先将任务分配给处理速度最快的服务器,通过这种方式达到最快响应客户端的目的
复杂度体现在:
1.负载均衡系统需要收集和分析每个服务器每个任务的响应时间,在大量任务处理的场景下,这种收集和统计本身也会消耗较多的性能。
2.为了减少这种统计上的消耗,可以采取采样的方式来统计,即不统计所有任务的响应时间,而是抽样统计部分任务的响应时间来估算整体任务的响应时间。采样统计虽然能够减少性能消耗,但使得复杂度进一步上升,因为要确定合适的采样率,采样率太低会导致结果不准确,采样率太高会导致性能消耗较大,找到合适的采样率也是一件复杂的事情。
3.无论是全部统计还是采样统计,都需要选择合适的周期:是10秒内性能最优,还是1分钟内性能最优,还是5分钟内性能最优……没有放之四海而皆准的周期,需要根据实际业务进行判断和选择,这也是一件比较复杂的事情,甚至出现系统上线后需要不断地调优才能达到最优设计。
Hash类
负载均衡系统根据任务中的某些关键信息进行Hash运算,将相同Hash值的请求分配到同一台服务器上。常见的有源地址Hash、目标地址Hash、 session id hash、用户 ID Hash等。
源地址Hash
将来源于同一个源IP地址的任务分配给同一个服务器进行处理,适合于存在事务、会话的业务。
ID Hash
例如session id
高可用架构模式
存储高可用方案的本质都是通过将数据复制到多个存储设备,通过数据冗余的方式来实现高可用,其复杂性主要体现在如何应对复制延迟和中断导致的数据不一致问题。
排除架构可用性隐患利器(FMEA方法)
经过多年的演进FMEA从定性和定量两个维度分别延伸出了FMECA和FMEDA
其实都是FMEA的扩展版,其实我们在具体实践的时候,分析纬度已经包括FMECA中的危害性分析(文中的“严重程度”),以及FMEDA中的诊断分析(文中的“已有措施”)
分析方法
给出初始的架构设计图
假设架构中某个部分发生故障
分析此故障对系统功能造成的影响
根据分析结果,判断架构是否需要进行优化
分析因子
1.功能点
2.故障模式
3.故障影响
4.严重程度
5.故障原因
6.故障概率
7.风险程度
8.已有措施
9.规避措施
10.解决措施
11.后续规划
双机架构
主备
主备复制是最常见也是最简单的一种存储高可用方案,几乎所有的存储系统都提供了主备复制的功能,例如 MySQL、 Redis、 MongoDB等
备机状态可以分为:冷备和温备
优点
对于客户端来说,不需要感知备机的存在
即使灾难恢复后,原来的备机被人工修改为主机后,对于客户端来说,只是认为主机的地址换了而已,无须知道是原来的备机升级为主机。
无须进行状态判断和主备切换这类复杂的操作
对于主机和备机来说,双方只需要进行数据复制即可,无须进行状态判断和主备切换这类复杂的操作。
缺点
备机仅仅只为备份,并没有提供读写操作,硬件成本上有浪费。
故障后需要人工干预,无法自动恢复。
人工处理的效率是很低的,可能打电话找到能够操作的人就耗费了10分钟,甚至如果是深更半夜,出了故障都没人知道。人工在执行恢复操作的过程中也容易出错,因为这类操作并不常见,可能1年就2、3次,实际操作的时候很可能遇到各种意想不到的问题。
主从
主机复制读写操作,从机只负责读操作,不负责写操作。
优点
主从复制在主机故障时,读操作相关的业务可以继续运行。
主从复制架构的从机提供读操作,发挥了硬件的性能。
缺点
主从复制架构中,客户端需要感知主从关系,并将不同的操作发给不同的机器进行处理,复杂度比主备复制要高。
主从复制架构中,从机提供读业务,如果主从复制延迟比较大,业务会因为数据不一致出现问题。
故障时需要人工干预。
主备/主从切换
主备复制和主从复制方案存在两个共性的问题:
·主机故障后,无法进行写操作。
·如果主机无法恢复,需要人工指定新的主机角色。
设计关键点
主备间状态判断
状态传递的渠道
状态检测的内容
切换决策
切换时机
切换策略
自动程度
数据冲突解决
常见架构
互连式
中介式
模拟式
主主
主主复制指的是两台机器都是主机,互相将数据复制给对方,客户端可以任意挑选其中一台机器进行读写操作。
特点
两台都是主机,不存在切换的概念。
客户端无须区分不同角色的主机,随便将读写操作发送给哪台主机都可以。
独特的复杂性
很多数据是不能双向
复制的
·用户注册后生成的用户ID,如果按照数字增长,那就不能双向复制,否则就会出现X用户在主机A注册,分配的用户ID是100,同时Y用户在主机B注册,分配的用ID也是100,这就出现了冲突。
·库存不能双向复制。例如,一件商品库存100件,主机A上减了1件变成99,主机B上减了2件变成98,然后主机A将库存99复制到主机B,主机B原有的库存98被覆盖,变成了99,而实际上此时真正的库存是97。类似的还有余额数据。
库存不能双向复制。
存在重复用户Id不能复制。
适用场景
临时性
可丢失
可覆盖
集群
数据集中集群
数据集中集群与主备、主从这类架构相似,我们也可以称数据集中集群为1主多备、1主多从。
复杂度
主机如何将数据复制给备机
备机如何检测主机转态
主机故障后,如何决定新的主机
数据分散集群
数据分散集群指多个服务器组成一个集群,每台服务器都会负责存储一部分数据;同时,为了提升硬件利用率,每台服务器又会备份一部分数据。
设计关键点
均衡性
算法需要保证服务器上的数据分区基本是均衡的,不能存在某台服务器上的分区数量是另外一台服务器的几倍的情况。
容错性
当出现部分服务器故障时,算法需要将原来分配给故障服务器的数据分区分配给其他服务
可伸缩性
当集群容量不够,扩充新的服务器后,算法能够自动将部分数据分区迁移到新服务器,并保证扩容后所有服务器的均衡性。
数据分区
数据分区指将数据按照一定的规则进行分区,不同分区分布在不同的地理位置上,每个分区存储一部分数据,通过这种方式来规避地理级别的故障所造成的巨大影响
设计考虑因素
1远距离集群网络延时较高,而且网络出问题的几率加大,导致数据复制的逻辑会比较复杂。
2成本过高,数据全量复制,等于存储多份。
所以更好的办法是从业务端对数据做分区,出现地理故障时只影响一部分用户或者功能的使用。
数据量
数据量的大小直接决定了分区的规则复杂度。
分区规则
城市分区
国家分区
洲际分区
备份复制规则
集中式
优点
设计简单,各分区之间并无直接联系,可以做到互不影响。
扩展容易
如果要增加第四个分区(例如,武汉分区),只需要将武汉分区的数据复制到西安备份中心即可,其他分区不受影响
缺点
成本较高,需要建设一个独立的备份中心。
互备式
优点
成本低,直接利用已有的设备
缺点
设计比较复杂
扩展麻烦
独立式
各个分区的备份并不和原来的分区在一个地方
优点
设计简单,各分区互不影响
扩展容易
缺点
成本高非常高
设计关键点
哪些服务器可以执行任务
每个服务器都可以执行任务
只有特定服务器可以执行任务
任务如何重新执行
对于已经分配的任务即使执行失败也不做任何处理
任务管理器决定是否需要将任务重新分配到另外的服务器
案例
微信红包
架构
南北分布
1.订单层南北独立体系,数据不同步
1)深圳用户发红包,深圳用户抢
2)深圳用户发红包,上海用户抢
3)上海用户发红包,上海用户抢
4)上海用户发红包,深圳用户抢
2.用户数据写多读少,全量存深圳,异步队列写入,查时一边跨城
3.支持南北流量灵活调控
4.DB故障时流量转移能力
拆红包入账异步化
1.Cache住所有查询,两层cache
2.DB写同步cache,容忍少量不一致
高并发
1.请求按红包订单路由,逻辑块垂直sticky,事务隔离
2.Dao搭建本机Memcache内存cache,控制同一红包并发个数
3.多层级并发量控制
1) 发红包控制
2) 抢红包控制
3) 拆时内存cache控制
4、DB简化和拆分
1) 订单表只存关键字段,其他字段只在cache中存储,可柔性。
2) DB双重纬度分库表,冷热分离
红包算法
如果红包只有一个,本轮直接使用全部金额,确保红包发完。
计算本轮红包金额下水位
计算本轮红包上水位
为了使红包金额不要太悬殊,使用红包均值调整上水位。
柔性降级方案
1.下单cache故障降级DB
2.抢时cache故障降级DB
3.拆时资金入账多级柔性
4. 用户列表降级
业务高可用
异地多活架构
应用场景
是否符合异地多活的两个标准
正常情况下,用户无论访问哪一个地点的业务系统,都能够得到正确的业务服务。
某个地方业务异常的时候,用户访问其他地方正常的业务系统,能够得到正确的业务服务。
代价
系统复杂度会发生质的变化,需要设计复杂的异地多活架构。
成本会上升,毕竟要多在一个或者多个机房搭建独立的一套业务系统。
架构模式
1. 同城异区
同城异区指的是将业务部署在同一个城市不同区的多个机房。
关键点
关键在于搭建高速网络将两个机房连接起来,达到近似一个本地机房的效果。
2. 跨城异地
跨城异地指的是业务部署在不同城市的多个机房,而且距离最好要远一些。
关键点
核心思想
异地多活设计的理念可以总结为一句话:采用多种手段,保证绝大部分用户的核心业务异地多活!
1.保证核心业务的异地多活
2.保证核心数据最终一致性
尽量减少异地多活机房的距离,搭建高速网络
尽量减少数据同步,只同步核心业务相关的数据
保证最终一致性,不保证实时一致性
3.采用多种手段同步数据
消息队列方式
二次读取方式
存储系统同步方式
回源读取方式
重新生成数据方式
4.只保证绝大部分用户的异地多活
补偿措施
挂公告
事后对用户进行补偿
补充体验
设计步骤
1.业务分级(分级标准)
访问量大的业务
核心业务
产生大量收入的业务
2.数据分类(分析维度)
数据量
唯一性
实时性
可丢失性
可恢复性
3.数据同步(同步方案)
存储系统同步
消息队列同步
重复生成
4.异常处理
异常处理主要有以下几个目的:
1. 多通道同步
多通道同步设计的方案关键点有:
2. 同步和访问结合
同步和访问结合方案的设计关键点有:
3.日志记录
常见的日志保存方式有:
4. 用户补偿
常见的补偿措施有送用户代金券、礼包、礼品、红包等,有时为了赢得用户口碑,付出的成本可能还会比较大,但综合最终的收益来看还是很值得的。
3. 跨国异地
跨国异地多活的主要应用场景一般有这几种情况:
例如,亚马逊中国是为中国用户服务的,而亚马逊美国是为美国用户服务的,亚马逊中国的用户如果访问美国亚马逊,是无法用亚马逊中国的账号登录美国亚马逊的。
例如,谷歌的搜索业务,由于用户搜索资料时,这些资料都已经存在于谷歌的搜索引擎上面,无论是访问英国谷歌,还是访问美国谷歌,搜索结果基本相同,并且对用户来说,也不需要搜索到最新的实时资料,跨国异地的几秒钟网络延迟,对搜索结果是没有什么影响的。
关键点
主要是面向不同地区用户提供业务,或者提供只读业务,对架构设计要求不高。
接口级故障处理
导致接口级故障的原因一般有下面几种:
程序 bug 导致死循环,某个接口导致数据库慢查询,程序逻辑不完善导致耗尽内存等。
黑客攻击、促销或者抢购引入了超出平时几倍甚至几十倍的用户,第三方系统大量请求,第三方系统响应缓慢等。
解决接口级故障的核心思想和异地多活基本类似:优先保证核心业务和优先保证绝大部分用户。
降级
系统后门降级
简单来说,就是系统预留了后门用于降级操作。
系统后门降级的方式实现成本低,但主要缺点是如果服务器数量多,需要一台一台去操作,效率比较低,这在故障处理争分夺秒的场景下是比较浪费时间的。
独立降级系统
为了解决系统后门降级方式的缺点,将降级操作独立到一个单独的系统中,可以实现复杂的权限管理、批量操作等功能。
熔断
熔断和降级是两个比较容易混淆的概念,因为单纯从名字上看好像都有禁止某个功能的意思,但其实内在含义是不同的,原因在于降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况。
限流
降级是从系统功能优先级的角度考虑如何应对故障,而限流则是从用户访问压力的角度来考虑如何应对故障。
基于请求限流
基于资源限流
排队
排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待一段时间,全世界最有名的排队当属 12306 网站排队了。排队虽然没有直接拒绝用户,但用户等了很长时间后进入系统,体验并不一定比限流好。
可扩展架构模式
基本思想
所有的可扩展性架构设计,背后的基本思想都可以总结为一个字:拆!
面向流程拆分
将整个业务流程拆分为几个阶段,每个阶段作为一部分。
展示层
业务层
数据层
存储层
...
面向服务拆分
将系统提供的服务拆分,每个服务作为一部分。
注册
登录
信息管理
安全设置
...
面向功能拆分
将系统提供的功能拆分,每个功能作为一部分。
手机号注册
身份证注册
邮箱注册
...
模式
不同的拆分方式,将得到不同的系统架构,典型的可扩展系统架构有:
分层架构
分层架构是很常见的架构模式,它也叫 N 层架构,通常情况下,N 至少是 2 层。例如,C/S 架构、B/S 架构。常见的是 3 层架构(例如,MVC、MVP 架构)、4 层架构,5 层架构的比较少见,一般是比较复杂的系统才会达到或者超过 5 层,比如操作系统内核架构。
C/S 架构、B/S 架构
MVC 架构、MVP 架构
逻辑分层架构
SOA(Service-OrientedArchitecture,面向服务架构)
SOA 出现 的背景是企业内部的 IT 系统重复建设且效率低下,主要体现在:
服务
ESB
松耦合
松耦合的目的是减少各个服务间的依赖和互相影响。因为采用 SOA 架构后,各个服务是相互独立运行的,甚至都不清楚某个服务到底有多少对其他服务的依赖。
微服务
对比一下 SOA 和微服务的一些具体做法,再来看看到底哪一种观点更加符合实际情况。
1.服务粒度
整体上来说,SOA 的服务粒度要粗一些,而微服务的服务粒度要细一些。
2.服务通信
SOA 采用了 ESB 作为服务间通信的关键组件,负责服务定义、服务路由、消息转换、消息传递,总体上是重量级的实现。微服务推荐使用统一的协议和格式,例如,RESTful 协议、RPC 协议,无须 ESB 这样的重量级实现。
3.服务交付
SOA 对服务的交付并没有特殊要求,因为 SOA 更多考虑的是兼容已有的系统;微服务的架构理念要求“快速交付”,相应地要求采取自动化测试、持续集成、自动化部署等敏捷开发相关的最佳实践。
4.应用场景
SOA 更加适合于庞大、复杂、异构的企业级系统,这也是 SOA 诞生的背景。这类系统的典型特征就是很多系统已经发展多年,采用不同的企业级技术,有的是内部开发的,有的是外部购买的,无法完全推倒重来或者进行大规模的优化和重构。因为成本和影响太大,只能采用兼容的方式进行处理,而承担兼容任务的就是 ESB。
微服务更加适合于快速、轻量级、基于 Web 的互联网系统,这类系统业务变化快,需要快速尝试、快速交付;同时基本都是基于 Web,虽然开发技术可能差异很大(例如,Java、C++、.NET 等),但对外接口基本都是提供 HTTP RESTful 风格的接口,无须考虑在接口层进行类似 SOA 的 ESB 那样的处理。
特点
服务力度细
服务通信轻量级
服务交付快
应用场景:互联网
陷阱
服务划分过细,服务间关系复杂
n 个服务的复杂度是 n×(n-1)/2
服务数量太多,团队效率急剧下降
调用链太长,性能下降
调用链太长,问题定位困难
没有自动化支撑,无法快速交付
最佳实践
服务粒度
“三个火枪手”原则
三个火枪手”的原则主要应用于微服务设计和开发阶段,如果微服务经过一段时间发展后已经比较稳定,处于维护期了,无须太多的开发,那么平均 1 个人维护 1 个微服务甚至几个微服务都可以。当然考虑到人员备份问题,每个微服务最好都安排 2 个人维护,每个人都可以维护多个微服务。
拆分方法
1.基于业务逻辑拆分
2. 基于可扩展拆分
3. 基于可靠性拆分
好处
避免非核心服务故障影响核心服务
核心服务高可用方案可以更简单
能够降低高可用成本
4. 基于性能拆分
基础设施
最基本的基础设施
服务发现
自理式
自理式服务发现实现比较简单,因为这部分的功能一般通过统一的程序库或者程序包提供给各个微服务调用,而不会每个微服务都自己来重复实现一遍;并且由于每个微服务都承担了服务发现的功能,访问压力分散到了各个微服务节点,性能和可用性上不存在明显的压力和风险。
代理式
代理式的方式看起来更加清晰,微服务本身的实现也简单了很多,但实际上这个方案风险较大。第一个风险是可用性风险,一旦 LOAD BALANCER 系统故障,就会影响所有微服务之间的调用;第二个风险是性能风险,所有的微服务之间的调用流量都要经过 LOAD BALANCER 系统,性能压力会随着微服务数量和流量增加而不断增加,最后成为性能瓶颈。
风险
可用性风险
性能风险
服务路由
路由算法
服务路由和服务发现紧密相关,服务路由一般不会设计成一个独立运行的系统,通常情况下是和服务发现放在一起实现的。对于自理式服务发现,服务路由是微服务内部实现的;对于代理式服务发现,服务路由是由 LOAD BALANCER 系统实现的。
随机路由
轮询路由
最小压力路由
最小连接数路由
服务容错
系统拆分为微服务后,单个微服务故障的概率变小,故障影响范围也减少,但是微服务的节点数量大大增加。
通常情况下,服务容错会集成在服务发现和服务路由系统中。
请求重试
流量控制
服务隔离
提升开发效率
接口框架
统一接口协议
统一数据格式
API 网关
统一的 API 网关,负责外部系统的访问操作
接入鉴权(是否允许接入)
权限控制(可以访问哪些功能)
传输加密
请求路由
流量控制
提升测试和运维效率
自动化部署
版本管理
资源管理
部署操作
回退操作
...
自动化测试
接口测试自动化
...
配置中心
配置版本管理
增删改查配置
节点管理
配置同步
配置推送
...
进一步提升运维效率
服务监控
服务监控的主要作用有:
服务监控需要搜集并分析大量的数据,因此建议做成独立的系统,而不要集成到服务发现、API 网关等系统中。
服务跟踪
服务监控和服务跟踪的区别可以简单概括为宏观和微观的区别。例如,A 服务通过 HTTP 协议请求 B 服务 10 次,B 通过 HTTP 返回 JSON 对象,服务监控会记录请求次数、响应时间平均值、响应时间最高值、错误码分布这些信息;而服务跟踪会记录其中某次请求的发起时间、响应时间、响应错误码、请求参数、返回的 JSON 对象等信息。
服务安全
从系统连接的角度来说,任意微服务都可以访问所有其他微服务节点;但从业务的角度来说,部分敏感数据或者操作,只能部分微服务可以访问,而不是所有的微服务都可以访问,因此需要设计服务安全机制来保证业务和数据的安全性。
接入安全
数据安全
传输安全
微内核架构(Microkernel Architecture)
微内核架构(Microkernel Architecture),也被称为插件化架构(Plug-in Architecture),是一种面向功能进行拆分的可扩展性架构,通常用于实现基于产品(原文为 product-based,指存在多个版本、需要下载安装才能使用,与 web-based 相对应)的应用。
基本架构
核心系统(core system)
设计关键点
1.插件管理
核心系统需要知道当前有哪些插件可用,如何加载这些插件,什么时候加载插件。常见的实现方法是插件注册表机制。
2.插件连接
插件连接指插件如何连接到核心系统。通常来说,核心系统必须制定插件和核心系统的连接规范,然后插件按照规范实现,核心系统按照规范加载即可。
3.插件通信
插件通信指插件间的通信。虽然设计的时候插件间是完全解耦的,但实际业务运行过程中,必然会出现某个业务流程需要多个插件协作,这就要求两个插件间进行通信。
插件模块(plug-in modules)
OSGi 架构简析
OSGi 的全称是 Open Services Gateway initiative,本身其实是指 OSGi Alliance。这个联盟是 Sun Microsystems、IBM、爱立信等公司于 1999 年 3 月成立的开放的标准化组织,最初名为 Connected Alliance。
OSGi 联盟的初始目标是构建一个在广域网和局域网或设备上展开业务的基础平台,所以 OSGi 的最早设计也是针对嵌入式应用的,诸如机顶盒、服务网关、手机、汽车等都是其应用的主要环境。
由于 OSGi 具备动态化、热插拔、高可复用性、高效性、扩展方便等优点,它被应用到了 PC 上的应用开发。
Eclipse 从 3.0 版本开始,抛弃了原来自己实现的插件化框架,改用了 OSGi 框架。需要注意的是,OSGi 是一个插件化的标准,而不是一个可运行的框架
1.模块层(Module 层)
2.生命周期层(Lifecycle 层)
3.服务层(Service 层)
规则引擎架构简析
规则引擎从结构上来看也属于微内核架构的一种具体实现,其中执行引擎可以看作是微内核,执行引擎解析配置好的业务流,执行其中的条件和规则,通过这种方式来支持业务的灵活多变。
而规则引擎却能够很灵活的应对这种需求,主要原因在于:
1. 可扩展
通过引入规则引擎,业务逻辑实现与业务系统分离,可以在不改动业务系统的情况下扩展新的业务功能。
2. 易理解
规则通过自然语言描述,业务人员易于理解和操作,而不像代码那样只有程序员才能理解和开发。
3. 高效率
规则引擎系统一般提供可视化的规则定制、审批、查询及管理,方便业务人员快速配置新的业务。
规则引擎的基本架构如下:
最常用的规则引擎是开源的 JBoss Drools
技术演进的方向
技术演进的动力
影响一个企业业务的发展主要有 3 个因素:市场、技术、管理,这三者构成支撑业务发展的铁三角,任何一个因素的不足,都可能导致企业的业务停滞不前。
业务
市场
技术
管理
业务分类
为何对于产品类的业务,技术创新能够推动业务发展呢?答案在于用户选择一个产品的根本驱动力在于产品的功能是否能够更好地帮助自己完成任务。用户会自然而然地选择那些功能更加强大、性能更加先进、体验更加顺畅、外观更加漂亮的产品,而功能、性能、体验、外观等都需要强大的技术支撑。例如,iPhone 手机的多点触摸操作、UC 浏览器的 U3 内核等。
对于“服务”类的业务,答案和产品类业务正好相反:业务发展推动技术的发展!
为什么会出现截然相反的差别呢?主要原因是用户选择服务的根本驱动力与选择产品不同。用户选择一个产品的根本驱动力是其“功能”,而用户选择一个服务的根本驱动力不是功能,而是“规模”。
例如,选择 UC 浏览器还是选择 QQ 浏览器,更多的人是根据个人喜好和体验来决定的;而选择微信还是 Whatsapp,就不是根据它们之间的功能差异来选择的,而是根据其规模来选择的,就像我更喜欢 Whatsapp 的简洁,但我的朋友和周边的人都用微信,那我也不得不用微信。
除非是开创新的技术能够推动或者创造一种新的业务,其他情况下,都是业务的发展推动了技术的发展。
产品类
用户选择一个产品的根本驱动力在于产品的功能是否能够更好地帮助自己完成任务。
服务类
用户选择一个服务的根本驱动力不是功能,而是“规模”
服务类的业务发展路径是这样的:提出一种创新的服务模式→吸引了一批用户→业务开始发展→吸引了更多用户→服务模式不断完善和创新→吸引越来越多的用户,如此循环往复。
技术演进的模式
基于业务发展阶段进行判断
互联网技术演进模式
量变到质变
更好的做法是在问题还没有真正暴露出来就能够根据趋势预测下一个转折点,提前做好技术上的准备,这对技术人员的要求是非常高的。
业务复杂性
1.初创期
业务重点不在于“完善”,而在于“创新”
2.发展期
主要目的是将原来不完善的业务逐渐完善,因此会有越来越多的新功能不断地加入到系统中。
堆功能期
优化期
技术团队或者产品人员再也受不了这种慢速的方式,终于下定决定要解决这个问题了。
如何解决这个问题,一般会分为两派:一派是优化派,一派是架构派。
优化派的核心思想是将现有的系统优化。例如,采用重构、分层、优化某个 MySQL 查询语句,将机械硬盘换成 SSD,将数据库从 MySQL 换成 Oracle,增加 Memcache 缓存等。优化派的优势是对系统改动较小,优化可以比较快速地实施;缺点就是可能过不了多久,系统又撑不住了。
架构派的核心思想是调整系统架构,主要是将原来的大系统拆分为多个互相配合的小系统。例如,将购物系统拆分为登录认证子系统、订单系统、查询系统、分析系统等。架构派的优势是一次调整可以支撑比较长期的业务发展,缺点是动作较大、耗时较长,对业务的发展影响也比较大。
相信在很多公司都遇到这种情况,大部分情况下都是“优化派”会赢,主要的原因还是因为此时“优化”是最快的方式。至于说“优化派”支撑不了多久这个问题,其实也不用考虑太多,因为业务能否发展到那个阶段还是个未知数,保证当下的竞争力是最主要的问题。
架构期
架构期可以用的手段很多,但归根结底可以总结为一个字“拆”,什么地方都可以拆。
3.竞争期
原来系统数量越来越多,到了一个临界点后就产生了质变,即系统数量的量变带来了技术工作的质变。
问题
重复造轮子
系统交互一团乱麻
解决手段
平台化
目的在于解决“重复造轮子”的问题。
服务化
目的在于解决“系统交互”的问题,常见的做法是通过消息队列来完成系统间的异步通知,通过服务框架来完成系统间的同步调用。
4.成熟期
当企业熬过竞争期,成为了行业的领头羊,或者整个行业整体上已经处于比较成熟的阶段,市场地位已经比较牢固后,业务创新的机会已经不大,竞争压力也没有那么激烈,此时求快求新已经没有很大空间,业务上开始转向为“求精”:我们的响应时间是否比竞争对手快?我们的用户体验是否比竞争对手好?我们的成本是否比竞争对手低……
此时技术上其实也基本进入了成熟期,该拆的也拆了,该平台化的也平台化了,技术上能做的大动作其实也不多了,更多的是进行优化。但有时候也会为了满足某个优化,系统做很大的改变。
用户规模
用户量增大对技术的影响主要体现在两个方面:性能要求越来越高、可用性要求越来越高。
1.性能
用户量增大给技术带来的第一个挑战就是性能要求越来越高。以互联网企业最常用的 MySQL 为例,再简单的查询,再高的硬件配置,单台 MySQL 机器支撑的 TPS 和 QPS 最高也就是万级,低的可能是几千,高的也不过几万。
2.可用性
用户量增大对技术带来的第二个挑战就是可用性要求越来越高。当你有 1 万个用户的时候,宕机 1 小时可能也没有很大的影响;但当你有了 100 万用户的时候,宕机 10 分钟,投诉电话估计就被打爆了,这些用户再到朋友圈抱怨一下你的系统有多烂,很可能你就不会再有机会发展下一个 100 万用户了。
互联网架构模板
互联网的标准技术架构如下图所示,这张图基本上涵盖了互联网技术公司的大部分技术点,不同的公司只是在具体的技术实现上稍有差异,但不会跳出这个框架的范畴。
存储层技术
为何没有出现存储平台的开源方案,但云计算却都提供了存储平台方案?
1,存储平台的开发成本高,由于存储平台是核心的平台,高可用,高性能是必须的,这就导致需要经验丰富的高级工程师来开发。而云平台作为服务提供商,有能力开发出来存储平台。
2,需要使用存储平台的公司不多,而且一般是大型的公司,小公司的业务规模都不大,对于存储平台的需求基本不高,云平台面向的是所以用户,众口难调,必然提供基础服务
3,云平台的存储平台是收费的,能为企业带来经济效益,而开源的存储平台,投入巨大,能使用的却很少,也就失去了意义
总结一下,云平台的存储平台,面向的是所有用户,包括大公司,有这方面的需求,而且是收费的,能够为平台带来收入;开源存储平台,服务的用户很少,投入巨大,所以没有
SQL
互联网公司流行的做法是业务发展到一定阶段后,就会将这部分功能独立成中间件,例如百度的 DBProxy、淘宝的 TDDL。不过这部分的技术要求很高,将分库分表做到自动化和平台化,不是一件容易的事情,所以一般是规模很大的公司才会自己做。
中小公司建议使用开源方案,例如 MySQL 官方推荐的 MySQL Router、360 开源的数据库中间件 Atlas。
假如公司业务继续发展,规模继续扩大,SQL 服务器越来越多,如果每个业务都基于统一的数据库中间件独立部署自己的 SQL 集群,就会导致新的复杂度问题,具体表现在:
NoSQL
NoSQL 发展到一定规模后,通常都会在 NoSQL 集群的基础之上再实现统一存储平台,统一存储平台主要实现这几个功能:
小文件
具有三个典型特征:
一是数据小,一般在 1MB 以下;
二是数量巨大,Facebook 在 2013 年每天上传的照片就达到了 3.5 亿张;
三是访问量巨大,Facebook 每天的访问量超过 10 亿。
淘宝 TFS 的架构:
大文件
互联网行业的大文件主要分为两类:
一类是业务上的大数据,例如 Youtube 的视频、电影网站的电影;
另一类是海量的日志数据,例如各种访问日志、操作日志、用户轨迹日志等。
只能用这几个流行的开源方案,例如,Hadoop、HBase、Storm、Hive 等。实力雄厚一些的大公司会基于这些开源方案,结合自己的业务特点,封装成大数据平台,例如淘宝的云梯系统、腾讯的 TDW 系统。
Hadoop 的生态圈:
开发层技术
开发框架
如果每个小组用不同的开发框架和技术,则会带来很多问题,典型的问题有:
Java
SSH
SpringMVC
Play
...
Ruby
Ruby on Rails
...
PHP
ThinkPHP
...
Python
Django
...
原则(优选成熟的框架)
1.成熟的框架资料文档齐备,各种坑基本上都有人踩过了,遇到问题很容易通过搜索来解决。
2.成熟的框架受众更广,招聘时更加容易招到合适的人才。
3.成熟的框架更加稳定,不会出现大的变动,适合长期发展。
Web服务器
Java
Tomcat
JBoss
Resin
Websphere
Apache
...
PHP
Nginx
Apache
...
Python
Nginx
Apache
...
容器
Docker
服务层技术
服务层的主要目标其实就是为了降低系统间相互关联的复杂度
配置中心
当系统数量不多的时候,一般是各系统自己管理自己的配置,但系统数量多了以后,这样的处理方式会有问题:
例如,我曾经遇到将 IP 地址的数字 0 误敲成了键盘的字母 O,肉眼非常难发现,但程序检查其实就很容易。
实现配置中心主要就是为了解决上面这些问题,将配置中心做成通用的系统的好处有:
集中配置多个系统,操作效率高。
所有配置都在一个集中的地方,检查方便,协作效率高。
配置中心可以实现程序化的规则检查,避免常见的错误。
· 配置中心相当于备份了系统的配置,当某些情况下需要搭建新的环境时,能够快速搭建环境和恢复业务。
服务中心
服务名字系统(Service Name System)
基本的设计如下:
服务总线系统(Service Bus System)
基本的设计如下:
消息队列
互联网业务的一个特点是“快”,这就要求很多业务处理采用异步的方式。
业界已经有很多成熟的开源实现方案,如果要求不高,基本上拿来用即可,例如,RocketMQ、Kafka、ActiveMQ 等。但如果业务对消息的可靠性、时序、事务性要求较高时,则要深入研究这些开源方案,否则很容易踩坑。
传统的异步通知方式是由消息生产者直接调用消息消费者提供的接口进行通知的,但当业务变得庞大,子系统数量增多时,这样做会导致系统间交互非常复杂和难以管理,因为系统间互相依赖和调用,整个系统的结构就像一张蜘蛛网,如下图所示。
消息队列就是为了实现这种跨系统异步通知的中间件系统。
整体结构从网状结构变为线性结构,结构清晰。
消息生产和消息消费解耦,实现简单。
增加新的消息消费者,消息生产者完全不需要任何改动,扩展方便。
消息队列系统可以做高可用、高性能,避免各业务子系统各自独立做一套,减轻工作量。
业务子系统只需要聚焦业务即可,实现简单。
网络层技术
单个系统的高可用和高性能并不等于整体业务的高可用和高性能,互联网业务的高性能和高可用需要从更高的角度去设计,这个高点就是“网络”,所以我将相关措施统一划归为“网络层”。注意这里的网络层和通常理解的如何搭建一个局域网这种概念不一样,这里强调的是站在网络层的角度整体设计架构,而不是某个具体网络的搭建。
负载均衡
1.DNS
DNS 是最简单也是最常见的负载均衡方式,一般用来实现地理级别的均衡。
DNS 负载均衡的优点是通用(全球通用)、成本低(申请域名,注册 DNS 即可),但缺点也比较明显,主要体现在:
HTTP-DNS 的优缺点有:
Nginx 、LVS
Nginx 是软件的 7 层负载均衡,LVS 是内核的 4 层负载均衡。
F5和A10
F5 是硬件的 4 层负载均衡。
软件和硬件的区别就在于性能,硬件远远高于软件,Ngxin 的性能是万级,一般的 Linux 服务器上装个 Nginx 大概能到 5 万 / 秒;LVS 的性能是十万级,没有具体测试过,据说可达到 80 万 / 秒;F5 性能是百万级,从 200 万 / 秒到 800 万 / 秒都有。硬件虽然性能高,但是单台硬件的成本也很高,一台最便宜的 F5 都是几十万,但是如果按照同等请求量级来计算成本的话,实际上硬件负载均衡设备可能会更便宜,例如假设每秒处理 100 万请求,用一台 F5 就够了,但用 Nginx,可能要 20 台,这样折算下来用 F5 的成本反而低。
CDN
CDN 是为了解决用户网络访问时的“最后一公里”效应,本质上是一种“以空间换时间”的加速策略,即将内容缓存在离用户最近的地方,用户访问的是缓存的内容,而不是站点实时的内容。
大部分程序员和架构师都不太需要深入理解 CDN 的细节,因为 CDN 作为网络的基础服务,独立搭建的成本巨大,很少有公司自己设计和搭建 CDN 系统,从 CDN 服务商购买 CDN 服务即可,目前有专门的 CDN 服务商,例如网宿和蓝汛;也有云计算厂家提供 CDN 服务,例如阿里云和腾讯云都提供 CDN 的服务。
多机房
1.同城多机房
2.跨城多机房
3.跨国多机房
多中心
相比多机房来说,多中心的要求就高多了,要求每个中心都同时对外提供服务,且业务能够自动在多中心之间切换,故障后不需人工干预或者很少的人工干预就能自动恢复。
业务层技术
这把“屠龙宝刀”就是“拆”,化整为零、分而治之,将整体复杂性分散到多个子业务或者子系统里面去。
随着子系统数量越来越多,如果达到几百上千,另外一个复杂度问题又会凸显出来:子系统数量太多,已经没有人能够说清楚业务的调用流程了,出了问题排查也会特别复杂。此时应该怎么处理呢最终答案还是“合”,正所谓“合久必分、分久必合”,但合的方式不一样,此时采取的“合”的方式是按照“高内聚、低耦合”的原则
发展阶段
第一阶段:所有功能都在 1 个系统里面。
第二阶段:将商品和订单拆分到 2 个子系统里面。
第三阶段:商品子系统和订单子系统分别拆分成了更小的 6 个子系统。
用户层技术
1.用户管理
互联网业务的一个典型特征就是通过互联网将众多分散的用户连接起来,因此用户管理是互联网业务必不可少的一部分。
单点登录(SSO)
单点登录(SSO),又叫统一登录。
cookie
JSONP
token
CAS
授权登录(第三方应用接入)
OAuth 2.0 协议
2.消息推送
途径
短信
邮件
站内信
App
iOS
APNS
Android
在国内,情况就复杂多了:首先是 GCM 不能用;其次是各个手机厂商都有自己的定制的 Android,消息推送实现也不完全一样。因此 Android 的消息推送就五花八门了,大部分有实力的大厂,都会自己实现一套消息推送机制,例如阿里云移动推送、腾讯信鸽推送、百度云推送;也有第三方公司提供商业推送服务,例如友盟推送、极光推送等。
GCM(国内不能用)
功能
1.设备管理(唯一标识、注册、注销)
2.连接管理
3.消息管理
技术挑战
海量设备和用户管理
连接保活
连接保活是整个消息推送设计中细节和黑科技最多的地方,例如应用互相拉起、找手机厂商开白名单等。
消息管理
3.存储云、图片云
现在有了“云”之后,除非 BAT 级别,一般不建议自己再重复造轮子了,直接买云服务可能是最快也是最经济的方式。
特点
数据量大
文件体积小
访问有时效性
平台技术
运维平台
四大职责
配置
配置:主要负责资源的管理。例如,机器管理、IP 地址管理、虚拟机管理等。
部署
部署:主要负责将系统发布到线上。例如,包管理、灰度发布管理、回滚等。
监控
监控:主要负责收集系统上线运行后的相关数据并进行监控,以便及时发现问题。
应急
应急:主要负责系统出故障后的处理。例如,停止程序、下线故障机器、切换 IP 等。
核心设计要素
标准化
需要制定运维标准,规范配置管理、部署流程、监控指标、应急能力等,各系统按照运维标准来实现,避免不同的系统不同的处理方式。标准化是运维平台的基础,没有标准化就没有运维平台。
平台化
传统的手工运维方式需要投入大量人力,效率低,容易出错,因此需要在运维标准化的基础上,将运维的相关操作都集成到运维平台中,通过运维平台来完成运维工作。
运维平台的好处有:
好处
1.可以将运维标准固化到平台中,无须运维人员死记硬背运维标准。
2.运维平台提供简单方便的操作,相比之下人工操作低效且容易出错。
3.运维平台是可复用的,一套运维平台可以支撑几百上千个业务系统。
自动化
传统手工运维方式效率低下的一个主要原因就是要执行大量重复的操作,运维平台可以将这些重复操作固化下来,由系统自动完成。
可视化
运维平台有非常多的数据,如果全部通过人工去查询数据再来判断,则效率很低。
优点
能够直观地看到数据的相关属性
能够将数据的含义展示出来
能够将关联数据整合一起展示
测试平台
测试平台核心的职责当然就是测试了,包括单元测试、集成测试、接口测试、性能测试等,都可以在测试平台来完成。
测试平台的核心目的是提升测试效率,从而提升产品质量,其设计关键就是自动化。
职责
单元测试
集成测试
接口测试
性能测试
...
核心设计要素
测试平台的基本架构如下图所示:
用例管理
测试自动化的主要手段就是通过脚本或者代码来进行测试,例如单元测试用例是代码、接口测试用例可以用 Python 来写、可靠性测试用例可以用 Shell 来写。为了能够重复执行这些测试用例,测试平台需要将用例管理起来,管理的维度包括业务、系统、测试类型、用例代码。
资源管理
测试用例要放到具体的运行环境中才能真正执行,运行环境包括硬件(服务器、手机、平板电脑等)、软件(操作系统、数据库、Java 虚拟机等)、业务系统(被测试的系统)。
除了性能测试,一般的自动化测试对性能要求不高,所以为了提升资源利用率,大部分的测试平台都会使用虚拟技术来充分利用硬件资源,如虚拟机、Docker 等技术。
任务管理
任务管理的主要职责是将测试用例分配到具体的资源上执行,跟踪任务的执行情况。任务管理是测试平台设计的核心,它将测试平台的各个部分串联起来从而完成自动化测试。
数据管理
测试任务执行完成后,需要记录各种相关的数据(例如,执行时间、执行结果、用例执行期间的 CPU、内存占用情况等),这些数据具备下面这些作用:
数据平台
数据平台架构如下图所示:
三部分
数据管理
数据管理包含数据采集、数据存储、数据访问和数据安全四个核心职责,是数据平台的基础功能。
数据采集
从业务系统搜集各类数据。例如,日志、用户行为、业务数据等,将这些数据传送到数据平台。
数据存储
将从业务系统采集的数据存储到数据平台,用于后续数据分析。
数据访问
负责对外提供各种协议用于读写数据。例如,SQL、Hive、Key-Value 等读写协议。
数据安全
通常情况下数据平台都是多个业务共享的,部分业务敏感数据需要加以保护,防止被其他业务读取甚至修改,因此需要设计数据安全策略来保护数据。
数据分析
数据分析包括数据统计、数据挖掘、机器学习、深度学习等几个细分领域
数据统计
根据原始数据统计出相关的总览数据。例如,PV、UV、交易额等。
数据挖掘
数据挖掘主要是指传统的数据挖掘方式。例如,有经验的数据分析人员基于数据仓库构建一系列规则来对数据进行分析从而发现一些隐含的规律、现象、问题等,经典的数据挖掘案例就是沃尔玛的啤酒与尿布的关联关系的发现。
机器学习/深度学习
机器学习和深度学习属于数据挖掘的一种具体实现方式,由于其实现方式与传统的数据挖掘方式差异较大,因此数据平台在实现机器学习和深度学习时,需要针对机器学习和深度学习独立进行设计。
...
数据应用
数据应用很广泛,既包括在线业务,也包括离线业务。例如,推荐、广告等属于在线应用,报表、欺诈检测、异常检测等属于离线应用。
如果数据没有达到一定规模,通常情况下做好数据统计就足够了,尤其是很多初创企业,无须一开始就参考 BAT 来构建自己的数据平台。
在线业务
离线业务
管理平台
管理平台的核心职责就是权限管理,无论是业务系统(例如,淘宝网)、中间件系统(例如,消息队列 Kafka),还是平台系统(例如,运维平台),都需要进行管理。
基本架构如下图所示:
身份认证
确定当前的操作人员身份,防止非法人员进入系统。
权限控制
根据操作人员的身份确定操作权限,防止未经授权的人员进行操作。
架构重构
从一大堆纷繁复杂的问题中识别出真正要通过架构重构来解决的问题,集中力量快速解决,而不是想着通过架构重构来解决所有的问题。
判断需要进行架构重构的方法:
假设我们现在需要从 0 开始设计当前系统,新架构和老架构是否类似?如果差异不大,说明采取系统优化即可;如果差异很大,那可能就要进行系统重构了。
那原来发现的那些非架构重构问题怎么办呢?当然不能放任不管。以 M 系统为例,我们在重构完成后,又启动了多个优化的项目去优化这些问题,但此时的优化主要由团队内部完成即可,和其他团队没有太多关联,优化的速度是很快的。如果没有重构就进行优化,则每次优化都要拉一大堆关联业务的团队来讨论方案,效率非常低下!
有的放矢
复杂度
业务已经上线,不能停下来
关联方众多,牵一发动全身
旧架构的约束
重构案例
1.M系统:后台系统重构:解决不合理的耦合
遇到的问题有很多:
M 系统是一个后台管理系统,负责管理所有游戏相关的数据,重构的主要原因是因为系统耦合了 P 业务独有的数据和所有业务公用的数据,导致可扩展性比较差。其大概架构如下图所示。
针对 M 系统存在的问题,重构目标就是将游戏数据和业务数据拆分,解开两者的耦合,使得两个系统都能够独立快速发展。重构的方案如下图所示:
2.S系统:游戏接入系统重构:解决全局单点的可用性问题
S 系统是游戏接入的核心系统,一旦 S 系统故障,大量游戏玩家就不能登录游戏。而 S 系统并不具备多中心的能力,一旦主机房宕机,整个 S 系统业务就不可用了。其大概架构如下图所示,可以看出数据库主库是全局单点,一旦数据库主库不可用,两个集群的写业务都不可用了。
针对 S 系统存在的问题,重构目标就是实现双中心,使得任意一个机房都能够提供完整的服务,在某个机房故障时,另外一个机房能够全部接管所有业务。重构方案如下图所示:
3.X 系统:解决大系统带来的开发效率问题
X 系统是创新业务的主系统,之前在业务快速尝试和快速发展期间,怎么方便怎么操作,怎么快速怎么做,系统设计并未投入太多精力和时间,很多东西都“塞”到同一个系统中,导致到了现在已经改不动了。做一个新功能或者新业务,需要花费大量的时间来讨论和梳理各种业务逻辑,一不小心就踩个大坑。X 系统的架构如下图所示:
针对 X 系统存在的问题,重构目标是将各个功能拆分到不同的子系统中,降低单个系统的复杂度。重构后的架构如下图所示(仅仅是示例,实际架构远比下图复杂):
合纵连横
合纵
要想真正推动一个架构重构项目启动,需要花费大量的精力进行游说和沟通。注意这里不是指办公室政治,而是指要和利益相关方沟通好,让大家对于重构能够达成一致共识,避免重构过程中不必要的反复和争执。
所以在沟通协调时,将技术语言转换为通俗语言,以事实说话,以数据说话,是沟通的关键!
连横
主要的阻力来自“这对我有什么好处”和“这部分我这边现在不急”。
对于“这对我有什么好处”问题,有的人会简单理解为这是自私的表现,认为对方不顾大局,于是沟通的时候将问题人为拔高。例如“你应该站在部门的角度来考虑这个问题”“这对公司整体利益有帮助”等。这种沟通效果其实很差,首先是这种拔高一般都比较虚,无法明确,不同的人理解也不一样,无法达成共识;其次是如果对公司和部门有利,但对某个小组没用甚至不利,那么可能是因为目前的方案不够好,还可以考虑另外的方案。
阻力
这对我有什么好处
对于“这对我有什么好处”问题,有的人会简单理解为这是自私的表现,认为对方不顾大局,于是沟通的时候将问题人为拔高。例如“你应该站在部门的角度来考虑这个问题”“这对公司整体利益有帮助”等。这种沟通效果其实很差,
首先是这种拔高一般都比较虚,无法明确,不同的人理解也不一样,无法达成共识;
其次是如果对公司和部门有利,但对某个小组没用甚至不利,那么可能是因为目前的方案不够好,还可以考虑另外的方案。
这部分我这边现在不急
策略
换位思考、合作双赢、关注长期
当然如果真的出现了对公司或者部门有利,对某个小组不利的情况,那可能需要协调更高层级的管理者才能够推动,平级推动是比较难的。
灵活处理
等待的策略
采取等待的策略也未尝不可,但要明确正式启动的时间。例如,3 个月后开始、6 月份开始,千万不能说“以后”“等不忙的时候”这种无法明确的时间点。
分阶段处理
方案上也可以灵活一点:我们可以先不做这个系统相关的重构,先把其他需要重构的做完。因为大部分需要重构的系统,需要做的事情很多,分阶段处理,在风险规避、计划安排等方面更加灵活可控。
运筹帷幄
随机解决问题
效果很差
原因
没有区分问题的优先级,没有集中有限资源去解决最重要或者最关键的问题
没有将问题分类,导致相似问题没有统筹考虑,方案可能出现反复,效率不高
迫于业务版本的压力,专门挑容易做的实施,到了稍微难一点的问题的时候,就因为复杂度和投入等原因被搁置,达不到重构的真正目的
解决方案
如果一个架构重构项目最后规划要 2 年才完成,你会怎么处理?
真要两年重构,那就直接重写了
分段实施
将要解决的问题根据优先级、重要性、实施难度等划分为不同的阶段,每个阶段聚焦于一个整体的目标,集中精力和资源解决一类问题。
好处
每个阶段都有明确目标,做完之后效果明显,团队信心足,后续推进更加容易
每个阶段的工作量不会太大,可以和业务并行
每个阶段的改动不会太大,降低了总体风险
策略
1.优先级排序
例如:先救火、后优化、再重构
2.问题分类
3.先易后难
4.循序渐进
每个阶段最少 1 个月,最长不要超过 3 个月,如果评估超过 3 个月的,那就再拆分为更多阶段。
开源项目
目前的云计算厂商很多都提供了和开源项目类似的系统(例如阿里云的云数据库 HBase),你倾向于购买云厂商提供的系统,还是只是将开源系统部署在云服务器上?理由是什么?
如果公司规模小建议可以直接使用云厂商的产品,因为运维方便。但是如果业务大,很多个性化的配置以及有自己的整套监控系统等,不适合用云厂商产品,无法进行系统整合。
用云产品的好处是,1.方便快捷,既然是产品那肯定经过包装,对很多bug进行了处理,因此上手快和使用方便;2.云产品自带维护功能,专业性比自建强,不用自己投入大量人力到维护的事情上;
缺点也有两个:1.羊毛出在羊身上,自带维护功能,意味着费用也会贵一些;2.维护交给第三方,意味着依赖第三方,出现影响业务的紧急情况可能出现支撑不到位,响应缓慢,解决问题需要时间长等问题;
自己用云服务器搭建的话,自己还是得亲力亲为,坑要自己踩,出现问题自己解决,但是也相应的灵活,有些问题可以结合业务情况来回避。
DRY原则
DRY,Don’t repeat yourself。翻译过来更通俗易懂:不要重复造轮子。
如何选
聚焦是否满足业务
有的架构师在选择时有点无所适从,总是会担心选择了 A 项目而错过了 B 项目。这个问题的解决方式是聚焦于是否满足业务,而不需要过于关注开源项目是否优秀。
聚焦是否成熟
在选择开源项目时,尽量选择成熟的开源项目,降低风险。
很多新的开源项目往往都会声称自己比以前的项目更加优秀:性能更高、功能更强、引入更多新概念……看起来都很诱人,但实际上都有意无意地隐藏了一个负面的问题:更加不成熟!
聚焦运维能力
如果要将项目应用到线上生产环境,则运维能力是必不可少的一环
开源项目日志是否齐全
开源项目是否有命令行、管理控制台等维护工具,能够看到系统运行时的情况
开源项目是否有故障检测和恢复的能力,例如告警、切换等
如何用
深入研究,仔细测试
小心应用,灰度发布
因为再怎么深入地研究,再怎么仔细地测试,都只能降低风险,但不可能完全覆盖所有线上场景。
先在非核心的业务上用,然后有经验后慢慢扩展。
做好应急,以防万一
对于重要的业务或者数据,使用开源项目时,最好有另外一个比较成熟的方案做备份,尤其是数据存储。
如何改
保持纯洁,加以包装
建议是不要改动原系统,而是要开发辅助系统:监控、报警、负载均衡、管理等
如果实在想改到原有系统,怎么办呢?我们的建议是直接给开源项目提需求或者 bug,但弊端就是响应比较缓慢,这个就要看业务紧急程度了,如果实在太急那就只能自己改了;如果不是太急,建议做好备份或者应急手段即可。
发明你要的轮子
其实选与不选开源项目,核心还是一个成本和收益的问题,并不是说选择开源项目就一定是最优的项目,最主要的问题是:没有完全适合你的轮子!
软件领域和硬件领域最大的不同就是软件领域没有绝对的工业标准,大家都很尽兴,想怎么玩就怎么玩。不像硬件领域,你造一个尺寸与众不同的轮子,其他车都用不上,你的轮子工艺再高,质量再好也是白费;软件领域可以造很多相似的轮子,基本上能到处用。
除此以外,开源项目为了能够大规模应用,考虑的是通用的处理方案,而不同的业务其实差异较大,通用方案并不一定完美适合具体的某个业务。
如果你有钱有人有时间,投入人力去重复发明完美符合自己业务特点的轮子也是很好的选择!
APP架构的演进
Web App(包壳架构)
简单来说就是在 Web 的业务上包装一个 App 的壳,业务逻辑完全还是 Web 实现,App 壳完成安装的功能,让用户看起来像是在使用 App,实际上和用浏览器访问 PC 网站没有太大差别
优点
快速开发(合适原则)
低成本(简单原则)
缺点
体验差
原生 App
优点
用户体验好
缺点
开发成本高
不同平台重复开发
Hybrid App
优点
根据不同的业务要求选取不同的方案
缺点
可扩展性差
不同平台重复开发
组件化 & 容器化
基本思想:将超级 App 拆分为众多组件
组件化采用的是静态发布,即所有的组件各自独自开发测试,然后跟随 App 的某个版本统一上线
容器化采用的是动态发布,即容器可以动态加载组件,组件准备好了直接发布,容器会动态更新组件,无需等待某个版本才能上线
缺点
不同平台重复开发
跨平台 App
解决不同平台重复开发
Facebook 的 React Native
阿里的 Weex
Google 的 Flutter