现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

经验之谈:设计全球级的分布式、任务关键型应用

2016-05-19 11:14 工业·编程 ⁄ 共 6662字 ⁄ 字号 暂无评论

关于Kris Beevers

Kris Beevers一直以来都在进行全球级的分布式大规模应用程序的设计工作。当年,他曾经逃课一年来为一个文件共享的初创项目来设计后端框架。而且,项目用户很快增加到了数百万。然而,RIAA律师的出现,导致了项目流产。Kris也不得不回学校继续学习。但是,通过这件事,Kris开始在扩展系统方面积累了一定的经验。

之后,Kris又曾在一家被Internap在2011年收购的互联网框架提供商——Voxel公司工作。当时,他负责构建供很多大型网络公司使用的全球互联网框架。具体来说,他负责构建全球级的分布式公有云、裸机即服务以及内容传输网络等等。从中,Kris遇到了系统扩展中可能碰到的许多问题,收获了很多经验和教训。

现在,作为NSONE的创始人和CEO,Kris在NSONE负责设计下一代智能DNS和流量管理平台。该平台服务于一些最大的互联网公司,其中很多是任务关键服务的提供商。因此,该平台是一个真正全球级的分布式、任务关键框架。随着NSONE平台的扩展,Kris以往的工作经历为完成在NSONE的工作提供了很多帮助。

接下来,Kris分享了一些自己的经验,希望读者可以把这些经验应用到其他项目应用中,避免走很多弯路。

每一个科技公司都会思考一个问题——随着公司业务的增长,如何应对应用程序或者系统在扩展时所不可避免遇到的各种挑战?由此,在设计任务关键技术时,一系列的基本问题就包括:如何从一开始就把可扩展性放入到考虑范围内,从而使得公司以后可以稳步、健康的发展呢?哪些才是值得现在密切关注的关键性挑战呢?而当建立一个分布式的框架时,无论是以可靠性为目标还是性能为目标,这些问题都很难回答。

使用正确的架构和流程可以使得系统和公司能够经受得住分布式、高流量应用程序所带来的冲击。由此,初创人员就可以进行不受约束的应用扩展、有效管理网络和系统失效、实时解决产品遇到的问题,从而引导公司和产品稳步成长。


从架构开始

当编写代码时,如果你总是试图使其尽可能的高效,同时又尽可能的少占用计算和存储资源。那么,停止这么干吧。

在现代分布式系统中,限制系统可扩展性的基本上不是系统的垂直深度或者特定角色内代码的效率,而是不同交互系统之间通信的效率和架构中每个角色的水平可扩展性。因此,不要优化你的代码,优化你的架构!

微服务是一个好想法。它们可以把应用中相互独立的角色进行解耦合,从而可以单独扩展其中的每一个。在担心深度之前,请先计划如何水平扩展角色。服务器速度快、便宜,而且性价比还在不断提高。然而,开发者的工资却在不断提高。因此,尽可能的使用水平扩展。只有当代码真的有问题时,再考虑代码优化。

因此,在写代码之前,请先花时间好好思考一下架构——画一画框图、考虑一下通信和数据约束、形成一份好的计划书。

以角色为单位来考虑系统

一个微服务架构在一定程度上表明你正在设计一个由解耦合的子系统所组成的应用。这些子系统中的每一个都解决一个特定的问题或者担当着特殊的角色。但是,需要重申的是,你应该以角色为单位来考虑你的架构——不仅仅是因为它解耦合了扩展约束,也因为它影响你设计代码、部署系统和建设框架的方式。

一个由解耦合、互相通信的角色所组成的应用可以以进程、VM或者容器的方式在一个单独的服务器中运行。这就使得你可以把应用部署在本地的开发环境、小的系统或者大的产品框架中。

随着应用的产品框架发展和流量及用户的增加,你可以把那些需要单独的系统或者集群的角色分离出去。但是,在你的MVP中,你可以通过组合产品角色来为扩展做好准备。NSONE的最早alpha版本就部署在AWS中一个小的单独的单播t2实例中。现在,NSONE的产品框架已经扩展到几百个服务器,并在全球拥有30个产品设施。随着系统的增长,Kris把关键性的子系统分离到单独的服务器或者集群中,并在需要时添加宽度和冗余。

跨越全球级的分布式系统进行通信是十分困难的

去建立并扩展一个初始只部署在一个服务器、后期部署在一个数据中心的应用是一回事。解决多个数据中心间应用程序的数据传输的难题又是另外一回事。当你的子系统需要与本地之外的系统进行通信时,通信的可靠性就变得十分脆弱。这时,你需要应对的就是服务器失效和通信失效这样的双重难题。

通过互联网进行连接是十分脆弱和不可预知的。在NSONE,Kris等设计系统时就假设边界DNS传输设备会经常性的与核心设备失去连接。在非洲、巴西和印度等市场,这是经常发生的事情。但是,需要保证的是,当通信恢复时,重新聚合一定要快速。慎重考虑命令和控制消息的创建以及排队策略是其中的核心。

考虑不同设备间不同通信模式的通信也十分重要。如果你正在发送延迟敏感的关键命令和控制消息到一个或多个设备,你可能需要考虑鲁棒的消息排队系统;如果发送的是非关键信息,你可能考虑更加轻量和可靠性略差的系统;如果你发送的是需要严格同步的应用数据,带有鲁棒WAN响应的现代数据库可能是不错的选择。当在不同设备间通信时,你需要根据任务选择正确的工具。

对于同步和分片而言,一致性哈希是一个杀手锏武器

现在,是时候来考虑水平扩展了。你已经拥有了一些相互之间通过局域网或者互联网进行通信的子系统。为了高效服务前端的请求,系统需要尽可能的把请求发送到本地的后端进行处理。但是,在一个特殊角色中的服务器不可能是万能的。那么,究竟选择一个水平层中的哪个节点来处理跨地域的请求或者任务呢?

直观而言,你可以配置一个静态或动态的路由表——拥有某些特定ID的请求发送到特定的服务器。但是,当系统扩展时,路由表的扩展将会十分痛苦。例如,你可以把请求id哈希到一个服务器。一旦一个新的服务器添加进来,所有的请求就都需要被重新路由,数据或者缓冲的局部性一下就会被打破,由此可能引起系统崩溃。当然,你也可以设计一个十分复杂的协议来解决这一问题。但是,通常情况下,这些协议都效率低下,且容易出问题。

一致性哈希应对这种情况十分有效。它可以在不进行实际通信的情况下,保证所有节点遵循同样的协议。而且,它易于实现和扩展,子系统资源变化时所引起的代价也很小。在一个拥有很多交互角色的复杂系统中,使用一致性哈希来分配多个节点集合间消息和请求是一个十分明智的选择。

在NSONE,Kris等采用了各种方式来使用一致性哈希,包括把DNS请求路由到特定的CPU核来最大化缓存一致性、不进行通信的情况下协商监控节点的任务以及聚合节点跨层大容量数据的分片等等。

测量和监控一切东西

这是一个老生常谈,但又不得不再次强调的东西。不测量,你就不可能充分理解。即使当你没有遇到扩展性挑战的时候,充分理解系统行为也能在将来帮助你了解系统扩展时的瓶颈所在。

当然,不要只是收集一些系统度量。要让你的应用能够理解数据库响应时间、消息延迟、缓存命中率、内存碎片以及磁盘I/O等。首先,收集这些信息,并理解系统正常运转时的值。这样,系统发生异常时,只要重新检查这些值中有哪些异常,就可以很快定位问题所在。而且,当一个新类型的负载或者额外的流量添加到系统中时,你可以迅速感知,并相应的调整架构或子系统。

在NSONE,Kris等就认真研究了度量值,并将其时刻显示在监视屏幕上。当发现异常时,团队人员会问为什么,并深入挖掘。通过这种深入理解,NSONE团队可以非常清楚平台对新的流量和负载的反应,从而特别自信的服务互联网中最大客户的DNS和流量管理需求。

与测量同样重要的是监控。测量和监控是两个不同的东西——测量是为了更好的理解系统行为,而监控则是探测行为中存在的异常。在项目成长过程中,监控和警告可以帮助你理解哪些异常是无害的、哪些是有害的。在项目启动之初,你可能花费很多时间来区分这些异常。但是当项目发展壮大以后,你就可以根据之前的理解,只关注那些可能影响服务的异常,避免重新设计系统的架构。

从多个有利的点来进行监控。不仅仅要从多个物理位置,还要从多个逻辑位置来进行监控。在NSONE,Kris等就监控内部异常监测数据,使用Catchpoint和Raintank等第三方服务进行由外而内的监控,而且利用多个数据源来观察平台和网络事件。

同样重要的是,监控不仅仅是只看到系统的状态。对于一个系统而言,提供好的服务是什么意思?是低延迟的响应时间?还是高的缓存命中率?监视这些度量,当他们超出正常范围时,发出警告。

集成和功能测试非常关键

每一个软件开发的课程都会反复强调单元测试的重要性。无论你是正在进行测试驱动的开发还是只想炫耀一下代码,没有进行单元测试的情况下,你仍然无法肯定代码的功能。而且,随着代码的修改,单元测试要不断进行,从而保证代码的功能正确性。

在一个分布式应用中,即使你拥有全世界最好的功能测试覆盖,系统仍然有可能出问题。单元测试永远不够!

你需要测试不同子系统之间的交互情况。例如,如果配置数据的一个特殊部分变化,会有什么影响呢?它会不会影响到子系统A与子系统B之间的通信呢?又如,如果你改变了一种消息格式,产生和处理这些消息的子系统还可以继续与其他子系统进行通信吗?代码修改后,需要依靠4个不同后端子系统给出的结果的一类特殊请求还会得到正确反馈吗?

单元测试回答不了这些问题,但是集成测试可以。在集成测试环境中投入时间和精力,并且在研发和部署过程中要适时进行集成测试。理想情况下,最好一直在产品系统中运行集成测试。

没有中断维护这种东西

如果你正在设计一个真正的任务关键型应用(客户需要依赖该应用来打理生意),那么该应用可能不需要开关。它永远不需要停止工作。那么,你永远都不会有终端维护的权利和可能。即使是世界上最复杂的后端架构也会有需要改变的时候。

这就是你需要认真思考架构的其中一个原因。几个小时的认真思考可能可以避免后期几个月的漏洞弥补时间。

Kris就遇到过一个真实的情况。NSONE的团队从一开始在架构设计阶段就投入了大量精力,使得项目沿着正确的方向前进。但是,该团队仍然有没有考虑到的问题——平台接收高频率的数据反馈。这些反馈会影响到如何对复杂流量管理配置下DNS记录的请求进行回答。数据反馈可以应用到多个DNS记录中,也就是说一个服务器负载信息的反馈可以通知牵涉到服务器上多个网站的流量路由决策。

当时,NSONE团队假设只有一个单独的数据反馈连接到若干个DNS记录。那么,就可以通过把到达系统的数据反馈根据每个所连接的DNS记录发送到相应的边沿位置来节约大量时间和精力。然而,团队的假设是不成立的。一些客户把数据反馈连接到了数千个DNS记录。前期的偷懒导致内部发生DoS,而且随着系统的扩展,问题会越来越严重。

其问题在于:团队不能通过发送比较少的控制消息来解决以上问题。Kris等需要修改数据模型、消息模型和一些一直在运行的系统。前期只需要2-3个小时就可以避免的问题却引起了6周的“马拉松”——头脑风暴式的会话、相当复杂的反应、大量正确性测试以及一系列的小心翼翼的部署和迁移等。所有这些都只是为了在不中断系统的情况修复以上的问题。

在极端情况下,新框架或者代码的每一次部署都会是无缝的——仔细的规划、滚动重启和集成测试等。一旦系统在服务用户,就永远不能关机。

非常小心地进行自动部署和配置管理

现在的开发生态系统拥有大量的自动部署和配置管理的工具,包括Chef、Puppet、Ansible、SaltStack、Terraform等数不胜数。选择使用哪个工具就像决定哪个模型更好一样根本无关紧要。真正有关系的是你要使用这些工具。即使是在公司的初创阶段,即使事情看起来非常容易搞定,你也千万不要手动管理配置或者部署——你会犯错、你会限制自身能力的提升以及使得以后的自动化变得十分艰难。但是,要小心:权力越大,责任越大。自动部署工具使得你可以把平台迅速搞砸。

利用Chef管理所有主机的IP表?一个疲惫工程师的一个无意的按键动作就可以使你的平台全部瘫痪。把一个经过反复测试的特性部署到开发用的环境中?一个真实环境和模拟环境中略有差别的流量就可以使得端到端自动部署的产品崩溃。要善用自动化工具!

Kris等就利用Ansible管理NSONE的配置和部署。这是一个非常好的工具。 NSONE团队本可以把一切自动化,实现一键部署新的DNS传输代码到所有的边缘位置。但是,他们没有这么做。他们选择从最低流量到最高流量,一个设备一个设备的部署。在一个设备内部,他们甚至挨个核或者挨个服务器,运行复杂的单步功能测试。在移动到更加关键的设备前,该团队还会花费数十个小时或者几天时间来研究度量,确认没有潜在的问题。而且在部署之前,他们还会进行应用程序代码以及Ansible配置等的仔细审查。

对于你的团队和应用而言,要适时的使用自动化。但是也别忘记,自动化加速项目发展的同时,也可以加速项目的死亡!

做好“消防演习”

坏的事情总会发生。每一个科技公司都会遇到服务器崩溃的情况。NSONE团队在项目之初就想到了各种服务器崩溃的情况——磁盘失效、网卡失效、RAM失效、内核崩溃、虚拟环境中的邻域效应等等。各种情况都可能非常容易的引起服务器失效。

此外,电源供应也可能出问题。还记得飓风桑迪吗?NSONE的员工不得不不断加注柴油,才能保证系统在发电机的帮助下保持工作。而且,光缆也可能会断。BGP有可能被黑。你也可能遭遇64k的ICMP报文、DNS和NTP放射放大攻击以及等等。你该怎么办呢?

唯一能做的就是提前进行演练。在坏的事情发生之间进行演练。Netflix的Chaos Monkey就是一个很著名的例子。你不可能直接模拟可能遇到的每一种状况,但是你可以尽力去模拟你的反应。唯一能够保证你在遇到状况时可以保持镇定的方法就是,使用你已经提前放置好的工具,然后快速反应。

最小化表面积

随着你的应用程序变得越来越分布,暴露给恶意攻击的表面积也会在不经意间迅速增加。看好你的系统,并最小化系统暴露给互联网的表面积。

架构中的每一个角色都应该只向那些允许访问的系统集暴露其服务接口。面向互联网的系统只提供面向用户的那些服务。在后端,系统之间应该尽可能的通过私有IP空间进行通信。否则,也应该通过加密的通道进行数据传输。无论是使用像AWS的安全组这样的提供商的工具,还是使用路由器ACL或已经防火墙进行ip表规则的自动管理,你应该尽可能的是防火墙。首先拒绝,然后在逐个按情况放行。

永远都不要允许通过SSH等对产品系统进行直接访问。人们应该通过采用多元素认证、端口试探、IP白名单等重重保护的主机来进入系统。确保主机分布于各种网络、多个地域。进入产品架构的权限应该也限制为只有堡垒似的主机才可以。

有很多种策略可以帮助你看好系统。你只要去寻找,然后从一开始就考虑到系统的安全,使得安全成为系统的一部分即可。千万不要随着系统的扩展而淡化安全因素。

了解供应商环境

几乎每一个现代技术公司都选择把第一代产品放置在AWS、DigitalOcean或者其他低价低门槛的云服务提供商中。互联网一直在快速发展。由此,云服务提供商也在近些年迅速增多。绝大部分公司都没有必要在扩张时着急放弃AWS。但是,每一个公司都应该尽可能的保留可选性。

你或许会发现应用程序中的一些负载在裸机中运行效率最高,也可能发现你需要一个在澳大利亚的CDN,或者在没有AWS存在的市场中获得很多关键性的用户。那么,花一些时间来让自己熟悉框架生态系统的各个方面——IaaS供应商、场地出租、DNS、CDN以及监控等等。在早期设计架构时,就考虑到以后可能需要迁移的地方。时刻准备着迅速迁移。

NSONE公司就操作着一个全球任意播的DNS传输网络。当建立原型的时候,AWS起了很大的帮助。但是在平台部署之前,他们就不得不迁移到别的地方来满足网络需求。设计一个非常具有竞争力的任意播DNS网络很大程度上依靠公司与框架供应商、网络供应商等进行协商,获得尽可能多的控制权。在很多情况下,Kris等人不得不帮助供应商来找到合适的合作方式。

千万别忘了:基础设施运营商也都是怪胎,他们也经常欢迎各种新的想法和挑战。时刻了解生态系统,做好准备工作。

总结

Kris已经分享了自己在设计和扩展分布式、任务关键系统时的经验和教训。或许,你不能在项目初创之时就把一些都规划好。但是,你可以努力把自己放在一个更好的位置,来迎接公司扩张所带来的挑战。

此外,正在设计互联网产品的公司也应该认真思考如何以分布式的方法来设计产品架构和扩展之路,从而为用户提供最大的性能、最好的可靠性和安全性。

给我留言

留言无头像?