您的位置:首页 > 运维架构 > 网站架构

大型网站之存储瓶颈(数据库的垂直拆分)

2016-03-30 11:54 495 查看
原文:http://blog.jobbole.com/83768/

[b]一、简介[/b]

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

示图如下:

图1



当然我们如过一个表太大的话,我们可以先拆表,再把他们拆到不同的数据库





 我比较下拆分前和拆分后会给调用数据库的程序带来怎样的不同,不同主要是两点:

第一点:被拆出的表和原库的其他表有关联查询即使用join查询的操作需要进行改变;
第二点:某些增删改(注意:一般业务库设计很少使用物理删除,因为这个操作十分危险,这里的删往往是逻辑删除,一般做法就是更新下记录的状态,本质是一个更新操作)牵涉到拆分的表和原库其他表共同完成,那么该操作的事务性就会被打破,如果处理不好,假如碰到操作失败,业务无法做到回滚,这会对业务操作的安全性带来极大的风险。

二、联表查询

关于联表查询还是相对比较简单的,方式方法也很多,下面我来讲讲我所知道的一些方法,具体如下:

方法一:在垂直拆表时候,我们先梳理下使用到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复杂的多,不可能简单的让我们写几个标记就能实现分布式事务,当然业界是有方案的,就是我上面提到的,如果有人真想知道可以自己研究下,不过我本人现在还是不太懂上面这些技术的原理和思想。

 解决分布式事务大多数应该从业务角度来解决的,而没不要首先去选择纯技术手段,因为技术手段太复杂难以控制。当技术方案难度过高,我们就不要轻易选择使用它,因为这么做是很危险的,今天我就举个例子吧,这样可能更有说服力。我现在做的系统很多业务操作经常要和其他系统共同完成,其他系统有我们公司自己的系统,也有其他企业的系统,这里我还是把业务操作比作一辆在高速公路的汽车,那么每个系统就是高速公路上的一个收费站,业务每到一个收费站,该系统的数据库就会在对应的数据库的某张表里某条记录上记录一个状态,当汽车跑完全程,各个收费站就会相互通知,告诉大家任务完成,最终将所有的状态置为已完成,如果失败,就废掉这辆汽车,收费站之间也会相互通知,让所有的记录状态回归到初始状态,就当从来没有这辆汽车来过。这个做法的原理就是使用了事务回滚的本质,状态的变迁和回退,这个做法在业务系统开发里也有个专有术语就是工作流。其实大多数问如何实现分布式事务如何实现的问题的本质就是想解决事务的回滚问题,我们其实不要被这个分布式事务的名字给吓住了,其实有很多不起眼的技术手段和业务手段都能达到相同的目的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: