数据库水平切分及问题

    前面一篇文章说到,当遇到数据存储层的高并发的时候,会首先想到读写分离,同时高并发有可能意味着数据量大,大量的查询或更新操做集中在一张大表中,锁的频繁使用,会致使访问速度的降低,并且数据量可能超过了单机的容量,因此咱们想到了分库分表。
    可是在分库分表以前,我仍是想多说几句,除非使用那些透明的分库分表方案,不然分库分表是一个大工程。 因此在分库分表前,我建议尽量先升级数据库的硬件,SSD/NVMe硬盘 + 大容量内存基本能够知足一个小型互联网公司大部分的应用, 对于中型互联网公司须要使用到分库分表的场景也不会太多,用户,订单,交易可能会用到的。可是即便是这些场景,如今也有了不一样的解决方案,在之前,大部分的应用仍是基于web的,若是一些操做须要用到用户校验,就要进行登陆,对数据库操做比较频繁,随着移动互联网的兴起以及Nosql的出现,现在大部分的应用都迁移到了App端,基本都是经过accessToken+NoSql来进行用户校验的,你们能够想一想你有多久没有进行过登陆操做了。对于订单交易,经过冷热数据分离,也能够解决大部分场景。
数据库切分分为水平切分,垂直切分, 垂直切分通常在拆分系统的时候使用,这里再也不赘述,下面主要说数据的水平切分。 
水平切分方式
    1. 只分表:在一个数据库下面,分红10张表,表名 user_0 ,user_1, user_2.....
    数据集中在一台服务器上,当单机性能瓶颈的时候,后续扩展困难。
    2. 只分库,分红10个库,每一个库一张表, 表名都是同样的。 db0-user  db1-user db2-user......
    出现跨库,没有办法使用事务。不过在分布式系统中,通常不会使用事务,保证数据最终一致性便可
    3. 分表分库,10X10这种方式。 先分10个库, 每一个库10张表。
肯定了表切分的方式,接下来就是要根据必定的规则把数据落入到指定表中,这里介绍两种形式,
取模:即找到某个字段,这个字段经过取模后的值尽量平均,根据字段取模的方式决定数据落在哪张表,好比,咱们订单根据用户Id来决定落在哪张表。
日期:这种方法在最开始的时候,并无决定要分多少张表,只是指定日期的数据落到指定的表中,好比用户注册,在2017-5-30日注册,则能够将数据落入user_201705,这张表中。 
这两种分表方法各有优劣,按照取模分,每张表的数据能够尽量的平均,可是若是后面表扩展则比较麻烦,按照日期来分,虽然能够解决表扩展问题,可是数据有可能不平均,好比在5月,举办了促销,结果那个月注册的人不少。 
当数据开始写数据库,Id就是要考虑的,这里有几个解决方案:
    1. Id自增,步进=分表数。好比分了3张表,每张表的起始Id不同,而且自增的幅度=表数量,这样Id就是1,4,7  2,5,8  3,6,9 ,可是若是之后分表数增长,Id的步进还要变。 
    2. 单独的表,用于记录自增,能够单独一张表,里面有一列,记录的是当前的id, 能够获取下一个Id,  只是这样每次都要访问这张表,会有性能瓶颈。 
    3. Guid
    4. snowflake算法
关于Id有几个注意的地方,若是你的Id有可能出如今页面上, 好比订单Id, 用户Id ,尽可能不要使用自增的,由于别人能够根据这些Id,看出你如今的用户规模,订单量。经过在两天中相同时间下单,能够看出你的交易量,若是权限没有控制好,经过遍历能够查出全部的信息。 
咱们这里以用户表为例,看把用户信息写入数据库的一些方法,以及这些方法的一些问题。 
    当一个新用户注册,并无Id,能够经过分布式Id获取到一个Id ,并对这个Id取模,获得具体的表,而后将Id连同用户信息写入表。 后面若是要根据Id获得用户信息,只要对Id采用相同的方法,便可查到对应的信息。 
    可是上面的方法没有覆盖到一个场景,用户登陆都是经过用户名登陆的,怎么查呢,这个时候,通常的方法就是并行查全部的表,好一点的方法就是,在注册的时候,额外创建一个映射,是username->表名的映射,当用户登陆的时候,能够根据这个映射找到对应的表名,而后根据表名,再插到具体的用户,这个映射能够放到数据库中,也能够放到redis/mongodb中。 
    上面的方法能够解决问题,可是须要引入映射,若是不想要映射, 这个时候能够采用复合Id , 咱们使用用户名进行取模,算出具体的表索引,而后经过分布式Id+表索引做为新的Id(NewId)插入到表中,这样咱们对用户名进行计算能够获得表索引,经过对NewId进行计算也能够获得表索引(NewId包含了分布式Id和表索引,经过分离,能够获得表索引)。 
    咱们上面没有考虑到扩展,好比如今咱们有10张表,过段时间,发现10张表的存储也快满了, 这个时候就要再扩展10张表,变成20张表,那取模算法也要改,这个时候再对之前的NewId进行计算可能就得不到准备的表了,因此在NewId中,咱们还要包含一个算法版本号。 
当全部的操做都在一个数据库的时候, 能够很方便的使用事务,好比Java下spring的声明式事务,可是当出现跨库操做,事务的使用就不怎么方便了,对于互联网公司,大多数系统都是分布式的,会选择柔性事物。
随着业务的发展,数据量增多,咱们须要再次对表进行扩展,扩展方式以下: 
1. 须要迁移数据 
     1. 首次扩展:假如咱们要开始分库分表,原来有一个库一张表,如今要扩展为10个,能够新建9个slave库,等9个slave库数据都和主库都同步了,而后修改路由算法。 
    2. 扩容后再扩容:好比如今3个表,要改成5个表,采用Hash算法,当数据扩容,则总体数据都要作迁移。 新增和删除都要作。 技巧:选择2个倍数,能够只作新表新增和删除,这个可能须要用到双写。
    3. 划分树形组,好比,以前一个数据库,不须要Hash,如今新增9个,同步数据,完成后,修改路由算法(321%10),再删除数据,若是10个数据库不够,则再次扩容,原来的10个库,每一个库各自再扩展9个库(可根据实际须要扩展),同步数据,方法和上面第一种相似,而后经过 321%10   321/10%10, 算出具体的数据库。若是不够,能够用一样的方式扩容。 
 
2. 不迁移数据
    1. 根据时间定义,好比一个月一张表。
    2. 定义mapping关系,能够放入到分布式缓存中,写入的时候,写入缓存,读取的时候读取缓存, 若是缓存失效,全量读取。
    3. 增量和Hash同时使用,定义策略,1-1000W一张组,1000-3000W一张组,组内,根据Hash避免热点数据。 新数据都会到新的数据库中。这里注意,这个组不要和机器绑定,好比1-1000W,在DB0机器,1000-3000W在DB1机器,虽然数据Hash, 热点数据不会在同一个表中,可是仍是在通一台机器。 要考虑以下的方式,热点数据仍是会分布在老机器上。 
  
    4. 生成的ID具备自描述性。 ID+DBIndex,好比Id为321,Hash命中到第一张表,变为32101,最后两位表示具体的数据库索引,老的数据库也会写数据,若是咱们的表增多,则修改hash算法,只命中到新表。 
目前分库分表的解决方案主要集中在这几层:
    1. dao层,在dao层根据指定的分表键决定要操做那个数据库。 
    2. ORM层
    2. JDBC层,比较有名的是sharding-jdbc. 
    3. 代理层,好比mycat. 
  分库分表是一个大工程, 如何分, ID怎么取, 事物怎么作都是要考虑 的。另外你们不要用操做单表的思惟去操做多表,有些人一接触分表就去考虑gourp by 怎么作, join怎么作。 从我接触的一些分表来看,若是牵扯到分表,操做都比较简单,基本在执行sql前都已经肯定了要操做哪张表。 固然若是真的遇到要join的操做或者没有办法肯定数据在哪一个表中,像sharding-jdbc,mycat这种透明分表分库的都会帮你处理。 
相关文章
相关标签/搜索