《数据密集型应用系统设计》 - 应用系统概览
引言
系统应用概览是纯理论的部分虽然很简单,但是看完之后发现其实很多时候有一些术语在自己的观念里面是很狭隘的,作者在书中用了更加严谨的解释话语论述一些软件和系统设计中常见的问题。
书中提到的一些实验和实际案例都比较有意思。也因为是第一章,内容通常不会一上来就会很难很枯燥,也是比较有意思的一个章节。
介绍
现代应用设计更加趋向单一化和模块化,现代信息系统到数据量极速膨胀,换来的是数据复杂和各模块多变,应用系统通常需要包含下面的内容。
- 数据库:存储数据。
- 高速缓存:为复杂操作减少操作代价,比如CPU的高速缓存,硬盘缓存等。
- 索引:建立数据快速搜索和过滤。
- 流处理:异步方式与另一个进程通信。
- 批处理:处理大量累积数据。
重新认识数据系统
在一个数据系统的架构中,我们通常会判断一个应用系统的三种特性支持,这三种特性即:可靠性、可扩展,可维护性。
可靠性
所谓可靠性不单单指的是系统能在发生异常的时候可以正常运行,实际上包含更多内容:
- 应用该程序执行用户期望功能。
- 容忍错误数据或者不正确的操作。
- 合理到系统负载和释放性能。
- 权限管理。
简单来说这样到可靠性指的是程序上的可靠和系统架构上可靠以及保证数据的安全性。
另外还需要对于一些和可靠性紧密联系并且常见的术语做出更为严格的解释:
- 容错:所谓容错不是指允许一定的错误,而是指在预见的前提下允许某些特定的问题产生。
- 故障和失效:故障是指组件偏离原始设定的情况,系统存在恢复可能,而失效则是指的整个业务系统瘫痪并且将无法提供服务。
- 消除故障很难,但是更多情况下并不是无法解决问题,而是由解决问题到程序产生问题,也就是BUG上叠加BUG。
- 有一些办法来检测修复系统本身是否“正常”,在过去Neflix 的 Chaos Monkey,直译过来就是吵闹的猴子,这个组件通过模拟一些常见故障检测系统问题,虽然比较小众但是比较有意思。
拓展:对于很多小团队和小项目而言 Simian Army 可能没有太多的意义,但 Chaos Monkey 背后的思想值得我们学习和使用。Chaos Monkey 中主要包含了下面的内容 1、Exception Assault (抛出异常攻击) 2、Kill Assault (杀进程攻击) 3、Latency Assault (延迟卡顿攻击) 4、Memory Assault (内存溢出攻击) 可以看看Simian Army项目:Netflix/SimianArmy · GitHub。如果你能访问slideshare,也可以看看这个slides: RE: invent: Chaos Monkey。
- 硬件故障:硬件故障一般是通过添加备用组件以备不时之需,比如RAID硬盘,锂电池,异步容备等,为了实现可靠性同时保证高可用都必不可少,但是这几年通过软件容错到方式逐渐变成新手段,比如升级补丁通过轮流下多节点方式,实现不破坏集群情况下升级。
- 软件错误:软件错误更多指的是长期隐藏而不被发现到BUG,虽然错误到概率比较小,但是一旦出错将会是非常复杂的排查流程。针对软件可靠性保证是永远半信半疑的不可靠,哪怕看似“永远”不可能错处的地方也需要做一些监控和防御措施,这样可以保证问题发生可以第一时间排查。(这句话很重要)
- 人为失误:越是复杂的环节越是容易出错,上线的流程由一个人负责基本只会出现在一些垃圾公司里面,但凡正规流程公司都有类似严格或者不那么严格的上线过程,然而上线配置却常常是系统上线运行更新最有可能遇到到人为失误。
可靠性的保证意味着开发和运营成本的高低,所以是最为值得关注的事情。
可扩展性
如何描述性能
可扩展性在书中提到了推特关于 巨大扇出结构的解决方案,这种结构的典型表现是某个用户受到海量到关注,之后当被关注用户发布新的内容会扇出巨大的请求,以此来支持海量消息发布的需求。
这种结构显然是典型的海量的单节点发布订阅和广播出现的业务场景,针对推特中百万粉丝的博主推送和小众主播的推送解决方案是下面两种:
- 如果是关系型数据库解决方案,是按照关注者关注时间顺序挨个推送新推文。
- 采用缓存的方式进行推送,推送用户的时候如果根据缓存找到同一目标,则直接取缓存推送,这样就减少了不少的系统开销。
这两种都存在一定的问题,第一种是会加剧读负载压力,第二种虽然明显能解决第一种的问题,但是明显存在浪费,最终扩展是发现结合两种情况,对于关注人数较少的用户完全可以使用第一种方案实时更新,但是对于关注量十分庞大的用户则需要第二种方式。
所以可以认为针对扩展性说明是在不同解决方案之间寻找平衡点。
所谓到平衡点则需要考虑下面两个因素:
1. 业务增大需要扩展多少机器维持原有性能。2. 系统资源不变的情况下如何维持性能。
延迟和响应时间差异?差异主要是响应时间会包含一个服务器从请求到返回那一刻所花费的时间,所以这里需要算上网络开销。而延迟体现在处理任务的时候用了多久。这里举个例子:我们上传文件从上传按钮点击到返回正确结果的那一刻所花费的全部时间叫做响应时间,而延迟指的是等待上传动作本身花了多久。
那么应该如何衡量性能指标,我们通常会使用平均响应时间作为参考,但是平均响应时间实际上不能还原性能。
结论是应该通过中位数 响应时间排序的手段判断性能,按照用户的响应时间依照大小排序进行处理。
这里有另一个案例是亚马逊根据用户购物访问网站的响应时间,响应时间1S和销售额的影响。
对于一个请求的优化,在初期把2-3S完成的时间降低到1S是非常有效的,但是到1S以内的优化,比如优化99%的满意请求和1%的不满意请求,优化到最后1%的代价要远高于实际的收益,所以这时候需要转化思路而不是在旧方法方死磕。
所以可扩展性的优化指标目的不是为了极致优化,优秀的优化是对数上升的过程,达不到这个指标就要考虑成本以及是否值得继续进行。
为了观察系统优化,在进行负载测试的时候请求生成端不能是阻塞式而必须是并发方式,否则会存在测试误差。这句话意思是说在任何测试之前需要保证测试和合理并且可靠的。
负载增加扩展
如何应对扩展增加,目前讨论较多的情况为垂直扩展和水平扩展,垂直扩展指的是升级旧系统配置,水平扩展则认为是部署更多的机器分担负载。
多数情况可能认为多台性能一般的机器强过少数几台强劲的机器,实际上如果保证架构足够强劲,只需要几台性能不错的服务器就可以抵掉多台服务器作用的效果,并且水平扩展到一定程度之后是有限的。
在垂直扩展和水平扩展中又分为有状态节点扩展和无状态节点扩展。
有状态节点更为通常的做法是使用一台高性能服务器利用单机负载服务请求(注意这里的服务仅仅只应用程序服务),当单点服务无法支撑之后才会考虑使用水平扩展的方案。
无状态节点的扩展则更倾向于水平扩展的方式,所以通常需要多个机器或者使用主备备份的方式进行容灾处理。
而未来应用很有可能是面向分布式的架构,现代的分布式编程接口和框架架构在不断完善。
最后一点是针对相同吞吐量的机器随着业务场景的不同会有完全不同的架构设计,可扩展的结构通常意味着组件之间的独立和本身的可扩展性,就像是TCP/IP 模型一般。
但是一旦确立业务架构,后续需要调整架构的代价就会越来越高。
可维护性
可维护性包含可运维,简单性和可演。可运维指的是在日常工作中可以通过运营团队维持系统稳定性,简单性既是能用最简洁的逻辑完成需求,还需要保证运营人员能够简单使用,也就是功能完善和系统完整,
总结
这三大特性实际上都指向一个特点:让运维人员更好地维护系统,因为无论多可以自己化的系统,最后还是需要运维人员完成维护操作。
另外,为了实现系统的简单性,不得不引入抽象来解决问题,高级语言也是使用抽象来掩盖CPU寄存器,汇编代码,系统调用复杂性等内容。
运用越庞大的系统越需要抽象的思维,在现代的系统中为此设置了敏捷开发模式,测试驱动开放模式以及重构,两个开放模式从国内环境来看滥用的趋势还算是比较多的,所以我们更应该关注重构的应用。
关于重构的细节,都可以在《重构》这本书当中进行了解。
写在最后
第一章探讨了可靠性、可扩展性、可维护性的理论概念,同时梳理了当前时代下各个应用系统所应对的挑战,并且展望未来随着分布式架构的代码成熟和技术完善,哪怕是微笑项目也有可能玩上分布式,并且可能就会在不久的将来......
关联
系统加速:阿姆达定律
核心:假设程序划分为两部分:不可并行部分和可并行部分。
解释:
假设一个磁盘中的程序加载到内存中,扫描目录并且进行创建文件。扫描目录和创建文件列表的部分不能并行化,但处理文件可以并行。
那么根据上面的说明,我们可以定义下面的变量:
- T = 串行执行的总时间
- B = 不可以并行的总时间
- T- B = 并行部分的总时间
从公式来看,T-B这部分时间是真正可以并行并且通过提高CPU或者线程性能的优化时间。当多个CPU和线程执行并行部分的时候计算公式为:
T(N) = B (T - B) / N(N为处理器或者CPU数量)
这里我们不需要记住复杂的公式,我们只需要知道这个阿姆达定理解释了要优化一个程序的性能,性能的提升远不如我们想象中的那么多,软硬件以及设备IO,内存等每一项都有可能影响程序的性能,同时单项性能优化效果可能并不显著,在进行优化的时候也需要根据实际情况多方位的考虑。
更多的参考资料
英文原版网站:http://tutorials.jenkov.com/java-concurrency/amdahls-law.html 中文介绍:http://ifeve.com/amdahls-law/
另外,阿姆达定律提供了下面的优化方式细节:
- 线程级并发(超线程技术)
- 指令集并行(流水线技术)
- 单指令多数据并行
- 超线程技术