英文原文来源:http://lenadroid.github.io/posts/adjustable-flexible-architecture.html
本文来源:http://www.infoq.com/cn/articles/architecture-optimization-and-design-the-architect-must-know
版权归原作者所有。本文有改动。
更多干货:ID:geekweekly
概述
你可以叫它SOA的新玩法、微服务、或者任意其它酷炫的名字。近几年来随着互联网的飞速发展,新的架构实践方式不断涌现,但是有一件事情是永恒不变的,那就是-“架构之道”;关于如何设计出灵活、高可用性以及能够快速适应变化的系统架构,我们依旧还有很大的发挥空间。本文会介绍关于如何构建前沿的、易维护的、安全的架构的几个要点,同时你也可以把它当作系统设计的准则或者用它来验证现有的架构是否合理。
就像我们经常所说的:没有最好的架构,只有最合适的架构。一个好的架构师,可以根据具体的需求、所拥有的资源等因素综合考虑而设计出最优的架构方案。特别是现在,业务的飞速变化、数据无处不在等这些因素的影响下,技术和框架也需要在变化的过程中不断地打磨和提升以适应新的业务需要。可能当时是最好的架构,但是后来我们还是要跟着业务的变化去做改进。这并不是一件坏事情,我们只要做好应对变化的准备即可。
与代码无关
架构师这个词的意义非常广泛,有些架构师是指在公司负责编写软件的某些模块的人。当然多数公司并没有这样的职位,他们会有一些技术leader来负责具体的功能。我们这里所要讲的架构师不会太过于关注代码的细节,而是更关注系统各个模块之间如何协同、交互等一些更全局的一些事情。他们主要关注一些可能经常会被人遗忘但是又会为系统带来恶劣影响的部分,职责是确保所有的功能都能够以较好的质量及时被交付。这种人对于软件产品的成功有着举足轻重的地位,当然他们往往在一个公司里面可能同时负责好几个项目。
想象一下,两个不同的架构师来建造一艘太空飞船。第一个选择用纸来糊一个样子比较好看的,然后把这艘飞船放到一个漂亮、大小合适的玻璃橱窗里面保护起来。飞般看起来可能像下面的这个样子:
第二个架构师决定用乐高模型来拼一个太空飞船,它们可以随意组合并且比较坚固,所以并不需要额外的特殊保护。
两艘飞船看起来都是挺不错的,但是第一个用了较长的时间来完成并且后来当他们需要对这艘飞船做改进的时候,问题就暴露出来了。
第一位架构师几乎炸了,因为每一次的改动时候,他们必须要移除那个保护罩,并且需要重新再造一艘完整的飞船。虽然他们已经有了所有的模型,再加上造飞船对他来说已经很熟悉,但是他们还是花了很长的时间去完成每一次改造,另外还需要再做一个新的保护罩才能装的下那艘新的飞船。
但是对于第二位架构师来说,这一切都是不需要的。他只需要对产生影响的一些组件进行改造,制作新的组件,当一切准备就绪再添加到原来的飞船即可。
再后来,第二位架构师希望能更进一步的优化他们的制作过程,因为他们现在投入了很多的时间在上面。在经过一段时间的研究之后,他们决定尝试用一种新的材料和方法来制作这艘飞船。也就是3D打印,他们申请了一台3D打印机,制作了所有的模型,这样他们就可以将一些常规的任何通过3D打印机自动完成了。
当然,这只是一个很简单的例子。但是我们能从中学到什么呢?虽然两位架构师在最开始的时候都能成功的完成最初始的功能,但是他们都面临着变化所带来的系统的调整。在集成阶段,复杂性开始显现出来,和最开始的目标无关,最终整个设计是否足够灵活、可调整、以及模块化起着至关重要的作用。
软件的架构至关重要,仅仅有较好的代码来完成功能不足以成为一个优秀的解决方案。因为它不仅仅涉及到代码,还有我们所写的各个模块之间如果交互和集成、数据如何存储、我们怎么样来进行开发和测试、以及在引进变动的时候的难易程度等等。
这些事情都是和编写代码无关,但是需要我们来花时间考虑, 并且是整个系统最后是否成功的决定性因素。
要去考虑的细节
还有一些原则比如:模块化、轻耦合、无共享架构;减少各个组件之前的依懒、注意服务之间依懒所有造成的链式失败及影响等。
DDD给我们提供了在不同的特定领域上下文以及业务功能的基础上拆分组件的指导方法。
把服务独立出来提供特定的功能,同时方便在应对变化的时候能够不影响其它的服务。
在大多数情况下,如果需要同步更新很多个服务则说明系统的耦合还不够低。当然,再完美的原则也会有例外的时候。比如当你想把系统部署在一些IoT设备上的时候,你可能要一次性部署所有的组件。这是允许的,但是,请尽量考虑服务之间的耦合及灵活性以应对将系统部署在不同平台上的需求。
即便如此,我们也不可能完全避免耦合,它总是会出现在某些场景下。这就需要我们提取一些抽象层将服务之间的交互定在契约上来避免复杂,提升灵活性。
这就需要我们有一种辨别能力,能够找到那些必须放在一起来做处理而不能拆解的功能。如果这些功能是值得放在一起的,那我们就可以将它独立成一个微服务,遵循高聚合的设计原因。
我们要记住的是,系统的设计要做到比较容易地增加或者修改原来的组件。无状态的架构是系统高扩展性的基石。
特别需要注意服务和组件之间如何交互,了解不同的协议的优缺点,包括速度以及可用性,来帮助我们来决定哪一种是最适合我们的。
基础设施、配置、测试、开发、运维
为配置管理定义策略,因为同时发生的配置变化对系统所有造成的影响也是很重要的,所以需要由全局层面的的自动化更新方案来完成。
在如今,对于一个数据敏感的大型解决方案来说如果没有自动化的一些基础设施和稳固的开发、测试和部署流程,那就和自杀无异。我们需要花费一定时间来计划和准备开发、测试、生产环境,可能还需要一些额外的环境以备不时之需。
测试流程和策略也是非常重要的。一些最佳实践使用Blue-Green开发、金丝雀部署、A/B测试等。尽量保持测试环境与生产环境是一致的,至少硬件结构上来说应该是一样的。一定要做压力和负载测试,并且尽可能快的在生产上进行这样的测试,这样能够更快速、精确的帮我们找到线上的问题。
可调整的架构同时也意味着服务要可以被灵活独立的部署以及简单的基础运维操作。
利用不可变基础设施的优势。
不可变基础架构,就是说系统一旦部署,就不再更变升级。当服务/应用需要升级时,只要部署一个新版系统,摧毁旧版就好了。在这个过程中,系统对外服务是几乎是持续的。(译者注)
保证打包/持续集成进程是基于统一的途径,并且不会对正在运行的服务进行任何更改(比如 禁用ssh),所有的更新都应该通过定义好的自动化配置和打包操作将它们应用到所有的对应的系统上,来避免配置遗漏。比如手工某个环境上修改配置,可能会漏掉其它环境的配置。
开发团队不应该过度关注基础设施,因为有一天可能基础设施也会发生改变,所以与业务相关的开发不要和基础设施有过重的绑定。
代码之间的东西(in-between the code)
"in-between the code" 可以统一的概括为一些基础设施所提供的功能,比如:服务发现、请求路由、网络通迅层、代理、负载均衡等等。很多生产上的错误并不是因为代码的业务逻辑错误或者每个独立组件本身的问题,而是由于这些把各个组件协调起来的一些通用基础设施。
随着系统的变化越来越快,更要注意我们所更改的一些组件,考虑可用性和扩展性。制定最小风险的计划来应对出现的问题。
无处不在的坑
做一个偏执狂。为失败而设计架构 - 列举所有可能失败的地方。和团队头脑风暴对所有可能失败的地方进行质疑然后提出保护方案。
如果连接建立失败怎么办?
如果花费的时间超出预计怎么办?
如果请求返回不清楚的数据或者不正确的答案怎么办?
如果请求返回的数据不是约定好的怎么办?
如果出现很高的并发能应对吗?
如果服务挂、机组、整个数据中心挂掉了怎么办?
如果数据库损坏了怎么办?
如果部署的时候失败了怎么办?
如果在部署成功之后生产环境上某些功能失败了怎么办?
集成性这方面的错误可以有千万种可能,那么我们应该如何来避免?
使用一些技术比如:熔断(circuit breaker)、超时设定(timeouts)、握手信号(handshaking)、舱壁(bulkheads)来帮助我们保护这些系统集成之前的问题。
熔断模式(circuit breaker)可以参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
舱壁模式(bulkheads)该模式像舱壁一样对资源或失败单元进行隔离,如果一个船舱破了进水,只损失一个船舱。比如采用微服务是首选,比如docker。Docker是进程隔离的,单个 docker失效不会影响其他docker容器。或者把大的并行处理工作,由多个线程池来负荷分担。(译者注)
当然,如果当它开始工作的时候,说明我们的系统出现了比较大的问题,需要我们去调查分析。
注意那些不能看到代码的组件、依懒项以及共享的资源。除了有正确的开发和测试流程以外,还应该尽量使用和真实生产环境一样的数据、以及硬件网络配置来进行测试。
跟踪系统的响应来防止一些比较常见的问题比如服务不可用的情况,留意系统的平均响应时间,当它有异常的时候需要寻找原因以及采取相应的行动。
搭建日志、监控、以及系统操作的自动化操作平台。由于微服务相对来说较独立,可以更方便检测失败 所以监控起来会更容易一些。一些比较不错的方式是在收集和分析日志的时候使用关联ID、通用日志数据格式等。注意日志数据可能会非常庞大,所以要考虑日志的时间周期,定义对日志进行归档。另外还有一些不错的工具可以将数据可视化在页面中,可以更直观的看到一些重要的进程。
为了保证服务的更新不会影响客户端的使用,对于服务的版本控制也是非常重要的。有些情况下同时运行不同版本的服务也是很常见的情形,我们要做好长期向后兼容的准备。
务必要记住的事情
大多数情况下我们并不是从零开始去构建,而是在现有的系统上继续添砖加瓦,而原有的系统在开发、运维、以及架构灵活性上都存在一些问题。想必很多优秀的开发人员在经历这样的情况的时候,都会想去拆解、重构整个系统,但是我们需要谨慎地来完成这个事情。当系统以错误的方式成拆分成组件或者服务单元之后也是一件很危险的事情 。
大多数系统在一开始的时候都是一个单体应用,后来不断地被拆解成为微服务。下面有一些基本的理念可以在我们做拆解地时候当作参考:
在开始拆分前了解具体的业务需求和业务领域
注意一些和其它业务共享的功能和数据,它们需要被正确地模块化
这种迁移和升级适合一步一步、一点一点地来完成,仅仅做当前最合适的事情
在开始之前很好地理解业务领域的范围及边界,因为对边界的调整将是非常昂贵的
对于改造有清晰的结构此次会涉及到哪些团队的调整
人、团队、和组织的影响
这个话题太大,大到我们需要专门写一篇文章来细述。在这里简单地概括一下,在本文中我们所提到的架构的灵活性以及稳健的开发、测试、运维等流程都会影响企业的组织结构。合适的组织结构能够给团队更大的灵活性并且更有机会持续地创新,在这种组织结构下,团队可以根据自己的节奏来工作。组织不应该按技术来拆分团队,比如前端团队、移动端团队、后端团队或者根据不同的技术语言拆分团队等,而是应该按照微服务来拆分团队(也可以理解为按独立的业务单元来拆分)。这样在一个团队里面就会包含各种不同的技术,可以用不同的语言来实现服务,这也给团队更多的自由和自主权。
如何实践?
容器化和集群工具
Docker
Docker Swarm
Kubernetes
Mesos
Serf
Nomad
基础设施自动化/部署
Jenkins
Terraform
Vagrant
Packer
Otto
Chef, Puppet, Ansible
配置
Edda
Archaius
Decider
ZooKeeper
服务发现
Eureka
Prana
Finagle
ZooKeeper
Consul
路由和负载均衡
Denominator
Zuul
Netty
Ribbon
HAProxy
NGINX
监控、跟踪、日志
Hystrix
Consul health checks
Zipkin
Pytheus
SALP
Elasticsearch logstash
数据协议
Protocol Buffers
Thrift
JSON/XML/Other text
等。