软件开发实际上跟英语比较类似,都是一项工具,服务于各行各业。从程序员的个人修养上来讲,一是要研习好软件开发这门技艺,二是要深入到所服务的行业。说到底,软件的终极目标是模拟业务,在此期间常常会有一个认知层面的小误会,即软件开发人员在入行之初所学习的都是与计算机、编程语言相关的知识,于是就形成“只需要把代码写完”事情就算完成的观念,显然这远远不及对软件架构师所要求的业务主导意识。
最近在看王概凯(Kevin)的《聊聊架构》,其中特别强调软件架构师应深入到业务中,并以业务的问题是否解决作为工作优良的判断标准,比较受启发,接下来我也根据过往工作经历说说自己的感知和体会。
软件所模拟的业务行为,其核心也在于数据,它们共同表达的都是行为背后的状态和结果,先看看下图:
在明确了业务主体后,业务目标自然就可以得到聚焦,有了清晰的业务目标,就值得花精力来探知业务的生命周期,接下来就可以像外科手术医生一样细致地实行架构拆分。
由此可见,软件架构师在实行架构拆分行为时,基本上都把业务从头到尾捋了一遍,否则势必陷入设计不足或过度设计的漩涡。
软件架构师需要走出时间困境
好的架构拆分是基于对业务的正确认识,但业务这个东西往往总令人心生畏惧。
在众多的传统企业中,信息技术部肩负起了全集团、全公司的以业务为导向的信息化重担。依靠所有成员去解构业务是不现实的,这时候软件架构师或者项目负责人就需要迎难而上,积极参与到需求分析中。可以肯定的是,只有直面业务的人,才能真正体会到按时、按需解决业务问题所面临的时间压力,这种压力的源头恰是职业人的天性,因为人们总倾向于认为自己从事一个行业然后精通该行业就可以,殊不知,软件行业其本质就是服务业,那么作为软件架构师,则必须超越对时间、对业务的恐惧,认识到需要解决问题的主体是业务人员而非自己,即需要解决的问题是“非软件行业”的问题,自己是在协助业务人员解决问题,从这个层面来讲,工作是否完成其实是由业务人员决定的,而不是软件架构师自己。
倘若选择避重就轻,尽管在短时间内部署上线了,但问题并未真正得到解决,业务部门的催促又会加重后续任务的时间压力。况且,仅仅以做好自己的编程工作为主要目标,并试图用自己的软件知识去理解另一个行业,又很容易陷入沟通困境。
在 2016 年,才加入申通快递不久就接到客服部关于开发备案系统的诉求,这个系统是涉及到全国网点,在备案请求提交后会由上级机构审核。我在接到这个任务的时候,首先按常规把会议记要过了几遍,然后对其中的审核功能做了重点铺开想像。我的脑补过程是这样的,首先它涉及到多机构递交审核,那么这期间有没有可能出现并行会审的情形?当流程出现退回时又该如何处理?要不干脆上一套流程框架吧,但发现 .NET 平台下开源的框架并不多,那就考虑 Java 平台的 Activiti 吧,可转而发现更换平台的成本又太高,而且照这样做下去,在预定的时间内恐怕很难交付。况且这个审核功能还只是所有问题之一,困惑之余,觉得还是要主动跟业务代表(客服部)做二次沟通。
通过主动出击,了解到审核的节点只有三步左右,只需采用最常见的串行单审即可,并不需要太多的复杂步骤。得到这样的答复后可谓喜出望外,回来的路上就想好了基于状态模式的实施方案,这样减去了引入各种工作流框架的麻烦,重要的是对时间压力和对交付 deadline 的焦虑减轻了许多。
虽然这个例子可能相比较于很多大型系统来说有点偏小,或者说是带有运气成份,但至少可以说明积极地与需求方沟通,能在很大程度上减小实现与需求的误差,以及自身对业务和时间的恐惧。
这里包含了一个隐形的要求,即软件架构师需要把对编程上的职业成就感,适当的转移到解决业务工作中,把完成业务工作作为自己的最大利益。如此,随着对业务的熟悉,那么对时间的恐惧便会慢慢消失。
软件架构师应树立生命周期的意识
人的生命周期无非是出生、成长、衰老直至死亡,而软件亦同,根据核心生命周期和非核心生命周期之分,通常可以把非核心的部分分工出去,让新进人员来承接,锻炼并促成其快速地融入大团队的协作。不妨思考一个问题,软件开发生命周期和软件运行生命周期哪一个更偏向于核心生命周期呢?通常企业开发一款软件的目的就是拿来使用的,其效益的持续提升依靠的正是软件的稳定运行,所以我觉得软件运行生命周期或者说运维才是核心。
可以稍微再提一提关于对非核心生命周期的分工问题,这是软件架构师需要日常考虑的问题。分工即拆分,而架构的拆分从本质上来讲也是一种利益的重新分配,毕竟每个人处理问题的承受力总是有限的,这就需要把一些非核心的业务分拆出去,无形中也是让自己走出时间困境的有效办法。然而在甄别一项业务是否为核心业务的时候就需要特别仔细,倘若对相关人的利益分析不透彻,便极易导致架构无法落地。
理解到了业务的主体及其生命周期后,就可以安心的做架构方面的拆分,从而形成不同的软件生命周期,从大的方面通常包括有开发和运行周期。
软件开发生命周期
开发的生命周期其实就是代码的累积过程,它以项目启动为始,然后和业务人员学习业务并形成需求文档,进而编码、测试以达到正确模拟现实业务的效果,最终得以部署上线,这时开发生命周期就终止了。最值得注重的其实是在开发之前就需要为软件的运行来考虑一系列的资源,比如机器、子域名申请以及监控等等,所以软件架构师在统筹方面需要更多的前瞻。
2017 年下半年,我参与到了申通快递的自动分拣项目中,这又是一个涉及全国各地的系统,而且是专为各大型转运中心所使用,为提升数据分发和故障排查等方面的效率,我们寻思着先从消息队列和监控两方面来改进。全新启用的开源消息队列 RabbitMQ 很好的帮我们桥连起各个子系统,它自身所带的监控页面,也帮我们在日常运维上省了不少心。趁此机会,又梳理了自动分拣项目所涉及的全部服务器,以期望并入统一的监控体系中,这时我想起了孙宇聪在高可用架构里提到的《SRE: Google运维解密》,大致解决办法还是在每台机器上运行一个代理程序,那我姑且就叫“埋点”吧,通过这样简单又很有效的方式对各子系统的运行状况做了监控,初步就以发短信的形式通知到组内人员,这样第一时间便能知晓线上运行状况。
其实,软件开发的过程可谓是八仙过海,各显神通。每一位软件架构师或者项目负责人能依据不同企业环境将软件构建出来就是最大的成功,究竟是采用传统的瀑布式(Waterfall),或是新兴的敏捷(Agile)迭代,甚至是两相结合都无可厚非,我最想强调的是对外沟通,即上面提到的对获取各项资源的前瞻性。毕竟“外行人”并不知道开发中的细节,不清楚我们一天到晚坐在那儿究竟在干什么,所以我们要对外保持 Open 状态并显现出沟通的意愿。
软件运行生命周期
软件运行生命周期才是一个软件真正的开始,一个有价值的软件从它启动的那一刻开始就已经为企业带来了效益,所以运行生命周期,即运维才是真正的核心竞争力。
运维的业务目标很简单,即保证软件稳定地被用户访问,那么风险控制工作便是重中之重。
首先是隔离措施。软件的开发生命周期通常是日常办公环境,而软件运行周期则是服务于用户的,因此要区分出办公环境(或测试环境)和生产环境。以我目前熟悉的快递行业,上规模的企业基本都有自建机房和运维团队,其电源、网络、空调、排风都是一体化的独立管控。
其次是控制变更。这里面还划分有被动变更和主动变更,被动变更包括机器主板或硬盘的损坏,以及用户访问量的突增等等,这时候作为软件架构师就需要警醒一下,必要时提醒运维人员针对上线的子系统做好负载均衡,小型应用通常保持两台机器,面向全国全网的应用就需要考虑四台以上机器了。主动变更多半情况下指的是频繁的发布更新,为了最大程度的降低影响面,要确保在指定地点、指点时间进行变更,并让所有的运维人员知情。从安全和便利角度考量往往还会用到运维堡垒机,如果要求更为严苛,还可以从帐号、公钥等方面进一步来加强。
在软件运行这样一个最为核心的生命周期中,发布更新的质量直接影响到企业信息部门的运维表现,结合孙宇聪的诸多建议我也列举一些值得注意的要点:
- 线下测试(Offline Test)。重点检查线下测试环境是否完善,除此之外还需要产品人员和开发人员的督促。除了一般的代码逻辑测试,尽量再兼顾到压力测试。
- 灰度发布(Gray Released)。指的是根据业务特点、数据特点来选择一批有极强代表性的线上服务器实例进行发布,其发布的比例可以考虑按 1%、10%,最后 100% 这一指数型方式来增长。这样有利于把新上线功能可能的不良影响降到最低,当然也可以作为小范围收集用户的反馈意见以待完善产品功能。灰度发布可以视作是上线前的最后一道安全防护机制。
- 回滚机制(Rollback)。一般来说用上一个版本的相关程序集进行覆盖即可,但有时还会出现数据格式的兼容性问题。比如数据库表字段的类型此前发生了变化,那这时候还需要有配套的回滚 SQL 脚本。另一种情形是新的变更内容把数据删掉了,回滚后数据也回不来了,这种情况是没有补救措施的,建议的做法是将程序代码分成两块逻辑,先发布第一段逻辑并记录好日志,等发现没有问题后再将删除的代码作为第二次发布。
在实际工作中,开发生命周期和运行生命周期往往是并行存在的。在软件部署上线后,总会不断地进行修改,比如出现了 Bug,或者是要增加新的需求,这时软件的开发、测试以及部署流程就需要再迭代一遍。无论新需求的开发多么重要,都要摆正软件运行生命周期的核心位置。
不管是软件架构师还是软件开发人员,通常都很专注于技术,以致于不太重视各种技术背后的业务,使得技术与应用总是无法较好的相融。计算机相关的技术,围绕的核心点始终是如何让用户能够更好的使用软件,这背后千丝万缕的业务关系无不是源于现实生活,所以认真工作之余还可以多读一些人文历史,并走进生活以获取灵感。