关于大型网站技术演进的思考(三)--存储的瓶颈(3)

  存储的瓶颈写到如今就要进入到深水区了,若是咱们所作的网站已经到了作数据库垂直拆分和水平拆分的阶段,那么此时咱们所面临的技术难度的挑战也会大大加强。前端

  这里咱们先回顾下数据库的垂直拆分和水平拆分的定义:java

  垂直拆分:把一个数据库中不一样业务单元的数据分到不一样的数据库里。sql

  水平拆分:是根据必定的规则把同一业务单元的数据拆分到多个数据库里。数据库

  垂直拆分是一个粗粒度的拆分数据,它主要是将原来在一个数据库下的表拆分到不一样的数据库里,水平拆分粒度比垂直拆分要更细点,它是将一张表拆到不一样数据库里,粒度的粗细也会致使实现技术的难度的也不同,很明显水平拆分的技术难度要远大于垂直拆分的技术难度。难度意味着投入的成本的增长以及咱们须要承担的风险的加大,咱们作系统开发必定要有个清晰的认识:能用简单的方案解决问题,就必定要绝不犹豫的舍弃复杂的方案,当系统须要使用高难度技术的时候,咱们必定要让本身感觉到这是无可奈何的编程

  我是以java工程师应聘进了我如今的公司,因此在我转到专职前端前,我也作过很多java的应用开发,当时我在公司的前辈告诉我,咱们公司的数据库建模很简单,怎么个简单法了,数据库的表之间都没有外键,数据库不许写触发器,能够写写存储过程,可是存储过程决不能用于处理生产业务逻辑,而只能是一些辅助工做,例如导入导出写数据啊,后面据说就算是数据库作到了读写分离,数据之间同步也最好是用java程序作,也不要使用存储过程,除非无可奈何。开始我还不太理解这些作法,这种不理解不是指我质疑了公司的作法,而是我在想若是一个数据库咱们就用了这么一点功能,那还不如让数据库公司为咋们定制个阉割版算了,不过在我学习了hadoop以后我有点理解这个背后的深意了,其实做为存储数据的数据库,它和咱们开发出的程序的本质是同样的那就是:存储和计算,那么当数据库做为一个业务系统的存储介质时候,那么它的存储对业务系统的重要性要远远大于它所能承担的计算功能,当数据库做为互联网系统的存储介质时候,若是这个互联网系统成长迅速,那么这个时候咱们对数据库存储的要求就会愈来愈高,最后估计咱们都想把数据库的计算特性给阉割掉,固然数据库基本的增删改查咱们是不能舍弃的,由于它们是数据库和外界沟通的入口,咱们若是接触过具备海量数据的数据库,咱们会发现让数据库运行的单个sql语句都会变得异常简洁和简单,由于这个时候咱们知道数据库已经在存储这块承担了太多的负担,那么咱们能帮助数据库的手段只能是尽可能下降它运算的压力安全

  回到关于数据库垂直拆分和水平拆分的问题,假如咱们的数据库设计按照咱们公司业务数据库为蓝本的话,那么数据库进行了水平拆分咱们会碰到什么样的问题了?为了回答这个问题我就要比较下拆分前和拆分后会给调用数据库的程序带来怎样的不一样,不一样主要是两点:服务器

  第一点:被拆出的表和原库的其余表有关联查询即便用join查询的操做须要进行改变;mybatis

  第二点:某些增删改(注意:通常业务库设计不多使用物理删除,由于这个操做十分危险,这里的删每每是逻辑删除,通常作法就是更新下记录的状态,本质是一个更新操做)牵涉到拆分的表和原库其余表共同完成,那么该操做的事务性就会被打破,若是处理很差,假如碰到操做失败,业务没法作到回滚,这会对业务操做的安全性带来极大的风险。框架

  关于解决第一点的问题仍是相对比较简单的,方式方法也不少,下面我来说讲我所知道的一些方法,具体以下:数据库设计

  方法一:在垂直拆表时候,咱们先梳理下使用到join操做sql查询,梳理的维度是以被拆分出的表为原点,若是是弱依赖的join表咱们改写下sql查询语句,若是是强依赖的join表则随拆分表一块儿拆分,这个方法很简单也很可控,可是这个技术方案存在一个问题,就是让拆分粒度变大,拆分的业务规则被干扰,这么拆分很容易犯一个问题就是一个数据库里总会存在这样一些表,就是不少数据库都会和它关联,咱们很难拆解这些关联关系,当咱们没法理清时候就会把该表作冗余,即不一样数据库存在雷同表,随着业务增加,这种表的数据同步就成为了数据库的一个软肋,最终它会演变为整个数据库系统的短板甚至是全系统的短板。

  方法二:咱们拆表的准则仍是按业务按需求在数据库层面进行,等数据库拆好后,再改写原来受到影响的join查询语句,这里我要说明的是查询语句修改的成本很低,由于查询操做是个只读操做,它不会改变任何底层的东西,若是数据表跨库,咱们能够把join查询拆分为屡次查询,最后将查询结果在内存中概括和合并,其实咱们若是主动拆库,毫不会把换个不一样的数据库产品创建新库,确定是使用相同数据库,同类型的数据库基本都支持跨库查询,不过跨库查询据说效率不咋地,咱们能够有选择的使用。这种方案也有个致命的缺点,咱们作数据库垂直拆分毫不可能一次到位,通常都是屡次迭代,而该方案的影响面很大,关联方过多,每次拆表几乎要检查全部相关的sql语句,这会致使系统不断累积不可预知的风险。

  如下三段内容是方法三:

      无论是方法一仍是方法二,都有一个很根本的缺陷就是数据库和上层业务操做耦合度很高,每次数据库的变迁都致使业务开发跟随作大量的同步工做,这样的后果就是资源浪费,作服务的人不能每天被数据库牵着鼻子走,这样业务系统的平常维护和业务扩展会很存问题,那么咱们必定要有一个服务和数据库解耦方案,那么这里咱们就得借鉴ORM技术了。(这里我要说明下,方法一和方法二我都是以修改sql阐述的,在现实开发里不少系统会使用ORM技术,互联网通常用ibatis和mybatis这种半ORM的产品,由于它们能够直接写sql和数据库最为亲近,若是使用hibernate则就不一样了,可是hibernate虽然大部分不是直接写sql,可是它只不过是对数据库操做作了一层映射,本质手段是一致,因此上文的sql能够算是一种指代,它也包括ORM里的映射技术)

       传统的ORM技术例如hibernate还有mybatis都是针对单库进行的,并不能帮咱们解决垂直拆分的问题,所以咱们必须本身开发一套解决跨库操做的ORM系统,这里我只针对查询的ORM谈谈本身的见解(讲到这里是否是有些人会有种似成相识的感受,这个不是和分布式系统很像吗)。

       其实具体怎么重构有问题的sql不是我想讨论的问题,由于这是个技术手段或者说是一个技术上的技巧问题,我这里重点讲讲这个ORM与服务层接口的交互,对于服务层而言,服务层最怕的就是被数据库牵着鼻子走,由于当数据库要进行重大改变时候,服务层老是千方百计让本身不要发生变化,对于数据库层而言服务层的建议都应该是合理,数据库层要把服务层当作本身的需求方,这样双方才能齐心合力完成这件重要的工做,那么服务层通常是怎样和数据库层交互的呢?

       从传统的ORM技术咱们能够找到答案,具体的方式有两种:

       第一种:以hibernate为表明的,hibernate框架有一套本身的查询语言就是hql,它相似于sql,自定义一套查询语言看起来很酷,也很是灵活,可是实现难度很是之高,由于这种作法至关于咱们要本身编写一套新的编程语言,若是这个语言设计很差,使用者又理解不深刻,最后每每会事与愿违,就像hibernate的hql,咱们常常令可直接使用sql也不肯意使用hql,这其中的原因用过的人必定很好理解的。

      第二种:就是数据层给服务层提供调用方法,每一个方法对应一个具体的数据库操做,就算底层数据库发生重大变迁,只要提供给服务端的方法定义不变,那么数据库的变迁对服务层影响度也会最低。

      前面我提到技术难度是咱们选择技术的一个重要指标,相比之下第二种方案将会是咱们的首选。

      垂直拆分数据库还会带来另外一个问题就是对事务的影响,垂直拆分数据库会致使原来的事务机制变成了分布式事务,解决分布式事务问题是很是难的,特别是若是咱们想使用业界推出的解决分布式事务方案,那么要本身实现个分布式事务就更难了,不过这里我要说明一下,我这里说的更难是和我写本文有关,我本篇文章之因此如今才写是由于我想先研究下业界推出的分布式解决方案,可是这些方案的原理看得我很沮丧,我就想若是咱们直接用方案的接口实现了它,由于仍是不懂他的不少原理,那么这些方案其实就是不可控方案,说不定使用过多就会给系统埋下定时炸弹,所以这里我就只提提这些方案,有兴趣的童鞋能够去研究下:

  1、X/OPEN组织推出的分布式事务规范XA,其中还包括该组织定义的分布式事务处理模型X/OPEN

  2、大型网站一致性理论CAP/BASE

  3、 PAXOS协议。

  这里特别要提的是PAXOS协议,我之前写过好几篇关于zookeeper的文章,zookeeper框架有一个特性就是它自己是一个分布式文件系统,当咱们往zookeeper写数据时候,zookeeper集群能保证咱们的写操做的可靠性,这个可靠性和咱们使用线程安全来控制写数据同样,绝对不会让写操做出错,之因此zookeeper能作到这点,是由于zookeeper内部有一个相似PAXOS协议的协议,这个协议相似一个选举方案,它能保证写入操做的原子性。

  其实事务也是和线程安全技术相似,只不过事务是要保证一个业务操做的原子性问题,固然事务还要有个特色就是回滚机制即业务操做失败,事务能够保证系统恢复到业务操做前的状态,回滚机制的本质实际上是维护业务操做的状态性,具体点我这里列举个例子:当系统将要执行一个业务操做时候,咱们首先为业务系统定义一个初始状态,业务执行操做时候咱们能够定义一个执行状态,操做成功就是一个成功状态,操做失败就是一个操做失败状态,若是业务操做是失败状态,咱们可让业务回滚到初始状态,更进一步若是执行状态超时也能够将整个业务状态回退到初始状态,其实全部事务回滚机制的本质基本都是如此。记得不久前,在群里有个群友就问你们如何实现分布式事务,他想要知道的分布式事务是有没有一种技术能像咱们操做数据库或者是jdbc那样一个commit,一个rollback就搞定,可是现实中的分布式事务比commit和rollback复杂的多,不可能简单的让咱们写几个标记就能实现分布式事务,固然业界是有方案的,就是我上面提到的,若是有人真想知道能够本身研究下,不过我本人如今仍是不太懂上面这些技术的原理和思想。

  其实当时我立刻给那位群友一个解答,我说咱们开发时候是常常碰到分布式事务,可是咱们解决分布式事务大多数从业务角度来解决的,而没去选择纯技术手段,由于技术手段太复杂难以控制。这个答案可能不会令提问者满意,可是我如今仍是坚持这个观点,这个观点符合我提到的原则,当技术方案难度太高,咱们就不要轻易选择使用它,由于这么作是很危险的,今天我就举个例子吧,这样可能更有说服力。我如今作的系统不少业务操做常常要和其余系统共同完成,其余系统有咱们公司本身的系统,也有其余企业的系统,这里我仍是把业务操做比做一辆在高速公路的汽车,那么每一个系统就是高速公路上的一个收费站,业务每到一个收费站,该系统的数据库就会在对应的数据库的某张表里某条记录上记录一个状态,当汽车跑彻底程,各个收费站就会相互通知,告诉你们任务完成,最终将全部的状态置为已完成,若是失败,就废掉这辆汽车,收费站之间也会相互通知,让全部的记录状态回归到初始状态,就当历来没有这辆汽车来过。这个作法的原理就是使用了事务回滚的本质,状态的变迁和回退,这个作法在业务系统开发里也有个专有术语就是工做流。其实大多数问如何实现分布式事务如何实现的问题的本质就是想解决事务的回滚问题,咱们其实不要被这个分布式事务的名字给吓住了,其实有不少不起眼的技术手段和业务手段都能达到相同的目的。

  晚上11点了,看来本文今天写不完了,今天就到此为止,最后我要总结下本文的内容,具体以下:

  1. 大型网站解决存储瓶颈的问题,咱们要找准存储这个关键点,由于数据库实际上是存储和运算的组合体,可是在咱们这个场景下,存储是第一位的,当存储是瓶颈时候咱们要狠下心来尽可能多的抛弃数据的计算特色,因此上文中我提出咱们数据库就不要滥用计算功能了例如触发器、存储过程等等。

  2. 数据库剥离计算功能不表明不要数据的计算功能,由于没有数据的计算功能数据库也就没价值了,那么咱们要将数据库的计算功能进行迁移,迁移到程序里面,通常大型系统程序和数据库都是分开部署到不一样服务器上,所以程序里处理数据计算就不会影响到数据库所在服务器的性能,就可让安装数据库的服务器专心服务于存储。

  3. 咱们要尽一切可能的把数据库的变化对服务层的影响降到最低,最好是数据库作拆分后,现有业务不要任何的更改,那么咱们就得设计一个全新的数据访问层,这个数据访问层将数据库和服务层进行解耦,任何数据库的变化都由数据访问层消化,数据访问层对外接口要高度统一,不要轻易改变。

  4. 若是咱们设计了数据访问层来解决数据库拆分的问题,数据访问层加上数据库其实就组合出了一个分布式数据库的解决方案,因而可知拆分数据库的难度是很高的,由于数据库将拥有分布式的特性,而分布式开发就意味开发难度的增长。

  5. 对于分布式事务的处理,咱们尽可能要从具体问题具体分析,不要一感受这个事务操做本质是分布式事务就去寻找通用的分布式事务技术手段,这样的想法实际上是回避困难的思想,结果可能会是把问题搞得更加复杂。

  好了,今天就写到这里吧,祝你们晚安,生活愉快!

相关文章
相关标签/搜索