点击上方“芋道源码”,选择“设为星标”
做积极的人,而不是积极废人!
源码精品专栏
来源:xybaby cnblogs.com/xybaby/p/8907880.html
一个网站的进化史
大型网站架构模式与核心要素
性能
高可用
伸缩性(Scalability)
可扩展性(Extensibility)
Others
花了几个晚上看完了《大型网站技术架构》(https://book.douban.com/subject/25723064/)这本书,个人感觉这本书的广度还行,深度还有些欠缺(毕竟只有200页左右)。但是作为一个缺乏大型网站技术的IT民工,看完一遍还是很有收获的,至少对一个网站的技术演进、需要解决的问题有了一个全面的认识。文中也有一些作者个人的心得、感悟、总结,我觉得还是很中肯的。
在网上一搜,这本书的读书笔记还是很多的,而我自己还是决定写一篇读书笔记,主要是为了避免自己忘得太快。笔记的内容并不完全按照原书的内容,主要记录的是我自己感兴趣的部分。
一个网站的进化史
作者反复在文中提到一个观点:大型网站是根据业务需求逐步演化而来的,而不是设计出来的。
不得不承认,互联网行业发展到了今天,大鱼吃小鱼还是很普遍的,大公司的微创新能力分分钟就能干死一个小的项目,所以小公司需要足够快的发展,不停的快速迭代与试错。
下面是是一个演化的过程,图片来自网络。
初始阶段的网站架构
![](https://i-blog.csdnimg.cn/blog_migrate/a7da0c71185cea28621cd5dad190adb5.png)
在初始阶段,访问量并不大,所以应用程序、数据库、文件等所有的资源都在一台服务器上。
应用服务和数据服务分离
![](https://i-blog.csdnimg.cn/blog_migrate/f5fe9b7f0022828bc51567525163e7d5.png)
随着业务的发展,就会发现一台服务器抗不过来了,所以将应用服务器与数据(文件、数据库)服务器分离。三台服务器对硬件资源的要求各不相同:应用服务器需要更快的CPU,文件服务器需要更大的磁盘和带宽,数据库服务器需要更快速的磁盘和更大的内存。分离之后,三个服务器各司其职,也方便针对性的优化。
使用缓存改善网站性能
![](https://i-blog.csdnimg.cn/blog_migrate/fe411e400a5511beaa6e78b4a40e0858.png)
“世界上没有什么问题是加一级缓存解决不了的,如果有那就再加一级缓存”
缓存的使用无处不在,缓存的根本目的是加快访问速度。当数据库的访问压力过大的时候,就可以考虑使用缓存了。网站使用的缓存可以分为两种: 缓存在应用服务器上的本地缓存和缓存在专门的分布式缓存服务器上的远程缓存。
使用应用服务器集群改善网站的并发处理能力
![](https://i-blog.csdnimg.cn/blog_migrate/db247193e10b173774f89d8879d8135f.png)
随着业务的发展,单个应用服务器一定会成为瓶颈,应用服务器实现集群是网站可伸缩集群架构设计中较为简单成熟的一种。后面也会提到,将应用服务器设计为无状态的(没有需要保存的上下文信息),就可以通过增加机器,使用负载均衡来scale out。
数据库读写分离
![](https://i-blog.csdnimg.cn/blog_migrate/70222b5a71dad2eb89edc984a7a21993.png)
即使使用了缓存,但在缓存未命中、或者缓存服务时效的情况下,还是需要访问数据库,这个时候就需要数据库的读写分离:主库提供写操作,从库提供读服务。注意,在上图中增加了一个数据访问模块,可以对应用层透明数据库的主从分离信息。
使用反向代理和CDN 加速网站晌应
![](https://i-blog.csdnimg.cn/blog_migrate/b66fb3f507dcfee26738754c9f8596a6.png)
CDN和反向代理其实都是缓存,区别在于CDN 部署在网络提供商的机房;而反向代理则部署在网站的中心机房。使用CDN 和反向代理的目的都是尽旱返回数据给用户, 一方面加快用户访问速度,另一方面也减轻后端服务器的负载压力。
使用分布式文件系统和分布式数据库系统
![](https://i-blog.csdnimg.cn/blog_migrate/bb6c57b74fe900e2d7357e84219d4013.png)
单个物理机的磁盘是有限的,单个关系数据库的处理能力也是有上限的,所以需要分布式文件存储与分布式数据库。当然,也需要”统一数据访问模块“,使得应用层不用关心文件、数据的具体位置。值得一提的事,关系型数据库自身并没有很好的水平扩展方案,因此一般都需要一个数据库代理层,如cobar、mycat。
使用NoSQL 和搜索引擎
![](https://i-blog.csdnimg.cn/blog_migrate/c57c17765976462f245cf540eb6465c6.png)
web2.0的很多应用并一定适合用关系数据库存储,更加灵活的NoSql能更加方便的解决一些问题,而且NoSQL天然就支持分布式。专门的搜索引擎在提供更优质服务的同时,也大大减轻了数据库的压力。
业务拆分
![](https://i-blog.csdnimg.cn/blog_migrate/008cc5886b057cc75d28aed2b7e29c0c.png)
将一个网站拆分成许多不同的应用, 每个应用独立部署维护。应用之间可以通过一个超链接建立关系(在首页上的导航链接每个都指向不同的应用地址) ,也可以通过消息队列进行数据分发, 当然最多的还是通过访问同一个数据存储系统来构成一个关联的完整系统
分布式服务
![](https://i-blog.csdnimg.cn/blog_migrate/8b53c5a02e0e4e02e9732e76d8f4a7da.png)
既然每一个应用系统都需要执行许多相同的业务操作, 比如用户管理、商品管理等,那么可以将这些共用的业务提取出来,独立部署。
通过服务的分布式,各个应用能更好的独立发展,实现了从依赖模块到依赖服务的过渡。将通用的公共服务独立出来,也方便做服务管控,比如对各个应用的服务请求进行监控,在高峰时期限制、关闭某些应用的访问等。
大型网站架构模式与核心要素
这一部分是说大型网站需要解决的核心问题,以及解决这些问题的常规思路。
核心要素
五个要点:性能,可用性,伸缩性,扩展性,安全
作者指出,很多时候大家都混淆了伸缩性(Scalability)与扩展性(Extensibility)。我以前也是把Scalability称之为扩展性,不过想想,在我们讲代码质量的时候,扩展性也是指Extensibility,以后还是直接说这两个英文单词好了。
这几点后面会详细介绍。
网站架构模式
对模式的定义,书中描述得很好:
" 每一个模式描述了一个在我们周围不断重复发生的问题及该问题解决方案的核心。这样, 你就能一次又一次地使用该方案而不必做重复工作" 。模式的关键在于模式的可重复性, 问题与场景的可重复性带来解决方案的可重复使用。
用我自己的话来说,模式就是套路。这些模式,都是为了达成上面提到的核心要素。那么,有哪些模式呢
分层
分层是企业应用系统中最常见的一种架构模式,将系统在横向维度上切分成几个部分,每个部分负责一部分相对比较单一的职责, 然后通过上层对下层的依赖和调用组成一个完整的系统。
在大型网站架构中也采用分层结构,将网主占软件系统分为应用层、服务层、数据层。
分层的好处在于:解耦合,独立发展,伸缩性,可扩展性。上面网站的进化史也凸出了分层的重要性。
但是分层架构也有一些挑战, 就是必须合理规划层次边界和接口,在开发过程中,严格遵循分层架构的约束, 禁止跨层次的调用( 应用层直接调用数据层)及逆向调用(数据层调用服务层, 或者服务层调用应用层)。
分割
分层强调的是横向切分,而分割是纵向切分, 上面网站进化史部分的业务拆分就包含了分割。
分割的目标是高内聚、低耦合的模块单元
分布式
分层和分割的一个主要目的是分布式部署,但分布式也有自己的问题:网络通信带来的性能问题,可用性,一致性与分布式事务,系统维护管理复杂度。
集群
一个机器解决不了的问题,就用几个机器来解决,当服务无状态的时候,通过往集群增加机器就能解决大部分问题。对应网站进化史中“使用应用服务器集群改善网站的并发处理能力”
缓存
缓存就是将数据存放在距离计算最近的位置以加快处理速度,同时大大减轻了数据提供者的压力
大型网站架构设计在很多方面都使用了缓存设计:CDN、反向代理、本地缓存、分布式缓存
异步
异步是解耦合的一个重要手段,常见的生产者-消费者模型就是一个异步模式。
出了解耦合,异步还能提高系统可用性、加快响应速度、流量削峰
冗余
冗余是系统可用性的重要保障,也是数据可靠性的重要手段
自动化
凡人总是会出这样那样的错误,能自动话的就要自动化。自动化大大解放了程序员、运维人员的生产力!
发布过程自动化、自动化代码管理、自动化测试、自动化安全检测、自动化部署、自动化监控、自动化报警、自动化失效转移、自动化失效恢复、自动化降级。
性能
奥运精神:更快、更高、更强
![](https://i-blog.csdnimg.cn/blog_migrate/ade44850f1f9bb7bd00bfbdbad5ea265.png)
技术人员对于性能的追求是无止境的。
性能,站在不同的角度,衡量指标是不一样的:
用户视角:响应时间,优化手段:(浏览器优化,页面布局,压缩文件,http长链接),CND,反向代理
开发人员视角:系统延迟、吞吐量、稳定性。优化手段:缓存,异步,集群,代码优化
运维视角:基础设施性能 资源利用率。优化手段:定制骨干网络、定制服务器,虚拟化
常见的衡量标准包括:响应时间、吞吐量、并发量。关于这些衡量标准,文中有一个很好的比喻:
系统吞吐量和系统并发数, 以及响应时间的关系可以形象地理解为高速公路的通行状况: 吞吐量是每天通过收费站的车辆数目(可以换算成收费站收取的高速费) , 并发数是高速公路上的正在行驶的车辆数目,响应时间是车速。车辆很少时, 车速很快, 但是收到的高速费也相应较少; 随着高速公路上车辆数目的增多,车速略受影响,但是收到的高速费增加很快; 随着车辆的继续增加,车速变得越来越慢,高速公路越来越堵,收费不增反降; 如果车流量继续增加,超过某个极限后,任何偶然因素都会导致高速全部瘫痪, 车走不动,费当然也收不着,而高速公路成了停车场(资源耗尽)。
web前端性能优化
浏览器优化:减少http请求,浏览器缓存,压缩。CDN优化,反应代理
应用服务器性能优化
四招:缓存、集群、异步、代码优化
缓存
首先自然是缓存
网站性能优化第一定律: 优先考虑使用缓存优化性能。
使用缓存,需要考虑的是缓存置换与一致性问题,其中缓存一致性问题也是分布式系统中需要解决的一个问题,主要的解决方法有租期和版本号。
并不是所有的场合都适合缓存,如频繁修改的数据、没有热点访问的数据。
缓存的可用性:理论上不能完全依靠,但事实上尽可能高可用,否则数据库宕机导致系统不可用。因此缓存服务器也要纳入监控,尽量高可用。
缓存穿透:如果因为不恰当的业务、或者恶意攻击持续高并发地请求某个不存在的数据,由于缓存没有保存该数据, 所有的请求都会落到数据库上,会对数据库造成很大压力,甚至崩横。一个简单的对策是将不存在的数据也缓存起来(其value 值为null )。
代码优化
多线程
为什么要使用多线程,IO阻塞 与 多核CPU
理想的load 是:即没有进程(线程)等待,也没有CPU空闲
启动线程数= [任务执行时间/ (任务执行时间-10 等待时间)J xCPU 内核数
资源复用
这个很常见,各种池(pool):线程池、连接池
高可用
网站年度可用性指标= ( 1-网站不可用时间/年度总时间) x lOO%
业界通常用N个9来衡量系统的可用性。如,2 个9 是基本可用, 网站年度不可用时间小于8 8 小时; 3 个9是较高可用, 网站年度不可用时间小于9 小时; 4 个9 是具有自动恢复能力的高可用,网站年度不可用时间小于53 分钟; 5 个9 是极高可用性,网站年度不可用时间小于5 分钟。
可用性是大型网站的命脉,是否可用,用户是可以立刻感知到的,短暂的不可用也会带来巨大的损失。这也是为什么大型网站在面对CAP问题时,更看重A(avalibility)的原因。
高可用架构的主要手段是数据和服务的冗余备份及失效转移。
在分层的网络架构中,通过保证每一层的高可用,就实现了整个系统的高可用。而每一层又有自己的高可用手段
应用层高可用
位于应用层的服务器通常为了应对高并发的访问请求,会通过负载均衡设备将一组服务器组成一个集群共同对外提供服务,当负载均衡设备通过心跳检测等手段监控到某台应用服务器不可用时,就将其从集群列表中剔除,并将请求分发到集群中其他可用的服务器上,使整个集群保持可用,从而实现应用高可用。
应用层的高可用很容易,因为应用服务器很多时候是无状态的。
但是也有时候需要有维护的数据,如session,这样就不能将一个请求路由到任意的应用服务器。要解决session的问题,有以下几种方法:
session绑定:利用负载均衡的源地址Hash 算法实现,负载均衡服务器总是将来源于同一IP 的请求分发到同一台服务器上
用cookie记录session:Cookie是存放在客户端(浏览器)的,在每次访问的时候带上cookie里面的信息即可
专门的session服务器:将应用服务器的状态分离, 分为无状态的应用服务器和有状态的Session。简单的方法是利用分布式缓存、数据库(redis)来实现Session服务器的功能
服务层的高可用
服务层的高可用也是利用集群,不过需要借助分布式服务调用框架。
服务层的服务器被应用层通过分布式服务调用框架访问,分布式服务调用框架会在应用层客户端程序中实现软件负载均衡, 并通过服务注册中心对提供服务的服务器进行心跳检测,发现有服务不可用,立即通知客户端程序修改服务访问列表,剔除不可用的服务器。
为了保证服务层的高可用,可以采用以下策略:
分层管理
超时设置
异步调用
服务降级,包括:拒绝服务,高峰时段,拒绝低优先级应用的访问;关闭服务,关闭某些不重要的功能
幂等性设计,方便失败时重试
数据层的高可用
包括分布式文件系统与分布式数据库,核心都是冗余加失效转移。
冗余(复制集、replica)需要解决的核心问题是一致性问题
失效转移操作由三部分组成: 失效确认、访问转移、数据恢复。
![](https://i-blog.csdnimg.cn/blog_migrate/09dc89f7b99dfeefc75e4aaa57541c90.png)
上面描述了失效确认的两种方法:控制中心通过心跳检测存储服务器的存活性;应用在访问存储服务失败的时候通知控制中心检测存储服务存活性
伸缩性(Scalability)
网站的伸缩性是指不需要改变网站的软硬件设计,仅仅通过改变部署的服务器数量就可以扩大或者缩小网站的服务处理能力。
应用层的伸缩性
将应用层设计成无状态,即可利用集群 + 负载均衡来解决伸缩性问题。
关于负载均衡,我之前也写过一篇文章《关于负载均衡的一切:总结与思考》(http://www.cnblogs.com/xybaby/p/7867735.html)介绍。
缓存的伸缩性
首先,缓存是有状态的,分布式缓存服务器集群中不同服务器中缓存的数据各不相同,缓存访问请求不可以在缓存服务器集群中的任意一台处理,必须先找到缓存有需要数据的服务器,然后才能访问。
如果缓存访问被路由到了没有缓存相关数据的服务器,那么该访问请求就会落地到数据库,增加数据库的压力。因此,必须让新上线的缓存服务器对整个分布式缓存集群影响最小,即缓存命中率越高越好。
在这个场景下,最好的负载均衡算法就是一致性hash
数据层的伸缩性
关系型数据库,依赖于分布式数据库代理。而NoSQL数据库产品都放弃了关系数据库的两大重要基础: 以关系代数为基础的结构化查询语言( SQL ) 和事务一致性保证( AClD )。而强化其他一些大型网站更关注的特性: 高可用性和可伸缩性。
伸缩性总结:一个具有良好伸缩性架构设计的网站,其设计总是走在业务发展的前面, 在业务需要处理更多访问和服务之前,就已经做好充足准备, 当业务需要时, 只需要购买或者租用服务器简单部署实施就可以。
可扩展性(Extensibility)
设计网站可扩展架构的核心思想是模块化, 并在此基础之上, 降低模块间的耦合性,提高模块的复用性。
主要有分布式消息队列和分布式服务。
分布式消息队列通过消息对象分解系统耦合性, 不同子系统处理同一个消息。
分布式服务则通过接口分解系统辑合性, 不同子系统通过相同的接口描述进行服务调用。
分布式服务
纵向拆分:将一个大应用拆分为多个小应用, 如果新增业务较为独立, 那么就直接将其设计部署为一个独立的Web 应用系统。
横向拆分: 将复用的业务拆分出来, 独立部署为分布式服务, 新增业务只需要调用这些分布式服务, 不需要依赖具体的模块代码,即可快速搭建一个应用系统, 而模块内业务逻辑变化的时候, 只要接口保持一致就不会影响业务程序和其他模块。
分布式服务依赖于分布式服务治理框架
分布式服务治理框架
这一块儿接触甚少,还需要花点时间专门学习学习。
![](https://i-blog.csdnimg.cn/blog_migrate/9eb31ead9b206ff8ebc812ad64e26a01.png)
服务治理框架的功能和特点:
服务注册与发现
服务调用
负载均衡
失效转移:分布式服务框架支持服务提供者的失效转移机制, 当某个服务实例不可用, 就将访问切换到其他服务实例上,以实现服务整体高可用。
高效远程通信
整合异构系统
对应用最小侵入
版本管理:分布式服务框架需要支持服务多版本发布, 服务提供者先升级接口发布新版本的服务, 并同时提供旧版本的服务供请求者调用, 当请求者调用接口升级后才可以关闭旧版本服务。
实时监控
Others
所谓问题, 就是体验一期望,当体验不能满足期望, 就会觉得出了问题。消除问题有两种手段:改善休验或者降低期望。
问题被发现,它只是问题发现者的问题,而不是问题拥有者的问题,如果想要解决一个问题,就必须提出这个问题,让问题的拥有者知道问题的存在。
提出问题Tips:
把" 我的问题" 表述成" 我们的问题"
给上司提封闭式问题, 给下属提开放式问题
指出问题而不是批评人
用赞同的方式提出问题 --》不是说 你这里有问题,而是说,方案不错,我有一点疑问(建议)
欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:
已在知识星球更新源码解析如下:
最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、Elasticsearch、分库分表、读写分离、SpringMVC、SpringWebflux、WebSocket、Dubbo、RocketMQ、Kafka、性能测试等等内容。
提供近 2W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
如果你喜欢这篇文章,喜欢,转发。
生活很美好,明天见(。・ω・。)ノ♡