系统设计思维的讨论

我们经常说自己熟悉了spring,能够搭建起一个项目基本框架,并且在此之上进行开发,用户or客户提出需求碰到不会的百度找找就可以实现。干个四五年下一份工作就去面试架构师了,运气好一些可能在中小公司真的找到一份架构师、技术负责人的职位,但很难在大厂得到一份满意的offer(可能大厂的码农岗位都进不去)。我总结原因有2点:

  1. 大厂雇佣了一帮中小厂中非常优秀的架构师和技术专家做码农
  2. 你觉得你熟练掌握了各种框架,甚至读了一些源码,但你不是架构师,而是框架师(这是我起的名字)

一个合格的架构师或技术专家我认为应当是这样历练出来的(或者说所具备的软实力):

  1. 经历过无数的事故和错误谦虚的从中总结经验并吸收,从事故中爬出来
  2. 具有清晰的系统设计思维和独立的思考能力
  3. 经历过复杂的项目以及真正的大用户量系统的设计和开发

以上3点可能在大厂才能历练的到,首先系统只有运行且有用户使用才能产生事故,其次业务复杂性、高并发或大数据量带来的技术复杂性几乎只有大厂才能存在。
难道在中小厂干到35岁真的要退休了么?大环境不好裁员真的会裁到自己头上么?很多人想去大厂,没有大厂hc所要求的经验和技术能力,似乎在中小厂的程序员去大厂就是个悖论。
但我觉得以上不是问题,我们要想清楚问题的本质和社会运转的基础机制:

  1. 随着疫情出现,社会经济大萧条,资本对互联网产业投入逐渐降低,企业逐渐进入选择更优秀的人、经验丰富的人的招人模式,虽然20多岁的小伙子精力旺盛,能加班,但中国的计算机和互联网发展时间与学校制度,注定80和90后这一代人到35+岁才能有足够的经验和技术实力。而刚毕业的学生在中国计算机教学方式下很难有所成就。目前来看,毕业即失业,工作2-3年的找不到工作,也印证了这点。所以我认为,35岁就失业这个事一直是被不懂行或一些既得利益者(培训机构、自媒体运营号)大肆渲染导致的。
  2. 在10几年前,中国互联网上升期,java程序员最火热的时候,一个培训班40人中只有少于10个人毕业后能留在这个行业,而剩下30人则认为这个行业不行。而在现在所谓大环境不行的情况下,我身边被裁员的同事陆陆续续进入了百度、字节等一线大厂。
  3. 大厂本质上是个围墙,进去的想出来,出不来,外面的想进来,进不来。我们姑且不讨论想出来的这些人,我觉得进不来的人问题在于是否拥有一个大厂所需的思维,即:系统设计思维和独立思考能力。

对于一个中小厂的程序员,项目很可能处于从未上过线或上线没几个人用的尴尬局面。业务本身不复杂,技术要求更不高。对于个人来说毫无成长和升值空间,更无法得到上面说的3点历练从而荣升架构师级别的大佬。这是无解的么?我觉得不是。
在这里,我总结出一些任何系统普适的设计思考原则,希望你能尝试用在你的日常设计和开发中(无论是多么小的系统)。这些思考原则是大厂每个人所需具备的,这种思考方式能够让你独当一面,能够让你实践出更可靠、高效的系统。这些思考原则不是特指掌握具体某个技术(譬如:熟读Linux内核源码),实际的技术点掌握再精通,只是一个点,无法形成面或建立立体的知识体系,也就无法应用到大型工程中。没有这样的思维,可能在你面试的时候,面试官会给你一个思维混乱的评价。
无论是在工作中还是面试中,我都推荐使用下面的思路设计或讨论系统:

系统设计要遵循3个原则:

  1. 合适原则:在系统设计的时候需要考虑采用的技术方案是否符合当下业务场景和团队能力。不要过分追求所谓的大厂就是这么用的,能抗多少qps,多么灵活可扩展,盲目追求不切实际的指标和技术。
  2. 演进原则:系统的架构是演进过来的,可以预测未来1年的业务发展趋势,选择合适的架构模式,单体(甚至是单机)并不是干不了活何必上来就追求复杂的分布式环境?大厂也是由曾经的几个人创业再到大厂的,用户体量不是一天增长来的。上来就设计能够支持高并发、大数据量的系统,技术实现和维护的复杂度、成本成直线上升。
  3. 简单原则:系统架构一直围绕着对抗复杂性和不稳定性的,多引入一个组件或模式就会增加一份复杂度。我们经常说屎山代码不好维护,对于一个简单的业务采用领域取代设计方法或引入mq、ES等中间件带来的复杂度比屎山代码还要恐怖。只有面试题会没事让人把if都转成策略模式,不用线程池而使用mq。简单原则也间接说明了系统是演进的。

如果你对面试官说你做的电商系统能够支持百万qps的下单,而你的公司并不是知名大厂,恐怕这次面试会失败。面试官会觉得你过度设计了系统,引入更多的成本。

通常对于一个业务功能,我们可以考虑上线后一周的增长量、一个月的增长量、半年的增长量、一年的增长量。而对于一个完整系统的设计,可以评估1年-5年的增长量。

系统设计要考虑的4个重点:

  1. 模块划分:有哪些模块,这些模块作为拆分服务或工程内模块(如maven模块)的基准,比如用户模块、交易模块等,划分模块的目的是专注在模块内部实现具体的业务。这不代表你要为用户启动一个数据库,交易也要启一个数据库。在逻辑上进行划分,在物理部署上可以适当拆分,也可以合并在一起。当逻辑模块划分好,更有助于分配工作任务,并对每个模块进行独立的设计和实现。

  2. 边界与职责:模块协同运作才能完成整体的业务流转,达到最终的目标。这里便出现了模块之间的交互,模块划分专注与业务领域的内部,而边界专注于与模块之间的交互。我们经常吵着领域驱动设计,也是在划分出业务领域(模块)后区分出界限上下文(边界),通过所谓的防腐层对外提供服务。为何要有防腐层?我们要可以站在自己模块内部看外面,我调别人的接口或我提供的接口可能存在2个问题(注意,不只是接口,消息队列通信也一样存在这样的问题):
    a. 故障和异常,作为服务者我们不应该对外暴露过多的自身错误,作为调用者我们不应该受外部影响而产生自身的错误。
    b. 变更,作为服务者我们不应该频繁变更导致调用者跟着变动,作为调用者我们必须考虑接受到外部的变动后对自身产生何种影响。
    所以,通过防腐层对变更和故障进行屏蔽,确保之上应用的稳定。
    同时当我们确定边界后就能明确了自己的职责,不要干预其他模块的行为和决策,不要实现其他模块的功能。在企业中工作也一样,不要把手伸到别人的篮子里,更不要让别人把自己的成果拿走。

  3. 面向异常做设计:从业10几年见过无数大牛,没有任何一个大牛能打保票说自己的系统百分之百稳定,而是制定出SLA,表明自己系统的稳定性指标和赔偿方式。业内也通过几个9(即系统可提供服务时间和故障的时间比例,如:99.99% 4个9)。系统架构师一直在与不稳定对抗,但实时证明做不到完全的可靠,这是宇宙的法则:熵增定理。我们能做到的是在出现故障后如何快速解决,人工介入或系统自适应。比如:合理的异常处理方式,合理的降级预案,流量灰度,快速回滚,设计冗余来确保挂了能继续提供服务。异常状态是个陷阱,很容易在设计的时候忽略掉。

  4. 核算成本:这包括了技术成本和资源成本,资源成本又包括了人员成本和投入资产的成本。所有的投入,要有足够的产出才是值得的(ROI要高),否则都没有意义
    技术成本:为了几个用户引入消息队列、分布式数据库显然对于一个3、5个人的小型技术团队是不合适的,引入一个组件或一种模式就需要有人能维护的下去,或能够熟练掌握这些技术。一项技术是否有足够的社区文档支持、后续是否方便维护与监控,这都是在设计中技术选型时需要考虑的。
    人员成本:企业要为员工发工资的,一个架构师或团队管理者实现一个系统不考虑人员配备开销,这个团队必定会因为钱而解散。
    资产的投入成本:引入各种中间件,就要多投入机器资源,在做设计时应该考虑是否有必要引入这笔开销。

系统设计要达到5个目标:

  1. 可靠性:可靠性保证系统可提供服务的时间,本质上我们引入分布式和各种中间件也是为了提高系统的可靠性。确保可靠性的最好的解决方法就是足够的冗余,比如集群部署,一个机器挂了其他机器还能提供服务。不过对于日常的编码,我们也应该考虑可靠性,包括:处理好异常给用户一个友好的提示、合理的重试机制、限流的自我保护、上报监控数据有问题及时发现、设计可降级方案、灰度发布、压测和故障演练。

  2. 可扩展性:可扩展性包括了系统物理上部署的可扩展性,和系统维护的可扩展性。在物理部署的可扩展性的角度来看,我们经常把应用实现成无状态的,即不记录用户请求的信息,这使得可以部署多台服务,形成对等集群并通过负载均衡技术实现新增机器或下线机器用户无感知。而从系统维护的角度来看,一个良好的架构设计可以确保功能的迭代不用牵一发而动全身。那如何评价一个系统架构设计是否是良好的?即上面提到的4个重点中的3点,这其中包括了如何解决耦合性——模块划分并确定边界和职责,拒绝引入难以维护的技术——核算成本中的技术成本。这些都有助于提高系统的可扩展性。

  3. 可观测性:可观测性是指一个系统具有良好的监控,能够自我上报各种状态,这样能够让系统具有足够的自适应型。异常出现也能够快速发现并自动恢复或者人工接入。一个好的可观测性包括:全链路跟踪、数据指标可视化大盘、异常检测和通知。Linux操作系统是一个可观测性做的非常好的系统,比如我们可以读取虚拟文件系统 /proc的运行时刻系统状态,syslog服务能够记录系统关键日志,全访问的监控命令(如top、netstat等),ebpf、systemtap技术实现内核级别的观测。Linux成功的很大程度上具有足够灵活的可观测接口,让用户能够掌握系统的状态。

  4. 性能:我们无时无刻都在讨论性能,衡量系统的性能包括如下指标:
    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等工具),利用这些工具能够定位性能问题所在的根源。

  5. 用户体验:用户体验包含2个方面,一方面是系统具有一个良好的用户界面并培养用户习惯,这是产品与UI/UE设计人员需要考虑的事情。另一方面是说,对于服务端提供服务时,要提供一个友好的接口表达方式和服务状态,这包括了对外的rpc或http接口,对内代码和模块中的接口。一个接口50多个参数,增加一个新的参数就会导致调用者也要跟着修改,这就是一个很差劲的接口表达方式。而接口和服务提的供者对调用者暴露了各种自己的内部异常,会让调用者沉浸在处理各种异常状态中,牵引着调用者自身进入不稳定的状态,这就是一个很差劲的服务状态。所以一个好的接口或服务要对外屏蔽复杂的状态,处理好内部的异常,提供精简合理的出入参(对于参数很多的情况,要进行封装)。

请记住,为了上面5个目标,你必定会引入复杂度:
1.为了可靠性要引入集群或其他中间件
2.为了可扩展性要引入了复杂的模式(编码的设计模式或系统的多模块拆分)
3.可观测性方面,为了完善精准和可靠的监控数据要牺牲性能上报数据,构建复杂的全链路监控和可视化大盘
4.为了解决性能引入各种中间件、缓存,甚至借助对硬件的深入理解进行编程(这些代码通常不太好维护,这取决于人员的技术能力)
5.提供良好的用户体验要绞尽脑汁地设计出好看的界面或接口,并预测变化,想办法屏蔽变化

系统设计要考虑:

  1. 存量与流量:
    存量代表系统中已经存在的量(包括数据存储和用户请求量)
    流量代表系统中流入和流出的量(包括数据的写入量和删除量,用户请求流量的增加与减少)
    对流量进行合理的预估,能够让系统不会因为流量的徒增出现突发性的故障。而由于存量的存在,系统展现自身状态会存在延迟,如:热点事件发生,qps突增进行扩容,但系统需要部署,新的机器上缓存从冷到热是需要一个过程的,所以新机器扩容上来并不能立刻分摊突增的qps,在这里缓存的从冷填充满整个缓存空间步入到热的状态,缓存未填充的部分就是存量。很多情况下,我们正是忽略了存量的存在,导致系统出现严重的问题。

  2. 调节回路与增强回路:
    调节回路是依托与流量的变动,即观测到系统的出入行为而进行的调节,如:用户请求量增加,结合监控系统通过k8自动扩容。
    增强回路在系统中起到改变系统状态走向的绝对因素,要么系统变好,要么崩溃。举例:引入redis会提升性能么?不一定,原因:跨网络请求redis会增加一些耗时,redis崩溃是否会导致系统不可用,要承担额外的维护成本。如果redis部署在内网耗时基本可忽略,可存储的空间上来看远高于应用内的缓存,能够在微妙级别对完成缓存的读写并实现极高的吞吐量。redis的引入作为一个增强回路,能够让系统变得更好,但要确保它的可靠性和性能。

  3. 信息流:
    信息流是指系统能够反馈或上报自身的信息包括确保可靠的异常状态、正常状态,系统产生的业务数据(拿到合理的数据才能评估系统的存在是否有价值)。同时要关注信息流动的方向,正反馈或负负反馈,正反馈有助于让我们看到系统好的一面,负反馈让我们知道系统存在哪些问题。我们基于信息流,建立监控系统,建立出现问题后的自我修复系统(如:自动扩缩容、故障隔离、自动降级、限流熔断等)。

  4. 无处不在的取舍:
    架构本质就是在做取舍,不存在既要又要还要。业内折腾了这么多年的分布式CAP原则,在CA、AP、CP中进行取舍,没有任何一个方案能够实现CAP全都要。降级自保也是一种取舍,丢掉不重要的,为重要的提供服务和算力。取舍也是对抗复杂性和不确定性的一种妥协,是控制系统向混乱发展的一种方式。老板对你说,我要把数据库中一个亿的用户数据在1分钟之内全部删除掉,不影响线上业务,这是不可能的,他只能接受更长的时间或因为不受控的删除操作导致的服务不可用。

以上是我总结的一些架构思考方式和经验,不知道你是否读懂?架构从来不是阅读一些框架源码和技术书籍,这些只是帮助你实现目标的一种手段和过程,而架构是一种思考问题的方式,是不断磨炼和沉淀出的方法论,这些方法论是脚踏实地的。

工作了四五年的你可能对以上经验嗤之以鼻,认为我一个30多岁的老东西怎么能和我这20多岁思维活跃的年轻小伙抗衡?你觉得写好一个ppt,列上自己干了多少活,达到了什么指标,就能够忽悠住客户与老板就行,拿这个方式跟面试官聊就能成为新公司的架构师,我的一位阿里朋友称之为ppt架构师。那么,我们35岁再看看,你是否还在这个行业拿着高薪。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/75839.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

项目部署-(二)Linux下SpringBoot项目自动部署

一、项目部署架构 二、项目部署环境 192.168.138.100(服务器A): -Nginx :部署前端项目、配置反向代理 -MySQL:主从复制结构中的主库 192.168.138.101(服务器B): -JDK:运行Java项目 -Git:版本控制工具 -Maven:项目构建…

【LeetCode 热题 100】哈希 系列

📁1. 两数之和 本题就是将通过两层遍历优化而成的,为什么需要两层遍历,因为遍历 i 位置时,不知道i-1之前的元素是多少,如果我们知道了,就可以通过两数相加和target比较即可。 因为本题要求返回下标&#xf…

【Kubernetes基础】--查阅笔记1

目录 Kubernetes 是什么为什么要用 KubernetesKubernetes 概念MasterNodePodLabelReplication ControllerDeploymentHorizontal Pod AutoscalerStatefulSetService服务发现机制 JobVolumePersistent VolumeNamespaceConfigmap Kubernetes 是什么 Kubernetes是一个开放的开发平…

卷积神经网络CNN到底在卷些什么?

来源: 卷积神经网络(CNN)到底卷了啥?8分钟带你快速了解!_哔哩哔哩_bilibili卷积神经网络(CNN)到底卷了啥?8分钟带你快速了解!共计2条视频,包括:卷…

Axios 的 POST 请求:QS 处理数据的奥秘与使用场景解析

在现代前端开发中,Axios 已经成为了进行 HTTP 请求的首选库之一,它的简洁易用和强大功能深受开发者喜爱。当使用 Axios 进行 POST 请求时,我们常常会遇到一个问题:是否需要使用 QS 库来处理请求数据?什么时候又可以不用…

java面试题带答案2025最新整理

文章目录 一、java面试题集合框架1. 请简要介绍 Java 集合框架的体系结构2. ArrayList 和 LinkedList 的区别是什么3. HashMap 的工作原理是什么,它在 JDK 7 和 JDK 8 中有哪些不同4. 如何解决 HashMap 的线程安全问题5. TreeSet 是如何保证元素有序的 二、java面试…

4.B-树

一、常见的查找方式 顺序查找 O(N) 二分查找 O(logN)(要求有序和随机访问) 二叉搜索树 O(N) 平衡二叉搜索树(AVL树和红黑树) O(logN) 哈希 O(1) 考虑效率和要求而言,正常选用 平衡二叉搜索树 和 哈希 作为查找方式。 但这两种结构适合用于…

CTF--shell

一、原题 (1)提示: $poc"a#s#s#e#r#t";$poc_1explode("#",$poc);$poc_2$poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5]; $poc_2($_GET[s]) (2)原网页:一片空白什么都…

基于51单片机的正负5V数字电压表( proteus仿真+程序+设计报告+讲解视频)

基于51单片机的正负5V数字电压表( proteus仿真程序设计报告讲解视频) 仿真图proteus7.8及以上 程序编译器:keil 4/keil 5 编程语言:C语言 设计编号:S0101 1. 主要功能: 设计一个基于51单片机数字电压表 1、能够…

hive数仓要点总结

1.OLTP和OLAP区别 OLTP(On-Line Transaction Processing)即联机事务处理,也称为面向交易的处理过程,其基本特征是前台接收的用户数据可以立即传送到计算中心进行处理,并在很短的时间内给出处理结果,是对用…

【实战手册】8000w数据迁移实践:MySQL到MongoDB的完整解决方案

🔥 本文将带你深入解析大规模数据迁移的实践方案,从架构设计到代码实现,手把手教你解决数据迁移过程中的各种挑战。 📚博主其他匠心之作,强推专栏: 小游戏开发【博主强推 匠心之作 拿来即用无门槛】文章目录 一、场景引入1. 问题背景2. 场景分析为什么需要消息队列?为…

运行小程序需要选择什么配置的服务器

主要是看有多少人浏览,如果是每天有几十个人浏览,通常2核或者4核就可以满足需求,内存的话建议4g或者8g,足够的内存可以使服务器同时处理多个请求,避免因内存不足导致的卡顿或程序崩溃。 硬盘存储方面,50GB…

基于SpringBoo的地方美食分享网站

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…

Solidity私有函数和私有变量区别,私有变量可以被访问吗

web3面试题 私有函数和私有变量区别,私有变量可以被访问吗 ChatGPT said: 在 Web3 开发,尤其是使用 Solidity 编写智能合约时,关于私有函数和私有变量的区别是常见的面试题。下面是详细解析: ✅ 私有函数(Private Fu…

mongodb 安装配置

1.下载 官网下载地址:MongoDB Community Download | MongoDB 2.使用解压包 解压包安装:https://pan.baidu.com/s/1Er56twK9UfxoExuCPlJjhg 提取码: 26aj 3.配置环境: (1)mongodb安装包位置: &#xf…

多模态大语言模型arxiv论文略读(十九)

MLLMs-Augmented Visual-Language Representation Learning ➡️ 论文标题:MLLMs-Augmented Visual-Language Representation Learning ➡️ 论文作者:Yanqing Liu, Kai Wang, Wenqi Shao, Ping Luo, Yu Qiao, Mike Zheng Shou, Kaipeng Zhang, Yang Yo…

[LeetCode 45] 跳跃游戏2 (Ⅱ)

题面: LeetCode 45 跳跃游戏2 数据范围: 1 ≤ n u m s . l e n g t h ≤ 1 0 4 1 \le nums.length \le 10^4 1≤nums.length≤104 0 ≤ n u m s [ i ] ≤ 1000 0 \le nums[i] \le 1000 0≤nums[i]≤1000 题目保证可以到达 n u m s [ n − 1 ] nums[…

前端面试宝典---闭包

闭包介绍 使用闭包: 在函数内声明一个变量,避免外部访问在该函数内再声明一个函数访问上述变量(闭包)返回函数内部的函数使用完毕建议闭包函数null;译放内存 function createCounter() {let count 0;return function () {coun…

GPT4O画图玩法案例,不降智,非dalle

网址如下: 玩法1:吉卜力(最火爆) 提示词:请将附件图片转化为「吉卜力」风格,尺寸不变 玩法2:真人绘制 提示词:创作一张图片,比例4:3,一个20岁的中国女孩…

4.12~4.14【Q】cv homework6

我正在写GAMES101作业6,在这段代码中,我十分想知道inline Intersection Triangle::getIntersection(Ray ray) 是由哪个函数,哪段代码调用的?什么是Inline?详细解释,越细节越好 我正在写GAMES101作业6&…