明翰MySQL教学系列之分布式数据库


传送门

  1. 明翰MySQL教学系列之概念篇
  2. 明翰MySQL教学系列之SQL篇
  3. 明翰MySQL教学系列之索引篇
  4. 明翰MySQL教学系列之Innodb事务篇
  5. 明翰MySQL教学系列之分布式数据库

前言

本文主要介绍以关系型数据库MySQL作为业务数据的存储背景下,
分库分表,主从分离,多主多从的玩法。

下面的文章中会提到一些术语,为了避免歧义,先解释如下:
数据库实例&节点:一个物理数据库或Docker容器数据库,
一般具有独立的硬件资源(IOPS,CPU,内存,硬盘,连接数等)。

数据库:一个数据库实例上可以创建n个数据库,一个数据库上可以创建n个表,
一个表中可以创建n个行和列(数据和字段)。


1. 分布式数据库架构演变

所谓的优化结构、架构,其实就是各种拆,类似于当前比较流行的微服务。
我们需要把单库单表拆成多库多表。

做分布式数据库的前提是考虑业务场景,不建议过度设计与提前优化,
不结合业务的设计都是耍流氓,劳民伤财。

快速增长到单表500万条数据以上并保持持续快速增长,
或者有预见性的增长,需要考虑分库分表。

一般我们可以秉承这样的原则:

  1. 产品初期业务量较小就用单库单表+索引。

  2. 有读性能瓶颈就用读写分离&一主多从+索引+缓存。

  3. 有写性能瓶颈就用分库分表&多主多从+索引+缓存。

1.1 原始人时代

在一些小微系统中,数据量和并发量往往很少,一般我们会使用一个数据库实例,
使用一个数据库,以及一张表来存放某一类相关的业务数据,
这就是所谓的"单库单表"。

但在数据量大、并发量高的互联网场景下,单库单表的架构就显得捉襟见肘了,
为了提升性能&吞吐量,我们只能去花更多的钱来升级我们的单机硬件性能,但这样真的合理合法吗?

缺点:

  • 单实例性能瓶颈
    单机(单个数据库实例)的性能是有瓶颈的,
    数据库往往会成为系统性能瓶颈的急先锋,包括IOPS、硬盘空间、连接数、CPU、内存等性能指标。

  • 查询效率低
    单表的数据量过大(一般超过500万条数据),会对索引非常不优化,索引的B+树高度提升,不利于查询性能。

  • 没法做高可用
    数据库一旦down掉或hung住后,整个系统全部瘫痪,牵一发而动全身。

1.2 提升读性能

大部分互联网业务场景都是读多写少,读操作往往最先成为性能瓶颈。

在访问量大幅增加的前提下,会导致单库单表的数据库节点面临着硬件资源的性能瓶颈,

我们可以通过读写分离的方式把压力分摊,把读与写这2个操作分开,写操作走主库,
读操作走从库,主从搭配的方式构成一个小集群,共同对外提供服务。

主库与从库通过binlog进行数据同步,相当于把主库的数据复制到从库上,
主库与从库存储的数据结构与数据完全相同。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8KQ6OEBr-1641905392764)(evernotecid://BCE3D193-8584-4CB1-94B3-46FF37A1AC6C/appyinxiangcom/12192613/ENResource/p299)]

1.2.1 增加从库

一主多从:一个主库可以对应多个从库,读库具备高可用,
一台从库挂了并不影响整体业务,减少锁冲突,提升读操作的性能。

缺点:

  • 从库数量越多,写的性能越慢,数据同步时间越长,产生数据一致性问题,同步数据并不是实时同步的,会有同步延迟,有概率会读到旧数据。
提升写高可用

一般来说写操作的高可用并不是必须,因为大部分互联网业务场景都是读多写少的场景,为了20%的场景引入80%的复杂度,会有一些得不偿失。

双主同步

解决高可用的普遍思路是冗余,冗余多个站点&服务&数据,用复制的方式来保证可用性。

如果必须要引入写操作高可用的话,我们可以使用冗余多个写库,把1个写操作变成2个写操作,做双主同步,主库挂了,流量自动切换到另一个主库。

如果使用整型自增主键ID,会出现引发双向同步主键冲突问题,导致同步失败,数据丢失。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dSRf2w58-1641905392767)(evernotecid://BCE3D193-8584-4CB1-94B3-46FF37A1AC6C/appyinxiangcom/12192613/ENResource/p300)]

解决方案:

  • 双主分别使用不同的ID初始值,相同的步长来生成ID,例如步长都为2,主库a从0开始(生成0,2,4,6等),主库b从1开始(生成1,3,5,7)。

  • 用业务层代码来生成ID来代替MySQL的auto-increment-id机制。

双主当主从

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VIxketxy-1641905392769)(evernotecid://BCE3D193-8584-4CB1-94B3-46FF37A1AC6C/appyinxiangcom/12192613/ENResource/p301)]

虽然看上去是双主同步,但是读写都在一个主上,另外一个主库没有读写流量,完全处于备用状态,当一个主库挂掉的时候,流量会自动切换到另一个主库上。

因为读写都在一个主库上,所以不会出现因为主从延迟而引发的数据一致性问题。

缺点:

  • 资源利用率只有50%
  • 不能用增加读库的方式扩容,但可以使用缓存。

1.2.2 增加缓存

这里指的缓存是分布式缓存中间件,类似于Redis、Memcached这种,而并非是Java缓存、MySQL原生缓存。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O1nIWNIy-1641905392770)(evernotecid://BCE3D193-8584-4CB1-94B3-46FF37A1AC6C/appyinxiangcom/12192613/ENResource/p302)]

一般我们可以这样操作缓存:

对于写操作:
先淘汰缓存,再写数据库。

对于读操作:
先读缓存,如果缓存命中则返回数据,否则读从库,
之后再把读出来的数据写入缓存。

数据一致性问题

这个知识点非常非常非常重要,是面试的重灾区,并且非常的tricky,一定要掌握。

考虑如下场景:

  1. 进行写操作,先淘汰缓存,之后写入数据库。
  2. 进行读操作,读缓存,未命中,然后读从库,此时数据还没有同步到从库上,于是读取了脏数据,并将脏数据写入缓存。
  3. 主从同步完成,脏数据一直留在缓存中,造成数据库与缓存中的数据不一致。

如果业务方对数据一致性的要求没有那么高,则不用考虑解决数据一致性问题。

解决主从不一致
引入数据库中间件

我们可以引入数据库中间件,业务层不直接访问数据库,
这个中间件会记录哪一些key上发生了写请求,在数据主从同步时间窗口内,如果key上又出现读请求,就将这个请求也路由到主库上去(因为此时从库可能还没有同步完成,是旧数据),使用这个方法来保证数据的一致性。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4u0l3Yav-1641905392771)(evernotecid://BCE3D193-8584-4CB1-94B3-46FF37A1AC6C/appyinxiangcom/12192613/ENResource/p304)]

缺点:

  • 数据库中间件技术门槛较高
使用双主当主从

如果使用双主当主从的用法,就不会存在主从不一致问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dnoCfQod-1641905392775)(evernotecid://BCE3D193-8584-4CB1-94B3-46FF37A1AC6C/appyinxiangcom/12192613/ENResource/p303)]

解决缓存不一致
缓存双淘汰

之前是在进行写操作的时候,先淘汰缓存再写入主库,
这样会在主从同步时间窗口内将脏数据写入缓存,
此时再发起一个异步的淘汰,即使脏数据进了缓存也会被淘汰掉。

设置缓存失效时间

设置缓存失效时间为x分钟,即使有脏数据进缓存,这个脏数据也最多存活x分钟。

缺点:

  • 每隔x分钟,每个key上会有一次读请求穿透到从库中,但尽管如此,从库的压力依然不大。

1.3 提升写性能

所谓的分库分表就是按照某种规则将原来单库单表的数据散落在不同的库表上(包括不同的节点上),
共同组成一个数据库集群共同对外提供服务。

就像58同城可以将100亿帖子数据分成256个库一样,玩多主多从,
理论上,我们可以把所有的业务数据无限扩容成n个主库,
同时再n个主库下还分出n个从库进行读写分离。

分库分表主要有两大类,:竖着拆(垂直切分),横着拆(水平切分)。
有的业务场景要结合这两种拆分方式,既竖着拆,也横着拆,先竖着拆,后横着拆。

分库分表后,每个库表中的数据结构一样但数据都不一样,
这一点与一主多从不一样。
所有的库表组成一个数据库集群,共同对外提供服务。

一主多从可以线性提升读性能,但不能提升写性能,分库分表可以线性提升写性能。

因此我们需要一些机制,在面对即将高速增长或已经高速增长的业务,
来对单库单表结构进行优化,让数据库也变得更分布式一些。
当索引、缓存、读写分离的从库都帮不了你的时候,你就需要进行分库分表了。

把单库单表的数据切分到多库多表中,再让每个库分散出一系列从库搞读写分离,
让更多的库表协同作战来共同承担压力,以此来减少每个单库单表的压力,提升整体吞吐。

秉承着鸡蛋不要放在一个篮子里的原则,即使一个库出事故挂掉了,
影响的也仅仅只是某一个业务线,跟"牵一发而动全身"彻底的拜拜了。

3.1 垂直切分

3.1.1 垂直分库

垂直分库多见于微服务架构(每个微服务一个库),按业务解耦(使得业务清晰),
将不同业务模块的表(单个或多个表)放到不同的数据库中,
每个数据库又处于不同的数据库实例&节点中,这样有利于单机硬件性能的提升。

3.1.2 垂直分表

假设一张表中有许多列,可以新建一张表作为扩展表,
将使用频率不高或特别长(存储数据特多)的列挪到新表中,
类似于冷热列分离。

拆分后的两张表或多张表可以做主键1对1关联关系(就是2张表的主键相同)。

MySQL底层存储使用“页”来存储数据,每个页是有容量限制的,对于一些大型列,
例如文章内容等,这些列的数据要占用很多的页,跨页查询会造成额外的性能开销。

读取数据时,数据以行为单位被读到内存中,由于内存容量有限,对于长列来说会增加磁盘IO。

3.2 水平切分

可以把水平切分称为Sharding、分片,
水平切分一定会面临一个问题,那就是数据路由,
请求来了之后,要路由到正确的库表上。

在做水平切分前,我们需要定义按照哪个或哪几个列来做切分?
我们可以把需要做切分的列称为分片列。

3.2.1 水平分表

将一张表中的数据按照一定的规则&算法划分到n张表中,
将切分完的表存储在同一个数据库中。

这样,单表的数据量就被有效的降低了,查询效率提升了,
对索引友好了。

这种只分表不分库的玩法有一点好处:就是在一个库中的事务是不用考虑分布式的。

虽然做了表切分,但这些表还是存在于一个数据库一个数据库实例中,
单机还是有性能瓶颈,因此我们需要更深一步,水平做分库分表。

3.2.1.1 按范围分片
按时间范围

我们可以按照时间作为切分的维度来对表中的数据做切分。
例如:我们可以把2018年的数据放在一张表,
2019年的数据放在一张表,
有点类似于冷热数据分离的感觉。

按自增ID范围

如果主键是自增整型ID的话,那么我们可以类似于把ID为1-100000的数据分到a表上,
ID为100001-200000的数据分到b表上。

按范围分片优缺点

优点:

  1. 路由规则简单。
  2. 扩展性好,在扩容时,只需要新增节点即可,不需要数据迁移。
  3. 一般不需要跨库跨表查询、效率较高。

缺点:

  1. 主键必须是自增整型。
  2. 数据请求不均匀,会出现热点数据频繁查询,热的非常热,冷的非常冷,不利于资源利用,
    热表很可能快速成为系统性能瓶颈。
3.2.1.2 按取模&哈希分片
按分片列

一般是按分片列做取模&哈希运算后,得知数据要放到哪个库的哪个表中,
例如:有2个分片就是按2取模路由,有10个分片就是按10取模路由。

如果查询的条件中没有涉及到分片列,那就需要进行全局轮训查询,这样的效率会很低。
因此我们需要结合业务来选择使用最频繁以及最重要的列作为分片列。

分片列的制定需要知道业务场景,类似于99%的查询场景都会使用某个列,
那这个列比较适合做分片列,其他1%的场景使用的列就不用做成分片列。

按非分片列
  • 映射关系法
    使用非分片列进行查询,效率较低,需要全局轮巡,
    此时我们可以创建一个映射关系表(或叫索引表),把非分片列与分片列做映射。
    前提是非分片字段是唯一的,不能有重复值。
    当使用非分片列查询时,先去映射表查询到对应的分片列的值,再用分片列的值进行路由。
    索引表只有两列,可以承载很多数据,KV结构,数据量大的话可以继续做水平切分,
    也可以把映射关系存储在分布式缓存中,提升性能。
    缺点:多一次数据库或缓存的查询。
    需要注意,采用映射关系法的非分片列的值变化后需要同步更新到映射表或缓存中。

  • 基因法
    将非分片列的值使用一个函数f随机生成一个3位的随机码,这个码相当于非分片列的值的基因。
    函数f类似于:128bit = md5(非分片列的值),再截取最后3位。
    再将基因放到分片列的后3位中,例如:分片列是64位整型,前61位跟原来一样,后3位放基因。
    需要保证这3位基因可以起到路由库表的作用。
    分片列的值 = 分片列的值(前61位) + f(非分片列的值)。
    再将有基因的分片列插入数据库中,落到不同的库表中。
    等回头用非分片列进行查询时,先用函数f得出3位基因,然后通过基因路由到对于的库表中。
    (基因的长度是根据分库数量制定的,如果分库数量多则需要增加基因长度,不能再是3位)
    当使用非分片列进行查询时,通过函数f进行计算得到分片列的值,再通过分片列的值进行路由。
    需要注意,采用基因法的非分片列的值是不能更改的。

    需要提前做好容量评估,预留基因长度:
    %8的本质 -> 最后3个bit决定这行数据落在哪个库上。
    %16的本质 -> 最后4个bit决定这行数据落在哪个库上。
    %32的本质 -> 最后5个bit决定这行数据落在哪个库上。

按某列取模&哈希分片优缺点

优点:

  1. 路由规则简单。
  2. 数据相对均匀,不容易出现热点数据瓶颈问题。

缺点:

  1. 扩容时需要做数据迁移(使用一致性哈希算法可最大程度降低这个问题)。
  2. 在使用非分片列查询时,没办法直接路由。
3.2.1.3 路由服务

路由服务解耦了路由规则与业务方,业务方每次访问数据库之前需要先访问路由服务,才知道数据具体在哪个库表中。

3.2.1.4 主流水平切分场景

结合业务场景,一般有四种主流场景。

单key业务-用户表

user(uid,uname,pwd,age,create_time)

1%的使用场景-登录或按其他列查询:
WHERE uname = ? AND pwd = ?
WHERE age = ?

99%的使用场景-按主键查询详情:
WHERE uid = ?

结论:
使用uid作为分片字段,非分片字段查询则使用映射关系法或基因法。

一对多业务-评论表

comment(cid,uid,title,content,create_time)

10%的使用场景-查询用户的评论:
WHERE uid = ?

90%的使用场景-按主键查询详情:
WHERE cid = ?

结论:
使用一对多里的一方的主键做分片字段,即uid,
之后使用映射关系法来映射uid与cid的关系或基因法来通过uid来生成cid。
cid = 全局唯一id(前61位)+ uid的分库基因(后3位)

多对多业务-好友表

friend(uid,friend_uid,remark,create_time)

50%的使用场景-查询我的好友:
WHERE uid = ?

50%的使用场景-查询加我好友用户:
WHERE friend_uid = ?

结论:
使用数据冗余方案,多份数据使用多种分库手段。

多key业务-订单表

order(oid,buyer_id,seller_id,order_info)

80%的使用场景-按主键查询详情
WHERE oid = ?

19%的使用场景-查询用户的订单
WHERE buyer_id = ?

1%的使用场景-查询商户的订单
WHERE seller_id = ?

方案A:使用2+3
方案B:1%的请求采用多库查询

3.3 先分库再分表

如果只是单纯的分表,那么分出来的一堆表还是在一个数据库实例中,
一个数据库实例就会有很多性能上的限制,因此我们要打破这种限制。

通俗的说,将一个业务表中的数据,按照某些拆分规则,
拆到不同的数据库实例,不同的数据库,不同的表中。
未完待续。


4. 分布式后带来的问题

4.1 分布式事务

分布式锁,2pc,消息队列,最终一致性,柔性事务。

跨分片事务也分布式事务,想要了解分布式事务,就需要了解“XA接口”和“两阶段提交”。
值得提到的是,MySQL5.5x和5.6x中的xa支持是存在问题的,会导致主从数据不一致。
直到5.7x版本中才得到修复。

如果必须要使用一致性事务,那就会导致同时锁住多个节点的资源,导致并发量下降,
容易造成死锁。

对于数据一致性要求不高的场景可以使用消息队列,
采用最终一致性的方式(事务补偿)。
例如:对数据对账,检查log,定期同步数据等方式。

Java应用程序可以采用Atomikos框架来实现XA事务(J2EE中JTA)。
感兴趣的读者可以自行参考《分布式事务一致性解决方案》,链接地址:
http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency

4.2 分布式join

分布式后,原本一个业务表的数据会散落在不同的库、表中,
那原来的join查询就会变得复杂,也会非常消耗性能,理论上应该避免分布式join查询。

注意:后台管理系统中的报表逻辑一般会join多张表,
这种玩法已经过时很久了,这种设计非常不合理。

可以借助离线分析、流式计算等解决方案。
下面列举了一些常见的解决方案。

4.2.1 增加冗余列

我们可以使用反范式增加冗余列来减少或消除join,提升性能,典型用空间换时间的玩法。

适用于读多写少以及冗余列较少的场景,
数据更新时需要同时更新冗余列数据,
这里还牵扯到一个问题,就是分布式场景下的数据一致性问题,
一般可以通过程序的业务代码来控制,细节暂不展开。

4.2.2 全局表

所谓全局表,就是有可能系统中所有模块都可能会依赖到的一些表。
比较类似我们理解的“数据字典”。为了避免跨库join查询,
我们可以将这类表在其他每个数据库中均保存一份。
同时,这类数据通常也很少发生修改(甚至几乎不会),
所以也不用太担心“一致性”问题。

当然,更一般的玩法是我们把数据字段表的数据放到分布式缓存中。

4.2.3 冗余关系表数据

在关系型数据库中,表之间往往存在一些关联的关系。如果我们可以先确定好关联关系,
并将那些存在关联关系的表记录存放在同一个分片上,那么就能很好的避免跨分片join问题。
在一对多关系的情况下,我们通常会选择按照数据较多的那一方进行拆分。

例如:假设a表与b表是一对多关系,a是一方,b是多方(b表中有a表的id)。

这样一来,我们就可以进行单节点的局部join,从而避免跨分片join,
但又引入了一个数据一致性的问题。

4.2.4 中间件或业务代码拼装或内存计算

简单场景下,我们可以借助数据库中间件或自己写业务代码的形式将不同库表的数据做拼装,
需要注意n+1次查询的问题。

如果是业务代码拼装,可以将第一次查询的结果存到一个集合中,
并将这个集合传给后面的查询方法做执行,之后再进行数据拼装。

也可以通过2次或多次查询的方式来消除join。
也可以通过spark的内存计算最终将结果返回。

4.3 分布式分页&排序&函数

一般来讲,分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候,
我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,
情况就会变得比较复杂了。

为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,
并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。

例如:业务逻辑要求查询前10条数据,数据分散在2个节点上。

从节点1取出前10条,从节点2取出前10条,
将这20条数据再进行排序,之后返回给上游最终结果。

但这也会有一个问题,假设要取出第100页的数据,
分别要把2个节点上的前100页数据都取出来,然后再进行排序,页数越大,性能越差。

函数处理也是同理,先在各个节点上执行一次,汇总后再执行一次,最后返回给上游。

4.4 分布式主键

在单库单表时代,我们往往会使用mysql的自增主键来自动生成主键id,有序且自增。
但在分库分表后,自增主键就没办法全局控制主键id的顺序性和唯一性了。

4.4.1 UUID

使用32位的UUID作为主键,UUID的生成包括了网卡mac地址,时间戳,namespace,
随机数等等因素。

优点:

  1. 通过本地生成,速度较快。
  2. 全局唯一。

缺点:

  1. 无序,无递增。
  2. 用字符串存储,占用空间多,性能没有整型好。

4.4.2 主键生成服务

需要启动类似于Ticket Server这样的服务,
每次生成id则需要调用服务来获取,需要考虑服务的高可用,
如果服务挂掉,则连累整个系统。

4.4.3 雪花算法

雪花算法(snowflake)生成64bit的长整型数据,
雪花算法是由Twitter公布的分布式主键生成算法,
由41位的timestamp+ 10位自定义的机器码+ 13位累加计数器组成,
它能够保证不同进程主键的不重复性,以及相同进程主键的有序性。
理论上可以使用到2086年。

4.4.4 全局主键表

建立一张MYISAM引擎的全局主键表,该表只有2个列,
分别为:主键列和表名列。主键设置为自增,
将表名列设置为唯一索引,可以同时为多张表生成主键。
MYISAM使用表级锁,不用担心并发读取时读取到同一个主键值。

4.4.5 mybatis-plus

如果使用mybatis-plus框架作为持久层框架,
可以使用框架自带的注解
@TableId(value=“id”, type=IdType.ID_WORKER)

使用了Sequence框架结合雪花算法生成了全局唯一自增有序整型ID。


5. 扩容后的数据迁移

在业务高速发展的过程中,往往需要进行软硬件升级或扩容的操作。
那就需要考虑到老数据的数据迁移问题

这里需要考虑一个点,就是容量设计(根据QPS与数据量),
类似于设计多少个库多少个表能满足当前业务的数据量,细节暂不展开。

  1. 读出老数据,按新的分片规则将老数据写入各个节点的表中。
  2. 一致性哈希。
  3. 将老库的数据备份到新库中,在新库中删除不符合标准的数据。
  4. 先使用范围落地,然后在每个范围内再进行哈希&取模运算拆分多个表。
    这样再扩容的时候就不用担心数据迁移问题,也不用担心热点数据问题。

如何做到不迁移数据又能避免热点?


6. MySQL数据库中间件

如果要把上述的所有问题都自己解决,那真的是太慢太复杂了。
不要重复发明车轮子,网络上有很多现成的数据库中间件来为我们排忧解难。

中间件一般实现了特定数据库的网络通信协议,模拟一个真实的数据库服务,
屏蔽了后端真实的Server,应用程序通常直接连接中间件即可。

而在执行SQL操作时,中间件会按照预先定义分片规则,对SQL语句进行解析、路由,
并对结果集做二次计算再最终返回。

引入数据库中间件的技术成本更低,对应用程序来讲侵入性几乎没有,
可以满足大部分的业务。增加了额外的硬件投入和运维成本,
同时,中间件自身也存在性能瓶颈和单点故障问题,需要能够保证中间件自身的高可用、可扩展。

使用中间件会或多或少的影响系统性能,所以不要过度设计。

常见的数据库中间件:
mysql_proxy(mysql官方)
TDDL(阿里)
sharding-jdbc(当当,已捐给阿帕奇基金会)
mycat(阿里的cobar上改进)
Atlas(360)

Sharding-JDBC

Sharding-JDBC定位为轻量级数据库中间件Java框架,
在Java的JDBC层提供的额外服务,0代码入侵,只需要配置文件即可。

https://shardingsphere.apache.org

它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,
可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

适用于任何基于Java的ORM框架,
如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。

基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL

随着国人在软件行业的崛起,各种优秀开源框架犹如雨后春笋般茁壮成长。
目前有很多耳熟能详的开源框架:
Dubbo、RocketMQ、Durid、Sharding-Sphere、Apollp、Arthas、
Mybatis-plus、xxl-job等等。

随着业务的增长、数据量&并发量的增长,单库单表的mysql已经无法再满足我们的需求,
那我们就需要把单库单表做拆分,使用读写分离+多库多表,理论上让mysql可以无限扩容。
今天,我们就要把理论知识进行落地,从单机数据库进化到分布式数据库。

关于mysql读写分离和分库分表的概念可以参考我的这篇文章:
https://yangminghan.blog.csdn.net/article/details/91410959

与此同时,我们的项目里可能会用到多个数据源,去满足我们多维度化的业务需求,
所谓多数据源,就是你的数据是通过多个不同的数据库查询出来的,
而一般情况下只需要一个数据源即可。

Sharding-JDBC是由当当网开源的关系型分布式数据库中间件,后被阿帕奇孵化。
ShardingSphere则相当于是一个全家桶,组成了一个分布式数据库中间件的完整解决方案。
ShardingSphere由:Sharding-JDBC、Sharding-Proxy、Sharding-Sidecar组成,
适用于数据库读写分离、分库分表、分布式事务、数据库治理等场景,
以及衍生出来的分片策略、分布式主键、XA强一致性事务、柔性事务补偿、配置动态化、
熔断、高可用、数据脱敏、权限、链路追踪、弹性伸缩等解决方案。

本文仅专注于Sharding-JDBC的读写分离+分库分表使用,其他组件暂不展开。
Sharding-JDBC是一个轻量级的数据库中间件,它可以做到零入侵,
你只需要引入它的jar包,写一些配置,就可以让自己的项目实现读写分离+分库分表,
你不需要搭建一个服务也不需要其他相关的依赖,可以把它理解为一个增强版的JDBC驱动。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ny2UMM7X-1641905392777)(evernotecid://BCE3D193-8584-4CB1-94B3-46FF37A1AC6C/appyinxiangcom/12192613/ENResource/p298)]

Sharding-JDBC目前有一些坑,首当其冲的就是它的版本号问题,
四个版本的包名都不一样,引入Maven依赖的时候需要额外注意,
不同版本的配置也不一样,无法做到向下兼容。

Sharding-JDBC版本:
1.x com.dangdang
2.x io.shardingjdbc
3.x io.shardingsphere
4.x org.apache.shardingsphere

目前Sharding-JDBC最新的版本是4.0.0-RC1,也是4.x里第一个release版本,
但很遗憾,这个版本我在接入的时候遇到了很多问题,里面的坑令人发指,例如:

  1. 在使用springboot的前提下,启动报错,数据源冲突问题:
    The bean ‘dataSource’, defined in class path resource [org/apache/shardingsphere/shardingjdbc/spring/boot/SpringBootConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class] and overriding is disabled.

  2. 官方文档的配置信息与实际不符,文档写的是:“url”,但实际要写“jdbcUrl”,
    报错Factory method ‘dataSource’ threw exception; nested exception is java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required

估计4.0.0-RC1现在还不是很成熟,从版本号也能看得出来,故弃之,我们使用3.x版本。

不能用springboot版本的。

https://shardingsphere.apache.org

并且shardingsphere的官网里的例子,我觉得真的很不友好,里面坑挺多的,
就是你一步一步按照官网的例子来,你发现走不下去,对萌新玩家不够友好。


7.总结

在数据量大、访问量大的前提下,
我们可以使用一主多从+分库分表结合的方式来构建成多库多表的分布式数据库结构,
来统一对外提供数据服务。

将MySQL进行分库分表拆分后,一般会尽量不使用或减少:
1.关联查询和子查询
2.自定义函数+存储过程+触发器
3.事务
4.外键

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值