一、阅读经典源码的层次境界
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、阿里工作,目前在滴滴小桔车服加油团队负责营销基础 (优惠券、奖励金),在分布式中间件和系统架构方面积累了一定的经验,擅长用通俗易懂的语言描述复杂问题。
出处:https://www.infoq.cn/article/sCF83heYqQJ4U8vTEQWk
相关文章: