前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅析:如何构建稳定的系统

浅析:如何构建稳定的系统

作者头像
CSDN技术头条
发布2018-02-11 17:04:01
8210
发布2018-02-11 17:04:01
举报
文章被收录于专栏:CSDN技术头条CSDN技术头条

作者:Jesper L. Andersen

原文:How to build stable systems

译者:孙薇

准备工作

第一个决策是最简单却最为重要的,属于意识形态的一种:那就是软件是由开发者控制的。开发者需要控制软件,而不是反过来,让管理者、产生负责人控制软件。 唯一能控制软件的人就是编写它们的人。

第二个决策就是必须拥有能够掌控的小型工作单元。先解决整个问题的一小部分,并部署到生产环境中,显然比让整个大型项目挂掉要好得多。将初期的小型工作单元作为后面探索的测试平台。

开发者有责任一直掌控软件,这是关键,其它都是手段。一旦出现bug,就会影响到开发者的工作日程(修复bug!);此外最后期限也会确定开发者的工作日程和软件质量(在此之前完成工作!); 这样等到最后期限到来时,开发者需要对尽在掌控的那部分软件实施部署,而将不在控制的那部分回滚。

对软件所做的任何变更都应该是简洁快速的,并且是将系统从一个稳定点移动到另一个稳定点。宁可少完成一些内容,但要保证完成的部分质量优秀。一旦部署的内容中有错误,就会影响到生产数据,修复起来代价极高。最糟的情况下,甚至必需重置多年来的数据,这样十分浪费时间。

软件是以一系列项目的形式写就。每个项目都是很小的、独立的,并有确定的完成时限。每个项目的人数都很少,不超过6个;完成时间都不会超过2个半月;并有确定的成功条件。项目的成功标准就是:该项目能够完成自己的工作。

每个项目都始于24-72小时全神贯注的基础工作,这段时间为整个项目奠定了核心,为系统可行性描绘出了原型。如果基础工作失败了,这个项目就会被放弃,同时让开发者从中学到经验。所有工作都能被简化或者切分为项目的核心工作,这部分工作的目标就是为了查看整体项目是否可行,并建立所有人对这个项目的信心。

每个项目通常都是一次赌博:做从未做过的事或者高风险/高回报就是赌博。选择新编程语言是赌博,使用新框架也是赌博,采用新的应用部署还是赌博,了解哪些地方是在冒险,哪些是软件的稳定因素,这是我们控制风险时需要知道的。准备回滚也是出于负面因素而进行的赌博。

每个项目始于“在这个项目中我们不会解决的问题”列表,列表中的很多东西似乎很有诱惑力,限定范围会帮助我们设定需求重点,定义哪些是未来需要拓展的,并将这些内容放在以后的其他项目列表中。

了解项目在整体中所处的位置:越是核心的组件就需要更多测试、开发速度越慢、对错误关注度也越高。需要了解公司的一般测试水平,否则要在添加更多测试以前,先准备好冒烟测试。测试的核心组件莫过于leaf组件。

了解你的实验,或者整个项目就是一场实验。——Mike Williams

进行实验:在开始项目之前,先进行小规模分析,将其标注为真正项目开始前的预备分析工作,让大家知道你的研发是朝向正确的解决方向的。

了解你要交互的每一部分的代码质量,仔细警惕,找出故障API。了解你要交互的数据质量,如果在使用前这些数据需要多次清洗,也许在清洗干净前不应在项目中使用这些数据。

任何建立在已有系统顶层的项目都需要过渡方案:我们如何逐渐从现有的点过渡到新的系统?大规模部署往往伴随有很多风险,在稳定的环境中,不要冒这种风险。了解数据源是怎样更新的:如何从一个数据源过渡到另一个。连接到多个数据源,并按需进行移植显然是选择之一。开发者必须总能掌控这些问题。

过程与人

人们总会选择对团队有用的方案,比如Agile/XP/SCRUM/Kanban/ThisYearsFad,当然也可以将其全部否定掉。

开发是数字化的,在家办公或者在办公室办公效率相同,需要避免那些有办公室才能执行的方法。5年来大家都在全世界范围内雇用人员,有的公司在印度、德国和SF有3个办公室。

喜欢异步交流的话:电邮、IRC、Slack等都能避免干扰,更容易进行同步。在办公室里设定一些隐蔽的角落,让想要不受干扰、安静工作一阵子的人有个去处。

总有人不喝咖啡,尊重他们的选择。一些人喜欢结对编程,用同一个键盘来解决问题;还有人烦透了这些互动。了解在团队中,人们偏好使用代码库的方式有哪些不同。

坐在椅子上并不代表这些时间都花在创造生产力上面了。很多时候,不在电脑前的时候反而能获得解决方案。灵活的工作时间和工作场合对生产力来说必不可少。聪明的人不会在离开工作时马上关闭工作状态的“开关”。所有团队都会有人犯错,这是正常的,从错误中学习,习惯解决问题。

系统设计

系统是为了生产力而构建。也就是说,系统并不是玩具,不能只完成自己那一份,就丢到生产环境不用再操心了。系统是用于生产消耗的:需要考虑如何在生产中配置系统,需要考虑内部依赖,并进行限制,还需要让系统易用、易维护。

系统建立在12-factor理论之上,包括一套松散耦合的模块,每个模块都有一个责任,并为了让软件的其他部分正常工作,而对这个责任进行管理。模块之间通过协议进行松散的通讯,也就是说在通讯中,各方都可以变更,只要仍旧通过相同的方式进行通讯。在设计协议时要考虑到未来的扩展问题,每个模块在设计时都要考虑依赖,各模块都可以随时替换掉,将其放在另一个系统中需要是仍然可用的。

要避免会滋生紧密耦合的深层依赖架构,在模块太过巨大时需要将其作拆分处理,但也要避免一大堆太过细小的模块。要保持复制功能的能力,从而减少依赖;依赖越少,有时候反而会越成功。

在通讯结构中,端点是智能的,中间件只需传递数据。即多层次发掘参数状态:所构建的系统可以接纳任何难解的数据团,并进行传递。避免中间件对数据进行解析和诠释。代码和数据一直在变化,因此在数据上保持参数化会让一切变得简单。

可以准备一个管理或重启策略,如果以Erlang进行编写,这个策略应该已经有了,如果用其他语言,必须在应用内部(细节更好)或者通过操作系统(保证粗粒度)构建这样的基础架构。

系统偏好通过幂等性实现棘轮效应的方法,从已知的稳定状态过渡到计算出的下一步状态:如果成功的话,会对一致性进行验证,然后保持在这种状态中;如果失败的话,就会放弃之前的尝试,再来一次。基本上只有在棘轮侧翼,计算出的系统和有状态的系统之间的系统会没有状态。这点对于分布式系统的尽力交付机制来说特别重要,因为在所有消息中拥有唯一ID,意味着超时状态下可以执行重试,并确保如果在接收系统中拥有执行日志的话,就不会被接收系统重复运行。

构建总是能与状态点及时“同步”的系统,这样就避免了所构建的系统通过单独模式进行在线处理、离线同步,使得代码路径重复存在、非常复杂。

遵循UNIX原则:每个工具只做好一件事情,避免在一个工具中增加更多功能,而是另起一个工具。

预先定义系统的容量:在正常的操作中,打算实现多大的负载?正常运转的负载峰值是多少?

安装工作

首先创建一个空项目,将其加入到持续集成中,再部署到staging环境中。如果是个没有用户的全新项目,也可以直接部署到将要设置生产环境的机器上。一旦运行起来,就可以开始构建应用了。随着需求增加,我们在部署链中也增加必须的配置。

持续集成会产生构件,以独立的方式,不依赖主机环境,保存简单设置的方式来构建代码。设法预先配置系统,在部署时就无需外部依赖了,在以后可以节省大把力气。在staging和生产环境中部署的构件是相同的,根据环境进行配置。可以采用磁盘、consul、etcd、DNS或从S3上下载的某个配置文件。这里宁可使用简单的技术,也不要过早使用高级技术。

构件具有可复制性,将依赖封锁在特定的标签或版本中,让升级的依赖关系成为自己可以掌控的决定,目的就是为了让应用包具有可复制性。避免外部因素对应用突然产生影响,把一切搞的一团糟。

构件包含运行软件所需的一切,或是二叉树,或是包含二进制的目录树。通过静态链接相连,Go binaries、OCaml binaries、Haskell (GHC) binaries或者Erlang/Elixir发布都是优秀的构件样例。部署系统会读取部署信息,而部署信息就填充在这些构件中。

在不到1分钟之内,在第一个实例中点击按钮执行操作(button-push-to-operational-on-the-first-instance)以实施生产环境的部署。

创建每个应用中都会包含的默认数据库。数据库包含debug/追踪组件,收集和输出指标的工具,让应用成为网络聊天机器人等等。在每个应用中使用同一个数据库。

开发工作

代码正确比开发速度更重要,代码优雅比开发速度更重要,代码质量比开发速度更重要,其实速度真的不太重要。

在开发开始前应当定义一个点,号称“足够好”的点,在到达这个点的程度之前都不要进行优化。大多软件的开发无需速度。

在执行算法和数据架构优化前进行测量,了解变更是否起到了想要的作用。构建的系统应当能够在运行时收集自身指标,将这些指标发送给中央点执行进一步分析,查看Gil Tene在HdrHistogram上的工作,并利用这个工具。

竭尽所能使用手边的工具:单元测试、基于属性的测试、类型系统、静态分析以及性能分析。完全没有理由拒绝使用能帮助你早点解决bug的工具。由于对生产数据造成变更,bug越晚发现,修复所花的成本就越大,要成指数倍的增加。

软件在构建时就是为了运行在不同的环境中,特别是UNIX。系统需要适应不同的运行环境,如果锁定特定平台,一般就会出现问题。如果只能运行在Windows上,那就糟糕了。因为锁定单独供应商的话,你的存亡要取决于他们所提供的软件质量。

大多时候对代码格式的讨论是没有意义的。因为标准在制定时也是随意的,之后大家都来效仿。在GO语言中,只要运行“go fmt”,讨论就被终结了。

了解在系统中出错的关键在哪里:哪些地方绝对不能出错,哪些地方对正确性的要求更为宽松。将出错的关键隔离出来,着重进行测试,在系统的边界使用负载调节,以避免过载的情况出现,而不要从内部进行负载调节。如果工作量太大,需要拒绝某些工作,为一些选中的人提供服务,要比想为所有人服务,结果却完不成要好得多。

使用循环切断器来切断级联依赖故障,由于可以手动进行切换,在需要维护时这些更是必不可少的。

选择数据库

默认的文本编辑器是ed(1),默认的数据库是postgresql,除非数据集大于10TB,否则选postgresql就可以了。如果需要MongoDB之类的功能,可以创建jsonb列。将数据放在postgres中进行分析,然后移出。利用Postgres来存储数据,从中输出到elasticsearch中;对postgres中存储的其他数据进行预热,除非负载增加,否则运行postgres的读取副本就可以了:使用pg_bouncer。

大多新的数据库在一致性与安全性保证上都有问题,特别是不够成熟的变体。它们的“call me maybe”运行模式很可能因为意外而导致数据无法存储,特别是在分布式数据库中,通过网络连接的情况下更是如此。咨询Kyle Kingsbury在Jepsen项目中的最新发现,使用它来获得你需要的保证。

很多新数据库性能都很有限:在特定情况下使用良好,一旦超出这个范围,或者将负载/压力增到承受能力之外时,就会惨遭失败。如果开始执行非常复杂的事务时,需要进行切换,我们很难长期抛开数据库设计,特别是在需要分布式操作时。将复杂的事务互动独立出来,只开放少量的存储区域,这一般是出于经济因素。在可能的情况下,寻找等幂的棘轮效应方案。

选择编程语言

想要系统稳健,必须得在系统中某处选择Erlang,除此之外没有其他语言更能满足运行稳定所需要的准则了。

如果没有选择Erlang,就必须在Weapon-of-Choice™中重新实现Erlang的概念。需要避免单一化,用C#或Java编写全部代码意味着有的项目能够解决地很容易,有的项目就会很难。尝试混用多种不同的语言,以便提供不同的权衡策略;这将会使得选中最切合问题语言的机会最大化。

了解语言的劣势:Python不太适合大规模并发,Erlang不适合需要原始计算能力的问题,OCaml在并行执行上不够成熟,Go不适合需要优秀抽象能力以及复杂故障模式的问题,某种语言只有在你拥有其轻松部署方案时才是成功的。工具部署必须在使用前完成,不管什么语言,所有的项目都使用相同的配置和构建工具:make(1)。

持续集成和部署的常用语言是make(1),在公司的所有项目中都使用make,新人上手简单,还能重复持续集成工具所做的工作,构建软件的方式也有了相关“文档”。

配置工作

构件中有着默认配置,需要变化的内容都是环境相关的。利用“12-factor”,从环境变量中选取配置。持久性数据会存放在专用的地方,位于构件路径以外的地方。这些应用日志记录到默认位置,上限不会超过提前设定的某个磁盘空间常数量,从而使得循环建立起来。

应用不可修改构件路径。

在生产与staging环境中使用不同的凭据,避免误配置,将staging与生产环境的网络分离开来,这样staging的部署就不会影响到生产环境的部署了。不要让开发者用笔记本随意访问生产环境,加上一些限制。

避免过早采用etcd/Consul/chubby等安装,除非规模足够大,否则都无需使用完全动态的配置系统。在引导时使用从S3下载的文件,多数情况下都足够了。

运营工作

为休息时间做出优化,必须尽力避免让工作人员——无论是运营者还是开发者半夜爬起来处理问题。系统必须能够完美降级,部分降级的系统一般还能运行,而无响应的系统则是出现故障。使用这种办法,避免打扰工作人员的休息。

系统可以使用monit、supervise、upstart、systemd、rcNG、SMF等等,但不会使用tmux/screen。运营系统在放弃前能多次重启出现故障的应用。

考虑给软件使用单独的堆栈,而不是借用系统堆栈,以避免不得不使用旧软件的情况,并使得在不同型号的机器间进行堆栈迁移更简单。

kill应用总是很安全的,对于Amazon控制的事件,我们是没有控制权的,必须按照命令,对应用执行停止和启动操作。引导主动请求并不是好选择,为了让工作完成,必须耗费时间按照启动的相反顺序一一关闭内部系统。

开发者一般不对生产环境的主机进行日志记录,每个日志文件都是在系统之外发送和索引的,指标也是这样。开发者在staging主机上工作,这样就可以在大多数情况下(超过90%),不用访问生产环境就能重现错误,并有足够的信息可以发送。

在故障出现之前,指标一般就会指示出故障。剔除用奇怪的方式放弃使用系统的消费者之后,剩下的往往代表了大家使用系统的方式。了解系统为什么在特定消费者那里出现峰值,能够让故障处理成为前瞻性风险。随着负载增加,极端价值成为常见的事情。

唯一能对生产环境主机进行变更的方式就是重新部署,唯一能对staging主机进行变更的方式也是重新部署。

现在我们的世界是弹性的,运转新机器、jail或域名都很便宜,因此可以用在运营中。重建数据中心,利用开关实现负载均衡,让回滚和部署降级变得容易,逐渐切换使用新代码的流量(高风险),在实现全额负载前观察是否适用。为风险部署和问题修复提供充足(超额)机器,然后逐渐向下缩减,观察软件运行是否良好。

人们很容易在生产环境部署出现故障时,通过回滚来处理问题。这样做的风险很有可能失去控制,因此要留好后路。如果想要一天多次部署生产环境,那么手边要准备一组稳定的主机,预备回滚。

截止2016年2月,Docker还不成熟,目前暂且避免在生产环境中使用它。目前它还不能满足需求,不过这种情况总会随着时间改变的,需要继续关注,了解该采用它的时间。


(责编/钱曙光,关注架构和算法领域)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2016-03-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CSDN技术头条 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
持续集成
CODING 持续集成(CODING Continuous Integration,CODING-CI)全面兼容 Jenkins 的持续集成服务,支持 Java、Python、NodeJS 等所有主流语言,并且支持 Docker 镜像的构建。图形化编排,高配集群多 Job 并行构建全面提速您的构建任务。支持主流的 Git 代码仓库,包括 CODING 代码托管、GitHub、GitLab 等。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档