大概只有自己能看懂的手绘架构图
处理分布式系统好几年了,发现很多使用者最大的障碍是不懂每个组件在干什么。有的人把每个组件都想的很重要,每做一步操作都要询问是否危险,有的人把每个组件都想的很简单,每做一步操作都不会考虑后果。这两种极端都不好,所以我想通过这篇文章,把我对一些现有的分布式系统的理解分享一下。可能也会包括一些别的系统。
这里的架构图和官方文档里的架构图不一样,在于官方图通常只会画出组件,组织方式是隐藏的,对于浅层使用用户来说,隐去细节是好的,但对于深入使用用户来说,组件之间的联系是很重要的。所以,这里的图是我自己画的,可能只有我自己能看懂,但我会尽量解释清楚。
这些结构通常不会在官方文档中展示,因为它有点深入系统,但又没有特别深入。按理说,设计文档里应该有这样的图可以参考,但大多数系统都没有提供设计文档等细节,好像都“藏了起来”。可能又是知识的诅咒吧,会的人觉得都理解了,画图干什么,不会的人也各种原因无法抽时间看代码,参考图都是抽象的不行,不懂就永远不懂。
如何快速了解架构
抽象架构图看完了,也是没办法把代码写的很细的,example当然容易,因为它都没有定制。真的需要你上手修改时,我想是有必要知道较为细节一点的架构图的。这里的细节是指,每个组件在什么情况下会联系到别的组件,联系的内容是什么,这样你才能知道你的修改会影响到哪些组件,哪些组件会影响到你。
在没有足够基础的时候,我是比较建议把example跑起来,然后debug或是打log去了解example的一步一步怎么走的,从哪个组件到哪个组件。熟练后,翻翻说明文档,扫一扫代码,就能知道这个系统的大概架构了。
至理名言:文档从来是不是给小白看的,小白看不懂文档。所以,也不要试图教会小白看文档,他缺的不是看文档的能力,而是基础知识。
OpenMLDB
五个server组件,分别是:ZooKeeper(以下简称zk),NameServer,TabletServer,TaskManager,APIServer。加上客户端,一共六个组件。我们先不管APIServer,无状态,只是特别的CLI而已,并不需要特别介绍;也不管TaskManager,它也是无状态,管理起来很简单。
架构上,我们先不管它们之间到底要做些什么,交流的具体内容是什么,只看它们之间是否会产生连接,就有下面这张图。
server之间的联系这里画的不多,不要认为它们不联系。上图主要是用户的视角,可以看到几个明显特点。
- CLI跟所有server(包括未画出来的tm和api)都有直连,区别于某些系统,它们有frontend,所有请求都要经过它。
- CLI还会跟zk联系,其实很容易想到,毕竟openmldb地址就是zk地址。一切跟集群的联系都是从zk获取某些信息开始的。
- table是分散放入tablet server的,每个分片还是多副本。副本协议暂不管。
从运维角度讲,用户需要保证:
- 组件都活着(当然,少部分掉线可能没关系,分布式的核心优势,但最终都应该恢复,掉线就靠近危险边缘了,不能就这么放着)
- tablet server上面存着数据,没有一个tablet server置身事外,所以,tablet server的storage要是正常的。tablet活着,storage不正常,该tablet还是无法服务,跟下线没太大区别。
而tablet server在意外或非意外的重启后,恢复数据是需要时间的,还有可能失败,但并不像server online/offline那么明显,很多用户会不知道有server处于正在恢复,或已经失败(但server仍然在线),直到某次使用时才发现使用出错。这就是一个常见的坑,很多分布式系统都可能发生,不过openmldb很容易出这个错,现在正在改进提示和运维方式。
用户基本不会去关心除了节点是否活着之外的其他事情,甚至节点活没活也不关心。运维角色在很多现实场景下是缺失的,也不能期望用户每次请求前都看下集群是否正常,所以,正规的话应该有“运维角色”,有报警机制,确保集群正常工作,不正常时会告知业务。如果运维不完善,就只能让用户更加容易获取集群状态,也就是事后的补救了。
SQL查询
组件在具体某个操作,某种请求下,各自扮演的不同角色,这不是组件架构图能体现的。所以,我们再画一张SQL查询的路线图。
类似impala,每个server都可以是一次SQL的主要负责人。当tablet1需要别的tablet上的数据时,是通过subquery找别的tablet查询的,走过engine去storage获取。也就是一个sql会被拆成多个task,有些事情可以在别的tablet上做,就在那边处理好了再返回。可以想象一种最简单的情况,直接从别的tablet获取原始的数据,然后,负责的tablet1里做全部的计算。这个要看SQL计划和SQL Engine的优化,具体不详谈了。
admin
Admin操作,例如增删表,增删deployment等等,也不是单点的,毕竟这是个多组件的集群。通常的情况是,CLI发送操作给nameserver,nameserver如有必要,会发送命令给所有tablet server,成功后再在自己本地操作,还可能写操作log到zk。
可以看到,参与者很多,而且各处都可能有元数据,有些操作又是异步的,某一个点失败了也可能无法回滚。所以,有时候还会发现元数据匹配不上,分布式的痛,只能具体问题具体分析了。
Spring
Java全家桶式的一些流行架构对基础弱的人来说,是十分不友好的。他们往往无从下手,我的经验来看,新手的主要障碍在于不知道一个行为的“流动过程”(一个操作背后走过了哪些组件才会产生最终的效果)。其实规范的组件,它们的名字基本可以说明它的功能,但新手基础知识不足,不能理解,看待代码就像是看一盘散沙。
我大致画一下一个简单的Spring Boot项目里,一次请求会经过哪些组件,怎么流动。先说明下前提,我是一个spring boot项目没有创建过的,目前只是根据文档和常识组织的,以后有空了会再改进。
大致讲讲,首先是左边的层级图,任何framework都是非独立存在的,是在某个语言的基础上提供的方便的“工具”,需要你使用它们做出一个独立的application。这个application简单但有能体现framework价值的形态就是example。
Spring Boot或者说Spring系设计初衷就是面向企业的,做什么都要考虑到工程化,所以它很大。一个简单的example中还会分出了一堆文件,让人觉得徒增复杂,但实际上,现实生产中的代码就类似这样,还更多更膨胀。
但无论Spring多么复杂,取了多少自己独有的名字,只要一画架构图,一般还是上图这样的。我不熟悉Spring能干多少事情,我的第一印象是做Web应用开发。所以,这里还是用Web举例。
试想一个系统要能接受很多人同时访问,比如,网页逛购物网站。用脚趾头想也知道它得前后端分离,前端也会比一个简单的html+css+js复杂很多,这里抽象成Web Server,具体是什么,可以有很多种,Spring应该内置了某几种;而后端也一样,它不会象一个计算器服务这么简单,你传给我两个数字,我加一下就返回给你,必然要操作更多的东西。
前端不太熟,先不乱讲了。
后端部分,先抽象一个Main Server,这个是主要的脑子,但也不可能只需要一个活着的进程,就能把所有事情都做完。所以,它还会跟别的组件交互,最简单的例子就是和数据库交互,这里画了2个DB Server。DB Server是独立的服务,也可能需要不止一种数据库,所以画了2个。
数据不在逻辑程序(Main Server)里,就仅仅存在数据库,这是一个最简单的后端分离设计。企业级的还会抽象出更多复杂的组件用于存储数据,比如缓存,消息队列,分布式存储等等。计算数据的组件就更五花八门了,比如,实时计算,离线计算,数据清洗等等,然后就有一大堆不同名字的系统可以选择。
但万变不离其宗,图上每个箭头就可以代表它们在网络通信,这些组件可以跑在不同机器上。每个组件只是一个抽象组件,如果单机并不能服务好,还会扩展成分布式,一个组件用一个开源系统或是什么别的处理不好,也会替换成A+B+C的组合。