在上一篇文章中提到技术架构等于解决业务上的技术问题加技术方案加技术组件。其中,技术组件是物理基础,当下也有很多开源的中间件,最核心的是技术方案,它是灵魂。笔者认为技术架构如同中医一样,药材是基础,而药方才是最核心的,药方好比技术方案。本篇文章中,以笔者的一些经历,从经典源码中汲取架构的一些思想,同大家分享。
一、阅读经典源码的层次境界
1.1 架构类比中医
中医在成长过程中,从识药开始,再读经典著作学习药方,到临床实践,最后通过自己的总结,创新某类药方,进行裁剪和扩充。大医以某一个药方就能闻名于世,所以这些也给我们技术人某些启示。笔者将架构类比中医,基础的组件(语言基础、系统结构、中间件)这些好比药材,设计模式、架构模式、经典框架这些好比药方,在实际的工作中好比临床实践,到底如何设计好的架构方案呢?笔者以自己的一些感想和大家分享。
1.2 源码的阅读层次
如果问一个人,你为什么阅读源码?得到的大家也不尽相同,常见的回答有:
掌握内部原理:框架内部到底做了什么?
应对面试:现在面试中为了考察是不是真的用了某个框架,会问到源码细节?
。。。
笔者认为阅读源码有三个层次境界(当然,能力更高的可以看得更远、更深):
了解层次:知道整体的流程是怎样的,会使用,这个阶段停留在表面。
深入层次:深入源码内部,对细节实现、原理非常熟悉,知道引入这个是为了解决什么问题、如何解决问题的。
内化层次:如果达到第二个层次,其实是已经很厉害了,内化层次就是从这些框架中,可以汲取它的设计思维,并能总结出通用方案,又能根据这个通用方案演化成不同的具体方案出来。这个听起来比较虚,没关系,下面会拿具体的例子来一步一步印证这个说法。
二、提炼共性的内核思想
2.1 从熟悉的 SPI 开始
如果工作了两三年人,对 SPI 或许并不陌生,不熟悉的可以在网上查阅相应的资料。简单的 SPI 用法在这里并不详细说,我们接触到的场景有最熟悉的数据库驱动。
在 META-INF/services 目录下,有一个 java.sql.Driver 文件。
然后在 DriverManager 中加载并初始化数据库驱动。
SPI 最常用的场景就是标准定义与实现解耦,运行时加载实现。为什么要在 META-INF/services 目录下建立文件呢,跟踪源码,在 ServiceLoader 类中可以看到有这个前缀定义,到这里可以看出这是一个约定俗成。
至此,可以看到 SPI 的实现原理是什么,说白了,就是运行时在指定的目录下加载对应的文件,读取文件中的内容,然后实例化对应的类。
2.2 源码进阶第一次
尽管已经知道了 SPI 的原理、用法、甚至在某些场景下还可以用 SPI 来解决问题,如果只是止步到这里,似乎隐约有些难以释怀,但这种又说不清楚,下面继续问两个问题?
为什么它要定义在 META-INF/services 目录下建立文件,能不能换个目录呢?
目录里的内容为什么只写实现类,能不能换成其它的格式来写呢?
有些人看后就说源码就是这样写的,哪有为什么,其实深入思考一下,这些就是约定的规范,换言之就是标准,这是得到一个重要的结论。再仔细看整个 SPI 的执行流程:从约定的路径下加载文件并实现化类、放到容器中、需要的时候从容器中取出并执行对应的操作。
从上面的流程中总结它的关键步骤:标准、识别、注册、运行。到这里是不是有一种恍然大悟的感觉,标准就是一种约定,你可以定义成文件,你也完全定义到其它的方式,如集中式存储,放到 redis、zookeeper 等;识别就是根据约定的方式去查是否有对应的数据;识别完成之后就注册到容器,待需要运行时再去取。所以你再去想,实现方案完全不止一种,SPI 只是其中的一种实现方案而已。
在这里有必要总结下:
系统的可扩展性可以遵循标准、识别、注册、运行总的法则去演化不同的具体的实现,可以根据实际的场景选择合适的方法,不再是 SPI,思维就会打开很多。
阅读源码的时候,除了要看它实现的原理,更重要的是找出通用的规律,然后可能演化成不同的解决方案。架构的方案就是从平时积累中选择合适的方法,就像药方一样,一定不会凭空想出来的,一定是根据某些药方中进行选择、合理配制而成的。
拿平时用得最多的一些框架源码,再去看它们的实现原理时,会显得很熟悉,如 Spring 通过 PostProcessor 开放了用户自定义的业务逻辑,它的流程也是遵循上面的八个字"标准、识别、注册、运行"。
2.3 源码进阶第二次
现在到这里了,不再关注某个具体的源码实现,现在开源框架太多了,一个人很难有精力看完所有的框架源码,但有一个关键的是:如何快速掌握源码是一项关键的能力。阅读源码的方法在网上很多,笔者不是讲方法的,而是传递一种理念,这种理念就是内核思想,掌握它,去学习新的框架就会事半功倍。听起来很美好,但要做到可并不简单。
那框架到底是什么呢?不同的人对它的定义也不一样,不管怎样,一定要达到灵魂级的理解,这样你的认识会直接决定你后面的走向。笔者对框架的定义是:有固定的流程,并且对用户开放业务逻辑的半成品,这里有几个关键点,先提一下,如果没有类似的经验,看到别人的定义一时并不能完全理解作者的意图。
固定的流程:这是框架解决具体问题的关键步骤,这里并没有涉及太多的细节,只是一个大的流程节点,强调的是有什么而不是如何做。想想 Netty 的流程是怎样?
开放用户业务:框架的核心是赋能于用户,解决某类问题,能执行用户的业务逻辑,这句话听起来并不好理解,举个例子,Hadoop 在执行 Map-Reduce Job 时,不同的用户他的业务逻辑是不一样的,Hadoop 能赋能用户,执行用户自己的业务逻辑。
半成品:在没有具体的业务场景下,框架本身运行是没有意义的,所以它是一个半成品,想想 Spring 框架,单独执行 Spring 并没有什么意义。
从这些框架中,可以学到一种架构思维:固定不变的流程,开放变化点。阿里 TMF 框架中的关键思想也有类似的表述。笔者所提的可扩展性架构设计方案就是从 SPI、插件模式中获取相应的思想,然后选择合适的方法去实现。
现在回过头再看上面对框架的定义有固定的流程,并且对用户开放业务逻辑的半成品,阅读源码的关键是什么?找固定的流程,知道了固定流程,再带着疑问去看源码实现,就快得多。笔者在学习 Hadoop 时,就使用这个方法能快速掌握其原理,列几个问题供大家思考:
上传的 Jar 包上传到哪里了?
如何执行 Jar 包里的 map 和 redece 方法?
节点资源是如何分配的?
上面的问题就是从流程出发,Job 的执行大致流程就是上传 Job、调度分配、Job 调度、具体执行,知道了流程,再去带着疑问看源码,会快得多,否则陷入细节中不能自拔,没有全局的方向感。
三、架构思维的积累
架构思维听起来是一个非常空洞的概念,尤其是对于没有架构经验的人来讲。对于一个刚开始接触架构的人来讲,如何学习呢,笔者认为应该是从小点着手,积累通用解决方案。
3.1 架构并不是高大上的花架子
在第一篇文章中就提到,架构就是为了解决特定的问题,它并不是空洞的东西,也不是简单画个 ppt,架构是要实实在在的解决问题,在业务初期,它的主要问题并不是技术、也不是业务,而是快速试错找到市场方向,所以这个时候,一般是没有架构的概念。当业务成了一定的规模,遇到了一些瓶颈时,才会考虑架构,如用户量猛涨,考虑性能、可用性、存储等,这时并不是简单的一个方案就能搞定,是要深入业务场景、设计出好的架构方案来解决问题。比如在营销场景中,有很多前置判断,很多业务都有这样的操作,如会场、活动、优惠券、奖励金…,每个应用都有重复的代码,怎么去做到功能复用和扩展呢?这就需要一定的架构能力去解决。架构的目的是为了解决特定的问题,核心就是为业务、用户赋能,能快速、更好地解决问题。
3.2 从小的点开始积累
坚实的基础对于架构师来讲是至关重要的,基础不扎实,只会一些框架的表面的东西并不能长远的发展。厉害的人通过一个小点就可以看到很深的东西,正所谓"一花一世界",听他们讲,真的是深入浅出、大道至简、如沐春风。这样讲给大家没有什么感觉,还是拿具体的例子来说让大家比较有同感。
插件对于我们来讲,并不陌生,插件是一种开放的体系,定义一套标准之后,可以有不同的实现,那它核心的逻辑有:定义插件接口、实现插件接口、识别插件、加载插件、运行插件。这个流程相信大家没有多少争议,其实我们平时接触的微信小程序也与之很相似,通过扫一个二维码就可以进小程序中,这个二维码就相当于是一个标准,小程序可以有多个实现,所以微信是一个开放的平台,它定义了标准和加载实现、运行实现,具体的实现它不关心。
通过上面的例子,可以看出插件是开放平台的一种实现,虽然不同的外在形式不一样,但本质就是插件的思想,历史上有名的中医凭一个药方就能闻名于世,这是抓住了问题的本质,不管它怎么变,本质是哪样。同样适用于架构,需要抓住问题的本质,一套方案能适用一类场景问题。技术在一个边界内是需要稳定和能复用的,否则投入的技术成本太高了。
四、小结
本篇文章主要分享了如何从优秀的框架源码中学习架构设计,除了知道源码的原理之外,可以更进一步地掌握是如何解决问题的、用到哪些方法,不断地积累这些方法,形成通用的架构设计方案,在实际的工作中,遇到类似的场景时,可以选择合适的方案来解决实际问题。
作者简介:
高福来,先后在 Oracle、阿里工作,目前在滴滴小桔车服加油团队负责营销基础(优惠券、奖励金),在分布式中间件和系统架构方面积累了一定的经验,擅长用通俗易懂的语言描述复杂问题。
相关文章:
评论 2 条评论