系统为什么拆分?
系统做大了,并发量无法扛得住,如何做?
业务做复杂了,单个应用中不能个性化,如何做?
模块和逻辑对各类资源开销非常特殊,如何做?
。。。。。。
拆分、拆分、再拆分。
由 全世界用一个系统表达全世界所有的企业和公司的业务开始,注定系统做大后必然拆分的走向,也就是一个大力士无法完成成千上万群众所能做到的一件大事,高集 成度的硬件和软件解决方案,为传统企业提供较为完善的解决方案,并在这种程度上是可以节约成本,高端机和高端存储的解决方案,当达到一个成本的交叉点后,随着数据量以及并发量的不断上升,其解决方案的成本也会随之直线上涨。
如何拆分?拆分后有什么后果,是其中一个问题?
首先我们看看应用一般是如何拆分的:
1、应用一般的企业内部都是按照业务增长方式比较多,所以随着业务的增加,将系统进行拆分的是比较多的,也就是将一个较大的系统拆分为多个小的系统。
2、在一些企业中,不愿意将系统拆分为小系统(原因后面说明),而是将所有的内容部署在一起,依赖于集群分发到多个节点上去做负载均衡,这样来完成一种切割,前序两种也就是应用系统级别的纵横向切割。
3、 独立工具、模块、服务的独立化和集群化,基于SOA服务的企业级应用,很多模块经过抽象后,并非子系统,而是一个独立的服务系统,不参与业务,只参与一个技术级别的功能服务,如MQ、JMS、MemCached等,我们经常也管这一类叫做中间件,也就是平台没有提供自己来做或第三方提供的中间件(当然中间 件也包含应用服务器)。
4、数据库拆分,数据库拆分是也是因为压力上升,以及存储容量的需求,最终在成本上认为拆分是必然的走势;数据库拆分有多重规则存在。
5、由于上述各类拆分导致的运维的困难,在数以万计的计算机集群下,如何动态资源分配和拆分以及抛开分布式的内部细节来编程,如何自动化运维系统就是大型计算机集群下需要考虑的问题-云存储与云计算。
我们拆分中面临哪些问题?(这些内容在后面的文章中说明,本文不再阐述)
1、负载均衡器的问题。
2、不同系统之间的通信问题。
3、数据写入和查找的问题。
4、跨数据库事务问题。
5、跨数据库序列问题。
6、不同应用的本地缓存问题。
7、系统之间的直接依赖和间接依赖问题。
8、独立模块面临的单点问题。
9、各类批量分组、切换、扩展的问题。
10、统一监控和恢复问题。
本 文我们暂时不讨论关于云存储方面的问题,先引入话题,不过每项技术的产生都是为了解决某些特定的问题而存在,所以云也并非万能的,后面的文章我们会介绍一 些基于纯java开发的hadoop相关架构和模块(如:MapReduce、Hbase、Hive、HDFS、Pig等子系统,说明当今海量信息的互联 网中大象的崛起)。
1、系统按照业务拆分:
首先看下企业中拆分为小系统的过程中的过程和遇到的问题,在大多数企业中,选择高端企业的解决方案,因为一台两台小型机一般的企业都没有问题,除非是做的项目的确太小了,这类系统的访问量大概每天保持在几十万左右高的有一百多万的,不过为什么要拆分,一般有以下原因
a.随着业务的发展,模块之间的耦合性越来越强
b.开发人员越来越多,相互之间代码版本也难以管理
c.系统启动加载PermGen的时间也会很长并且需要很大的PermGen,更加重要的原因是JVM在CMSGC出来之前管理大内存是有问题的
d.尤其是发生Full GC时候在大内存的JVM上暂停时间是相当的长,长得让人无法接受.
e.在单个机器上硬件厂商做得集成度越高,算法就越来越复杂,尤其是CPU的个数始终有限,这样就导致的是单位时间内处理的请求数也就受到限制,拆分水平扩展是非常容易的.
f.一个大系统由多个开发商完成,多个开发商都有自己的主打产品,为自己节约成本,将各个产品以集成的方法完成一个大系统的业务过程。
等等原因。
那 么系统拆分这样的系统拆分有什么技巧吗,可以说原因就算是技巧,也就是在什么时候再拆分,一般系统我们能不拆分就不拆分,因为拆分有有很多麻烦要去面对, 面临的第一个困难就是以前一个工程内部的系统,相互之间的调用就可以直接调用到,现在很麻烦,要两边来做接口,接口还得联调,联调是一件比较恶心的事情,尤其是两个厂商之间来联调。
所以拆分应当具有的最基本条件是高内聚、 低耦合的条件,也就是说,这个系统和外部系统的调用模块对于整个系统的模块来讲是比较少的,而不是大部分模块都是在和外部系统交互,除了专门用于处理系统交互的系统外,这样的拆分设计是肯定不合理的,因为通信的代价远远大于本地JVM的代价。
开 发人员越来越多,从最初的一个人,几个人,到几十人,几百人甚至上千人,在一个工程中来写代码是很恐怖的事情,谁改了没法查出来,无法定位,很乱,所以拆 分在一定程度上可以将版本控制的粒度细化一下,但是并不代表拆分后就没有版本问题;随着产品不断模块化和抽象化,在大多数的应用中,独立的子系统就会成为一个独立的行业产品,可以基于配置模式的适用于大部分的地区工厂或者企业的应用,也可以通过一个顶层版本分发出来的多个地区化个性化版本(可能有两层结 构);也就是在节约大部分共享劳动力的基础上如何做到个性化平台,这也是行业软件中非常盛行的,不过这样将绝大部分程序员控制在一个小匣子里面了,几乎没有发挥的空间。
上面也说了,系统可能由几十人、几百人甚至于上千人去 写,如果大家都写一个工程,代码量可想而知,系统初始化需要加载代码段到PermGen,这块空间将不可预知的大小发展,并且随着业务的复杂性,需要的引 入的第三方技术越来越多,第三发包的class同样会占用PermGen的空间,不用多说,这块空间的大小是不可预知的。
当 发生Full GC的时候,遍历整个内存,在没有CMS GC出来之前,或者现在G 1的出现,Full GC对于几十G上百G的大内存是一件非常痛苦的事情,延迟时间可以打到十几秒甚至于上百秒(这里在16个4 core的CPU使用了并行GC,时间是应用暂停时间),这是不可以接受的,虽然CMS GC已经可以在较短的暂停时间内回收掉大内存(只是暂停时间减少,但是回收时间可能会更加长),不过在它目前解决的主要问题是这个,同时由于大内存部署逻辑节点的个数减少,使得负载均衡器的负载目标成倍减少,这样可以让同样的负载均衡器支撑起更加庞大的后台访问集群;不过大部分早期的系统还没有看到CMS GC的诞生,更加没有想到G1会出现(其实早在N多年前,论文就出来了,只是一直没有实现而已),所以一直都还是在沿用比较老的拆分方法,不过拆分始终是 有它的好处的,不仅仅是因为GC的问题,在传统企业中一般的负载均衡器也足以支撑,不会面临更大的问题。
对 于集成度较高的,通过芯片等方式来完成高性能的服务方法,对于传统软件来讲是非常好的,因为通过硬件完成的,一般情况下比软件完成的速度要快(所谓通过硬 件完成除了通过集成电路增加各类特殊指令外,还有就是基于芯片或底层语言实现使之效能更高而且封装操作),不过遇到的问题就是随着集成度的高度集中,算法越来越复杂,导致了内部的诸多冲突,水平扩展性受到了严重的限制,所以几乎没有多少算法的拆分,是一个必然的发展趋势。
多 个开发商完成了一个自己的系统,开发商为了产品化系统,并且由于系统的复杂性,以及提升开发商在行业内部的积淀,所以就需要不断完善产品,不断版本化,以 及本地化的不断改善;这个目的是好的,不过一定要做好版本的事情,以及一个大型的行业软件的顶层架构以及继承关系,否则不如不做,部分软件厂商可能只考虑到前者,也就是产品化,不过代码顶层架构几乎没有,只有业务架构,产品化和本地化代码更加是随心所欲,软件五花八门,就像贴补丁一样,谁要做本地就加一个 else if,甚至于有直接对地区判定的硬代码,很无语的做法,我个人认为这样做不如直接拿一个模板来改出来一个系统,就不要做什么版本,因为这样的版本的代码是越来越烂,面对这种代码唯一的办法就是重构,如果不想重构就永远下去吧,不面临改变终究会被淘汰掉;这种情况也面临在系统底层版本升级上,包括JDK的升 级,如果只是考虑到成本和风险的话应该说真的永远都无法升级,没有做不到的升级,关键是否愿意去做,越晚去升级,所带来的成本代价是越高的,类似国际上有多少大型软件的底层版本也是在不断的升级中,而上层的代码由于繁杂而不断的重构,虽然说不一定要时时刻刻重构,这样程序员也会很累,并且也体现不出他们的 价值,因为成天就是改代码,但是该重构就应该要去重构。
负载均衡,首先负载均衡可以是硬件也可以是软件,硬件在一定程度上支撑不上去的时候就要考虑通过软件的负载均衡来解决了(硬件一般情况下比软件要快速,但是它本身设计的复杂性导致了在一定场景下不如软件的扩展性好),系统在拆分后不论是分布到各个机器上还是在一个机器上虚拟出来多个节点都是需要,将其负载均衡的,按照URL和端口+虚拟目录名称进行负载,负载均衡器需要 知道这些内容,也需要知道那个session访问了那台机器,中间负载均衡器会采用一些特殊的算法来解决一些问题,这里简单介绍到这里,在下一篇文章中会 介绍下负载均衡的大致原理和作用。
负载均衡器并不简单承担这个问题,在负载均衡器中一般还会有很多算法存在,如负载均衡器比较经典的算法就是一致性hash算法,或者轮训检测;而在有限的线程下,为了得到更大的连接访问,异步IO的访问策略应运而生,著名的Nginx到目前为止都是全世界大型互联网前端负载均衡的设计蓝图的标准,其QPS极限情况可以打到30000-50000左右,内部还存在各种模式来支持不同的情况(NAT、DR、RUN),当然还有很多类似的负载均衡设备(设计上有些差别)。
2、系统水平拆分:系统水平拆分即同一个子系统,或者整个系统部署在多个node上,这些node可以是多个主机或同一个主机上的多个软件节点;但是这些节点目前来讲即使应用拆分得再细,在分布式系统上的这种低端机器也不可能扛得住高并发的访问,一般这类低端服务器代码调解得较好等各种情况下,服务器的QPS一般都是保持在200以内的(这是以16个CPU来处理,一个请求在80ms内处理完成请求分派,业务处理和数据请求,反馈结果等过程已经是非常快速的了,很多时候一个SQL都会超过这个时间),当然单用几个字节去做压力测试,反馈几个字节,并且中间几乎没有IO方面的额请求(如数据库、文件、远程方法调用等),那么这个QPS你可能会上千,甚至于在好的机器上可以上万也有可能。
也就是系统真正运行的时候,前端的用的并发量都是有限的,而且很多时候代码不好的时候,一般应用的QPS还会更低;面对高并发,在这种情况下,我们唯一可以做的就是加机器,也就是水平扩展,它的分发也是依赖于负载均衡设备,加机器的过程就好比是工厂里面的请很多工人来做同一件事情一样,相对来讲第一种拆分就是请不同的人来做不同的事情,不要让一个人从头做到尾部,那样会搞得很累,而且对人的要求也很高。
这种拆分没有什么太高的要求,只要负载均衡设备可以支撑就可以,为了让负载均衡可以支撑更大的压力,那么就尽量让节点数量减少,那么就希望在同一台实体机器上尽量一个节点(通过对实体机器进行虚拟化可以在某些情况下节约成本,并将物理机本身的性能发挥到一个极限,并可以将一个比较好一点的机器分摊给多个访问量较低的系统,不过虚拟化本身也会产生很多开销,在这些方面需要综合权衡一下好坏),可惜目前来讲Oracle的Hotspot VM还不足以支撑大型的非常好的实时系统(我们很多时候不得不在同一个大内存机器上部署多个小的JVM节点),尤其面对几种场景显得力不从心:
1、大内存的管理(包括GC、内存分析、跟踪等都没有完善的体系和解决方案)。
2、做实时应用不适合,实时应用的延迟一般是毫秒级别(如2ms响应,最慢也不能有十多毫秒的响应,当然这种不包含IO操作,只是做一些内存处理和反馈,并且数据量不大),而java在正常情况下,如果一旦发生GC,即使并行GC,而且仅仅只针对Yong空间做GC,也需要一段延迟(在一个16CPU的机器上,配置了并行GC,发生YGC的时候(Yong的大小大概为330M左右),延迟大概为10ms-15ms左右,发生Full GC的时候(Heap大小为1.5G),延迟大概为30ms-40ms左右),如果是更大的内存,就更加蛮了,因为回收的时间很多时候取决于内存的大小,增加一倍的内存,并不代表回收时间只增加一倍,因为随着内存的增加,回收过程中产生的开销和冲突也变化,所以内存增加一倍,时间不一定只增加一倍,曾经在96G的JVM内存上,采用16CPU进行全局GC,大概需要3分多钟,也就是说这3分多钟外部是无法访问的,在实时应用面前这就是垃圾。
3、做缓存不适合,分代垃圾回收考虑的是绝大部分对象都应该死掉,而Old会采用全局GC,即使是CMS也会有各种问题;很多时候我们在缓存的时候,数据初始化就会装载进去,而很少甚至于不用去做GC,至少可以说99%的内存是不需要考虑GC的;而且做缓存的服务器内存都是大内存,也就是没有地方让自己来操控可存放不做GC的内容,但是程序员发现这部分内容占据了绝大部分内存而自己却无法控制它。
4、目前不支持半长命对象,也就是要么是长命鬼、要么是短命鬼,但是很多非常规应用中,有很多半长命对象,采用不同的算法,会提高更好的性能,如一些page cache数据,在内存中启用LRU策略,这些队列的数据,不能说他们的寿命很长,也不能说他们的寿命很短,但是LRU本身在使用的过程中,不想受到类似Yong和Old之间的这种晋升策略,因为放在Eden中觉得命太短,来回倒腾,有很多还是会到Old中(具体有多少进入old要看应用场景),进入old它又并不是什么太长命的东西,随时可能就挂掉了,真是无奈啊。
其实还有很多JVM不方便去做的服务器方面的特殊应用,不过随着JVM的发展已经比以前有了很大的飞跃,而且越来越多的硬件厂商和学术界的顶尖高手在为java的发展而努力,所以我很期待java能解决掉这些问题。
3、 独立工具、模块、服务的独立化和集群化
其实这种拆分和第一种拆分有相似之处,几乎可以算是一样的拆分模式,不过说到工具化、模块化、服务化、集群化,这种属于更为专业的拆分,第一种拆分的依据是系统各项压力上来,为考虑扩展性,而不得不拆分系统,而将很多高内聚、低耦合的系统拆分出来,也就是模块成为了子系统。
而这种拆分是一种技术独立性拆分,将很多较为复杂,不好解决的技术以及工具特征独立出来,虚拟化为一种服务模式,为外部提供服务,你可以将它理解为一个传统的子系统,不过它是属于很多系统里面都需要的一个公共子系统,而前者仅仅一般只为自己的兄弟模块提供相应的服务以及一些自己的对外用户服务;比如:将邮件系统独立、短信系统独立就是为很多应用服务,大家都可以使用,将通信技术独立、将分布式缓存独立、将配置管理独立、将版本管理独立就是属于技术上的独立进而逐步个性化成为一种服务。
第一种和这种拆分方法没有明显的区别,可以说这种拆分的思想是受第一种拆分的影响或者说基于它来做的,它的目的是以一个个体或者集群为外部提供一种公共服务;当一个企业或者一个大的互联网公司,将这些公共服务开放出来后,形成一种全局的数据、技术的服务平台。
4、数据库拆分
这个话题扯得有点大了,因为数据库拆分这个拆分方法的确太多,本文也不能完全说明数据库的拆分方法,只是概要性的提及一些内容。
首先,前端有压力,数据库自然也有,而且数据库压力肯定比前端压力会更多(当然前端可以采用很多缓存技术来环节数据库的压力),数据库的复杂性比前端要更多,因为它是数据的核心,需要对数据库的安全、一致性等做保障,在很多处理上它都是采用磁盘IO操作,而普通的sata盘是很烂的,sas盘可能会稍微好一些,这些盘上做几个KB的IOPS测试,一般只能达到180的IOPS就很不错了,当然根据磁盘本身的尺寸和转速会有所区别;在早期的技术上,我们大部分的都是采用高端存储,如:EMC、IBM这类公司就是专门做高端存储的,其IOPS可以达到万级别,其实其原理也是在普通存储级别上做了很多类似多存储控制器、镜像、cache等技术来解决很多问题,但是其价格非常昂贵,小型机+EMC的解决方案相信是诸多企业的绝佳解决方案,因为根本不用担心性能、稳定性和存储空间,但是在数据量达到非常大的时候,他们也会显得力不从心,此时在这种解决方案下也不得不去拆分,拆分过程中出现的问题就需要技术人员来解决,付出的成本将是指数级的上升,而不是平衡上升的;SSD的出现虽然颠覆了传统的磁盘存取效率(主要是随机存取效率,顺序读写优势并不大),不过目前还有很多问题存在,最近Intel也称其发生过丢失数据的问题,而且SSD目前的成本非常高,不过我们可以看到它的来临是传统磁盘开始被取代的标志。
好,OK,随着磁盘性能提高,但是容量还是和以前差不多,而且更加贵,所以就当前来讲我们绝大部分还是用传统磁盘来解决,在这种一块磁盘做一百多的IOPS的情况下(注意一个SQL并不代表只做一次IO,具体做多少次IO和实际的应用场景、实际的优化方案、以及SQL的写法所决定;而一个业务请求也一般会做多个SQL操作),我们如何提高数据库的性能呢?和上面一样,在很多时候我们先选择的是小机+高端存储的解决方案;但是随着复杂性的增加,成本开始补课预测,所以为了接触这种耦合性,我们需要一种高可扩展的分布式技术来解决,在多个分布式的机器上来解决这些问题。
首先,这种可以认为是一种分区技术在分布式上的一种实现,也就是将原有分区的技术应用在多台机器上,按照一种规则拆分到多台计算机上,如果跨机器查找也就是原来的跨分区查找,显然性能不如在单个机器上查找快速,所以如何设计分区成为一个性能关键,而不是仅仅为了拆分而拆分;另外拆分之前要有预算,计算所需要的TPS、QPS等负载情况,拆分到多少个机器上可以承受得起这样的访问量,一般最少需要预留一半的余量才可以预防突发性事件,如果需要未来几年都不受到拆分上的干扰,那么就可以预留更多;因为这种数据库拆分的代价是很高的。
在早期的数据库拆分中,有主备读写分离,ORACLE RAC多实例运算,不过面对越来越庞大的系统,他们都显得力不从心了,当然读写分离还是可以和现有的人工拆分所兼容,人工拆分主要是为了得到更好的水平扩展。
首先我们来看看传统应用中的range分区,如果用在分布式上,就是放在多个主机上的多个库上的多个表,这种用于自动增长列或时间上比较多,如刚开始可以将1-1亿的数据一般可以用多久,选择多少个机器来做,每台机器可以存放多少数据,而这种拆分Insert操作始终落在最后一台机器的最后一个表的最后一个block上,而且在刚开始使用的时候,后面所有的机器的所有的表都是空的,没有任何用处,显得非常的浪费,也就是没有数据的机器一直都是闲着的;这个问题比较好解决,你可以用一个无穷大来代表最后一台机器,当觉得应该加机器的时候,再将最后前一台机器的上限控制住,不过前一个问题是没办法搞定的,所以这种方法是用在insert压力并不是很大的情况,每秒要是有几千个insert这样做肯定是不行的,其余的update、delete等如果有最近访问热点,那么最后一台机器也必将成为热点访问区域,一般最近访问的都是热点,不过这种思路最容易让人接受,而且最容易做出来。
那么在大部分应用中我们为了考虑负载较为均衡,所以我们选择hash算法,但是绝对不是一致性hash算法,因为一致性hash在扩展时会导致数据不一致,一些数学模型可以解决,但是非常复杂而且也会存在数据版本的问题;hash算法最简单的就是求模,如将一个表拆分为100个表,那么按照绝大部分情况按照某个编号去求模得到的是0-99之间的数据,这一百个表编号为0-99,分别对应存储即可,无论是自动增长还是非自动增长也不太可能落在同一个表上面去;而对于某些热点用户,如果按照用户拆分,这些热点用户的访问就会被反复访问到同一个表,比如类似微博这种东西,也许某个热门人物的他的好友个数就会非常多,可能会导致某个表非常大,所以为了缓解这种问题,我们会再做二次hash;而对于一些非数字类的数据,我们一般会采取对其前几个字符的ascii或者hash值等取出来做操作,具体看实际情况而定;那么hash算法就是完美的吗?当然不是,它最痛苦的就是拆分,一旦拆分将会面临各种问题,应用要重启,配置要修改,数据要迁移;虽然用了一些手段来解决,但是这些手段一般都是需要提前预估出来的,比如hash算法一般都是2的次方拆分法则,因为数据库都会有备库,而且很多时候会有多个备库,所以如果做2倍数拆分的时候,可以直接将一个备份库上的数据拿上来用,如原来拆分规则为100,现在变成200,按照100求模=1的机器的主库数据和200求模等于1的都还在这个上面,只是有一部分和200求模会变成101(理论上可以认为是一半),备库也是这样,所以在乘以2以后这个备库变成主库后,数据是完全可以路由到的,剩余的工作就只需要将原有主库上和200求模等于101的数据删掉,以将这部分空间节约出来,而原有备库替换成的主库上和200求模等于1的数据删掉,删掉的这个过程是痛苦的,时间长,资源多,全是IO操作,而且还有各种锁,性能影响极大;试图想到我可以直接把他干掉,几乎不影响性能就除非需要删掉的数据是一个独立的逻辑单位,在同一个表上能想到的就是分区,也就是它如果是一个分区你就可以直接把他很快的drop掉或truncate掉,这种必须要有提前的预案才可以,否则这些都是空想;所以这种基于hash的拆分一般不要随便拆分,代价是很大的,因为这个上面的每个节点都需要做切割,甚至于只有一个备库的需要做迁移,要尽量少做,压力来了也得做,所以需要预估未来几年几乎不太可能做这样的事情,这个估算是根据业务的发展速度和趋势来决定的。
剩下是一种很常规但是很少用到的拆分,就是基于位图的拆分,也就是认为的讲某个字段(这个字段的值是可以被列举的),某些值放在某个放在某个表里面,也就是表的个数是被定义好的,拆分的个数收到值的个数的限制,除非和其他字段再进行二次组合;虽然它本身用途不多,但是如果以range或hash作为前提它也有可能是有用途的。
上面阐述了几种基本的拆分方法,都有各自有优缺点,为了更好的解决问题,我们考虑得失,会考虑使用他们进行组合,组合的方法根据实际情况而定,如我们在一些数字列上,既想考虑扩展性,又想考虑负载均衡,那么在可接收的条件下,那么我们将range-hash或hash-range,至于是那一种要看具体情况,我们简单说下range-hash,它在做range的时候,每个hash值就面对多个主机目标,在这部分主机目标内部做相应的hash负载均衡,如果出现热点,在这个range内部做二次拆分,其他的range是不需要的,如果负载较低,可以合并一些数据,range拆分的条件只是负责某个数据段的数据太多,较为均衡的分布数据,多个range如果以后不是怎么用了,可以将多个range的数据进行再次合并(这个代价相对较大,因为每个range下面的hash规则可以是不一样的,但是如果只要2的多少次方来完成这个动作,就不会出现太大的问题);而面对字符串的数据,或者不是自动增长类的数据,range没有办法,因为范围不可预知,虽然可以通过ascii来取,我们的范围也可以用正无穷和负无穷来代表,但是我们无法保证数据的均衡的,所以建议还是先做hash,而在拆分的过程中,为了使得应用不停需要设置一个版本号,也就是拆分过程中,的时间戳标记,所有在这个时间点以后的数据都在新的分布式规则中,老的数据读取的时候在老的规则中,然后可以迁移数据,但是迁移过程中性能是很低的,迁移完成后就将中间规则去掉就完成了整个的拆分过程,这个拆分过程就不局限于必须是2倍拆分了。
有关组合条件有很多,可以根据自己的应用场景去选取不同的组合方法,使得它的性能最佳,尽量少出现跨库跨表的操作,如果是按照非拆分字段进行查询,要么做二级拆分,要么就是做索引表,索引表也可以是拆分的表,也就是先查索引表然后再从索引表得到的主表的分表字段去找主表内容(但是由于索引表的结构完全又开发人员自己定义,所以索引表的维护完全是程序来控制,一致性需要开发人员来保证)。
如上,拆分解决了很多问题,也带来了很多新问题,如维护成本极度上升,需要大量外围软件来支持,否则发生任何问题将无从下手;其二,开发人员要编写很多的代码来处理路由规则信息和分布式的一致性数据的问题;切分和数据库切换过程中,一次要切换一大堆机器,应用重启时间很长;动态扩展要实现就需要非常复杂的代价。
为了解决第一个问题,公司需要较好的基层架构的人员,来编写很多外围的类似分布式一致性监控、问题跟踪处理等工具软件,并且这些软件要可持续的,否则经常换成本永远无法控制,只要基层做好了,以后这些成本就会越来越少了,或者这些成本在同等的业务水平下会越来越少。
为了解决第二个问题,让开发来编写路由等信息肯定是不合理的,一个是很多开发人员水平有限,数据是业务关键,路由更加保护这数据存储在哪里,所以要是代码写得不好就死定了;于是我们需要独立中间件,这个中间件可以只是保留在应用中的一个算法,也可以是一个独立的服务模式,服务模式为了保证其不是单点问题以及访问压力过大,需要优化的是提供服务应当是一个集群,而所有访问它的应用系统应当做一定算法的本地缓存来处理;这又回到我们上一章节说的应用系统的拆分了。
为了解决第三个问题,切换要让应用不知道,那么就要让应用感觉不到IP、PORT、库名称的变化,那么就必须将这些东西抽象出来,抽象为如上所示的独立服务模式或本地配置,当一个统一的配置被修改后,将会通知相关的应用系统进行修改,并一致性将多套机器全部一次性切换完成。
为了解决第四个问题,我们想了很多拆分的动态扩展性,但是算法十分复杂,就增加第二个中间件的复杂性,并且面临各种风险,所以传统RDBMS的拆分再次受到水平扩展的限制,人为介入太多,主要原因就是先独立做数据库,再做上层管理,是一个从下到上的过程,也就是有问题贴补丁的过程,而并非从一个站在高处把控一切的思想;于是为了解决更多的特殊的问题,如数据量超级大,而且增量也很多,读的访问非常多的情况,nosql这种低耦合的拆分技术出现了,也是云计算来临的基础(现在云计算这个词汇彻底被用乱了,尤其是中国,太过分了,这么好歌东西,在国内就被到处使用,用着当招牌,对此我表示无语),关于这部分不是本文的重点,最后一章节会简单提及一些bigtable的思路和原理,以及其开源版本的实现HBase的大概的架构模式。
Nosql技术概述:
谷歌是一家伟大的互联网公司,其引领着互联网时代的发展,bigtable的经典一直在世界各大互联网公司所效仿,后来还有多个升级版本,但是大家还是喜欢叫他bigtable;谷歌取名字很奇怪,就是直截了当,bigtable就是大表,什么样的表的是大表,每个几十亿、几百亿、几千亿什么的,不是大表,谷歌的架构可以承受万亿级别的数量,它的MapReduce也是一个非常简单的名词,也就是先做Map处理(也就是将需要分析的目标中将需要分析的有效数据提取出来并组织为K-V结构),而Reduce就负责将这些K-V数据进行处理;Apache也是一家伟大的公司,开源社区的大拿,在java界更加孕育了非常多的经典,它的Hadoop架构就是仿照谷歌的架构来完成的,这套架构完全是java编写的,这个公司也有自己的特点,就是很多名字都是动物的名字,hadoop号称就是大象的崛起,呵呵,这个hadoop架构里头有什么:pig、zookeeper就是什么猪、公园什么的意思,整个就是动物园,他们不需要多么玄的名字,就是纯属喜欢什么就用用什么,甚至于某些食物的名字或某个亲人的名字。
Hadoop虽然还不可以和谷歌的架构抗衡(基本差一个到两个数量级),但是对于绝大部分的应用是绝对没有问题,在分布式运算上,它的MapReduce架构可以支撑4000台(在雅虎)的机器同时进行分布式运算;对于线上应用就是其子模块的HBase架构,全世界最大的是960台(也是在雅虎)。
HBase算是nosql中的一种,每一种nosql都是为了解决某些特殊的问题而存在,因为前面看到分布式的拆分方法有很多种,nosql也不可能解决所有的问题,HBase总体来讲需要配合的子系统有多个Region Server、Zookeeper、Master、HDFS来配合,其中HDFS为存储引擎,所有和hadoop相关的内容不论是不是HBase都基本是存在这个上面的,少部分内容是存储在本地文件(如MapReduce中Map后的中间结果可能会用本地文件来存储,因为用完后中间数据就没有用了,没有必要放在HDFS上面);Zookeeper就是公园,管理这些动物,哪里出事或者门票都是它说了算,在这里如果谁宕机了(那个Region server宕机了),它要知道,然后告诉Master(管理者)去处理这些问题,Master宕机了就会启动备用的Master,客户端要请求首先就就是从Zookeeper请求,Zookeeper从Master哪里得到数据分布信息后,反馈给客户端,客户端会缓存主分布信息,然后开始访问,如果访问不到就再次请求Zookeeper,所以Zookeeper必须是多台机器才能保证稳定性。
也就是客户端最终访问的Region Server(其实这里可以看得出来它是基于范围,但是这个范围绝对不是基于某个自动增长列什么的,而是基于数据的字节码匹配,可以放中文、数字什么的都可以,但是放进去前都需要转换为二进制,所以转换的过程完全是业务层自己定义的),这个东西你就可以理解为一个JVM节点,数据cache在内存的是memstore,内部存储很多storefile,以hfile为单位,对文件进行读写;memstore一般都是64M两个之间来回写,一个写满就flush,另一个也在写,如果另一个写满了,这个flush还未完成,就要锁住两个部分。
Region Server负责和HDFS通信,其另一个需要做的就是HLog的编写,Hlog一般是数据实时写的,但是也可以不是实时写的;HBase数据的版本个数等方面都是可以设置的,并且可以保证单个操作的一致性;Master在初始化的时候,就会从HDFS上去获取字典Meta信息,所以这些内容都是存储在HDFS上的。
OK,这部分并不是本文的重点,本文重点在于拆分,这里携带阐述了下HBase,但是它也有很多问题,相信问题最多的就是他是用java做的,对于后端的实时应用一旦发生GC就有很多的问题,尤其是我们前面也简单说了下GC对于这种半长命的鬼东西是很有问题的;其次是虽然ZK可以发现宕机,但是时间很长,这个心跳时间设置太短可能会是一种假死,心跳时间太长就宕机很多也不会被人发现,总之还有很多问题,但是在JVM进步的同时,我们相信这些问题都可以得到解决,OK,本文就写到这里,后续会参数拆分后各种问题的一些常见的解决方法。