浅谈系统拆分
今晚回家路上,突然想到关于系统拆分的事情。举的例子比较极端,不一定有实际的意义
推演
我感觉拆分系统,和拆分代码,本质上是一样的。小到一个方法,大到几个系统,都是一个从输入到输出的序列
Level 1:
1 | public void doSomething(){ |
这种情况是最简单的,所有的代码都在一个方法里,没有任何拆分
Level2:
1 | public void m2(){ |
效果和Level 1是一样的,只是中间多了一次方法的调用,实际上是内存指针偏移。但比起将所有代码写在一个方法里,这里已经做了最简单的拆分
Level3:
1 | public class Class2{ |
还是很简单,但是比Level2又多了一层,将部分代码拆分到了另一个类里
直到目前为止,无论拆分与否,所有的代码都在同一个进程里,所以都是本地调用,过程类似下图:
这里要补充一点,就JAVA平台来说,凡是要调用的代码在同一个JVM里,且classloader可见,都可以认为是本地调用。不管它是打包在.jar文件里,还是直接存放在classpath里,也不管其源码是否来自一个地方
Level4:
现在,输出222这段逻辑,被拆分到了另外一个系统里
所以,只有通过远程调用(RPC),才能调用到这段逻辑了。但是本质并没有改变,还是从输入到输出的一串序列,只是本地调用,和远程调用的区别
事实上,这种区别对调用者来说,常常是透明的
1 | public void doSomething(){ |
对这个调用者来说,它并不需要知道proxy的实现来自哪里。这是一次本地调用还是一次远程调用,对调用者来说是透明的。只有在上面的部署图里,才能看出区别
通过这个例子,我想说明的是:核心的问题是逻辑,而不是怎么拆分系统。逻辑不会因为放在一个方法里,或者放在10个不同的系统里,就发生变化。最终的结果都是一样的。同时,当需要思考“为什么要拆分系统”的时候,某种程度上和“为什么要拆分代码”是类似的
优势
实际的设计中,拆分系统一般都是必要的,我认为至少有以下3个方面的好处:
复用服务
前面举的例子过于简单了,可能无法说明问题。但是想象中间那行
1 | System.out.println("222"); |
是一段非常重要的代码,其他很多逻辑都需要它。那么如果它是混杂在一个方法中,应用的其他部分就无法重复地调用它了(复用);而把它抽取出来之后,就可以实现复用
系统拆分也是一样,比如我画的这两张示意图,是用QQ截图功能截成JPG的。为了截这2张图,我只好把QQ给打开了。如果QQ截图被抽取为单独的模块,我就可以单独打开截图功能了;如果腾讯其他的应用也需要实现截图,就可以复用这部分功能了
当然,系统之所以被称为“系统”,而不是“应用”、“模块”、“方法”,是因为它比较大(我认为除此之外,完全是一回事,有输入、输出、逻辑的都是系统)
所以,作为系统,复用的往往不是截图这么小的功能,一般希望能够复用一个完整的服务。比如独立出“用户权限管理系统”,就可以在多个业务系统中,复用“用户校验和鉴权”的服务了。我想这就类似几年前比较流行的一个大词SAAS
性能考虑
一个大的系统,往往其中的某个部分,承担了很大的并发压力,或者逻辑运算特别复杂,因此成为系统的性能瓶颈
这时就可以考虑将这部分独立出来,从而实现单独部署,再通过负载均衡等手段,来解决性能的问题
逻辑清晰
根据业务上、流程上的不同环节,将整个系统拆分为不同的子系统,在逻辑上会更加清晰,从而更容易在架构上说清楚,或者进一步做优化