前言
每一个程序员都有一个架构师的梦,可理想很丰满,现实很骨感---大部程序员工作中都做着简单的 CRUD,我也不例外。如果就这样还常把“架构”两个字挂在嘴边,估计程序员们都会脸红。但就因为暂时还不能成为架构师,我们就要放弃成为架构师的梦想了吗?显然不能,掌握架构设计的相关理论是成为架构师的前提,有了方法论可以更好地指导我们干活。机会总是留给有准备的人的,万一哪天梦想实现了呢?
为了学习“架构”,我偷偷的看了两本武功秘籍《从0开始学架构》《大型网站技术架构》。并从中发现了一些秘密:两本书讲的关于架构的核心内容有很多共同的地方,说明架构师是有套路的。掌握了套路并勇于实践,未来并不遥远。
架构的前世今生
1、什么是架构
在理解什么是架构之前,先来看下与它相关的一些词汇:架构、框架、组件、模块和系统。《从0开始学架构》下的一条评论很精炼的总结了它们的定义与区别,摘抄如下:
- 架构是顶层设计;
- 框架是面向编程或配置的半成品;
- 组件是从技术维度上的复用;
- 模块是从业务维度上职责的划分;
- 系统是相互协同可运行的实体。
软件架构是软件系统的顶层设计,它明确软件系统包括哪些个体:子系统、模块和组件等;同时明确了个体运作和个体之间协作的规则。
2、架构的历史背景
从机器语言到汇编语言到高级语言、从结构化程序设计方法到到面向对象编程思想到面向接口编程、从单机单进程到单机多进程到分布式集群。每一次语言、编程思想和技术的升级都是为了满足软件规模越来越大的需要。而随着软件系统大到一定程度,数据结构与算法不再构成软件设计的主要问题;当系统组成部分越来越多时,整个系统的组织或者说“软件架构”导致了一系列新的问题。而不合理的架构的复杂系统往往面临如下问题:
- 系统规模庞大,内部耦合严重,开发效率低;
- 系统耦合严重,牵一发而动全身,后续修改和扩展困难;
- 系统逻辑复杂,容易出问题,出问题后很难排查和修复。
软件架构便应运而生,但由于软件系统的复杂性和多变性,没有一种架构可以满足所有系统的设计需求。它与面向对象编程、软件工程一样,不是软件设计领域的银弹。
3、架构设计的目的
做任何事情只有明确了目的,才能把握方向,从而制定方案并执行,架构设计也不例外。架构设计的主要目的是为了解决软件系统复杂度带来的问题。小到管理系统大到淘宝、微信,在设计开发过程中,都需要进行架构设计。而由于软件系统之间的复杂度不同,架构设计难度也不一样,但基本流程都相似,掌握了架构设计的流程,即使简单的对内的工具平台也能玩出花儿来。
4、架构作用的总结
架构设计可大可小但不是可有可无,架构并不是遥不可及也别嗤之以鼻。
1、架构是为了应对软件系统复杂度而提出的一个解决方案。
2、架构即(重要)决策。
3、需求驱动架构,架起分析与设计实现的桥梁。
4、架构与开发成本的关系。
架构设计理论
1、架构设计复杂度来源
软件架构定义中我们总是能看到复杂软件这个词,什么是复杂软件呢?大规模网站就是复杂软件的一种,大规模网站架构(其他软件系统也类似)需要考虑的复杂度来源主要如下图所示:
包括高性能、高可用、易扩展、可伸缩、安全性和低成本六个来源。注:为了更具化的说明软件架构设计本文使用大规模网站作为复杂软件来讲解
大部分程序员毕生所学除了用来做业务逻辑开发,大部分技能都是为了解决以上六个问题。我们看过很多书、写过很多代码、做过很多设计,今天终于在这里找到了做这一切的根源。这棵大型网站架构要素的树可以将你看过绝大部分知识包含进去,是你结构化脑海中知识的根。只要你的知识够丰富,一层层的挂上去,这棵树可以独木成林。这也是是我写这篇文章的目的,并不只是简简单单的复制和总结。也是想告诉绝大部分程序员:通过这棵树开始结构化你脑海中的知识吧。
架构设计三原则(来源于网络)
1、合适原则:合适优于业界领先。
架构无优劣,但存合适性。“汝之蜜糖,吾之砒霜”;架构一定要匹配企业所在的业务阶段;不要面向简历去设计架构,高大上的架构不等于适用;削足适履与打肿充胖都不符合合适原则;所谓合适,一定要匹配业务所处阶段,能够合理地将资源整合在一起并发挥出最大功效,并能够快速落地。
2、简单原则:简单优于复杂
"我没有时间写一封短信,所以只好写一封长信"。其实,简单比复杂更加困难。面对系统结构、业务逻辑和复杂性,我们可以编写出复杂的系统,但在软件领域,复杂代表的是“问题”。架构设计时如果简单的方案和复杂的方案都可以满足需求,最好选择简单的方案。但是,事实上,当软件系统变得太复杂后,就会有人换一个思路进行重构、升级,将它重新变得简单,这也是软件开发的大趋势。简单原则是一个朴素且伟大的原则,Google 的 MapReduce 系统就采用了分而治之的思想,而背后就是将复杂问题转化为简单问题的典型案例。
3、演化原则:演化优于一步到位
大到人类社会、自然生物,小到一个细胞,似乎都遵循这一普世原则,软件架构也不例外。业务在发展、技术在创新、外部环境在变化,这一切都是在告诫架构师不要贪大求全,或者盲目照搬大公司的做法。应该认真分析当前业务的特点,明确业务面临的主要问题,设计合理的架构,快速落地以满足业务需要,然后在运行过程中不断完善架构,不断随着业务演化架构。
架构设计三原则很重要,在设计你的软件系统之前先问问自己这样做架构合适吗?简单吗?可演化吗?
架构设计流程
1、识别复杂度
在做软件系统开发的时候不可能一上来就直接敲代码,常常需要进行一些分析。除了功能上的分析,还需要对软件系统复杂度进行分析,这决定了你后续方案的选型。
架构的复杂度来源虽然有六个,但主要复杂度来自于高性能、高可用和易扩展。在讨论架构设计的时候我们更多的是讨论这三点,本文也不例外。
但架构并不一定任何时候都必须满足这三个需求。根据软件系统的不同,大部分情况下只需要满足其中的一个到两个,很少能用到三个的。
在进行高性能、高可用和易扩展复杂度来源分析的时候最好有指标支撑,比如高性能要达到多少 QPS、高可用要达到几个 9、可扩展要扩展到什么程度。千万不要凭自己想象,这样要么将复杂度分析的过于简单,上线后不能满足需求;要么将复杂度分析的过于复杂,导致过度设计,项目迟迟不能上线。
2、设计备选方案
解决高性能、高可用和易扩展三个复杂度来源的成熟的技术方案有很多。比如,高性能的缓存、负载均衡、多路复用,高可用的主备方案、集群方案、异地多活,易扩展的设计模式、架构分层、插件化等。在明确了软件系统的复杂度(三选 N )以后就可以按图索骥的找到可选的解决方案。
架构师在设计方案的时候需要注意以下几点,别落入俗套:
- 总想着设计最优秀的方案。
- 只做一个方案。
- 方案设计的过于详细。
这就是为什么很多大公司的面试都喜欢问一些开源框架或者软件的源码,他们的目的不在于让你熟悉源码,而是在软件系统架构设计时能够快速的对现有的各种框架进行选型以找出相对合适的。
3、评估和选择备选方案
方案有了,还需要评估和挑选出最合适的方案。这里需要遵守架构设计的三个原则合适、简单和演化,但还会面临以下挑战:
- 每个方案都必须是可行的
- 每个方案都是不完美的
- 评价标准主观性比较强
架构师对方案的选择往往存在以下几派:
- 最简派:只选择最简单的
- 最牛派:只选择最流行的
- 最熟派:只选择最熟悉的
而正在的备选方案评估以上几派都不靠谱,真正要做的是对各个方案进行 360 度环评:列出我们需要关注的质量属性点,然后分别从这些质量属性的维度去评估每个方案,再综合挑选适合当时情况的最优方案。常见的方案质量属性点有:性能、可用性、硬件成本、项目投入、复杂度、安全性、可扩展性等。根据你们团队对以上各个属性的侧重点,选择最合适的方案。
4、详细方案设计
开始对备选方案进行一个详细的设计,比如各个个体如何运行、个体与个体之间如何协作等。
在这一步的时候可以让一线开发来完成细节的填充,架构师做最后的审核即可。一方面降低了架构师的工作量,另一方面可以培养新人。
结论
我们从以上内容了解到软件系统设计的发展历史以及架构设计的定义、目的、使命、原则和流程这样一个完整的闭环,为接下来的架构设计实战夯实了理论基础。
架构设计实战(以大型网站为例)
高性能
高性能是架构设计复杂度来源之一,任何软件系统对高性能都有所要求,只是要求的标准不一样。对性能要求越高的软件系统架构设计越复杂,反之越容易。但如果高不能用指标来量化,那便无法进行合理的架构设计和进一步优化。所以高性能的两部分性能指标和性能优化同等重要。
性能指标
评价大型网站高性能的主要指标如下:
- 响应时间:是一次请求从发送请求到收到响应的总时间,直观的反映系统的快慢。
- 并发数:是单位时间处理的请求数,通常用 TPS 来表示,是系统容量的直观体现。
- 吞吐量:是系统同时能处理的请求数。通常包括 TPS、QPS 或者 HPS。当然,光有指标的定义还不够,还需要对这些指标有基本的量化,比如响应时间小于 300ms、并发数不小于 1W 个链接,吞吐量达到 1W QPS 等。
性能优化
大型网站从前台到后台再到数据可大致分为三个层次,本文分别从三个层次来介绍性能优化所涉及到的技术点,但系统的性能优化方案从来不应该是大而全的,这里只是泛泛而谈。如果这里涉及到的知识点你未曾涉及,建议自行百度,弄懂后挂到脑图树上;如果对涉及到的知识点有着更深的理解,扩充你的脑图树叶。
1、Web 前端优化
减少 HTTP 请求:由于一次 HTTP 请求中包含很多有效数据以外的无效数据(比如响应码、Header),所以尽量将多次 HTTP 请求合并成一个,比如 JS 文件与 CSS 文件合并、小图片合并成一张大图片等。
使用浏览器缓存:很多网站的 CSS、JS、图片这些静态文件更新频率很低,而这些静态文件又需要频繁请求,便可以将它们缓存到浏览器中。通过设置 HTTP 头中的 Cache-Control 和 Expirs 属性,可以设定浏览器缓存,缓存的时间可以是数天或者几个月。(HTTP 协议 Header 中各个字段的作用)
启用压缩:在服务器端返回数据以前进行数据压缩,特别是 JS/CSS/HTML 等文本的压缩,压缩率极高可达 80%。但数据的压缩和解压会给服务器端和浏览器端都造成一定的压力,在设计时需要权衡。(数据压缩算法有哪些)
减少 Cookie 传输:不要将大量的用户数据放在 Cookie 中,一方面不安全,另一方面会增加网络传输压力。最常用做法是将用户数据存储在 Session 中,Cookie 中只需要存入 Session ID 即可。(什么是 Cookie/Session,以及它们之间的区别)
CDN 加速: CDN(Content Distribute Network,内容分发网络)的本质仍然是一个缓存,它一般由网络运营商提供,将数据(一般是静态数据 JS、CSS、图片、文件等)缓存在在离用户最近的地方。(什么是 CDN)
反向代理: 反向代理工作在浏览器和后端服务器之间,它屏蔽了后端服务器的细节。一方面它可以用来进行后端服务器的负载均衡,另一方面可以缓存静态内容加速网站的访问。(什么是正向代理和反向代理)
2、应用服务器优化
使用缓存:缓存在提高性能方面随处可见,从前端到后台。前端前面已经介绍,而后台服务的缓存分为本地缓存和分布式缓存(本地缓存与分布式缓存各自的原理以及它们的优缺点)。
本地缓存可以使用简单的 Hash 表实现也可以使用开源软件,比如 SpringBoot 下的 Encache。(Hash 定义、如何避免冲突)。
分布式缓存是比较常用缓存解决方案,特别是在集群环境。分布式缓存有很多成熟的开源软件,比如 Memcached 和 Redis,由于使用广泛,分布式缓存原理和一些开源软件是面试中的常客。(分布式缓存的原理、Memcached 和 Redis 各自的优缺点,了解缓存中一些常用名词:缓存穿透、缓存热点、缓存雪崩等)。
异步操作:异步是提高性能的另一个手段,也包括本地异步和分布式异步。本地异步可以用简单的多线程或者线程实现,也可以用成熟的事件框架,比如谷歌的 Guava Eventbus。而本地进程间可以使用共享内存实现异步,只是需要在共享内存中实现一个并发安全的本地消息队列。
分布式异步可以使用消息队列,消息队列不仅可以解耦系统还可以削峰填谷。它是分布式系统异步的终极解决方案,常见的消息队列有 RabbitMQ、MetaQ、Kafka。(消息队列的原理和 RabttiMQ 的优缺点)
使用集群:一台机器的性能再好,整个系统的吞吐量都是有限的。为了支持更大的吞吐量,除了优化代码、提高单机服务器性能,还可以将系统从一台机器部署扩散到多台。所谓的分布式系统都是这种操作,因为只有这样才能破解摩尔定律失效的魔咒。
代码优化:从代码层面来提高性能是程序员必备技能,涉及到的知识点太多,这里只简单的列举一下几点,算是抛砖引玉:
(1)使用多线程、多进程。
(2)充分利用内存。
(3)资源复用:单例和池化技术。
(4)更好的算法和数据结构。
(5)JAVA 虚拟机参数调优以及避免 FULLGC。
(6)合适的 IO 模型和线程/进程模型。
3、存储性能优化
磁盘 IO 优化:使用 SSD 代替机械硬盘,使用 HDFS 代替普通文件系统。 数据库优化:数据库优化是个老生常谈的问题,也是面试中的常客。主要有以下几种优化方法(高性能 Mysql):
(1)增加索引
(2)优化 SQL 语句
(3)分库分表
(4)读写分离
(5)分布式关系型数据库
(6)使用 Nosql
高可用
如果系统不可用,性能再高也没卵用。与高性能一样,高也需要有指标来量化,比如 4 个 9。大型网站的高可用又分为应用高可用、服务高可用和数据高可用。
高可用的服务
1、接口服务高可用
服务降级:将业务或者接口功能降低,只提供部分功能或者完全停掉所有功能。常见实现降级方式有:系统后门降级和独立系统降级。
流量限制:降级是故障后的事后处理,而限流是故障前的预防。限流是指只允许能够承受的访问量进来,超出系统系统访问能力的将被放弃。常用的限流方式可以分为分类:基于请求限流和基于资源限流。
流量排队:是流量限制的一个变种,限流是直接拒绝用户,排队是让用户等待一段时间。 服务熔断:熔断与降级概念非常容易混淆,降级是为了应对服务自身的故障。熔断的目的是为了应对外部系统故障的情况:主动断开依赖的其他外部的不可用服务,防止造成更多的雪崩效应。
分级管理:根据服务功能的重要性按照等级进行划分,将不同等级的服务进行隔离,防止非核心服务的故障影响核心服务。
2、业务服务高可用
集群 1、多机器部署
2、服务注册与发现
异地多活:
1、同城异区 + 专线:复杂度低、有用性高、无法应对大灾难(如同城地震、停电)
2、跨城异地:复杂度高、网络延迟高、数据无法强一致性。并非所有业务都适合。
3、跨国异地:缺点同跨城异地,主要用来:
- 为不同地区提供不同服务,如亚马逊中国/亚马逊美国
- 只做只读类业务多活,对延迟没那么敏感,如谷歌搜搜。
微服务
服务描述:服务描述用来描述服务的以下信息:(1)服务名称 (2)调用服务需要提供的信息 (3)服务调用后返回的数据格式 常见的服务描述包括 RESTFUL API、XML 配置以及 IDL 文件三种。
注册中心:通过注册中心进行服务的发布和订阅。微服务分为服务调用者和服务消费者,注册中心是他们沟通的桥梁。
服务框架:提供服务消费者与服务提供者之间进行沟通的约定:(1)服务通信采用什么协议 (2)数据传输采用什么方式 (3)数据压缩采用什么格式
服务监控:服务监控是了解服务是否可用的重要手段,主要包括三个流程:指标收集、数据处理和数据展示。
服务追踪:除了需要对服务调用情况进行监控之外,你还需要记录服务调用经过的每一层链路,以便进行问题追踪和故障定位。阿里鹰眼
服务治理:服务监控能发现问题,服务追踪能够定位问题所在,而解决问题需要靠服务治理。负载均衡和故障自动转移。
高可用的数据
1、数据一致性
CAP 理论:对于一个分布式计算系统,不可能同时满足一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三个设计约束。CAP 关注的是对数据的读写操作而不是整个系统。CAP 最多只能满足三者中的两个,P必须满足,注重一致性则必须是 CP(如zookeeper),注重可用性则必须是 AP(如eureka)。
ACID:ACID 是数据库系统为了保证事务的正确性而提出来的理论,ACID 包含四个约束:Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)和 Durability(持久性)。
BASE:BASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性(Eventual Consistency),核心思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性。
2、数据备份
存储高可用方案的本质都是通过将数据复制到多个存储设备,通过数据冗余的方式来实现高可用,其复杂性主要体现在如何应对复制延迟和中断导致的数据不一致问题。因此,对任何一个高可用存储方案,我们需要从以下几个方面去进行思考和分析:
- 数据如何复制
- 各个节点职责是什么
- 如何应对复制延迟
- 如何应对复制终端
主备复制:
(优缺点分析)可以衍生出一主一备、一主多备。备机只是单纯的为备份,并不进行读写操作。当主机故障不可用以后,需要人工干预切换到备机。主备架构的优点是简单,可以直接利用 Mysql 提供的 Binlog 即可实现。常用于学生管理系统、员工管理系统和假期管理系统等。
主从复制:
与主备复制一字之差,架构却不一样。主从复制中的主机负责读写,从机除了备份数据外还支持读,优点是主机故障不可用也不影响读业务同时充分利用了硬件资源。缺点是
(1)客户端读复杂度提高,需要感知主从关系并选择一台机器进行读操作。
(2)如果数据复制的延迟比较大,会出现数据不一致的中间态。
(3)故障时需要人工干预。
主主复制:
主主架构两台都是主机,主要优点如下:
(1)两台主机都可以进行读写,其中一台故障不影响整体业务。
(2)客户端无需区分角色,可以将读写请求发送到任意一台机器上。
看似完美的主主架构也有众多缺点:
(1)双向复制也并不能保证实时性,如果延迟较高也会存在数据不一致的中间态。
(2)很多数据并不能进行双向复制,比如用户自增 id、库存数据、余额数据等。
主主复制架构对数据设计有严格要求,一般适合那些临时性、可丢失、可覆盖的数据场景。
数据集群:上面介绍的无论是主备/主从/主主都只有一台主机,它们有如下两个缺点:、
(1)主机存储的数据有限。
(2)主机宕机后需要手动恢复。
数据集群由多台机器组成形成一个系统,专门用来存储数据。数据集群又分为数据集中集群和数据分散集群:
(1)数据集中集群:集群中机器上的数据都是一样的,由一台机器进行写然后复制到集群中其他机器。可参考 Zookeeper 的原理和架构。
(2)数据分散集群:数据分散在不同机器上,当然也会备份在几台集群中的机器上。可参考一致性哈希算法和 Amazon 的 Dynamodb 原理以及架构。
数据分区:将数据按照区域进行划分,防止某个区域发生非常大的灾难,比如洪水、地震、海啸等。而每个区域的数据备份又可以采用上述的主主/主从/主备/集群的方式保证高可用。数据分区架构复杂度需要从以下方面去考虑:
(1)数据量
(2)分区规则
(3)复制规则:集中式、互备式和独立式
高可用的质量保证
1、网站发布:在柔性的发布过程中,每次关闭的服务都是集群中的一小部分,并在发布完成后立即可以访问;
2、自动化测试:使用自动测试工具或脚本完成测试;
3、预发布验证:引入预发布服务器,与正式服务器几乎一致,只是没有配置在负载均衡服务器上,外部用户无法访问;
4、代码控制:目前大多数网站采用 SVN,分支开发,主干发布模式;另外,目前开源社区广泛采用 Git 作为版本控制工具,正逐步取代SVN的地位;
监控和告警
1、监控数据采集
(1)用户行为日志收集:服务器端的日志收集和客户端的日志收集;目前许多网站逐步开发基于实时计算框架 Storm 的日志统计与分析工具;
(2)服务器性能监控:收集服务器性能指标,如系统 Load、内存占用、磁盘 IO 等,及时判断,防患于未然;
(3)运行数据报告:采集并报告,汇总后统一显示,应用程序需要在代码中处理运行数据采集的逻辑;
2、监控管理
(1)系统报警:配置报警阀值和值守人员联系方式,系统发生报警时,即使工程师在千里之外,也可以被及时通知;
(2)失效转移:监控系统在发现故障时,主动通知应用进行失效转移;
(3)自动优雅降级:为了应付网站访问高峰,主动关闭部分功能,释放部分系统资源,保证核心应用服务的正常运行;
易扩展
这个世界唯一不变的是变,与建筑架构不同,软件系统架构是一个不断演变的过程。如果软件系统从一开始没做好软件架构,遇到每次大的改变都需要重构,将是不能接受的。
核心思想
可扩展性架构设计方式有很多,但万变不离其踪,所有的可扩展架构的背后基本思想都可以总结为一个字:**"拆"**。
拆分思路
按照不同的思路来拆分软件系统,就会得到不同的架构。常见的拆分思路如下:
- 面向流程拆分:将整个流程拆分成一个阶段,每个阶段作为一部分。
- 面向服务拆分:将系统提供的服务进行拆分,每个服务作为一部分。
- 面向功能拆分:将系统提供的功能进行拆分,每个功能作为一部分。
更具体的例子:
流程 对应 TCP/IP 四层模型,因为 TCP/IP 网络通信流程是:应用层 → 传输层 → 网络层 →物理+数据链路层,不管最上层的应用层是什么,这个流程都不会变。
服务 对应应用层的 HTTP、FTP、SMTP+等服务,HTTP 提供 Web 服务,FTP 提供文件服务,SMTP 提供邮件服务,以此类推。
功能 每个服务都会提供相应的功能。例如,HTTP 服务提供 GET、POST 功能,FTP 提供上传下载功能,SMTP 提供邮件发送和收取功能。
面向流程拆分更像纵向拆分,面向服务拆分更像横向拆分,面向功能拆分是比面向服务更细的拆分。它们之间并不是非此即彼的,而是相辅相成的。一个系统的架构既可能用到面向流程拆分也可能用到面向服务拆分,甚至面向功能拆分。
已有架构
面向流程拆分:分层架构 面向服务拆分:SOA、微服务 面向功能拆分:微内核架构
1、分层架构和 SOA
分层架构:比较常见,最简单的例如将系统分为应用层、服务层和存储层。复杂的软件系统还会分更多的层次。分层架构需要保证各层之间的差异足够清晰,边界足够明显,让人看到架构图后就能看懂整个架构。传统的分层架构:
SOA(Service Oriented Architecture) 架构:提出服务、ESB 和松耦合三个概念,为微服务的出现提供了理论支撑。典型的 SOA 架构:
2、微服务
微服务的英文定义:In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery.
微服务的关键词:Small、Lightweight 和 Automated。微服务需要的基础设施:
3、微内核架构 微内核也称插件化脚架构,是一种面向功能进行拆分的可扩展性架构,主要包含核心系统(Core System)和插件模块(Plugin-in Modules)微内基本核架构图:
总结
以上种种,几乎涉及了 架构开发的所有知识点,都只是蜻蜓点水、点到即止。每个人所知道知识深度不一样,但要想成为一名合格的架构师得先有广度再有深度也不迟,以上总结也算全面的、系统的阐述了一个程序员该掌握的知识点。