垂直切分就是要把表按模块划分到不同数据库中,这种拆分在大型网站的演变过程中是很常见的。当一个网站还在很小的时候,只有小量的人来开发和维护,各模块和表都在一起,当网站不断丰富和壮大的时候,也会变成多个子系统来支撑,这时就有按模块和功能把表划分出来的需求。如下图所示:
其实,相对于垂直切分更进一步的是服务化改造,说得简单就是要把原来强耦合的系统拆分成多个弱耦合的服务,通过服务间的调用来满足业务需求看,因此表拆出来后要通过服务的形式暴露出去,而不是直接调用不同模块的表,淘宝在架构不断演变过程,最重要的一环就是服务化改造,把用户、交易、店铺、宝贝这些核心的概念抽取成独立的服务,也非常有利于进行局部的优化和治理,保障核心模块的稳定性。这样一种拆分方式也是有代价的:
上面这些问题是显而易见的,处理这些的关键在于如何解除不同模块间的耦合性,这说是技术问题,其实更是业务的设计问题,只有在业务上是松耦合的,才可能在技术设计上隔离开来。没有耦合性,也就不存在表关联和事务的需求。另外,大数据瓶颈问题可以采用水平切分。
二、对数据库表的字段访问比较均衡,业务导向不明显(对单一应用的高并发访问)
水平切分没有破坏表之间的联系,完全可以把有关系的表放在一个库里,这样就不影响应用端的业务需求,并且这样的切分能从根本上解决大数据量的问题。它的问题也是很明显的:
对于第一个问题,可以参考如何整合应用端和数据库端。对于第二个问题可以参考一致性hash的算法,通过某些映射策略来降低数据维护的成本
2)当然还可以把水平切分和垂直切分结合起来
由上面可知垂直切分能更清晰化模块划分,区分治理,水平切分能解决大数据量性能瓶颈问题,因此常常就会把两者结合使用,这在大型网站里是种常见的策略,这可以结合两者的优点,当然缺点就是比较复杂,成本较高,不太适合小型网站,下面是结合前面两个例子的情况:
三、对数据库表的单一字段访问比较集中(秒杀、大量用户对同一账户操作)
对于这种情况有很多种解决方案,但是每一种都不是很完美:
1)采用内存缓存或者缓存数据库来缓解数据的库的压力
具体做法是:在利用内存缓存或者缓存数据库把后台数据库服务器上相关的表数据加载到内存中,所用用户高并发的对内存数据进行处理,然后再定时轮询的方式把内存的数据刷新到后台数据库表中,这种做法有以下问题:
a)不能很好保持内存数据与数据库数据的一致性;
b)如果出现断电、内存损坏等情况,会有数据丢失;
2)采用对数据库表水平切分,然后在后台的程序中对各个表的数据整体控制
例如,有10000亿人民币为1亿人并发提供贷款业务。在数据库中建立一个总表存下10000亿人民币,然后再建立10张分表,初始设为空;后台java程序在访问数据库时会有一个控制程序(中间件),开10个 线程池,每个线程池对应一个数据库分表,当中间件接受到贷款申请时,中间件就会根据用户的ID(可以ip地址,账户编号)hash到相应的线程去到总表中借款,这个借款数目可以根据总表的资金和用户的要借的资金去申请额度(比如用户申请10w,总表有1000亿,对应线程可以向总表申请10亿),存入相应分表,供这个用户提供贷款,如果再有下个用户再到此线程池操作数据库表,就直接操作,分表中金额不够的时候再到总表中借款。
这样的设计解决了,高并发存储数据库的问题,但是增加了后台的程序设计的难度,加大了程序的耦合度。
3)采用“记流水不记账“的方式应对
还用上一个例子,这种方式,需要在数据库中设计两个表,一个用来存储账户金额(账户表),另一个记录”流水“(流水表), 所谓”记流水“是指每当有个请求到来,就向流水表中插入一条记录,然后定时对所插入的记录进行统计,update账户表的数据,当然这种方式,需要在内存中增加变量,来控制所用用户的贷款不能超过所贷款的总金额。这种处理方式是数据库端处理秒杀、高并发集中访问数据库表字段的有效方式,使用比较广泛。
4)针对网购秒杀还有其针对性的设计,因为网购秒杀和高并发操作银行账户不同,网购秒杀允许用户请求丢失,简单的来说,只需要在内存缓存或者内存数据库(充当队列)中保存较早的用户请求,然后再异步的处理这些请求来操作数据库(更新数据库)。