之前我们在提“无线城市”架构时,说的大部分都是功能架构。如下图:
(图隐去)
这个架构设计首先将平台划分成了五个子系统,然后将系统的各种功能塞到这几个子系统里边。有的功能是业务层面的,有的是技术层面的(比如服务封装)。这种架构看上去只跟功能相关,但其实已经隐含了几个难以表面上发现的事实,而这些事实其实对整个系统是影响巨大的:
- 隐含了整个系统是可以分布式实现的,而且是按业务功能划分的分布式。比如“用户注册”这一个流程,分布到了展现子系统、运营子系统。
- 省平台的“运营管理子系统”、“服务子系统”是在32个省平台层面上是重复的。
下边分别来分析。
1、分布式的业务
1.1 背景和现状
先说第一条,分布式实现架构。这条看上去没啥特殊的,人畜无害的又闪闪发亮的样子,但其实已经违反了分布式对象设计的第一原则!
业内观点
l Martin Fowle:分布式对象第一原则:不要使用分布式。——企业应用架构模式 http://www.drdobbs.com/errant-architectures/184414966
PS: 企业应用架构模式是架构设计的一本圣经!这本03年的书准确预测了EJB2架构的消亡,rails和.net mvc架构的崛起,在今天依然有着很大的参考价值
这一原则并不是随随便便提出来的,实际上在EJB2.0的年代,与这种架构类似的“分布式对象架构”曾经非常流行。SUN曾经大力推荐将业务逻辑和存储逻辑都写到EJB里,业务逻辑用会话Bean,实体逻辑用实体Bean。会话Bean和实体Bean都可以基于J2EE容器分布在不同的机器上,理论上这些Bean都可以分别部署,以致无限分布式扩展。淘宝早期的架构中也采用了这种架构。(因为淘宝当时,请了SUN做咨询,抵挡不住SUN工程师的强大忽悠能力)。
我们的无线城市架构也类似这样,用户注册的展现逻辑在展现子系统,业务逻辑和持久化逻辑在运营子系统。这样做的优势很明显:展现子系统与运营子系统是可以分别扩展的,而且某各子系统内部的变化其他子系统不需要关心,貌似很好体现了业务封装的原则。最重要的,分出来五个子系统可以分别找合作伙伴建设。
但是即使有这个优势,EJB2.0仍然成为了Java史上最被人诟病的技术。现在基本已经消亡了。原因包括几个方面:
- 性能问题:如果不使用分布式,同一进程内的基于内存的调用是非常快的。但分布式系统必须使用远程调用,只要是非计算密集型的业务,都会比进程内调用慢一个数量级以上,调优不好的话差三个数量级也不奇怪!
什么是计算密集型的业务?可以参考天气预报,正常业务是碰不上的。(涉及数据分析的业务又除外,比如推荐引擎,后边另文说明)
- 接口的问题:分布式系统在使用时必须使用远程接口进行通信,而非分布式的系统,只要在进程内调用发一个消息即可。远程接口带来了两个问题,
- a) 违反了DRY(Don’t Repeat Yourself)原则。接口的参数必须在多个系统去声明,这些声明其实是完全重复的代码,业务发生变化时,如果涉及参数变动,每个重复的接口代码都要修改!
- b) 接口中Schema变化时,失去了编译期检查的优势。Java作为一个企业级的开发语言,一个优势就是可以进行编译期检查以减少错误。比如一个对象”User”的属性改了名字,项目其他部分引用到这个属性的地方会编译出错,只有你修正了所有错误才能运行。但基于远程接口就不行了,Schema变化,引用这个Schema的地方不会有任何提醒,必须手工检查。当然,这点缺陷可以使用单元测试来避免。而且编译期检查的优势也是有限的。但丧失了这层优势之后,语言层面就不如使用ruby等动态语言了。
EJB2.0消亡还有一个原因是设计的复杂性,使用时非常复杂,要生成stub,使用接口但却没有编译期检查。但这个跟无线城市关系不大不再赘述。(关于更详细的阐述,可以看J2EE Development without EJB一书,Spring框架的作者编写)。
1.2 架构优劣分析
OK,在最前边我们说过,架构是一个权衡的过程。分布式对象设计其实还是优势的,哪怕是它实际上在消亡。但这并不代表无线城市不能采用。无线城市有一个第一原则:必须分多个合作伙伴进行建设。在这个原则下,我们再对具体的劣势和优势进行对比:
l 优势1,功能分离部署,可独立扩展。
分析:对于部署的扩展性,业界有更多的解决方案,最典型的是WEB集群部署。这是一个比较大的话题。这里只简单说明这个优势是可以被替代的。
l 优势2,业务独立封装,逻辑变化时只动独立的子系统就OK。
分析:这一条优势仔细分析其实并不存在。就是社会主义的优势是可以集中力量克服其他世界不存在的困难一样,这条困难如果不将功能分离设计,压根也是不存在的。在一个系统内,为了减少系统的修改变动有很多原则,比如开-闭原则,设计模式等等。分布式了反而用不上。唯一的缺陷是减少系统整体重启的次数,这在集群环境下不是什么大不了的事情。
l 优势3,系统独立划分为5个子系统,分别建设,鸡蛋不放在一个篮子里。
分析:这一条优势只能被有限替代。子系统划分的方式很多,比如按层级划分(展现、业务、持久),按域模型划分,按业务场景划分等等。这些划分方式都或多或少存在上边描述的分布式系统的缺陷。我们应该可以找到影响最小的划分方式,但确实无法完全消除这个缺陷。
再说劣势,在特定的场景下,劣势不一定真的是劣势。
l 劣势1,性能。
分析:这一条劣势不是大问题。在用户量不出现激增,业务没有重大变化的情况下性能可以用硬件搞定。这是EJB2.0消亡的重要原因,但不是无线城市不采用这个架构的原因。
不过讽刺的是,我们经常为了所谓的性能优化作出很多的方案来回报,而不得不忽略这这个明显的性能瓶颈。
l 劣势2,接口。
分析:这一条劣势已经开始对现有业务产生了不良影响。多层接口的划分为故障排查时互相扯皮带来了极好的接口。在业务扩展时,同一个schema的修改也必然会涉及多个子系统,一个小的功能也需要大家联调发布。对版本规划和上线方案都带来很多困难。
如在统计分析日志中增加“门户版本”这个需求,如果采用单一系统架构,或者门户访问部署的垂直架构,都只需要在用户访问信息中直接增加一个字段即可。但我们的现实情况是,客户端、展现、运营、统计分析四个子系统都要变化!由于方案的一点疏忽(注册等功能运营增加了接口,展现和客户端未及时别花),导致运营子系统的版本迟迟无法发布,这种本可避免事情很大程度是因为分布式后夸张的复杂度引起的,对系统非常有害!
三条优势,一条不是优势,一条可被替代, 一条可被有限替代。两条劣势,一条没影响,另一条已经产生害处。基本已经可以得出结论了。不过保险起见,我们在多个层面再加以分析一下:
l 项目管理层面:呵呵。。。;
l 项目运维层面:故障排查、故障恢复、系统上线都带来五倍以上的复杂性;
l 代码开发层面:增加了接口复杂性;
l 项目集成层面:呵呵。。。
l 用户体验层面:不好不坏,无影响
l 系统运营层面:不好不坏,无影响
结论已经非常明显了:现在的分布式功能架构不是最合适的架构。
1.3 演化思路
有了结论,我们应该继续分析架构该如何演化?很自然的,可以想到两个方案:
方案一、完全不再使用分布式设计;
方案二、在现有的分布式设计上加以改进。
两个方案如何权衡,先看看业内的观点:
业内观点:什么时候应该使用分布式?
Martin Fowle:
l One obvious separation is between the traditional clients and servers of business software. PCs on users’ desktops are different nodes to shared repositories of data. Since they are different machines, you need separate processes that communicate. The client/server divide is a typical interprocess divide.
l A second divide often occurs between server-based application software (the application server) and the database. Of course, you can run all your application software in the database process itself, using such things as stored procedures. But often that’s not practical, so you must have separate processes. They may run on the same machine, but once you have separate processes, you immediately have to pay most of the costs in remote calls. Fortunately, SQL is designed as a remote interface, so you can usually arrange things to minimize that cost.
l Another separation in process may occur in a Web system between the Web server and the application server. All things being equal, it’s best to run the Web and application servers in a single process—but all things aren’t always equal.
l You may have to separate processes because of vendor differences. If you’re using a software package, it will often run in its own process, so again, you’re distributing. At least a good package will have a coarse-grained interface.
l Finally, there may be some genuine reason that you have to split your application server software. You should sell any grandparent that you can get your hands on to avoid this, but cases do come up. Then you just have to hold your nose and divide your software into remote, coarse-grained components.
福勒认为需要使用分布式的五个场景,其实第一点我们也需要,因为客户端软件与其余内容天然是分布式的,这个先不加以考虑。第四点无线城市是可以靠上的: You may have to separate processes because of vendor differences. If you’re using a software package, it will often run in its own process, so again, you’re distributing. 我们有五个供应商,那剩下的问题是,这些供应商的软件包需不需要运行在自己的进程/线程中?如果大家可以共享线程,那就使用方案一,如果不可以,那就使用方案二。
那么,能否通过技术手段实现不同合作方的软件包运行于同一个进程中?
其实是可以的,最简单的方案,不同的合作方将业务功能提交为java library,对外暴漏java interface,而不是http webservice接口。由前端(门户和客户端)进行集成调用。这样,除统计分析和管理门户外,多个子系统都可以运行在一个JVM进程中。
但是这种做法的缺点非常明显,也许是无法接受的,即多个子系统间系统运维责任难以划分!比如JVM上的WEB容器因为内存泄露挂掉,将无法在第一时间分析出哪个vendor的部分出了问题,所有合作伙伴都必须投入人力排查,期间少不了扯皮。这条路风险非常高,不太合适。
1.4 建议方案
按上边的分析,是不是方案一这条路已经完全堵死,必须采用方案二?其实换一种思路,可以提一种折中方案,即:
同一用户访问入口线程下不使用分布式,不同用户线程入口下有限度使用分布式,外部系统均使用分布式。 注意这里是线程而非进程。含义很简单,根据用户访问入口进行区分,展现门户负责从头到尾所有业务,管理门户负责从头到尾所有管理业务。再结合前边已经讨论过的统计分析和客户端,那么这个这种划分方案即为:
l 展现运营:包含原展现子系统、原运营子系统所有功能;
l 客户端软件:不变;
l 统计分析:不变;
l 服务子系统:不变;
l 其他外围系统:如需求管理、工单管理等。
架构图略去。
这种划分方案虽然仍然进行了功能分布,却完全避免了分布式系统的性能缺陷,并减少了接口重复的缺陷。对于多子系统间接口,我们可以提一种优化建议:不采用事务型接口,而采用资源型REST接口。使用“资源中心”
再从多层面分析下:
l 项目管理层面:版本管理变得简单;
l 项目运维层面:故障排查、故障恢复、系统上线都带来有限度的优化;
l 代码开发层面:接口复杂性有限度降低;
l 项目集成层面:集成复杂性有限度降低
l 用户体验层面:不好不坏,无影响
l 系统运营层面:不好不坏,无影响
这是最佳的架构演化方案吗?在有限制条件的情况下,理论上这应该是分布式设计的一种很好的办法了,但是实际上还要考虑更多复杂的因素。包括:
l 架构迁移的成本;
l 合作伙伴的特点;
l 其他非技术因素。
瞬间又变得无比复杂了。写到这里已经没有标准答案,甚至没有推荐的答案了,现实是如此的残酷,我们只能走一步看一步,向着理论最优的方向去演化。
2、省平台功能重复
这个涉及到了太多非技术因素。后边再说吧。
浙公网安备 33010602011771号