我们经常说自己熟悉了spring,能够搭建起一个项目基本框架,并且在此之上进行开发,用户or客户提出需求碰到不会的百度找找就可以实现。干个四五年下一份工作就去面试架构师了,运气好一些可能在中小公司真的找到一份架构师、技术负责人的职位,但很难在大厂得到一份满意的offer(可能大厂的码农岗位都进不去)。我总结原因有2点:
- 大厂雇佣了一帮中小厂中非常优秀的架构师和技术专家做码农
- 你觉得你熟练掌握了各种框架,甚至读了一些源码,但你不是架构师,而是框架师(这是我起的名字)
一个合格的架构师或技术专家我认为应当是这样历练出来的(或者说所具备的软实力):
- 经历过无数的事故和错误谦虚的从中总结经验并吸收,从事故中爬出来
- 具有清晰的系统设计思维和独立的思考能力
- 经历过复杂的项目以及真正的大用户量系统的设计和开发
以上3点可能在大厂才能历练的到,首先系统只有运行且有用户使用才能产生事故,其次业务复杂性、高并发或大数据量带来的技术复杂性几乎只有大厂才能存在。
难道在中小厂干到35岁真的要退休了么?大环境不好裁员真的会裁到自己头上么?很多人想去大厂,没有大厂hc所要求的经验和技术能力,似乎在中小厂的程序员去大厂就是个悖论。
但我觉得以上不是问题,我们要想清楚问题的本质和社会运转的基础机制:
- 随着疫情出现,社会经济大萧条,资本对互联网产业投入逐渐降低,企业逐渐进入选择更优秀的人、经验丰富的人的招人模式,虽然20多岁的小伙子精力旺盛,能加班,但中国的计算机和互联网发展时间与学校制度,注定80和90后这一代人到35+岁才能有足够的经验和技术实力。而刚毕业的学生在中国计算机教学方式下很难有所成就。目前来看,毕业即失业,工作2-3年的找不到工作,也印证了这点。所以我认为,35岁就失业这个事一直是被不懂行或一些既得利益者(培训机构、自媒体运营号)大肆渲染导致的。
- 在10几年前,中国互联网上升期,java程序员最火热的时候,一个培训班40人中只有少于10个人毕业后能留在这个行业,而剩下30人则认为这个行业不行。而在现在所谓大环境不行的情况下,我身边被裁员的同事陆陆续续进入了百度、字节等一线大厂。
- 大厂本质上是个围墙,进去的想出来,出不来,外面的想进来,进不来。我们姑且不讨论想出来的这些人,我觉得进不来的人问题在于是否拥有一个大厂所需的思维,即:系统设计思维和独立思考能力。
对于一个中小厂的程序员,项目很可能处于从未上过线或上线没几个人用的尴尬局面。业务本身不复杂,技术要求更不高。对于个人来说毫无成长和升值空间,更无法得到上面说的3点历练从而荣升架构师级别的大佬。这是无解的么?我觉得不是。
在这里,我总结出一些任何系统普适的设计思考原则,希望你能尝试用在你的日常设计和开发中(无论是多么小的系统)。这些思考原则是大厂每个人所需具备的,这种思考方式能够让你独当一面,能够让你实践出更可靠、高效的系统。这些思考原则不是特指掌握具体某个技术(譬如:熟读Linux内核源码),实际的技术点掌握再精通,只是一个点,无法形成面或建立立体的知识体系,也就无法应用到大型工程中。没有这样的思维,可能在你面试的时候,面试官会给你一个思维混乱的评价。
无论是在工作中还是面试中,我都推荐使用下面的思路设计或讨论系统:
系统设计要遵循3个原则:
- 合适原则:在系统设计的时候需要考虑采用的技术方案是否符合当下业务场景和团队能力。不要过分追求所谓的大厂就是这么用的,能抗多少qps,多么灵活可扩展,盲目追求不切实际的指标和技术。
- 演进原则:系统的架构是演进过来的,可以预测未来1年的业务发展趋势,选择合适的架构模式,单体(甚至是单机)并不是干不了活何必上来就追求复杂的分布式环境?大厂也是由曾经的几个人创业再到大厂的,用户体量不是一天增长来的。上来就设计能够支持高并发、大数据量的系统,技术实现和维护的复杂度、成本成直线上升。
- 简单原则:系统架构一直围绕着对抗复杂性和不稳定性的,多引入一个组件或模式就会增加一份复杂度。我们经常说屎山代码不好维护,对于一个简单的业务采用领域取代设计方法或引入mq、ES等中间件带来的复杂度比屎山代码还要恐怖。只有面试题会没事让人把if都转成策略模式,不用线程池而使用mq。简单原则也间接说明了系统是演进的。
如果你对面试官说你做的电商系统能够支持百万qps的下单,而你的公司并不是知名大厂,恐怕这次面试会失败。面试官会觉得你过度设计了系统,引入更多的成本。
通常对于一个业务功能,我们可以考虑上线后一周的增长量、一个月的增长量、半年的增长量、一年的增长量。而对于一个完整系统的设计,可以评估1年-5年的增长量。
系统设计要考虑的4个重点:
-
模块划分:有哪些模块,这些模块作为拆分服务或工程内模块(如maven模块)的基准,比如用户模块、交易模块等,划分模块的目的是专注在模块内部实现具体的业务。这不代表你要为用户启动一个数据库,交易也要启一个数据库。在逻辑上进行划分,在物理部署上可以适当拆分,也可以合并在一起。当逻辑模块划分好,更有助于分配工作任务,并对每个模块进行独立的设计和实现。
-
边界与职责:模块协同运作才能完成整体的业务流转,达到最终的目标。这里便出现了模块之间的交互,模块划分专注与业务领域的内部,而边界专注于与模块之间的交互。我们经常吵着领域驱动设计,也是在划分出业务领域(模块)后区分出界限上下文(边界),通过所谓的防腐层对外提供服务。为何要有防腐层?我们要可以站在自己模块内部看外面,我调别人的接口或我提供的接口可能存在2个问题(注意,不只是接口,消息队列通信也一样存在这样的问题):
a. 故障和异常,作为服务者我们不应该对外暴露过多的自身错误,作为调用者我们不应该受外部影响而产生自身的错误。
b. 变更,作为服务者我们不应该频繁变更导致调用者跟着变动,作为调用者我们必须考虑接受到外部的变动后对自身产生何种影响。
所以,通过防腐层对变更和故障进行屏蔽,确保之上应用的稳定。
同时当我们确定边界后就能明确了自己的职责,不要干预其他模块的行为和决策,不要实现其他模块的功能。在企业中工作也一样,不要把手伸到别人的篮子里,更不要让别人把自己的成果拿走。 -
面向异常做设计:从业10几年见过无数大牛,没有任何一个大牛能打保票说自己的系统百分之百稳定,而是制定出SLA,表明自己系统的稳定性指标和赔偿方式。业内也通过几个9(即系统可提供服务时间和故障的时间比例,如:99.99% 4个9)。系统架构师一直在与不稳定对抗,但实时证明做不到完全的可靠,这是宇宙的法则:熵增定理。我们能做到的是在出现故障后如何快速解决,人工介入或系统自适应。比如:合理的异常处理方式,合理的降级预案,流量灰度,快速回滚,设计冗余来确保挂了能继续提供服务。异常状态是个陷阱,很容易在设计的时候忽略掉。
-
核算成本:这包括了技术成本和资源成本,资源成本又包括了人员成本和投入资产的成本。所有的投入,要有足够的产出才是值得的(ROI要高),否则都没有意义
技术成本:为了几个用户引入消息队列、分布式数据库显然对于一个3、5个人的小型技术团队是不合适的,引入一个组件或一种模式就需要有人能维护的下去,或能够熟练掌握这些技术。一项技术是否有足够的社区文档支持、后续是否方便维护与监控,这都是在设计中技术选型时需要考虑的。
人员成本:企业要为员工发工资的,一个架构师或团队管理者实现一个系统不考虑人员配备开销,这个团队必定会因为钱而解散。
资产的投入成本:引入各种中间件,就要多投入机器资源,在做设计时应该考虑是否有必要引入这笔开销。
系统设计要达到5个目标:
-
可靠性:可靠性保证系统可提供服务的时间,本质上我们引入分布式和各种中间件也是为了提高系统的可靠性。确保可靠性的最好的解决方法就是足够的冗余,比如集群部署,一个机器挂了其他机器还能提供服务。不过对于日常的编码,我们也应该考虑可靠性,包括:处理好异常给用户一个友好的提示、合理的重试机制、限流的自我保护、上报监控数据有问题及时发现、设计可降级方案、灰度发布、压测和故障演练。
-
可扩展性:可扩展性包括了系统物理上部署的可扩展性,和系统维护的可扩展性。在物理部署的可扩展性的角度来看,我们经常把应用实现成无状态的,即不记录用户请求的信息,这使得可以部署多台服务,形成对等集群并通过负载均衡技术实现新增机器或下线机器用户无感知。而从系统维护的角度来看,一个良好的架构设计可以确保功能的迭代不用牵一发而动全身。那如何评价一个系统架构设计是否是良好的?即上面提到的4个重点中的3点,这其中包括了如何解决耦合性——模块划分并确定边界和职责,拒绝引入难以维护的技术——核算成本中的技术成本。这些都有助于提高系统的可扩展性。
-
可观测性:可观测性是指一个系统具有良好的监控,能够自我上报各种状态,这样能够让系统具有足够的自适应型。异常出现也能够快速发现并自动恢复或者人工接入。一个好的可观测性包括:全链路跟踪、数据指标可视化大盘、异常检测和通知。Linux操作系统是一个可观测性做的非常好的系统,比如我们可以读取虚拟文件系统 /proc的运行时刻系统状态,syslog服务能够记录系统关键日志,全访问的监控命令(如top、netstat等),ebpf、systemtap技术实现内核级别的观测。Linux成功的很大程度上具有足够灵活的可观测接口,让用户能够掌握系统的状态。
-
性能:我们无时无刻都在讨论性能,衡量系统的性能包括如下指标:
a. 能承载多少QPS/TPS
b. 响应耗时(RT),P99、P50
c. EPS(每秒钟异常数量),这包括了:在要求的响应耗时内产生的异常请求与超过最大可容忍的响应耗时
d. 系统内部的资源使用情况:CPU使用率与load、磁盘IOPS、内存分配速度与使用率、网卡过包量 丢包 重传等
讨论和设计这些指标时,需要考虑单机和集群情况下,评估出单机可承载的最大量,预计系统的峰值和均值来决策集群中需要部署多少台机器。
设计高性能服务手段有很多:
a. 利用缓存和缓冲平衡慢速设备与快速设备的时间差,在一个请求中缓存到处可见:客户端缓存、cdn缓存、nginx和jvm内部缓存、redis缓存、cpu L1-L3缓存、操作系统文件系统的buffer与cache、数据库的buffer pool等。可以说现在互联网系统就是缓存为王。而引入了缓存,自然引入了缓存的一致性问题。
b. 分而治之,这包括了把流量拆分到分布式环境下进行处理,加速整体请求处理速度。大数据量的拆分(分库分表、分布式数据库等),本质上也是利用并行计算或降低局部处理数据量级的手段。提供冗余服务来应对流量的增长,分摊计算压力。服务拆分出核心与非核心部署,为核心服务提供更多的计算资源。
c. 设计上的取舍,比如分布式情况下选择最终一致性而不选择强一致性。
d. 利用合理的数据结构与算法,如:b+索引、LSM索引、倒排索引等。能够评估一项技术所使用的核心算法的时间复杂度,有助于对系统性能的整体把控。
在出现性能问题时,我们可以依赖可观测性中提到的监控系统观测各种指标从而进行宏观上的性能分析。而采用一些性能分析工具,如:jvm的内存分析工具(jmap、jstack、jstat等)、第三方的profile工具(arthas、bcc等工具),利用这些工具能够定位性能问题所在的根源。 -
用户体验:用户体验包含2个方面,一方面是系统具有一个良好的用户界面并培养用户习惯,这是产品与UI/UE设计人员需要考虑的事情。另一方面是说,对于服务端提供服务时,要提供一个友好的接口表达方式和服务状态,这包括了对外的rpc或http接口,对内代码和模块中的接口。一个接口50多个参数,增加一个新的参数就会导致调用者也要跟着修改,这就是一个很差劲的接口表达方式。而接口和服务提的供者对调用者暴露了各种自己的内部异常,会让调用者沉浸在处理各种异常状态中,牵引着调用者自身进入不稳定的状态,这就是一个很差劲的服务状态。所以一个好的接口或服务要对外屏蔽复杂的状态,处理好内部的异常,提供精简合理的出入参(对于参数很多的情况,要进行封装)。
请记住,为了上面5个目标,你必定会引入复杂度:
1.为了可靠性要引入集群或其他中间件
2.为了可扩展性要引入了复杂的模式(编码的设计模式或系统的多模块拆分)
3.可观测性方面,为了完善精准和可靠的监控数据要牺牲性能上报数据,构建复杂的全链路监控和可视化大盘
4.为了解决性能引入各种中间件、缓存,甚至借助对硬件的深入理解进行编程(这些代码通常不太好维护,这取决于人员的技术能力)
5.提供良好的用户体验要绞尽脑汁地设计出好看的界面或接口,并预测变化,想办法屏蔽变化
系统设计要考虑:
-
存量与流量:
存量代表系统中已经存在的量(包括数据存储和用户请求量)
流量代表系统中流入和流出的量(包括数据的写入量和删除量,用户请求流量的增加与减少)
对流量进行合理的预估,能够让系统不会因为流量的徒增出现突发性的故障。而由于存量的存在,系统展现自身状态会存在延迟,如:热点事件发生,qps突增进行扩容,但系统需要部署,新的机器上缓存从冷到热是需要一个过程的,所以新机器扩容上来并不能立刻分摊突增的qps,在这里缓存的从冷填充满整个缓存空间步入到热的状态,缓存未填充的部分就是存量。很多情况下,我们正是忽略了存量的存在,导致系统出现严重的问题。 -
调节回路与增强回路:
调节回路是依托与流量的变动,即观测到系统的出入行为而进行的调节,如:用户请求量增加,结合监控系统通过k8自动扩容。
增强回路在系统中起到改变系统状态走向的绝对因素,要么系统变好,要么崩溃。举例:引入redis会提升性能么?不一定,原因:跨网络请求redis会增加一些耗时,redis崩溃是否会导致系统不可用,要承担额外的维护成本。如果redis部署在内网耗时基本可忽略,可存储的空间上来看远高于应用内的缓存,能够在微妙级别对完成缓存的读写并实现极高的吞吐量。redis的引入作为一个增强回路,能够让系统变得更好,但要确保它的可靠性和性能。 -
信息流:
信息流是指系统能够反馈或上报自身的信息包括确保可靠的异常状态、正常状态,系统产生的业务数据(拿到合理的数据才能评估系统的存在是否有价值)。同时要关注信息流动的方向,正反馈或负负反馈,正反馈有助于让我们看到系统好的一面,负反馈让我们知道系统存在哪些问题。我们基于信息流,建立监控系统,建立出现问题后的自我修复系统(如:自动扩缩容、故障隔离、自动降级、限流熔断等)。 -
无处不在的取舍:
架构本质就是在做取舍,不存在既要又要还要。业内折腾了这么多年的分布式CAP原则,在CA、AP、CP中进行取舍,没有任何一个方案能够实现CAP全都要。降级自保也是一种取舍,丢掉不重要的,为重要的提供服务和算力。取舍也是对抗复杂性和不确定性的一种妥协,是控制系统向混乱发展的一种方式。老板对你说,我要把数据库中一个亿的用户数据在1分钟之内全部删除掉,不影响线上业务,这是不可能的,他只能接受更长的时间或因为不受控的删除操作导致的服务不可用。
以上是我总结的一些架构思考方式和经验,不知道你是否读懂?架构从来不是阅读一些框架源码和技术书籍,这些只是帮助你实现目标的一种手段和过程,而架构是一种思考问题的方式,是不断磨炼和沉淀出的方法论,这些方法论是脚踏实地的。
工作了四五年的你可能对以上经验嗤之以鼻,认为我一个30多岁的老东西怎么能和我这20多岁思维活跃的年轻小伙抗衡?你觉得写好一个ppt,列上自己干了多少活,达到了什么指标,就能够忽悠住客户与老板就行,拿这个方式跟面试官聊就能成为新公司的架构师,我的一位阿里朋友称之为ppt架构师。那么,我们35岁再看看,你是否还在这个行业拿着高薪。