理解「分布式系统」曾经发生的事情
分布式系统主要包涵的内容很多,我就针对两个核心方面做一下解读:分布式应用服务和对象远程调用、数据的分布式存储。先说说分布式应用服务以及对象远程调用的元老之一EJB/RMI(Enterprise Java Beans/Remote Method Invocation)吧。
(一)分布式应用服务和对象远程调用
那个时候的Java工程师,对于EJB的大名如雷贯耳,曾经EJB VS Spring大战(Spring With Not EJB)让程序辕们的论战激情兴奋。其实争论的主题就是需不需要组件间实现分布式调用,并在分布式网络环境保持住组件状态,到底什么是分布式环境的组件状态呢?所谓分布式服务组件的无状态 VS 有状态
简单点说,有状态就是后端服务组件让远程调用过程看起来更像本地化调用,客户端不用考虑过多组件状态hold的问题,这样更容易设计出纯粹的面向对象化组件。而无状态反过来,后端服务组件专注于接收客户端请求并处理问题,给予客户端回应就够了,不要hold住状态徒增烦恼,状态保持的事情让客户端自己解决。
例如:购物车就是个例子,无状态的购物车,服务组件是让客户端通过cookie解决购物清单问题;有状态的购物,组件服务是自己保持住购物清单状态,那么客户端和服务端的对象看起来责任更清晰。
Rod Jonhson(Spring之父)就是用这本书掀起了当年的Spring的夺位之战。
大战最终的结局大家已经很清楚了——Spring逆袭完胜,EJB从此淡出江湖。无状态调用模式成为了主流,当然EJB本身的问题也不少,虽然之后EJB3标准由Hibernate作者重新操刀设计,也是为时已晚。曾经我也是EJB力挺者之一,也跟着EJB一起淡出江湖了!^_^"
其实这种胜出,本质上是分布式系统架构向单态架构的认怂。JavaEE的头牌EJB(企业级JavaBean)在当时代表着一种超前的设计,这种设计架构也算是当前流行的微服务架构的前浪吧,只是被拍死在了沙滩上。我们看看当时EJB VS Spring 架构:
上面的图是JavaEE的组件服务通讯图,有独立部署的远程Remote EJB容器,也有和WEB一起部署的本地Local EJB容器,它们作为组件互相之间通信(RMI),既可以WEB应用访问EJB,也可以EJB访问EJB,通过JTA(JAVA事务接口)统一管理数据库的事务操作,实现真正的分布式事务。像不像现在的微服务,像不像现在的常用的RPC调用,作为分布式系统架构,难道它不标准吗,难道这个架构它不美吗!!!
我们再看看Spring早期逆袭的架构:
上图是Spring的SSH架构图,典型的单实例架构,通过一个AOP切面拦截技术,对程序员代码层面的实现了事务调用的隐藏和透明化,让开发专注于自身的服务。这就很有亲和力,架构看起来是不是很简单。
对了,就是用这种简单的架构打败了看起来很美但复杂的架构。所以你问我,分布式系统的优点是什么?它很美,通过网络服务的组件化,实现更纯粹的面向对象模型,给程序员一个统一的编程模式来应对分布式服务在网络上的复杂性。
那分布式系统的缺点又是什么呢?
头号缺点,部署复杂,就凭这点,就极不利于测试,严重影响敏捷。这个真的不是单EJB部署复杂,只要是分布式应系统,就一定部署复杂,我们可以想想目前的微服务,再轻量化设计,依然逃不开部署复杂的问题,虽然生产环境部署分开发布也有好处,但是90%的部署都发生在开发阶段,那么频繁的测试、开发调试和重新启动部署,对于个体程序员来讲,就是比单实例服务要更加是个沉重负担。
第二个缺点就是将原来集中在数据库的SQL访问模式转变成了网络服务之间的接口调用,无论是EJB也好,微服务也好,这种分布式系统的调用模式本质都是反人性的,因为人都喜欢集中在一个中心去解决问题,形式简单,出现变化更容易定位和适应,分散不同地方的服务,无论从服务编排也好,接口变更也好,错误跟踪也好,让人去处理都太费劲了。
因此Spring的单体服务就是凭借着部署简单、适应敏捷、人性化等特质,打败了EJB,分布式系统第一次完败。但是为什么最终依然演化出了微服务架构呢?而且热度不减当年,其实我认为就是曾经EJB热度的又一次轮回,SpringCloud也开始了当年EJB之路。微服务在架构拆解之路上,甚至开始了数据库和微服务打包独立的情况,下面是微服务的架构模样:
上图是API网关进行多个微服务的调度,微服务之间互相调度,每个微服务拥有自己的独立数据库,都是从一个传统Master单库切分而来,Master库也逐渐成为数据中心库,提供基础数据交换和数据分析(OLAP)
我们看看微服务的架构和上面的EJB分布式架构多么的像,其实都是一个血脉遗传下来的,那就是分布式系统。但是微服务演化到拆库就真的好吗?我不敢苟同,咱们可以把数据库的不同数据表比喻成一个家庭的多个成员,难道把家庭成员强行拆开就一定好吗?家里原来公用一台电视机,现在拆分一个成员,就要配一台电视,难道看电视还要跑回来看吗?这就是类比了数据字段到底是冗余,还是走接口调用?这个会让设计者太痛苦的,本质是反人性的。
为什么非要这样做呢?说到底就是想让关系型数据库实现垂直切分,形成更好的性能水平提升,也就是说,问题根子出在关系型数据库身上了,关系型数据库天然就不支持分布式化,无法更好的实现数据库级的水平伸缩,所以将来一定是分布式数据存储系统替代关系型数据库的时代。因为我们如果有了一个廉价且性能强大的数据库系统,通过水平伸缩解决性能问题,我们又何必费劲的搞业务数据的垂直切分呢!这就是下来说分布式存储的关键作用。
(二)数据的分布式存储
数据的分布式存储具体表现形式也就是分布式数据库,分布式数据库不同于分布式应用服务,不存在开发测试阶段费劲的发布重启,所以并不影响敏捷性;另外数据调用都集中在一个访问代理上,因此不需要像分布式应用服务那么反人性的考虑接口管理,分布式数据库的水平伸缩力,恰恰解决了微服务必须分库的尴尬问题,可以让程序员专注于数据访问的业务问题上。既然这么多好处,为什么不能大规模应用呢?问题的根源就出在事务上了!
为什么关系型数据库就具备ACID的事务的优势呢?先简单点说说事务的原理,事务就是对数据的加锁解锁,给数据行集加锁,我(事务)要处理一行数据,我(事务)就申请一把钥匙,给数据行上个锁,其他人(事务)等着我解锁了,其他人才能访问。这个操作放到以单机设计为主的关系型数据库上好解决,但是放到如今的分布式数据库环境,这就特别麻烦了,因为数据被分成片,分布在不同的机器节点库中,事务加锁就要定位到所有相关的节点,事务问题就淋漓尽致的体现出了分布式系统在网络环境中多节点协作的复杂性,协作越紧密的事情,分布式系统干起来越吃力,为什么Key/Value数据库,例如Redis用起来最舒服,也很流行,就是数据相互之间没什么协作关系。
分布式数据库事务这个够呛的问题,也没有难住伟大的计算机科学家们,例如:TIDB继承自Google Percolator,使用 Percolator 事务模型,实现了分布式事务。也就是现在又出现的一个新名词:NewSQL,传统关系型数据库ACID特性+NoSQL。
我们先说说分布式数据库的关键是什么?就是对一大块数据进行分片(分块/分区),平平整整的放到不同的物理节点上,保持每个节点的数据量差不多是最佳的,这样吞吐性能最好,若节点有的多,有的少,这就是出现倾斜了,那么数据多的节点就会承载更大的数据访问压力。
不同的数据节点有管理者进行管理,有的管理者是集中式任命的,例如HDFS的namenode,有的管理者是被推举的,例如Elasticsearch master node。总之分布式数据库就有管理节点负责调度数据节点,也有数据节点服务数据读写。就是这么一回事儿。
上图就是两种不同的数据管理方式,第一个是主从模型,例如HDFS的分块,由namenode统一负责数据块节点的分配调度;第二个是对等模型,例如Kafka的分区,也就是每个节点即是主又是从,关键看自己节点对应的分片状态是主还是从。
我们再看看分布式事务的架构有多么复杂,就看看TIDB的架构吧!
上图是TIDB作为一个整体存在,作为玩分布式数据库的专家看了这个架构都会头大。
TIDB作为面向客户端的SQL请求和逻辑处理者,PD是集群的管理者,TiKV又通过key/value的形式持久化数据,TIDB、PD、TiKV,各自又是分布式集群,那么事务的发起、数据路由由PD负责,SQL的接收、事务过程的控制由TiDB负责,数据的落地由TiKV负责。通过这么一个责任分工明确的体系,就形成了真正的分布式事务。
其优点就是事务加锁终于去中心化了,到达了分布式数据库技术航行最远的地方了,这可是谷歌的Percolator论文在2011年就发表了,谷歌真的是神族所在地。
可付出的代价依然不小,第一个很明显,物理机器少不了,但是能到了这份上的业务也不在乎这么多资源了。第二就是TiDB的事务过程控制是在内存中进行的,等事务一致性同步好了,才会进入TiKV持久化,因此内存的消耗一定不得了,遇到延时类bug,内存就有可能因为数据洪水决堤,最后的问题还是网络交互太频繁了,保障一个良好的网络环境极为重要。
好,就聊这么多吧,希望本文的一些浅见使我们对分布式系统有一些更深刻的理解,一句话:分布式系统太复杂了,你很难去控制它,需要的是深入去理解它。