不要把分层当做解耦!
公司的老员工很喜欢一种他们称之为“解耦”的做法:
先这么进行系统分析:把系统运行拆解为若干个环节,先执行A,再执行B,形成一个一个的步骤。
然后进行“解耦”:把每个环节变成一个服务,环节之间用 MQ 连接。环节衔接的输入输出数据形成协议规范。
“解耦”之后得到的好处是:
环节用MQ串联,当服务死掉后,MQ能存储消息,等服务重启可以继续执行;当MQ无法消化时,可以为这个环节的服务增加机器;当这个环节清闲时,可以裁剪机器。
这套“解耦”的办法和部门划分一脉相承,部门人员富裕就裁剪,工作量完不成就加入,公文始终堆在那儿。
程序员的特点是手里有锤子到处找钉子,凡是看到系统没有这么分割的,他们就认为“解耦不充分”。程序人生就是不断复制这个模型的人生。
很多公司的架构也停留在这种软件设施级别的架构:如何实施 KAFKA,如何上SPARK,如何玩ZOOKEEPER等等,妄图通过引入这些灵丹妙药改变世界。
然而效果如何呢?由于涉及公司内部系统,保密起见这里不做展开,简单的说,系统烂的一比!
为什么会演变成这样呢?
第一,系统烂源于业务架构识别不当
如权限方面,本人在05年就搞组织机构无限划分,而这套系统里公司都是平齐的,难道它们都搞扁平化管理了?
权限不是目前系统的核心业务,本系统核心业务对象识别的也一塌糊涂,但涉及公司业务这里不展开。
我认为,系统分析能力主要指的是业务架构的能力,识别核心业务对象,让业务在层级之间活跃,围绕业务进行架构。一个解耦得当的系统,首先是业务之间的穿插少,板块之间相对独立,不互相污染缠绕。其次,抽象度高,针对上层抽象的逻辑不影响下层抽象。
还是拿组织机构划分来说,在我的设计里,组织机构只有一张表,不论公司、部门、科室还是机场、医院、学校、院系、场站等等,隶属关系都记录在这张表里,表达组织结构就用这个表,需要侧重表达时就从这个表继承,通过共享主键继承派生出医院表学校表机场表等等、现在有了JSON字段,甚至可以不继承。这样的组织机构表,充分表达且仅表达了对权限系统至关重要的层级关系,而与权限系统无关的如公司联系人注册资金地址法人等等信息,则根据业务需要发配到JSON或派生表里。派生表和组织机构表,分别表述公司不同的侧面,既支持又独立,这才是真正的本质性的解耦,是符合形式逻辑的结构。从概念来说,组织机构的概念级别更高,机场医院部门科室的概念层级更低,组织机构包括了机场医院部门科室,组织机构的性质是管辖-隶属。这样的解耦是每个系统都需要的,它能真正节约人工投入,形成清爽愉快的代码组织架构。
即使这批代码全部在一个项目一个进程又有何妨,没有必要隔离时为何要隔离到两个进程!一个聪明的架构师,要有因地制宜具体问题具体分析的头脑,要有洞穿现象分离出业务本质的洞察力和思考深度。在一个人体内,神经系统,血液循环,呼吸系统等等,各自独立又共用一个物理区域,不能把人斩成几段来划分人体区域。斩成几段谁不会?是个人就会,那叫莽夫不叫大夫,属于原始思维不属于科学思维。
第二,遏制自己到处耍锤子的冲动
公司的老员工特别爱玩消息队列,却对 AM 模型一窍不通。
要了解Actor-Message模型最好的途径就是读Joe Armstrong关于 erlang 的论文,然后下场用 erlang 实战做一个小应用。
除了erlang的虚拟机,别的主流的虚拟机或操作系统都不是为 ActorMessage发明的,它们原本是不支持 AM模型的,于是,有人用 erlang 搞了 RabbitMQ,让其他不是 erlang 虚拟机的程序无需多大代价就能沾点光享受一些AM的好处,但是这种享受是有限的。后面又有其它语言实现的 MQ,以及由于Java毕竟不支持真正的原生 Actor 导致整体往流的方向发展,这里就不展开了。
这里要认识到几个问题:
1. MQ不是真正的Actor-Message,是跨进程访问。在erlang里可以随手建一百万个Actor,而在MQ里,你只能拣几个主要的对象关联到MQ的收发者。这是因为,MQ走的是网络协议跨进程,其速度再怎么快,也不可能快过进程内部调用。我做过一个测试,通过RabbitMQ做RPC,跑一万次加法需要15s!当你要做的系统是一个实时系统,不要搞MQ!如果你的运算非常紧密,不要搞MQ!你的电脑有很多传输方式,有 PCI、有 PCIE、有 SATA、有 USB,为什么不全用 USB 呢?跨进程通讯的成本永远高于进程内调用,一个跨进程调用,需要将数据打包成 bytes,要组MQ的包,要组TCP/IP包,要传输,再从 bytes 恢复为程序对象,这些步骤的开销惊人,只要允许请尽量直接访问同一个进程内的、同一个VM上的程序对象!
2. 你的系统内部用了 Actor-message 吗?在 erlang 里,Actor位于跨进程的节点和位于同一进程的节点,递信方式没有多大差别。如果你的系统本身就是 Actor-Message的,你只需要将系统复制几份就能获得增长能力,不需要去拆解成几个服务!当然,你玩的不是erlang,即使用了Akka框架之类AM框架,和其它语言通讯也是问题。但是这不妨碍你把系统抽一下头,加几个MQ的注入点。这样做的好处是,你的程序代码还在一起,你还能看懂代码,而不需要在一堆工程之间跳来跳去才能搞明白一段业务逻辑。公司有一个项目,东西不多,核心功能就那么一点,居然搞出 9 个工程!
3. 你考虑过 erlang 的其它好处吗?erlang 还有热部署、无副作用、监督树等等优势,你想过你玩的MQ支持吗?
4. 你知道任务调度吗?在任务调度眼里,MQ只是一个手段,任务才是重点!!! 能做任务调度的框架有 celery 等等。为什么要上任务调度而不是停留于MQ?人的认识总要往更高层次发展,一个物理层的概念离产品颇有距离,你需要一个真正的任务调度来和业务耦合。业务->任务调度->MQ,这是正确认识。我们知道,在真正的 ActorMessage里,任务也是一个 Actor,但是你用的毕竟是 MQ 而不是 erlang,erlang 可以迅速创建一百万个 Actor,MQ搞一百万个队列试试。
第三、技术层面的解耦不等于拆进程上消息队列!!
技术层面的解耦,需要认知和发现中立的业务无关的或者业务无知的组件或服务。
技术解耦的结果除了物理分层变成若干进程,也可能形成一种程序组件,或者一种程序概念的逻辑分层。譬如地图组件就是一种解耦,TTS组件也是一种解耦,解耦得到的组件仍然可以放在一个进程内使用。又如MVC也是一种典型的解耦,解耦后,有的将M层独立为一个后台服务器,有的将M和VC都放在UI进程。解耦并不意味着跨进程上消息队列!!
结论
一个系统的所谓解耦,最重要的是业务层面的解耦,业务层面的解耦更多的来自对业务本身的透彻认知。
在实施技术解耦之前要想明白,经过技术解耦后,你的业务会不会流窜到各个技术解耦后的层,如果会,冲动是魔鬼,你需要悬崖勒马好好反思再做定夺。