第1章和第2章讲述可伸缩系统的核心概念与软件设计原则。强烈建议认真阅读这两章,这部份内容包含了开发一个可伸缩的Web系统甚至开发一个良好软件的基本原理和设计原则,是其它一切技巧和方法的元规则。第9章讲述可伸缩的系统运维及可伸缩的我的和团队,若是你正处在一个高速发展的创业团队中,若是你对从技术走向管理感兴趣,我相信你能够从本章的内容中收获多多。前端
习惯的旧秩序被互联网打破了;这是一个创新的时代,任何奇思妙想都有被用户接受成为现实的可能java
线下的思惟转换为线上的思惟算法
互联网催生了一种新型的生活方式,也催生了适应于互联网的一种新的人群。数据库
链接人群和线上生活的纽带就是一个一个的互联网及移动互联网的应用。编程
做为应用的典型IT形态,互联网应用经历了独占型应用、SOA应用,在云时代就是云原生(Cloud Native)的应用。后端
构建一个良好的可伸缩的应用,不只仅是一个优秀工程师的职责,同时也表明了对一种生活方式的承认。设计模式
弹性架构的概念,软件设计的原则,以及如何构建一个优质的互联网应用。数组
一个最初由两三个工程师开发的产品雏形,如何通过逐渐地重构、演化、迭代、伸缩,最终成为一个巨无霸系统?浏览器
第一章和第二章讲述可伸缩系统的核心概念与软件基本设计原则,这部份内容包含了开发一个可伸缩的Web系统甚至开发一个良好的软件的基本原理和设计原则,是其它一切技巧和方法的元规则。缓存
能够在工做中遇到问题时快速浏览,寻找方法和灵感。
可伸缩系统的设计是一种权衡的艺术,必须对第一种方案的优缺眯都了如指掌,才能在面对实际问题时作出最合适的选择。
高并发可伸缩系统的设计看似纷繁复杂庞大无比,实际上关键的核心技术也就那么几样,若是深刻掌握了这些关键技术,就抓住了可伸缩系统设计的核心。这几样关键技术,可能须要在不一样场景,从不一样视角反复思考琢磨,才能真正掌握。
胸有成竹
极具弹性的系统,在快速变化的内外部环境中保持快速响应的能力。
那些在传统企业中须要花费全年时间才能搞定的事情,创业公司可能必须在几个星期内搞定。若是足够成功而且足够幸运,可能须要在几个星期内把你的系统的处理能力扩大十倍。固然,也可能在几个月后又把系统的处理能力缩小回去。
就伸缩性技术自己而言,目前不少公司提供这方面的基础服务,好比亚马逊云、微软云、Google云,以及阿里云,以及阿里云,腾讯云等,这些云服务可让你彻底没必要考虑伸缩性方法的问题而只需关注自身的业务需求。
要彻底理解伸缩性须要一个渐进的过程。
在阅读过程当中把这些基础设施图及架构图再画一次,这不只有助于理本书的内容,若是只是匆匆一扫而过,可能会忽略不少有用的信息。
伸缩性是指系统能够根据需求和成本调整需求和成本自身处理能力的一种能力。伸缩性经常意味着系统能够改变自身的处理能力以知足更多用户访问、处理更多数据而不会对用户体验形成任何影响。此外,还有一件重要的事情必须提醒工程师们注意,伸缩性不只须要伸(加强系统处理能力,即扩容),有时候也须要缩(缩减系统处理能力,即缩容)。同时,这种伸缩还必须相对比较省钱和快速。
正如在不一样场景下对伸缩性有不一样要求,能够在不一样维度上对伸缩性进行度量。
伸缩性主要从如下几个方面度量:
处理更多数据
处理更高的并发
度量并发的指标一般是:应用系统能够同时服务多个用户。
对于一个Web应用系统而言,并发度的意思是最多有多少个用户能够同时访问你的网站而不会感到访问速度变慢。
因为服务器的CPU数目是有限的,能同时运行的线程数也是有限的,所以处理高并发是一件很是有挑战的事。若是还要同步某些代码的执行以保证数据的一致性,那些这个挑战将变得更加艰难。
更高的并发也意思着系统须要同时打开更多的链接,启动更多线程,处理更多消息,CPU须要更屡次上下文切换。
处理更高频次的用户交互
用户交互是指你的系统和你的用户之间的交互频次。这个维度和并发度看起来很像,可是稍有不一样。交互频次衡量你的用户和你的服务器交换信息的频繁程度。与交互频次相关的最大挑战是响应得延迟。
若是应用的交互频次在增加,你就必须让应用响应得更快速,这就要求系统有更快的读写速度进而将系统并发度推到一个更高的高度。
例如若是你作的是一个Web站点,用户大概须要第15秒或2分钟从一个页面跳转到另外一个页面;
但若是你作的是一个多人交互的移动游戏,用户可能须要每秒种和服务器通讯好几回,所以交互频次相对独立于用户并发度。
一般会综合上述三个维度云考量一个系统的伸缩性。通常说来,实现系统伸缩性的过程,扩容比缩容更重要也更常见。
性能更多的是衡量系统处理一个请求或者执行一个任务须要花费多长时间
伸缩性则更多关注系统是否可以随着请求数量的增长(或减小)而相应地拥有适应的处理能力
EG:你有100个并发用户,每一个用户平均第5秒发送一个请求,那么系统吞吐量就是每秒20个请求。
性能指的就是系统须要花费多少时间处理这每秒20个请求
伸缩性指的是在不影响用户体验的前提下,系统最多可以处理多少个并发用户及用户最多能发送多少个请求
软件产品的伸缩性也许会受限于软件开发团队的规模。若是要系统具备伸缩性,那么对应的软件开发团队也必须具备伸缩性,不然就没有足够的工程师资源去快速影响需求变动。
尽管看起来软件开发团队的伸缩性和技术无关,但事实上系统架构和设计会影响团队规模。
若是你的系统设计是紧耦合的,全部人都在一份代码上工做,那么你就很难扩展你的工程师团队
因为团队的沟通效率和团队规模成指数关系,所以当完成同一个工做的技术团队的规模达到8-15人时,效率就会变得很是低。
为了更好地体会伸缩性对创业公司的影响,让咱们试着从公司视角观察。问你本身:制约咱们公司持续发展的问题有那些?
答案不只包括咱们前面提到的处理同吞吐量致使的技术架构方面的问题,也包括开发过程、团队、代码结构等一系列问题。
这里展现的不少伸缩性演化架构阶段仅仅在你开始作规划时有参考意义。
不少状况下,真实世界并不彻底按照这个方式去演化,更可能会经历屡次重写。还有一些时候,系统在设计和诞生之初就处于某个演化的特定阶段并一直保持不变,或者在发现架构制约的时候直接向上跳跃一到两个阶段而不是逐步演化
尽可能避免彻底重写应用,特别是创业公司。重写老是比你预期的时间要长,也比你预估的难度更大。基于个人经验,一次重写带来的麻烦须要两年才能终结。
除DNS外,网站服务器须要响应各类Web页面、图片、CSS文件和视频文件,全部的响应都只在这一台服务器上处理,全部的网络流量都只通过这一台服务器进行传输。
最便宜的主机方案是共享主机,用户只购买一个用户账号而没有管理权限,这个账号和其它客户的账号安装在一个服务器上。这种主机方案适用于那些只须要一个最小的Web站点或者只有一个着陆页(Loading Page)的网站。可是这种方案限制太多,不值得推荐。
瓶颈:
你的用户在持续增加,所以访问量在持续增加。每一个用户访问都会对服务器形成负载压力,服务每一个用户又须要更多的计算资源,包括内存、CPU时间,以及磁盘读写(I/O)。
你的数据库存储的数据在持续增加。在这种状况下,数据库因为须要更多的CPU、内存和I/O请求而变慢。
你要扩展系统增长新的功能,这使得每一次用户请求都要消耗更多的系统资源
上面这些因素经常会叠加在一块儿。
一旦你的应用达到服务器的极限(因为网络流量、数据处理规模或者并发度等因素的增加),就必须决定如何去伸缩系统。
有两种不一样的伸缩性方案:
垂直伸缩
水平伸缩
经过升级硬件和网络吞吐能力能够实现垂直伸缩。因为不须要改变应用架构,也不须要重构任何东西,因此一般被认为是最简单的短时间伸缩性方案。
垂直伸缩的方案有不少:
经过使用RAID(独立冗余磁盘阵列)增长I/O吞吐能力。I/O吞吐量和磁盘存储是数据库服务器的主要瓶颈。使用RAID并增长磁盘数量有助于将读写请求分布到多个磁盘上。最近几年,RAID10变得格外流行,这种RAID方案即提供了数据冗余存储又提升数据吞吐能力。从应用角度看,整个RAID看起来就是一个数据卷标,可是在实际底层实际包含了多个磁盘共同对外提供读写访问。
经过切换到SSD(固态硬盘)改善I/O访问速度。随着固态硬盘技术愈来愈成熟,价格愈来愈低,SSD也变得愈来愈流行。根据不一样的其准测试方法,SSD随机读写速度大约比传统磁盘快10到100倍。应用经过替换SSD硬盘能够节约更多I/O等待时间。不过,顺序读写的速度提高就没有这么高了,因此现实中应用性能提高也没有特别巨大。事实上,大多数开源数据库(好比MySQL)会优化数据结构和算法,尽量多地执行顺序硬盘操做而不是随机操做。而某些数据存储系统,好比Cassandra,作的更完全,全部的写操做和多数读操做都只使用顺序I/O操做,这也使得SSD更缺少吸引力。
经过增长内存减小I/O操做(若是你的应用部署在独立的物理服务器上,128GB内存是一个比较实惠的常规配置)。增长内存意味着文件系统有更多的缓存空间,应用程序有更多的工做内存。此外,内存大小对于数据库服务器效率的提高也格外重要。
经过升级网络接口或增长网络接口提升网络吞吐能力。若是你的服务器须要处理大量视频等媒体内容,也许须要升级网络供应商链接,甚至升级网络适配器以得到更高的吞吐能力。
更新服务器得到更多处理器或者更多虚拟核。拥有12或者24线程(虚拟核)的服务器是一个比较实惠且合理的升级方案。CPU和虚拟核越多,可以同时执行的进程数就越多,系统所以变得更快,这不只仅是由于多个进程能够没必要共享同一个CPU,还由于操做系统不须要在同一核执行多个进程而执行没必要要的上下文切换。
垂直伸缩的一些严重的制约:
成本。当越过某个点后,垂直伸缩会变得格外昂贵。
垂直伸缩是有极限的,这是个比较大的问题。不管你愿意花多少钱,内存都不可能无限地增长下去。相似的限制还有CPU的速度,每台服务器的虚拟核数目,硬盘的速度。简单的说,到了某个极限,没有任何硬件能力可以继续增长。
操做系统的设计或者应用程序自身制约着垂直伸缩最多只能达到某个点。举个例子,你不能经过增长CPU数目而无限增长MySQL的处理能力,由于同时锁竞争也会增长(特别是若是你使用MyISAM这种比较老的MySQL存储引擎)
若是多个线程共享诸如内存、文件这类资源时,须要用锁进行同步访问。低效的锁管理会致使锁竞争成为瓶颈。
应用操做应该使用细粒度的锁,不然,应用须要花费很长的时间去等待释放锁。一旦锁竞争成为瓶颈,增长更多的CPU核数也不会改善应用的处理能力。
高性能的开源应用或商业应用能够扩容到几十个CPU核,然而,在购买新硬件前最好仍是要确认下系统的扩容伸缩能力。因为高效的锁管理是一项有挑战的任务,须要大量的经验和详细的调试,因此本身开发的应用一般在锁竞争方面作的差一点。在一些极端的状况下,由于在设计之初就没有考虑任何高并发的场景,结果致使增长CPU核对应用处理能力提高没有任何帮助。
垂直伸缩不会对系统架构产生任何影响。你能够垂直伸缩扩容任何一台服务器、网络链接或者路由器而无须修改任何代码或者重构任何东西。惟一要作的就是用更强大更快速的硬件替换现有的硬件。
单一服务器,可是是更强大的服务器
服务分离背后的核心理念是你应该将整个Web应用切分红一组不一样的功能模块,而后将它们独立部署。这种基于功能将系统划分红独立可伸缩的模块的方式称为功能分割
另外一个简单的方案是经过在不一样的物理机上安装不一样类型的服务,使得一个系统的不一样部分被分离部署在不一样物理服务器上。要这种场景下,一个服务能够是一个相似Web服务器这样的应用(好比Apache)或者是一个数据库引擎(好比MySQL)。这就使得应用服务器和数据库分离到不一样的服务器上。
相似地,也能够把FTP、DNS、缓存及其它服务器部署在不一样的物理机器上。
对单一服务器部署进行可伸缩扩容,对可分离的服务进行分隔部署是一种比较轻的解决方案。不过,这种伸缩方案并不能一直持续进行下去,一旦你的全部服务类型都已经分别部署在独立服务器上,就没有办法用这种方法扩容下去了。
缓存是一种重要的服务,目标是经过快速返回提早生成好的内容下降请求响应延迟。缓存是构建可伸缩系统的一种重要技术。
服务器经过在第三方数据中心,数据中心由一组安装不一样功能的服务器组成。每一个服务器都承担不一样的角色,好比Web服务器、数据库服务器、FTP、缓存等。
对单一服务器部署而言,服务分离是一种巨大进步。相比之前,能够将负载压力分摊到更多的服务器上,并且能够按需进一步对每一个服务器进行垂直伸缩。
若是某个微型网站受欢迎,访问量大,就会把它分离出来,部署在一台独立的Web服务器和一台独立的数据库服务器上。
一个Web应用利用功能分割将负载分布在更多服务器的场景。应用的每一个部分都使用不一样的二级域名,这样就能够基于Web服务器的IP地址进行流量分发。
不一样的功能模块也许部署在不一样的服务器上,这些不一样的功能模块也会有不一样的垂直伸缩需求。显然,一个系统越是可以分割成不一样的部件,每一个部件就越有弹性越好。
随着应用不断发展用户不断增加,可能经过CDN服务减轻应用网络流量负载压力。
CDN的全称是Content Delivery Network,即内容分发网络。其基本思路是尽量避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。
CDN是一种提供静态文件(好比图片、JavaScript、CSS及视频)全球分布的服务。它的工做原理有点像HTTP代理。用户若是须要下载的图片、JavaScript、CSS或者视频内容,能够经过链接CDN服务器下载而不是链接应用服务器。若是CDN服务器没有用户须要的内容,CDN服务器会请求服务器获取这部份内容而后再缓存到CDN服务器上。一旦文件缓存在CDN服务器上,后面的用户就不再须要链接应用服务器了。
经过将应用集成到某个CDN服务,能够显著减小应用服务器须要的网络带宽。能够用更少的Web服务器提供Web应用静态内容。CDN会从距离用户最近的服务器提供静态内容,进而加速这些用户的页面加载时间。
咱们不是必定要增长不少的服务器或者学习如何对HTTP代理进行伸缩。咱们只须要简单地使用第三方的服务,而后依赖它提供的伸缩性能力就能够了。虽然这看起来有点像"伸缩性游戏的小把戏”,但它真的是很是强大的手段,特别是在创业公司的早期开发阶段,可能根本就没有足够的时间和金钱去调查这些技术。
水平伸缩是指经过增长服务器提高计算能力的一类架构方法。
水平伸缩被认为是伸缩性的圣杯,水平伸缩能够克服垂直伸缩带来的单位计算成本 随着计算能力增长而迅速飙升的老是。另外水平伸缩老是能够增长更多服务器,这样就不会像垂直伸缩那样遭遇到单台服务器的极限。
水平伸缩能够经过修改应用架构在后期进行“添加”,可是大多数状况下,必须付出至关的开发代价。
一个真正意义上的可伸缩系统不须要很强大的服务器,甚至相反,它们运行在大量的廉价商业服务器上,而不是少许的强大的服务器上。
水平伸缩技术带来的好处要在系统发展的后期才能体现出来。一开始,水平伸缩由于技术比较复杂须要更多的工做量,会花费更多的成本。
这些成本体如今可水平伸缩的架构比基本的架构须要部署更多的服务器
水平伸缩的架构须要更有经验的工程师去构建并维护它们
不过,一旦你的系统的计算能力达到某个点,水平伸缩就变成更好的策略。
使用水平伸缩,能够没必要花费购买顶级服务器的高昂的价格,也不会触及垂直伸缩会出现的天花板(买不到更强大的硬件)
水平伸缩使用CDN这样的第三方服务不只更节约成本,并且更透明。
云服务商愿意为高流量的客户提供更低的价格,由于这些客户以前的付费已经覆盖了必要的维护集成成本。对于访问量很大的客户,他们也确实很在乎价格高低。
具备水平伸缩特性的系统和先前架构演化阶段说起的系统的不一样之在于:
数据中心的每一种服务器角色均可以经过增长服务器进行扩容伸缩
事实上,在不一样演化阶段能够部分地实现水平伸缩,有的服务实现水平伸缩,而有的服务则不实现。
达到真正意义上的水平伸缩很困难并且须要丰富的经验。所以,构建水平伸缩的系统先从那些容易作到的地方作起。好比Web服务器、缓存;暂时难以作到的地方,好比数据库及其它的持久存储
在演化的这一阶段,一些应用使用轮询DNS服务实现Web服务器流量分发。
轮询DNS不是实现Web服务器流量分发的惟一手段
轮询DNS是DNS服务器的特性之一,它容许将一个域名解析到多个IP地址中的一个。通常的DNS服务器会将一个域名解析到一个单一的域名。然而轮询DNS容许将一个域名映射到多个IP地址上,每一个IP地址都指向不一样的机器。所以,每次用户请求域名解析,DNS都会返回这些IP地址中的一个。目的就是将每个用户访问的流量分发到Web服务器集群中的某一台上,不一样的用户在不知情的状况下链接到不一样服务器上。一旦用户接收到一个IP地址,他就会只与这台选中的服务器通讯---至关于服务器是有状态的,若是服务器宕机也会有其它的老是,服务器集群上访问压力也会不均匀。
架构演化的最后阶段就是打造一个全球最大的Web站点,实现全球用户的可伸缩性。一旦你服务的用户从几百万扩展到全球,你须要的数据中心就不止一个。一个数据中心能够部署不少的服务器,可是其它大洲用户的体验可能并很差,并且多个数据中心也能让你比较从容地应对那些可能的宕机事件(水灾、火灾引发的停电)
服务全球用户须要面临不少挑战,用到不少技巧,其中一个技巧是使用GeoDNS服务。
GeoDNS是一种基于客户地理位置进行IP地址解析的DNS服务。通常DNS服务器收到一个域名,比较baidu.com,对台解析成一个IP地址。GeoDNS从用户视角看也是这样,然而,GeoDNS会基于用户的地理位置返回不一样的IP地址。一个欧洲用户和一个澳洲用户获得的IP地址可能不一样,结果是每一个用户都会访问到距他最近的一个Web服务器。GeoDNS的目标就是将用户分发到离他最近的数据中心进而实现最小的网络延迟。
基础设施层面的另外一个扩展是在全球范围部署若干边缘缓存(edge-cache)服务器,进一步减小网络延迟。边缘缓存服务器的使用依赖于应用的天然特性。边缘缓存服务器最高效的用法是像反向代理服务器那样缓存整个页面。固然,边缘服务器也会提供其它服务。
边缘缓存是一种距离用户较近的HTTP缓存服务器,便于部分缓存用户的HTTP流量。从用户浏览器发起的请求到达边缘缓存服务器,边缘缓存服务器决定从缓存中直接返回响应页面,仍是经过发送背景请求到Web服务器获取响应页面的其它部分最后进行组合。
边缘缓存服务器还能够决定某个页面是不可缓存的,也能够决定是否能够代理整个Web服务。边缘缓存服务器能够缓存整个页面也能够缓存页面片断。
随着应用将来的持续发展,你也许会考虑将主数据中心切分红多个数据中心并把它们部署到离用户较近的位置。经过将应用和数据存储部署到离用户较近位置的方式,能够实现更短的访问延迟和更少的网络成本。
数据中心基础架构高层概览1-10
前端:应用栈睥第一个部分,包含了直接与用户设备交互的一系列组件。前端可能在数据中心内,也可能在数据中心外,这要看部署细节和第三方服务的使用状况。这些组件不包含任何业务逻辑,主要目的是提高系统处理能力和伸缩能力。
负载均衡器是一种软件或者硬件组件,能够将访问某个IP地址的访问流量分发到多个服务器上,这些服务器则隐藏在负载均衡器的后面。负载均衡器能够将用户访问负载平均地分发到多个服务器上,而且容许动态地增长或者移除服务器。因为用户只能看到负载均衡器,因此能够在任什么时候候添加新的Web服务器而无须中止服务。
Web应用层:整个应用栈的第二层是Web应用层,由Web应用服务器集群构成,主要职责是处理用户的HTTP请求生成最后的HTML页面。这些服务器一般用轻量级的Web开发框架(PHP、JAVA、Ruby、Groovy等)构建,实现最小的业务逻辑,主要任务是生成用户界面。整个Web应用层的主要用途是处理用户交互并转换成内部服务调用。这个Web层越简单功能越少,整个系统越好。复杂业务逻辑应该在Web服务层完成,实现更好的复用及减小需求变动,由于展现层的变动是最频繁的【与用户交互的Web页面或App】.
Web服务层:应用栈的第三层是Web服务层,由各类Web服务组成。这是很是关键的一层,包含了最主要的应用逻辑。展现层剥离出来的逻辑,就集中在这一层。高内聚的业务逻辑,更容易实现功能分隔;功能分隔后的服务,能够独立地对它进行伸缩。譬如电子商务应用中的产品类目服务和用户信息服务是有彻底不一样的伸缩性需求的。
附加组件,对象缓存和消息队列。
对象缓存服务器既在前端服务器使用,也在Web服务中使用,主要目的是减轻数据存储服务器的负载压力及经过对部分数据进行预先计算实现响应加速。
消息队列被用来 将某些处理延迟到稍后的阶段处理,而且将处理操做委托给消息处理者服务器。发送给消息队列的消息一般来源于前端应用及Web服务,而后这些消息被特定的消息处理者机器处理。
定时任务,不会去响应用户请求,它们是离线的做业处理服务器,提供相似异步通知、订单处理,以及其它一些容许较高延迟的功能。
数据持久层:通常来讲,这是最难以进行水平伸缩的一层。
图1-10中数据中心基础架构鸟瞰图中各组件的布局是通过深思熟虑的,主要目的是帮助那些相对比较慢的下降负载访问压力。
只有在很是必要的状况下,Web服务层才会链接搜索引擎及主要数据存储去读写必要的信息。
有一点须要特别注意,就是没有必要为了伸缩性实现图1-10中的全部组件。相反,要尽量少地使用不一样种类的技术,由于每增长一种技术,就是在增长系统的复杂度,也是在增长维护的成本 。使用不少不一样的技术看起来很酷,可是却让发布、维护、调试变得更困难。
若是应用只是须要一个简单的搜索功能,也许只要一个前端服务器和一个搜索引擎集群就可以知足全部的伸缩性需求。
若是能增长服务器实现现有的每一层的伸缩性,而且也能知足所有的业务需求,为何还要不厌其烦地使用各类额外的组件呢?
应用架构是将业务逻辑放在应用架构的核心位置,是关于业务模型的演化,不是关于某个框架或者某个特定技术,也不是关于java、PHP、PostgreSQL或者数据库表结构的。
业务需求驱动着各类决策。
没有正确的领域模型和正确的业务逻辑,数据库、消息队列及Web框架都没有任何意义。
无论应用是一个社交网站,仍是一个药品服务,或者是一个视频APP,它总归是有某些业务须要一个领域模型。经过将这个模型放到架构的核心,确保各类组件围绕这个核心展开,服务于这个业务,而不是其它什么东西。
若是把技术放在核心位置,也许会获得一个很赞的Rails应用,但不太可能获得一个很棒的药品服务应用。
市面上已经有不少不错的关于领域驱动设计和软件架构的书,能够帮助更好地熟悉软件设计的最佳实践。
领域模型是关于应用要解决的业务问题的本质描述。
领域模型表示一个应用的核心功能,重点在于业务而不是技术。领域模型解释了关键术语、角色和操做,而不关心技术实现。
一个自动柜员机(ATM)的领域模型的关键词是:现金、账户、负债、信用、身份认证、安全策略等。
同时,领域模型不关心硬件和软件实现。
在系统内部,应用被分解成多个(高度自治的)Web服务。
应用架构高层鸟瞰图1-11
应用架构的核心是主要业务逻辑
前端:主要职责是成为用户的接口,用户经过网页、移动APP或者Web服务调用完成和应用的交互。是介于公开接口和内部服务调用的处理层。
前端能够被视做是应用的“皮肤”或者应用的插件,是系统向用户呈现的功能展现,所以不该该是系统的核心或者重点。
通常来讲,前端应该尽量保持简单。
经过保持前端简单,能够复用更多业务逻辑。
服务的简单与职责单一,使服务只关注逻辑而不是视图呈现。
业务逻辑只存在Web服务层,能够避免视图和业务强耦合的问题,而且能够实现前端的独立伸缩,即只关注处理较高的并发访问请求。
能够认为前端是一个可移除的插件,能够用不一样的语言重写、能够输入各类类型的前端。
能够移除一个基于HTTP的前端输入而插入一个移动应用的前端或者一个命令行前端。
即前端展现要与核心业务逻辑分离。
前端不该该关注数据库或第三方服务。容许前端代码出现业务逻辑会致使代码难以复用及系统更高的复杂度而难以维护。
能够容许前端发送事件消息给消息队列,以及容许使用后端缓存,消息队列和缓存都是提高性能和改善伸缩性的重要手段。
不管是缓存整个HTML页面仍是HTML片断,都会比在构建HTML时缓存数据库查询更节省处理时间。
Web服务:处理主要流程和实现业务逻辑的地方。要点服务职责单一,方便复用和伸缩。
SOA就像雪花,不会有两个彻底相同。--David Linthicum
Web服务在整个应用架构中处于中心位置,这种架构一般被称为面向服务的体系架构(SOA)。
SOA是一种以低耦合和高度自治的服务为中心的软件架构,主要目标是实现业务需求。
SOA倾向于全部的服务都基于有清晰定义的约定,而且都使用相同的通讯协议。
在SOA定义中,不太关注SOAP、REST、JSON或者XML,这都是实现上的细枝末节而已,不论使用什么技术或者协议,只要你的服务是松耦合的并解决了一组特定的业务需求就能够了。
SOA不是全部问题的惟一答案,还有其它一些架构:好比分层架构、六角架构和事件驱动架构。
分层架构:是一种将不一样功能划分到不一样层次的架构。低层的组件暴露一组API给高层组件使用,不太低层组件永远不会依赖高层组件提供的功能。
分层架构的一个例子是操做系统及其各类组件。
每一层都消费其低层提供的服务,可是低层永远不会消费上层提供的服务。
另外一个比较好的例子是TCP/IP编程栈,每一层都依赖低层提供的协议并增长新的功能。
分层能够强制结构化并减小耦合,低层组件变得更简单和系统,其它部分更少耦合。替换低层组件只要实现相同API就能够了。
分层构架的一个重要影响方面是越到底层稳定性越强。
变动高层组件的API很容易,由于依赖它们的东西不多。可是变动低层的API就不划算了,由于有大量代码依赖这些已经存在的API。
六角架构认为业务逻辑是架构的中心,全部数据存储、客户端、其它系统之间的交互都是平等的。业务逻辑和每个非业务逻辑组件之间都有一个约定,可是没有底层和顶层的划分。
在六角架构中,用户和应用的交互 与 应用和数据库系统的交互没有区别。它们都存在于应用业务逻辑以外并且都遵循一个严格的约定。定义好这些边界,就能够用一个自动化测试驱动代替一我的 或 用某个存储引擎代替数据库系统而不会对系统核心形成任何影响。
简单地说,是以一种不一样的方式去思考行动,即 对已经发生的事件作出反应。
传统编程模型中,咱们认为咱们是请求某项工做要完成的人。
譬如,createUserAccount(),咱们指望在咱们等待结果的过程当中,这个操做会被执行完成,
而后咱们继续后面的过程。
EDA中,咱们不会等待事情被作完。
不管何时,咱们和其它组件交互,咱们只是宣布某件事情已经发生,而后就处理后面的过程了。
能够认为Web服务层由若干高度自治的应用组成,每一个Web服务本身就是一个应用。
Web服务也能够彼此依赖,不过说到底仍是少互相依赖为好。
Web服务提供一个更高层次的抽象,可让它看清整个系统并理解它。
每一个服务都隐藏了自身实现的细节并呈现一个简化的高层次的API。
支撑技术
消息队列、应用缓存、主数据存储、搜索引擎等,这些一般是用一些其它技术实现,通常都是一些第三方的产品,经过配置和咱们的系统集成起来。
数据库(主数据存储)仅仅是一种技术,是实现的细节而已。
咱们并不关心须要多少服务器,如何进行伸缩,怎么进行数据复制及容灾,甚至如何存储数据。
若是决定更换持久层存储或者更换缓存后端,应该作到只更换数据链接组件就能够,保证整个架构不受影响。
经过将数据存储的抽象化,能够自由选择各类数据库,不论MySQL仍是其它数据库。
若是应用逻辑有不一样的需求,也能够考虑NoSQL数据存储或者内存型存储。
第三方服务在咱们的控制以外,处于咱们系统边界以外。由于咱们不能控制它们,咱们也就不能期望它们一直动做正常、没有BUG、像咱们指望的那样快速伸缩。
架构是从软件设计的角度看
基础设施是从系统工程师的视角看
每一种视角都是从不一样方面展现同一问题:如何构建可伸缩的软件。
许多实际项目中遇到的伸缩性问题其实能够归因于违反了软件设计的核心原则。软件设计原则比伸缩性更抽象更通用
使事物尽量简单,可是不要过于简单。---阿尔伯特.爱因斯坦
软件自然就是错综复杂的。
判断一个东西是否是过于简单的时候,首先要回答的是对谁而言及对何时而言。譬如,对你而言仍是对客户而言过于简单?是对如今开发而言仍是对未来维护而言?
简单不是走捷径,不是为手边的问题找一个最快的方案。
重温写过的代码,区分复杂度,以此寻找简单的方案。从本身的错误开始是学习的每一步。
培养敏锐的感受和能力,从而快速判断出长期而言什么是更简单的方案。
若是有机会可以找到一个导师或者可以和擅长发现简单的人一块儿共事,会进步更快。
1.1 隐藏复杂与构建抽象
隐藏复杂与构建抽象是实现简化最好的方法之一。
人类的大脑处理能力有限的,要么对系统总体有个了解而不知道细节,要么知道系统的一小部分细节而不了解总体。
不过,若是系统很庞大,是没法保持总体简单的,你能作的只是保持局部简单。
局部简单的主要方式是确保在设计和实现两个方面上,任何单个的类、模块、应用的设计目标及工做原理都能被快速理解。
看到一个类时,可以快速理解它的工做原理而无须知道系统其它部分的所有细节。只看着这个类就明白这个类能干什么。
看一个模块的时候,能不看这个模块自己,而是把这个模块看成一大堆类来看,而每个类都是可理解的。
再缩小。当看一个应用的时候,能一眼看出那些关键的模块和它们的主要功能,而无须知道模块里的类的细节。
看整个系统的时候,能快速看出顶层的应用和它们的职责而无须知道每一个应用是如何运行的。
节点表示类,边表示类之间的依赖。
为了实现局部简单,须要将不一样的功能分割到不一样的模块中,这样当你从一个比较高的抽象层次去观察应用时,无须操做每一个模块的职责是什么,只需考虑这些模块是如何交互的就能够了。
模块的层面,能够忽略模块内部的细节,只关注模块间的交换便可。
在庞大又复杂的系统中,当你建立一个独立服务的时候,须要添加一个抽象层。
服务暴露这个更高层次的抽象,实现这些抽象所承诺的功能,从而隐藏其复杂性。
1.2 避免过分设计
会不由自主地被那些想象出来的问题牵着鼻子走,不知不觉地就过分设计了,最后会开发出一个比实际需求复杂得多的大而无当的系统。
好的设计方法是能够在后期逐渐添加新的功能特性,而不是一开始就开发一个超级大的系统。
要比一开始就设计好所有的功能为未来各类可能进行开发好的多。
"这个设计是否能够更简单而且能够在未来依然保持弹性"
每次进行软件设计前都要问本身。
“这里我须要如何作出权衡”
“这里我是否真的须要”
也建议你和业务相关方多接触,尽可能去了解什么是最大的风险及还有什么没有搞清楚。
不然,你会按照那些没有用的教条花费大量的时间去构建一个没人用的系统。
1.3 尝试测试驱动开发TDD
接受TDD的方法论能够提升简化性。
TDD是一种开发实践:先写测试代码,而后写功能实现代码。
好处:
1.没有单元测试就没有代码,因此也就没有无用的代码。
因为开发先写了测试,就不会写那些不必的代码,不然就又要去写测试代码。 😙
2.UT能够被当作某种文档,能够展现代码的实现意图、具体用法、指望的执行结果等
TDD会强制工程师从用户的角度看待问题,有助于开发更清晰更简单的接口。由于是先写UT,因此必需要先想象如何使用你要开发的组件,就不会只想着这个组件的内部实现细节。
这种细微的差异会带来代码设计上的巨大提升和应用程序接口API的简化
要从用户视角思考问题,这样就会更好地开发那些易于用户调用的代码。
别人使用的你开发的接口,那么他想调用的方法是什么?
他想传递的参数是什么?
他期待返回的响应结果是什么?
1.3 从软件设计的简化范例中学习
若是复杂性处理得够好,反而难以体会软件简化的理念。若是事情老是顺其天然,若是能够绝不费力地理解系统,那么也许你遇到的是精雕细琢后的某种简化。认识到你使用的系统是良好设计的系统是一种很是赞的体验。不管什么时候发现这一点,仔细分析它并寻找其中的模式。Grails、Hadoop、Google Maps API,都是一些简化作得很好的范例,值得认真研究。 1.3.1 Grails是Groovy语言上的一个Web框架。Grails是一个简化如何变得透明的极好的例子。随着研究这个框架的深刻并使用,你会意识到这里的每同样东西都被仔细考虑过。你会看到事情问题和指望的同样,扩展功能老是绝不费力。你会意识到这个框架就得更简单是不可想象的。Grails是一件让开发变得容易的杰做。 Grails in Action及Spring Recipes1.3.2 Hadoop 熟悉MapReduce编程范式及Hadoop平台。Hadoop是开源技术领域的杰做,能够处理PB级的数据。Hadoop是一个庞大而复杂的平台,可是它很好隐藏了期复杂的实现。开发发现它解决了多少困难的问题,才让开发者处理几乎无限的数据变得如此简单。若是想对MapReduce和Hadoop有个基本的了解,推荐阅读MapReduce原始论文及Hadoop in Action
1.3.2 Google Maps API一直都以一种格外简单的方式使这些API在解决复杂问题的同时保持足够的弹性。one person->workshop->studio
小结:简单对你的系统的伸缩性有一些潜在的价值。没有简单性,工程师就无法理解代码,不能理解软件,系统就没法保证持续发展。必定要记住:
2.低耦合(coupling)【高内聚(cohesion)】:第二个重要的设计原则是保持系统各个部分之间尽可能低耦合。
耦合用来度量两个组件之间互相关联及依赖的程度。耦合度越高,依赖强。低耦合是指两个组件之间只有必要的一点点关联和依赖,而没有耦合则是指两个组件之间彻底不感知对方的存在。
保持系统低耦合对于系统的健康及伸缩性相当重要,它甚至会影响团队的土气。低耦合和高耦合的不一样影响:
高耦合意味着任何一行代码的改动都须要检查系统各个部分是否存在问题。耦合度越高,各类出乎意料的依赖越多,引入BUG的机会越高。譬如,若是对用户认证作了一点改动,而后你想起还必须重构其它五个模块,由于这些模块都依赖了认证的内部实现。
低耦合能够保证复杂性局部化,即复杂只体如今模块内部,不影响整个系统。将系统分解成低耦合的多个部分,能够安排多个工程师分别独立开发各个部分。这样就能够招募更多的工程师扩展你的团队,每一个人均可以在不了解整个系统细节的状况下针对局部模块进行开发。
在更高层面上进行解耦合意味着将系统分红多个应用,每一个应用都只关注相对较小的一部分功能。这样每一个应用就能够按需分别伸缩。有些应用须要更多CPU,而有些应用则须要更多IO吞吐能力或者更多内存。经过将系统解耦成不一样部分,能够针对具体应用特色提供相应的硬件配置以达到更好的伸缩性。
2.1促进低耦合。
这个准则适用于类之间的依赖,模块之间的依赖,以及应用之间的依赖系统是所有---它包括了一切:你开发的全部应用和你系统环境中用到的全部软件。应用是系统内部最高层次的抽象,它们提供最高层次的应用服务。你也许会用到五个账户应用,五个资产管理应用,或者五个文件存储应用。在应用内部会有一个或者多个模块负责实现更精细量多具体的功能特性。就像不一样的应用一般是由不一样的团队开发和部署,不一样模块(好比信用卡处理、PDF展示、FTP接口)也应该对依赖它的开发团队保持足够独立,以便它们能够并行开发。若是你不够信任某个团队开发的某个特定模块,那最好不要让这个模块和你的应用耦合得太紧。最后,模块内部包括了一些类,这是最小的抽象粒度。一个类应该只有一个目的并且代码最好不要超过两三屏。
能够使用public、protected、private关键字促进低耦合。应该尽量将方法声明为private或protected你的类越少访问其它类,你就越少须要关注这些类的工做原理。由于private方法只在类的内部被调用,因此这些方法能够被更容易地重构和修改,类的复杂性也被局限在类的内部,不用处处查找这些方法在哪里被使用,是否会引发潜在的BUG。暴露太public方法,就会增长外部代码使用它们的机会。一旦一个方法是public的,你就不知道它会在哪里被使用,你就不得不在应用中仔细查找那些调用它的地方。
在写代码的时候,要吝啬一点。在知足需求的状况,尽可能少暴露内部信息和功能。暴露的信息越多越早,越增长耦合性,将来需求变动的时候越麻烦。这个法则也适用于各个抽象级别,不管是类、模块,仍是应用。
要在较高的抽象层面下降耦合度,你须要让系统不一样部分的接触面尽可能少。低耦合可让你重构或者替换系统中的任何部分而不用对其他部分作太多调整。找到解耦与过分设计之间的平衡是个艺术活,工程师常常会忽略抽象的重要性。能够经过设计图来帮助本身作出正确的权衡。当你画应用设计图的时候,链接两个部分之间表示依赖关系的线条的数目决定接触面的大小。
低耦合,模块B的对外开放功能被隔离为一个很小的子集并被显式地声明为public,这样能够下降和外界的接触面,而且让模块有更好的私密性。低耦合的应用中模块间没有循环依赖。2.2 避免没必要要的耦合在实践中,某些被普遍使用的方法事实上会增长耦合性。一个典型的致使没必要要的耦合的例子是经过public的getter和setter方法暴露类的private成员变量。这种用法源于java Bean的一些早期实践,当时提供getter和setter方法主要是为了适应代码操做工具和IDE。这种实践在Java社区范围内都被彻底地误解了。代码和IDE集成这种不良的习惯也会影响到其余的语言和平台。
最好一开始的时候所有方法都是private,而后视状况将那些须要的方法改为protected或public。
有时这些作是有合理缘由的,可是更多的时候是源于糟糕的API设计,好比须要调用一个初始化方法。类及模块的客户端使用者应该不须要知道类的设计者指望他们如何使用这些类及模块。
若是你要求你的客户端使用者必须知道API须要以何种方式被调用,那么你就是在增长耦合性。
处于不一样层次的应用、模块、类之间若是出现循环依赖,就意味着在设计的耦合性方面比较糟糕。通常来讲,画系统架构设计图很容易暴露循环依赖。一个良好设计的模块架构图,看起来像一棵树(有向无环图),而不像一个社交网络图。2.3 低耦合范式
要想很好地理解低耦合必须经历大量的实践。你像理解简单同样,你能够经过阅读别人的代码,分析别人的系统获取大量的经验。低耦合设计的一个很好的例子是UNIX命令行编程及管道(pipe)的用法。另外一个比较好的低耦合的例子是SLF4J。强烈建议了解SLF4J的结构,而且和Log4J及Java Logging API作个对比。SLF4J的角色像是一个非直接层,把log接口调用和实现的复杂性分离开来,显露了一组更简单更清晰更容易理解的API。低耦合是开发弹性软件的最基本的原则之一。建议把设计低耦合的模块放在一个更高的优先级上。
3.不要重复本身(DRY)我认为避免重复是最有价值的原则之一。只作一次,这是极限编程的格式--Martin Fowler重复本身意味着一样一件事你要作好几回。
若是你把一样一件事作了一次又一次,就是在浪费生命。不要把一件事作好几回,而是去作些比较酷的事,好比开发一些新特性或为客户思考一些更好的解决方案。试着说服你所在的团队或者你的老板采起一些基本措施以免重复----好比,若是有重复的代码那么代码审查不经过,每一个新写的类都必须先写单元测试。
让开发工程师浪费时间作重复的事有不少缘由:
采用低效的过程:在软件开发的各个阶段都会发生,设计新功能、交付、开会等,经过对这些地方进行持续改进能够得到良好的收益。得到反馈、持续改进、而后重复这个过程。不少时候,团队意识到本身在浪费时间却迫不得已。当你听到“咱们就是这么干的”或者“咱们一直都这么干”时,经常意味着这里有低效的过程并有改进的机会。
缺少自动化当手工部署、编译、测试、打包、配置开发机器、准备服务器、为API写文档的时候,-----这就是在浪费时间。巨大的浪费就是创建在这种不断增长的微小的工做量之上。从第一天开始就尝试将编译部署的工做自动化,固然你也须要一直维护这些自动化的工具。
非得本身发明,也就是常说的重复发明轮子这个问题的表现症状是:不复用已有的代码而非要本身写代码。年轻的工程师,乐于重写那些很容易就能用到的东西(对公司内部或者开源世界而言),好比实现hash功能、排序、b树、MVC框架或者数据库抽象层。虽然这种重复不是字面意思上的重复,但这依然是在浪费时间。由于彻底能够使用别人已经开发好的工具和代码库。任什么时候候,想写一个通用类库的时候,都先上网搜一下,一般已经有不少实现得很好的开源代码了。
复制粘贴代码为了节约时间,把这索代码复制粘贴过来,而后只对其中一小部分作了一点改动。祝贺你,你如今有两份类似的代码须要维护了。总会有某个时候,你会意识到你对这部分代码作的任何改动都不得不在其余地方再改一次,总会有一些已经修复有BUG会再一次出现,由于你忘了修改粘贴过来的那部分代码了。试着让团队创建一些规则:好比“咱们永远不会复制粘贴代码”。同时给每一个人权力在代码评审时指出重复的代码,让每一个人彼此之间都感觉到一些良性的压力。
“这个代码不会用第二次因此就凑合一下”的态度有时,这些问题会再一次出现,你就不得再也不用一次这些当初被看成一次性的随便搞搞的代码。那么问题来了,这些代码即没有文档,又没有UT,也没有合适的设计,很难用。更糟糕的是,其它工程师可能会复制粘贴这些随便搞搞的代码用在他本身的一次性脚本上,悲剧再一次上演。
我相信复制粘贴代码是一个常见的问题。出现这种状况主要是由于R&D一般没有意识到本身写的代码越多,维护的成本和代价越大。复制粘贴致使应用的代码变得更多,更多的代码致使维护的成本更高,随着各类技术问题的堆积,这种成本会呈指数级上升。应用中存在重复代码,一个地方修改就须要全部重复的地方都修改,不要追踪复制的代码之间的差别,对全部复制粘贴的代码作回归测试。因为复杂度随代码行数成指数级增加,因此复制粘贴的代码极其昂贵。事实上,复制粘贴代码是一个很是严重的问题。处理代码复制粘贴多是一件让人很头痛的事情,不过没有什么事是重构不能搞定的。一个好的开端是在代码库中搜索每个重复的功能。一旦你这么作了,你就能够更好地了解哪些组件受到影响而且该如何重构它们,能够考虑建立一个抽象类或者将重复代码抽取到一个独立的更通用的组件里。
另外一个对付复制粘贴代码的好办法是使用设计模式和共享类库。设计模式是解决一类通用问题的抽象方法,是软件设计层面的解决方案,能够应用到各类系统各类领域中。设计模式须要考虑如何组织OO代码、依赖、交互,可是不须要考虑具体的业务问题。设计模式会建议如何在模块中组织代码,可是不会指出须要使用算法或者业务特性及如何动做。也能够在更高层面上经过Web服务去对付代码复制。不要在每一个应用中开发相同的功能,而是建立一个服务,而后在整个公司范围内复用这个服务。
阻止功能复制的一个办法是让功能使用变得最简单。 例如,你的类库提供了20个功能,80%的时间被使用的都只是其中的5个功能,那么保持这5个功能尽量地容易使用。
容易使用的东西更容易被复用
若是你的类库是能够最容易完成一件事情,那么每一个人都乐意使用你的类库。若是使用你的类库很困难,那么你们就会想重复再搞一个。
4.基于约定编程,是解耦的主要方式。
基于约定编程是将客户端代码和功能提供代码解耦的主要方式。客户端仅仅看到这些约定并只依赖这些约定编程。将系统不一样部分解耦并将变化隔离开,这会对开发带来不少好处
约定是一组功能提供者承诺实现的规则
客户端代码能够理解这些规则并依赖它们。它们描述了哪些软件提供了哪些功能,可是不须要客户端代码知道这些功能是如何实现的
“约定”这个词在不一样场合表明不一样意思。
当讨论OOP的成员方法时,约定是指方法的签名,定义了指望的输入参数和指望的返回结果。约定不会规定结果该如何计算出来,这是实现的细节,使用无须关心这些。
当讨论类时,约定是指类的public接口,包含了全部可访问的方法和签名。
到抽象层,模块的约定包含了全部的公开可见的类/接口及它们的方法签名。
对整个应用而言,约定一般意味着一些描述Web服务API的表格
约定有助于将客户端和功能提供者代码解耦。只须要保持按约定开发,客户端和功能提供者能够各自独立修改。这会让代码更隔离更简单。在设计代码的时候,尽量建立明确的约定,也尽量地依赖约定(而不是实现)进行开发。每一个提供者都是独立的模块,因为它们都履行相同的约定,客户端能够使用其中任何一个而无须直接知道究竟使用的是哪个。在客户端中,任何完成相同约定的代码都是同等的,这样进行重构、UT、修改实现都变得很是容易。
想一想让基于约定的编程更简单,能够考虑把约定看成真实的法律文件。当人们赞成按法律文件作事的时候,他们会变得对细节更敏感,由于若是某个条款没作到的话,他们就要承担相应的责任。在软件设计中也相似,低耦合的约定的每一个部分都是在增长将来的责任。做为一个功能提供者,太多没必要要的东西就是在增长未来的负担,由于任什么时候候若是你想作出改变,你就不得不和全部的客户对改变的约定进行谈判(这个改变会在整个系统中蔓延)
设计类的时候,首先考虑的是客户端真正须要的功能是什么,而后设计一个最小的接口看成约定,最后实现代码以完成约定。针对类库和Web服务也遵循一样的方式,当你暴露一个Web服务API的时候,应该是明确的并要仔细考虑究竟暴露给客户端的是什么。须要的时候,应该作到容易增长新特性及发布更多数据,可是开始的时候应该尽量地实现一个简单的约定。HTTP是基于约定编程的范例。HTTP被不一样系统使用不一样的语言在不一样平台中实现,而后,HTTP是最流行的协议之一。有些HTTP协议约定的客户端是Web Browser,好比Firefox、Chrome。它们被不一样的组织以不一样方式实现,按不一样的进度更新和发布。HTTP提供者,主要是Apache,IIS,Tomcat这样的Web服务器,代码也被不一样组织用不一样的技术实现,而且被独立地部署在全世界的各个地方。更棒的是,还有不少人们不知道的其它技术实现的HTTP约定,好比Web缓存服务器Varnish和Squid,同时实现了客户端和提供者。客户端和提供者经过HTTP协议松耦合。尽管因为生态环境的复杂性,全部的应用都被互联网联系在一块儿,但HTTP仍是体现出实现上的弹性及提供者透明的可替代性。HTTP是经过约定进行解耦合的一个漂亮的例子,全部应用都有一个共同点,它们实现或依赖一个相同的约定。
4.画架构图 “你知道真正的架构是什么吗?是一门画线的艺术。画一条线链接依赖并指明依赖的方向”--Robert C.Martin
一张架构图能够表述不少信息,所谓一图胜千言。经过架构图,能够将系统设计文档化,跟其余人分离信息,也能够帮助本身更好地了解本身的设计。特别是适应了敏捷开发实践,学了不少创业方法学之后,不会花太多时间作前期设计,可是这并不意味着一直都不须要。
若是你以为画架构图挺难的,你能够尝试先画那些已经开发好的系统的架构图。你本身开发过的系统画架构图更容易一点,由于你毕竟更了解。等你熟悉了各类架构图的画法,再开始前期设计架构图。从使用者的视角观察类接口的设计并进一步充实它们,试着为接口写单元测试,而后画一些简单的架构草图。经过假想用户视角及画简单架构图,你能够在写代码以前验证本身的设计并发现其中的缺陷。等你熟悉了画各类架构图之后,尽可能多作前期设计。若是你发现前期设计很难作的话,也不要沮丧,从先写代码变成先作设计原本就不是一件容易的事,因此请作好准备花几个月甚至几年的时间去熟悉这个过程。
假设你须要设计一个断路器组件。断路器是一种设计模式,用于加强系统健壮性,保护系统不受其它组件(或者第三方系统)失败的影响。断路器在你的代码执行某个操做以前先去检查其可用性。
开始的时候先写主要接口的代码草稿,
而后写使用者代码草稿验证接口。使用者代码能够是单元测试,也能够是一些无须编译的代码草稿。这个阶段仅仅是确认接口设计是明晰的易于使用的。
而后你就能够画一个时序图展现使用者如何和断路器进行交互,以此进一步确认设计的正确性。
最后,再画一个类图的草图,检查是否违反某些设计原则,结构是否简单清晰。
我相信遵循前面讨论的这些简单的设计过程就能够在创业公司作好前期设计工做了。你能够从多个角度审视本身的设计,并会所以获益良好。同时,你也能够下降开发风险,不至于作了一个不靠谱的设计,等发现的时候就已经掉坑里了。在写设计文档或者了解大规模系统的时候,有三种架构图特别有用:
用例图
类图
模块图
随着你的公司规模越大,你的团队人越多,你越会从这三种架构图中获益。
4.1.用例图,是一种比较的图
用例图不考虑技术方案,仅仅关注业务需求,是一种将功能特性和业务需求提炼出来的好办法。当你要作一个新功能的时候,先画一个简单的用例图。用例图包含用一个小人图标表示的角色、角色能够执行的操做,以及各个操做之间的关系结构。用例图也能够呈现与外部系统的交互,好比远程Web服务API或者任务调度器。用例图不须要包含太多需求细节,作到简单、清晰、易于阅读就好的。画用例图的时候尽可能在一个较高的层次上提炼关键操做和业务过程,而不是陷入到海量的细节中去。ATM应用的用例图虽然ATM机包含大量的关于认证、安全、事务处理方面的细节,可是用例图只须要关心ATM完成用户的哪些操做就能够了。从这个角度看,按钮在屏幕上怎么排列或者ATM怎么去实现每一个功能,这些都不重要。你只要关注一个需求的概要,就能够了解ATM系统的主要意图并定义各类约定。
4.2.类图
一个典型的类图包含接口、类、关键方法名,以及它们的关系。因为每一个类都用一个节点表示,每一个依赖都用一条线表示,因此从类图上很容易看出耦合关系,能够看出哪些类耦合度更高,那些类更独立。简单看每一个节点上链接的线的数目就能够判断出这个节点包含的依赖有多少。类图是一种很是好的可视化手段,很好地展示了类、接口及其交互关系。简化的E-mail模块类图 ,这里的关键元素是类、接口、最重要的方法及依赖关系,不一样的依赖关系用不一样类型的线条表示。在这个例子中,EmailService有两个实现类,一个使用SMTP协议直接发送E-mail,另外一个将E-mail加入队列延迟发送。EmailService也是一个基于约定编程的例子,任何依赖EmailService的程序均可以使用SMTP或者队列发送E-mail,却无须知道E-mail最终是使用哪一个实现类发送的。接口只能依赖其它接口而不能依赖具体类that's to say ,要尽量地依赖接口编程。
4.3.模块图
模块图能够认为是代码层面经缩略图,是类图的上面一层架构图。模块不一样关注类和接口,而是关注系统更大粒度的组成部分,展现模块之间的依赖和交互。模块能够是一个包,也能够是逻辑上实现特定功能的组件。eg:一个支付服务(PaymentService)模块图的例子,展现了一个实现支付功能的应用中支付服务和其余部分之间的依赖关系。模块图一般关注应用中的特定功能有关的部分,而忽略那些不想干的部分。因为系统未来会变得更大,因此最好一开始就画多个模块图,每一个模块图只关注一个特定的功能,而不是把全部的功能都画在一个模块图中。理想状况下,每一个模块图都应该足够简单,你能所有记住并在脑子里重画一次。
建模语言UML及设计模式推荐使用ArgoUML做为桌面UML绘图工具,这是一个开源的Java应用程序,能够在整个公司内协做使用,无须 将软件设计上传到云上。若是你喜欢基于云的方案进行在线协做设计,试试draw.io,这是一个集成了Google Drive的免费且容易使用的在线服务。 draw.io是个人最爱,本书里几乎全部的架构图都是用draw.io画的。5.单一职责
单一职责原则是说你的类应该只有一个职责而不宜更多。单一职责能够下降耦合,增长简单性,易于重构,代码复用及单元测试----这些 目前为止,讨论过的全部的核心原则。遵循这个原则设计出来的类更简单更小,所以易于重构和复用。有时候就眼前来讲,也许无视新功能是否为某个类的职责,直接往一个类里简单地添加方法更容易然而,这样作几个月后,你的类会变得很是庞大和其余类的耦合也更紧密。你会看到类之间出现的各类交互操做并不像你指望的那样,这些类会进行一些无关的操做。同时,因为类变得很庞大,所以几乎很难彻底理解它的行为和角色。随着时间的推移,这种状况会变得愈来愈严重,几乎每行代码都会带来复杂度的巨大增长。5.1改善单一职责事实上,并不存在一个必须遵照的法则去衡量你的类是否符合单一职责原则。不过仍是有一些最佳实践帮助你作出判断。
一个类的代码少于2-4屏
确保一个类的依赖不超过5个接口/类
确保一个类有一个明确的目标
用一句话总结一个类的职责,并把这句话放在类名上面做为类的注释。若是你发现很难用一句话总结一个类的职责,那有可能这个类的职责并不单一
若是你的类不能知足这些最佳实践,那你可能须要好好看看这个类而且准备重构。在更高的抽象层面,你须要将功能按模块分隔并避免功能重叠。具体作法能够参照类的设计------试着用一句话总结模块或者应用的功能,不过要从更高层次总结。例如,你能够说:“文件存储系统容许用户上传文件、管理文件并支持复杂的文件搜索”,这样应用目标看起来就很清晰。能够使用一个明确的接口(好比一个Web服务定义)限制模块职责范围并将其从其它部分隔离开来。
5.2单一职责的例子咱们看一个E-mail地址验证的例子。若是你把验证逻辑直接放在建立用户账号的代码里。就没有办法在其余地方复用了。你就不得不复制粘贴这些代码或者在这些原本没什么关联的类之间进行一些让人难受的依赖,总之,你会破坏软件设计的核心原则。把验证逻辑剥离出来,你就能够保证其只有一个实现并能在其它复用。过了一段时间, 你若是须要对验证逻辑进行修改,好比须要在域名中支持UTF-8编码,只须要重构这个类就能够了。-------------单一职责, 单独变化设计一个类专门负责E-mail验证比将这部分代码处处复制更简单,更容易将变化隔离开来。单一职责原则的另外一个做用是你会变得更乐于写可测试的代码。由于类的内容变得更少逻辑、更少依赖,因此更容易进行独立测试。进一步掌握单一职责的办法是研究策略、迭代器、代理、适配器模式。了解领域驱动设计及更好的软件设计
6.开闭原则
开闭原则是指,当需求变动或者增长新功能时,你不须要修改现有的代码。开闭的意思是:
若是咱们设计的代码在未来扩展新功能的时候无须修改,那么咱们就遵循了开闭原则。正如Robert C.Martin所言,开闭原则可让咱们保留作出选择的余地,延迟到未来作出某些细节决定,这样就减小了现有代码的需求变动。这个原则的目的是增长软件的弹性,使未来变动的代价 最小化。eg.考虑一个排序算法,须要对一个记录雇员对象的数组基于姓名进行排序。通常实现中,你能够在一个类里包含算法及其余所有必要的代码,假设类就叫EmployeeArraySorter。你能够在这个类里只暴露一个方法,容许对雇员对象数组进行排序,而后就能够说这个功能开发完成了。虽然这样是能解决问题,可是显然不够有弹性,事实上,这是一个很是僵硬的实现。因为全部的代码都在一个类里,你几乎不能在不修改代码的状况下扩展或者增长任何功能。假如你有一个新的需求,须要对城市基于人口进行排序,就不能复用这些已经开发好的代码。这时,你就不得不面临一个尴尬的选择,要么扩展EmployeeArraySorter作一些和原先设计彻底无关的事,要么复制粘贴这个类的代码而后再修改。幸运的是,你还有第3个选择,就是重构这个类使其符合开闭原则。开闭原则须要你将这个排序问题分解成几个更小的任务 ---社会分工的进一步细化??第个任务都就应该是独立的,不影响其他部分的复用。你能够设计一个接口Comparator用来比较两个对象,再设计一个接口Sorter用来执行排序算法。Sorter会利用Comparator对输入的数组进行排序。例如,你想改变排序对象,只须要增长一个新的Comparator实现便可,无须对现有代码作任何修改。若是你想改变排序算法,也不须要修改Comparator或者Sorter,只要从新建立一个Sorter接口的实现便可。各个不一样部分独立变化,能够减小变化影响的范围,也能够扩展示有的代码而无须对它们作任何改动。另外一个开闭原则的好例子是各类MVC框架。这些框架因为其简单和易扩展的特性而普遍应用于Web开发。回想一下,你何时须要修改MVC框架的某个组件? 若是这个MVC框架的架构设计得足够好,答案是永不须要。不过,虽然不须要修改,但你仍是能够扩展MVC中的某个组件,增长新的路由、拦截请求、返回不一样的响应、覆盖缺省的行为。没必要修改现有的框架代码,就能扩展其功能,这就是开闭原则所起的做用。 和掌握其余设计原则同样,学习开闭原则,开始的时候先熟悉了解各类框架。观察开闭原则的各类实现思路,领会开闭原则的实现模式,例如Java语言实现的SpringMVC框架就是一个很好的开闭原则的例子。用户能够弹性地实现各类功能而无须修改框架自己,而客户端代码跟框架也几乎没有多少耦合。经过使用annotation和基于约定编程,你开发的绝大部分类甚至都不知道Spring框架的存在!7.依赖注入依赖注入是一种下降耦合 改善开闭特性的简单技术。依赖注入对类须要依赖的对象提供一个引用实例,而无须类本身建立依赖的对象。
尽量地让类不要知道如何去建立须要的依赖,以及这些依赖来自哪里,是如何实现的。这看起来只是从“拉取”变成“推送”的一个微小变化,但事实上会对软件设计的弹性产生巨大影响。咱们来看一个CD播放器的依赖注入的例子。全部的CD播放器都持有一个CD接口,知道如何去读取音轨,如何对音乐进行解码,读取CD内容的Laser device的光学参数是什么。插入到CD播放器的CD是一个依赖,没有CD,CD播放器就无法正常工做。将发现依赖的职责交给用户,CD播放器就能够保持简单。从而使CD播放器的利用性更强,它没必要知道burn到CD上的每个标题,也没必要知道专辑上的每一个组合,只须要知道CD的各类可能形式及其组合形式就能够了。CD播放器须要你(用户)去提供一个可读的CD,一旦你提供了一个CD实例,CD播放器就能够正常工做了。此外,还有一个附带的好处就是CD播放器容许插入非标准的CD。你能够插入一张空白盘(NothingToDo对象的实例),或者一张音轨畸变的测试盘,这样你就能够作异常测试了。eg:经过硬编码的方式记录各类CD,若是你想播放一张新CD,就不得不修改它的代码。-----一个负担太重的CD播放器。再看一下使用依赖注入方式实现的CD播放器,没必要知道任何CD自身的信息,只须要提供一个CD的实例就能够。使用这种方式,你就能够实现对新增开放(播放新的CD)而对修改关闭(CD播放器永不须要修改),这里面还有基于接口的约定,CD的编码方式要和CD播放器的解码想匹配。
若是使用得当,依赖注入能够有效下降类的局部复杂度,使其更简单。无须知道谁来提供依赖的实例,也无须知道实例从何而来,这样类就能够聚焦本身的主要职责。类的代码更简单,不须要太了解系统的其余部分,不太须要变化和单元测试。将集成的代码从类中移除,能够使类更加独立、可复用、可测试。依赖注入在面向对象编程OOP社区已经有多年实践。实现依赖注入不须要任何框架。学习依赖注入推荐Spring框架或者Grails框架,它们是依赖注入方面很是好的例子。
8.控制反转IOC
之前面向过程,一个方法写到底,相关的外面依赖都须要本身初始化,核心业务逻辑和外面依赖的初始化及管理都堆积在一块儿IoC 反转是对这种状况的改变,相关依赖经过DI的方法注入,外面依赖的控制交于容器或第三方
控制反转(IOC)是一种从类中移除职责的方法,从而使类更简单,与系统其它部分更少耦合。控制反转的核心是没必要知道是谁、怎样、什么时候建立和使用对象,尽量地简单易于理解,对于软件设计而言,各部分互相知道得越少越好。
IOC在一些框架中重度使用,包括Spring、Symfony、Rails,甚至JavaEE容器。你没必要建立类的初始化方法,这些通通交给框架来作。IOC框架接收Web请求建立对应处理类的实例交给对应的模块处理。IOC也被称为好莱坞原则,好莱坞原则Hollywood principle是说“don‘t call us, we‘ll call you“运用到实践中,就是你的类实例没必要知道本身何时 被建立,被谁使用,本身的依赖又是如何被建立的。你的类是一个插件,外部力量决定这些类何时被建立,如何被使用。想象一下只用纯Java不须要任何框架开发一整个Web应用。没有Web服务器,没有框架,没有API,只有Java。为了完成这样一个复杂的应用,你须要本身写很是多的代码。即便你决定使用一些第三方的库,你也必须本身控制整个应用的流程。经过使用框架,你能够减小代码的复杂度 。不止是能够减小须要写的代码的数量,还能够减小开发者须要关注的事情。须要作的只是让框架回调你的代码,框架会建立类的实例。请求到达的时候,框架会调用你的方法进行处理,并控制执行的流程从一个扩展点到另外一个扩展点。eg: 一个用Java(没有用任何框架)编写的Web应用。这个例子中,大量的代码模块用于和外界进行通讯。应用须要本身打开网络端口,写日志文件,链接外部系统,管理线程,解析消息。应用须要控制全部的事情,这意味着你须要考虑太多与业务无关的事。若是你使用IOC框架,框架处理了大量的非业务事务,应用甚至不须要关心这些事情的存在。虽然系统仍是要处理这些事情,可是都和应用无关。这不会改变整个系统的复杂性,可是会极大地下降应用的复杂性。IOC是一个很通用的概念。你能够为任何类型的应用建立各类控制反转的框架,不仅是MVC框架或者Web请求处理框架。
视频这些东西,超过半分钟,10秒或20秒,他以为没兴趣,他就换了。 执行困难
短是一个感受,以为这个时间,有没有讲透
公众的节目,众口难调
记得第一道菜的样子
一个好的IOC框架包含以下特性:
能够为框架开发插件
插件是独立的,能够被随时插入移除
框架能够自动检测到插件,或者经过配置文件加载插件
框架为每一种插件类型定义接口,而不会与具体插件耦合
基于IOC框架写代码就像把钱放在鱼缸里。鱼缸里能够放不少鱼,它们互不干扰,可是它们生活在一个它们不能控制的世界里。你决定鱼缸的环境是什么样的,何时喂鱼。你就是一个ICO框架,你的鱼就是插件,它们生活在一个被保护的本身殊不知道的泡泡里。
9.为伸缩而设计为伸缩而设计是一个有难度的艺术活,本书中描述的每一项技术都要付出相应的成本。做为一名工程师,你须要在没完没了的伸缩和每一种对应的方案实践之间进行仔细的权衡。不要为了那些永远也用不着的伸缩性需求进行过分设计,要认真评估系统最可能的实际伸缩性需求进行相应的设计。
从业界状况看,不少创业公司彻底等不到作任何系统伸缩就倒闭了(这样的公司大概占90%)。剩下的大部分公司也只须要较少的伸缩性就够(这样的公司占了大概9%)。只有极少数的公司会持续成长,须要进行水平伸缩(只占1%左右)。
目前为止,咱们讨论的各类设计原则都是为解决复杂性和耦合性,同时,这些原则大部分也有助于改善系统伸缩性。随着你对伸缩性的深刻了解,能深入意识到伸缩性方案均可以浓缩成三个基本设计方法。
增长副本:同一组件重复部署到多台机器
功能分割:基于功能将系统分割成更小的子系统
数据分片:在每台机器上都只部署一部分数据
这些方法会提供某一方面的好处,同时也须要付出另外一方面的成本。9.1 增长副本若是你从头开始构建一个系统,最简单最多见的伸缩性策略是增长副本。副本指的是组件或者服务器副本。任什么时候候两个副本都是能够互换的,任何请求在任何副本上处理都是同样的。换句话说,发送请求到任何一个副本上响应都是同样的。经过增长副本实现伸缩时,须要关注应用状态如何存储及副本之间如何同步状态变动。因此这种方式最适用于无状态的服务,无须同步状态。若是应用是无状态的,那么对于请求而言,一台新服务器和一台已经存在的服务器没有区别。
无状态服务是指一个服务不会依赖本地状态,处理一个请求也不会影响到服务自身的行为方式。要得到正确的结果无须特定的服务器实例。
增长副本实现伸缩不是无状态服务的专利。事实上,数据库使用这种方式经过副本进行伸缩已经多年。经过增长副本进行伸缩就像医院里的急救室。若是你预算充足,能够雇用足够多的职业医生而且购买足够多的手术室与设备,就能够很容易地让不少病人获得救治。医生和病房的数量同样,能救治的病人数目同样。对于Web层而言,增长副本是最容易、成本最低的伸缩方案。增长副本实现伸缩的主要挑战是对于有状态的服务难以用这种方式伸缩,须要找个办法同步状态以实现副本任意可交换。9.2 功能分割第二个重要的伸缩性策略是功能分割,适用于各个地方 各个层面。这个方案的核心是发现系统的各个功能部分,而后将它们建立独立的子系统。当咱们讨论基础设施架构的时候,功能分割批的是物理服务器分离部署。将数据中心的服务器根据类型进行划分,有对象缓存服务器、消息队列服务器、队列工做者服务器、Web服务器、数据存储服务器、负载均衡器等。这里每个组件均可以部署到主应用服务器上,可是这么多年来,工程师们逐渐到更好的办法是将这些功能组件部署到独立的服务器上。功能分割的另外一种更先进的方式是:将系统切分红一些自给自足的应用。主要适用于Web服务层,是面向服务架构(SOA)的一种主要实践。回到eBay竞价应用这个例子,若是你有一个Web服务器层,就能够构建一系统的松耦合的Web服务处理实现各类功能。这些服务能够拥有本身的逻辑资源,诸如数据存储、队列、缓存等。一个功能分割的场景,两个独立的功能:资料服务和调度服务。这些服务能够共享底层基础设施,也能够拥有独立部署的底层基础设施,好比数据存储等。给服务更多的自主性,一方面能够更好地基于约定编程,另外一方面也可让服务本身决定须要什么样的组件及如何对本身进行伸缩。功能分割经常使用于底层:将应用分割成多个模块,而后将不一样类型的软件部署在不一样的服务器上(好比,数据库和Web服务部署在不一样服务器上)。在大一点的公司,也会对顶层进行功能分割,即建立多个独立的Web服务。这种状况下,须要将单个应用切分红多个小一点的功能服务。这样作还有一个好处是多个团队能够基于各自的代码库 独立开发,不一样的服务也能够根据各自的伸缩性需求独立伸缩。功能分割会带来不少好处,可是也有缺点。分割后的功能,最开始时会带来不少管理方面的困难。功能分割能够分割的数量 有上限,限制了对功能分割这种伸缩性方案的使用。你不能无限地使用这种方式对应用无休止地切分,以使Web服务变得更小。
9.3数据分片第三种主要的伸缩性策略是将数据分片,而后将不一样的分片数据保存在不一样的机器上,以保证每台服务器上存储的数据量都不会太大。这种方式也被称为无共享架构原则,每台服务器都拥有本身的一个数据子集,彼此之间彻底独立,每一个节点都彻底自治。这样,每一个节点均可以独立变动本身控制的部分数据,而无须在各个节点之间复制状态变动信息。无共享状态意味着没有数据须要同步,不须要锁,失败也会被隔离,由于节点之间没有依赖。 再来看eBay竞价应用。回顾一下,首先咱们经过增长服务器(增长备份)实现伸缩,而后将Web服务切分红两个 独立的服务,这样咱们就得到了两种不一样的伸缩方式。此外,还有一种伸缩方式:对数据负载的分布式伸缩。这种方式的一个例子是:对Web服务的对象缓存中的数据进行切分。 要想让对象缓存实现伸缩,一个办法是增长副本,可是这就须要在全部的服务器之间同步状态。另外一个办法是功能分割,能够将Web服务器响应缓存在一台缓存服务器,而数据查询结果缓存在另外一台服务器上。不过这两种方法都不怎么样,都没有办法让咱们的伸缩持续下去。
根据缓存对象的首字母将缓存对象分布到不一样的服务器上。实践中,应用会使用更复杂的方式进行数据分片,但基本根据是同样的。每一个服务器都获得一个数据子集,并只需管理这个子集便可。因为每一个服务器都只存储较少的数据,因此服务器能够把更多数据存储在内存中并获得更快的响应速度。未来若是须要更多存储能力,只须要增长服务器并修改映射关系便可。仍是用医院作类比。若是医院提供预定就诊服务,就能够用数据分片的方式对专家(数据)进行伸缩。不能让病人每次预定到的都是不一样的专家,解决这个问题的一个办法是将医生的名字写到每一个病人的病例卡中。病人来挂号时,就把分配给对应的医生,这样客服人员能够很容易地根据预定帮病人找到医生。即将数据请求须要的数据--->某个缓存服务器 是固定的,每一个缓存服务器只须要存储部分数据便可数据分片配合增长备份,能够实现几乎无限的伸缩性。只要能对数据进行正确的切分,就能增长更多的用户,处理更多的并发链接,收集更多的数据,将系统部署在更多的服务器上。不幸的是,数据分片几乎是最复杂*、代价最昂贵的技术。数据分片最大的挑战是在进行数据访问前,必须先找到存储须要的数据所在的分区的服务器,而若是一个查询涉及多个数据分区,那么实现就变得异常低效和困难。
10.自愈设计
一个足够大的系统必定处于某种部分失效的连续状态之中---Justin Sheehy
一个系统被认为是可用的,就是说从用户角度它一直按预期运行,完成其功能,而并不关心其内部是否存在某种失效状态,只要不影响用户使用就能够。按句话说,你须要让你的系统表现出全部组件都在正常运行,即便出现某些设备宕机或者进行发布维护也是如此。
一个高可用的系统对于其它用户而言,任什么时候候都是可用的。对于高可用没有一个绝对的衡量准则,不一样的系统有不一样的高可用需求。系统的可用性通常不会用一个绝对值直接度量,而是用“几个9”来衡量。咱们说一个系统两个9,是说这个系统99%的时间可用,也就是每一年大概有3.5天不可用(365天*0.01=3.65天)。相对地,一个系统有6个9的可用性,是说这个系统99.999%的时间可用,每一年不可用时间5分钟。
能够想象,不一样的业务场景有不一样的可用性要求。要记住的是,系统越大,系统失效的几率越高。若是你须要链接5个Web服务,每一个服务链接3个数据存储,那么你就要依赖15组件。任何一个组件失效,整个系统都会不可用,除非你能优雅地控制失效进行透明地失效转移。当你进行伸缩扩容的时候,失效也会变得更加频繁。你有1000台服务器,那么天天均可能有几台服务器宕掉。糟糕的事情可能远不止于此,不少缘由都会致使失效,好比停电断网,以及人为失误。设计一个伸缩性架构必须把各类失效状态看成常态,而不是特殊状况对待。若是想设计一个高可用的系统,就必须作最坏的准备才能获得最好的期待。要不停地想哪里会出错,出错后该怎么办。
保持系统高可用是一种态度,将这种态度发挥到极致的一个例子是Netflix开发的一个叫作Chaos Monkey(捣蛋鬼)的系统。Netflix可用性工程师认为证实一个系统真的高可用的方式是,真的去制造一些事故,而后看这个系统怎么处理。Chaos Monkey就是这样一个服务,它会在上班时间随机关掉一些 Netflix的基础设施组件。这看起来很荒谬是否是?公司可能都会所以而挂掉。可是最后真的证实了他们的系统能够处理任何类型的失效。
另外一种相似的高可用态度是一个叫作Crash-Only(只需宕机)的概念。Crash-Only的鼓吹者认为系统应该随时为宕机作好准备,系统任什么时候候重启,都无须人工干预。这意味着系统是否正在处理请求,处理队列消息仍是作任何类型的工做,都可以本身检测到本身的失败,修复必要的错误数据、重启,而后像正常时同样工做。CouchDB是一个很流行的开源数据库,它就是遵循这个原则开发的,这个系统不提供任何的关闭功能。若是想关闭一个CouchDB实例,只须要终止进程便可。
如何才能证实你的系统可以处理各类失效情况?我曾经见证过不少宕机事件,有的由于本地服务器存储了状态,有的由于不能正确处理网络延迟。持续地 测试各类失效场景是提升系统可用性的重要方式。实践中,确保系统高可用的主要手段是消除失效单点的风险和优雅地进行失效转移。
失效单点是指基础设施中任何一个部分对系统的正常工做都是必要的。失效单点的一个可能的场景是域名系统(DNS)服务器,若是你只有一个域名服务器的话。另外一个可能的场景是数据库主服务器或者文件存储服务器。
识别失效单点的一个简单方法是画出数据中心架构图,每一个设备(路由器、服务器、交换机等)都画上去,而后问本身,当某个设备宕机的时候会发生什么。当识别出失效单点之后,就和业务团队一块儿讨论对其作冗余是划算。有时,冗余很便宜很简单,有时又很昂贵很复杂,这须要仔细权衡。冗余是指数据或组件有至少两个备份。当其中一个备份失效了,系统能够使用另外一个备份服务用户。一个缺少冗余的系统须要特别关注,这方面的最佳实践是准备一个灾难恢复计划(有时候也叫业务持续计划),记录对系统各类灾难性问题如何进行恢复。最后,若是你想要一个高可用的彻底耐受各类失效的系统,也许该实现系统的自愈功能。自愈比优雅地处理失效更进一步,它可以检测并自动修复问题,无须人工干预。能自愈的系统是Web应用的圣杯,不过想打造这样一个系统也是很是困难且代价高昂的,比嘴上说说不知道要难多少倍。这里给一个自愈的例子---开源数据库Cassandra处理失效的办法。Cassandra中系统能够透明地处理数据节点的失效。一旦集群发现某个节点失效,就马上中止发送新的请求给失效的节点。对于客户端而言,失效的时间就是发现失效节点须要的时间。一旦失效节点被放到黑名单里,客户端就能够正常读写数据,集群中其他的节点会对失效的节点提供数据冗余。当失效节点恢复上线后,会带着丢失的数据运行,好像系统什么都没有发生同样。相同地,若是用一个新的空白服务器节点替换一个失效的节点,也不须要系统管理员像传统关系数据库那样,必须从备份节点恢复数据。增长一个新的空白数据节点会引发Cassandra集群的数据同步,过一段时间,新加入的机器就填满了数据。若是一个系统能够检测到自身 存在部分失效或者潜在的不可用,而且能尽快进行自我恢复,这个系统就是自愈的。
平均恢复时间是可用性方程的一个重要组成部分。你越快检测到,去处理,进行修复,可用性就越高。可用性的真实计算公式是:平均失效时间/(平均失效时间+平均恢复时间)。减小平均恢复时间,就能够增长可用性,即便失效次数无法控制的状况下也是如此。当使用AWS Web服务这类云主机服务时,因为云服务商使用廉价硬件,他们会在低失效比率和低价之间权衡。这种状况下,你无法控制平均失效时间,因此只能更多地关注平均恢复时间。
小结不管你是软件工程师、架构师、开发组长、仍是技术经理,软件设计原则对你都很是重要。软件工程是一门关于信息决策、创造商业价值、为将来作准备的技术。要记住:
它们能为你指明方向,增长你成功的几率,可是最终,仍是要你本身决定什么样的方式最适合你的系统。做为一名软件工程师或者架构师,你的工做就是根据金钱、时间、能力,提出对实现商业目标最好的解决方案。若是你意识到本身角色的重要性,你就应该让本身的思想开放,用各类视角考虑问题。最“干净”的方案不必定老是最好的方案,由于可能会花费更多的开发时间或者会引入一些没必要要的管理成本。好比,解耦合和过分设计之间的那条线就很是细。你的工做就是识别出这些诱惑,避免带来美好的想象向着那些彷佛酷毙了的方向越走越远。根据业务需求作出决策,在伸缩性、弹性、高可用、成本、推向市场的时间之间作出权衡。
每一个系统都是不一样的,每家公司的需求也是不一样的,你要发现本身和之前和别的工程师工做场景的不一样之处。构建可伸缩软件的路不止一条,技术要好,开发工具要顺手,还要去发现业务驱动因素。但愿本章提到的各类设计原则为你设计高质量软件系统开了个好头。Come on!
九 伸缩性的其它维度
本书主要内容是关于设计与开发可伸缩的Web应用的一些技术细节,这些内容的主要受众是软件工程师。事实上,开发一个可伸缩的系统不仅是写代码,还有其它可伸缩的维度也须要关注。
运维可伸缩性生产环境可以运行多少台服务器?系统一旦部署在上百台服务器上,如何高效地管理这些服务器就会成为一个挑战。若是每增长20台服务器就须要多招一个运维管理员,那么运维就无法快速廉价地伸缩。
我的影响力的伸缩性你我的能为客户和公司创造多大的价值?随着创业公司的成长,你我的的影响力也应该同步增加。经过扩展你的职责范围及对各个业务主管施加影响,你的工做效率和我的绩效会变得更高。
工程师团队的伸缩性创业公司的工程师人数达到多少后工做效率公降低?随着公司的成长,公司须要在不影响工做效率的状况下招募更多的工程师壮大技术部门。这意味着须要创建良好的工程师文化,规划合理的团队和系统架构,保证你们可以可伸缩地 并行开发与协做。
优先级=价值/成本