研发员工指南-原则篇

这是一个新的产研团队、新研发员工加入我们的技术体系所应遵循的技术流程规范、工具、原则合集,也是我们花费了昂贵学费换来的……经验教训。

上一篇:研发员工指南-流程篇

5.原则篇

软件工程和IT技术领域里虽说法无定法,但也并非可以天马行空,否则稍不留意就可能墙倒屋塌,覆水难收。心中无原则,这样也行,那样也行,那就有一百万种死法。

You!Leaders!一定要通过层层嵌套的“Rules”建立起本能反应,一遇到类似的事情,应激般地就知道该怎么设计,怎么行动,怎么救火。而这些“Rules”一定是经历了血与火的洗礼铸造的,每一条都有来龙有去脉。

比如说,我们在2018年定义的DevOps新八荣八耻,每一条都是血肉长城:

1.以随时可扩容、可缩容、可重启、可切换机房流量为荣,以不能迁移为耻。

2.以可配置为荣,以硬编码为耻。

3.以系统互备为荣,以系统单点为耻。

4.以交付时有监控 警为荣,以交付裸奔系统为耻。

5.以无状态为荣,以有状态为耻。

6.以标准化为荣,以特殊化为耻。

7.以自动化工具为荣,以人肉操作为耻。

8.以无人值守为荣,以人工介入为耻。

法则从何而来?

瑞?达利欧曾经说过,每个人都会犯错,主要的区别在于,成功人士能从错误中吸取教训,而普通人不能。为了从自己和他人的错误中吸取教训,你必须坦诚、公开地承认错误,并努力避免再次犯错。在这个问题上,很多人会说:“不,谢谢,这不适合我,我宁愿不管这些事。”但这不符合公司和团队的最佳利益,并会阻碍我们达成目标。如果你回顾一年前的自己,而没有为自己所做的傻事感到震惊,就说明你还没有吸取足够多的教训。不要患得患失,要朝着目标努力前行。要自省自警,别人对你很到位的批评,是你能得到的最宝贵的建议。想想看,你的滑雪教练告诉你,你摔跟头是因为你滑行中的重心移动不对,此时你要是认为他在责骂你,那你是多么愚蠢和低效啊。

不要纠结于“埋怨”还是“赞美”,而要专注于“准确”还是“不准确”。在意他人的“埋怨”“赞美”或者“正面”“负面”评价,不利于你从反复的工作流程中学习。要记住,过去的事情就让它过去,除了作为未来的教训,不要再纠结。

记住:痛苦+反思=进步

5.1运维原则

对我们这种量级的业务做保障,意味着高强度的神经紧绷,一出事儿就是大事儿,我们必须把下面的原则深深地烙印到脑海中。

5.1.1核心业务高级别故障处理口诀

口诀:遇事不乱,分头核查,群里同步,简单陈述,绝不恋战,恢复服务

分头核查:质量控制部负责人要争取复现现象,确认问题是否存在;运维部负责人核查业务对应的机房、数据库、内外 流量、应用负载是否正常。

绝不恋战:如果迟迟定位不了问题(比如五分钟之内),就不可恋战,必须快速恢复业务,如利用异地双活系统切换流量。切记:第一,不要把生产环境当成测试环境,不要在线调试,第二,不要一直留着现场观察来观察去。

5.1.2严重BUG不过夜

严重BUG不过夜,这是我们的承诺。这时候,你不能说马上下班了,不能说明天再说吧,不能说已经半夜了。

实际例子:腾讯深夜改BUG

2013年3月24日晚上11点22分,陈皓想改QQ密码,不想触发了QQ安全中心的一个 BUG,如下图所示。

图5.1 QQ安全中心的BUG

于是发微博吐槽。过了半个多小时,腾讯运营人员做了反馈:

图5.2 腾讯发现并反馈

又过了一个多小时,腾讯的人又来这条微博评论,说BUG已经改好了。

图5.3 BUG已经改好了

5.1.3永远的后备方案

我司的研发哲学里强调,所有技术方案都要有后备方案,不能做成一锤子买卖,行还是不行全靠运气。

如果必须上线成功,没有退路,那必死无疑。

如果只有一个机房,必死无疑。

如果只有一个支付通道,必死无疑。

如果只有一条线路,必死无疑。

……为什么这么说?

2017年1月,公有云 UCloud 公司正在开年会,结果在他们北京B可用区的数据中心外3公里处,架空光缆线杆因卡车撞倒导致光缆断裂,工程师们被迫在年会现场紧急处理。

2015年6月21日上午9点到10点之间,阿里云公告称由于运营商电力问题造成香港机房故障。因为供电系统故障导致数据中心大楼整体断电,并触发消防 警。根据当地的消防规定,必须彻底排查隐患并完全消除后,才能获准进场做电力抢修。直至当晚21点22分机房正式恢复稳定供电,阿里云立即执行既定预案逐项恢复服务,21点32分安全防护服务恢复正常,各项服务陆续恢复,截至23点39分全部服务恢复。

2015年9月1日,阿里云官方发布致歉公告,称“因云盾安骑士server组件的恶意文件查杀功能升级触发了bug,导致部分服务器的少量可执行文件被误隔离”,但造成的影响无法估量。

2018年6月27日,阿里云工程师团队在上线一个自动化运维新功能中,执行了一项变更验证操作。这一功能在测试环境验证中并未发生问题,上线到自动化运维系统后,触发了一个未知代码bug。错误代码禁用了部分内部IP,导致部分产品访问链路不通,进一步导致一些客户访问阿里云官 控制台和使用部分产品功能出现问题。

2019年3月3日,阿里云华北二机房停服三小时。

2019年3月23日,微信支付上海机房光缆被挖断。

2019年4月4日,114.114.114.114DNS服务全国停服。

2019年6月2日,亚马逊北京机房光缆被挖断,AWS云服务北京区服务中断。

2019年6月3日,谷歌云全球断线。

灾难,总是在你意料之外。所以,我们必须准备一个后备方案,比如在这些场景:

  • 大版本上线和数据迁移
  • 升级第三方服务
  • 保护数据安全
  • (一)大版本上线和数据迁移

    大版本上线,老系统向新系统迁移,肯定涉及到方方面面,甚至有可能是通宵上线,那么就需要提前制定回退方案,并且召集相关人等评估上线方案和回退方案。临时抱佛脚,必然出大事。

    (二)升级第三方服务

    升级开源系统,也会遇到不可预知事件,要想好退路。我在第二章讲过升级RabbitMQ集群失败的例子。敢于升级就是因为通过老集群后台的Exportdefinitions功能导出JSON数据,它包含了集群的关键信息(如各种配置、Topic和订阅关系),再通过新集群的Import definitions功能导入。新集群启用之后,老集群不要立即销毁,作为后备方案,遇到问题后还可以平滑回退。

    (三)保护数据安全

    但凡是物理介质,都可能会物理毁灭,数据和配置彻底遗失的后果想必没有人能承受得起。所以我们不仅仅要备份,还要异地备份,并且要检查备份的可恢复性。

    哪些需要备份?不要只关注生产环境,线下环境也一样要重点关注:

  • 代码仓库:三地备份
  • CE:数据库两地备份
  • Redmine和Mantis:数据库两地备份
  • ……
  • 一般说来,我们有这些后备措施:

  • 代码和配置的灾难可恢复性:
  • Docker镜像库:所有应用如果是基于Docker容器,那么它们的配置都在 Docker 容器镜像里,而镜像库线上线下都有;
  • 我们有Harbor私有分布式镜像仓库,在混合云多机房各处都有自动同步的镜像库;
  • 有异地双活,等于说异地备份了Nginx/Redis/ES/Consul/ZK等服务配置信息;
  • 我们的CE里保存了各种工程的应用属性(也是配置信息),而CE的数据库是异地备份的;
  • 数据的灾难可恢复性:
  • 异地备份:我司的iDB能够做到数据库自动备份以及备份的可恢复性自动检查;备份文件会同步到云下的多个机房;
  • 有异地双活,等于说异地实时同步了全量数据库,并且有活的应用随时在校验。
  • 5.1.4竭尽可能消灭Exception和慢查

    我们十年来一直奉行“Exception日清日结”原则。曾经线上日志里每天新增数万条异常,工程师们已经麻木不仁、视若无睹了。面对连续宕机,我终于再也忍不下去了,抽出两周时间,停工清Exception,并且引入ELK,在此基础上开发了每日异常分析汇总邮件。从那以后,每新增一种异常日志,我们就能立即发现,并在数分钟内决定要不要处理、归谁处理以及如何处理。

    慢查也是同样道理。工程师们总喜欢说,稍等一下,等我把手头的活儿忙完,再改这个慢查。放纵慢查,就等于烧炭自杀,不用等“早晚”,“中午”你就会得到“回 ”。

    5.2设计原则

    5.2.1设计原则先行

    每一位设计师都需要知道这个常识:

    当你开始构建或重构一个复杂系统的时候,请先把大的设计原则写下来,然后在这些设计原则的框架内做推演

    而不是这种常见的工作方式:

    根据需求分析,天马行空,不设边界,想到哪儿是哪儿,自己做一番推演之后,自我感觉良好。

    阿里巴巴资深技术专家毕玄这样总结自己的系统设计方法:回顾了自己做过的几个系统的设计,发现自己在做系统设计的时候确实是会按照一个套路去做,这个套路就是:

    系统设计的目的->系统设计的目标->围绕目标的核心设计->围绕核心设计形成的设计原则->各子系统和模块的详细设计

    (一)系统设计的目的

    指的是做这个系统设计的目的到底是什么。很多人在做系统设计时,搞不清为什么要做一个新系统的设计,或者为什么要做一个系统的重构/演进的设计。如果搞不清楚这个目的,后面的系统设计上很容易形成偏差,导致本来是为了解决一个问题,才去做的重构或升级,但最后完全脱离了初心。

    另外,还有一点很重要,一个大架构师是需要给很多人讲解系统设计的,只有理解并讲清了系统设计的目的,团队才能更好地去实现。

    (二)系统设计的目标

    围绕上面的目的,能不能形成一些可衡量的目标,从而确保最终系统实现和最初的目的不要出现太大的偏差。相信很多人都经历过最终的系统实现和系统设计偏差极大的现象,主要的原因基本都是没有制定衡量系统设计的目标,并在系统设计上让系统能透出这些目标。

    (三)围绕目标的核心设计

    这一步最重要的就是通过设计如何去实现上面的目标。这个环节中技术的专业、视野、全面的考虑、权衡取舍的主观原则、解题的思路,是形成核心设计的关键。

    在核心设计的这个阶段中,会产生一些新的目标,可以衡量设计的最后实现情况,这些也都要追加到系统设计中,确保最后的实现和设计的偏差度是可视的。

    (四)围绕核心设计形成的设计原则

    有了上面的核心设计后,可以真正地形成一些设计原则,确保后面的子系统和模块的详细设计中能够遵循,并在详细设计中体现出来,这样才能保证整个大的系统设计的一致性。

    (五)各子系统和模块的详细设计

    到了这个时候,难度不会太大,毕竟有了前面的铺垫,只是解好一个更小范围的题目而已,程序员群体在解题能力上通常是不错的。

    5.2.2关键数据历史可追溯

    历史不得直接篡改和历史可追溯是一对,一个问题的两面。

    (一)历史不得直接篡改

    互联 核心服务容易产生数据不一致,一旦出现数据不一致,一定要有旁证来修正。所以数据库中以下关键资源的记录,原则上不允许直接修改历史数据

  • 下单/支付
  • 生码/验码/快递物流
  • 退款/调换货
  • 商户结算
  • 用户注册和注销
  • 这里的“直接修改”特指,没有把变更行为记录到日志表里,而是直接在原始记录上update甚至delete。这种“毁尸灭迹”是明文禁止的,即使留下了文件类型日志也是不允许的。我们应该这么做:

    第一,要修改这些记录的关键字段时,必须在数据库相关日志表里保留变更日志,并记录操作人和发起人,一定要确保历史可回溯

    第二,严禁对核心关键记录做物理删除,只能是软删除。

    (二)历史可追溯

    系统对关键记录做了一系列修改,甚至有程序在某个时间段内误写引入了脏数据,我们必须能从各种操作日志表中随时倒推回历史某一个时刻的快照,一是确保随时能安全地把数据还原回去,二是管理平台可以清晰地展示出由谁引发、怎么变化的历史,三是便于排查问题。

    比如,对于记录了订单信息的 order_info 表,会员如果点击使用账户余额支付了订单的应付金额,那么该订单操作日志表就会做如下记录,原订单记录的重要字段(what)由谁(who)因为什么(why)在什么时候(when)从什么变为了什么(how),都会详细记录。

    5.2.3系统高可用原则

    商业系统上线的时候,必须是监控告警配置到位,必须是高可用的,不允许引入单点风险。这是铁律,这是高压线,请不要挑战这个原则。

    另外,核心业务要经常做设计评审和CodeReview。Leaders要多发起挑战,让大家一起来做脑筋体操,设计评审的目的是让大家一起来想想如果是我做这块工作,如何防并发、防重复提交、高可用、高性能、可伸缩,而不是闭门造车、测过就算。Web安全防范也要做在前面,不要等到用户财产受侵害才恍然大悟。我们组织设计评审时,提出的第一个问题是高可用性和可伸缩,第二个问题就是安全防范。

    5.2.4不拿生产环境做试验

    对于核心系统关键业务,第一不要盲目引入未经验证、尚不成熟的组件和服务,用时一时爽,出事火葬场;第二不要引入过多介质,增大维护难度和压力。

    5.2.5数据补偿原则

    我们能看到不管是哪一家的关键业务,都是围绕着一个核心系统,一层一层叠加各种自动化规则,叠加各种数据补偿定时任务,一层补丁摞一层补丁,以此确保服务的高可靠性。

    举个例子,设计之初就要建立校验订单交易数据一致性的定时任务,比如以数据库操作日志为依据,挨个校验单个订单、交易流水的各项数据是否一致,如果发现不一致,第一要告警,第二尽量自动修复,比如说原路退返。这也是设计评审的一个重要考察环节。

    5.2.6接入异地双活原则

    凡是收银系统肯定是要接入我们的异地双活体系的,这是躲不过去的,别怕麻烦,因为单机房是不可靠的!

    订单操作、资金账户划转、券操作等敏感业务的设计都需要提前考虑双活,免得回头还要推倒重做。

    比如说订单 、券 、卡 等序列 的生成规则里,都应该有机房ID,能看出来这个订单是在哪一个机房生成的。

    举例:我们的 餐团队利用TwitterSnowFlake算法生成如下规则的订单 :

    25位 = 时间戳(13位)+机房ID(2位)+容器ID/IP(5位)+线程 (2位)+序列 (3位)

    我们团餐团队的订单 规则为:

    起始位固定2(1位)+日期(6位)+根据商户ID获取hashRoute(2位)+步长(7位)+机房ID(1位)

    再比如说事先划定“多活表”(如订单表和退款表)、“非多活表”(如队列表)和全局表(如支付配置表)。

    5.2.7谨慎使用updatetime作为更新依据

    与钱、支付相关的重要配置的数据库设计,必须叫停以updatetime为更新判断依据的设计方式。

    比如说,客户的支付配置、支付渠道、费率等重要信息是否有变更,不得以“设置了ON UPDATE CURRENT_TIMESTAMP 属性”的字段为准,如createtime,updatetime这种常见字段,否则非常容易在刷库时被自动更新,引发致命错误。

    5.2.8数据库设计和变更提前与DBA沟通

    工程师切勿自行其事。请提前与专业人士沟通。

    举几个例子:

    大表(如订单表)上需要预留足够多(如10个)扩展字段,后期复用时改字段名即可,不影响业务,无需停服。

    大表真的被迫加字段的话,请提前一天加好,千万不要与上线动作绑在一起。大表加字段,请选择凌晨执行,因为以5G的数据量为例可能耗时10分钟左右,对业务有致命影响。

    异地双活里,对于双活数据库而言,有主机房概念,如果先在主机房的双活数据库上做表结构变更,会导致otter同步中断,所以请先变更从机房的双活数据库的表结构,然后再变更主机房的。

    5.2.9重构的两个“有利于”原则

    如果你在繁忙的业务迭代中开始系统重构,恭喜你,说明你的业务已经完成了从0到1,正在从1走向10,或者从10走向60。至于重构后的技术栈是Spring MVC + Dubbo,还是Spring Boot + SpringCloud?是Vue + ElementUI,是React,还是Ant.design?是k8s,还是mesos+ marathon?是Thrift,还是Hessian,亦或 Protobuf?我并不在意。每个技术团队都可以有自己的技术选型思路。

    我在意的是两个“是否有利于”:

    一,是否有利于发布部署;

    二,是否有利于排除故障(是否有利于快速定位线上问题和解决问题)。

    随着业务规模越来越大,随着应用越来越多,随着容器化,随着前后端分离导致内部接口越来越多,随着 API 关的引入,我们越来越难以在5分钟之内断定系统出了什么事儿。因此,我要求:

    原则一:凡是中间件,不管是自主研发的,还是以开源软件为内核构建出来的,都必须自带监控 警,否则不允许上线。

    原则二:本着 Don’t make me think的哲学思路,所有对排除故障有帮助的信息,都必须一站式可视化展示。

    举几个例子,定时任务管理与调度平台有运行情况展示,自带监控 警。

    图5.5 Jobcenter的监控

    异步消息可靠推送系统有可视化的内部详情展示,自带监控 警。

    声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

    上一篇 2021年3月9日
    下一篇 2021年3月9日

    相关推荐