Fork me on GitHub

数据库架构

分库

mer,l1u
fre,
acc,
tra,
other,
backup

tra中的chinaarea表copy到l1u中

 

数据库架构演变

架构的拓展周期的想法(仅个人观点)
首先,我认为初期规划不该太复杂或者庞大,无论项目的中长期可能会发展地如何如何,前期都应该以灵活为优先,像分库分表等操作不应该在开始的时候就考虑进去。
其次,我认为需求变更是非常正常的,这点在我等开发的圈子里吐槽的最多,其中自然有 “领导们” 在业务方面欠缺整体考虑的因素,但我们也不该局限在一个观点内,市场中变则通,不变则死,前期更是如此,因此在前几版的架构中我们必须要考虑较高的可扩展性。
最后,当项目经过几轮市场的洗礼和迭代开发,核心业务趋于稳定了,此时我们再结合中长期的规划给系统来一次重构,细致地去划分领域边界,该解耦的解耦,该拆分的拆分。

 

======================================================

也许有的同学会提出分库、分表、分区、拆字段、上缓存、上搜索引擎、上大数据分析…但是这些执行完毕后,项目代码也要配套更新,紧接着是测试、安全、并发等等问题亟待解决(目前团队稳定么?有这些真实力么?技术成本提高后紧接着带来的是招聘成本和维护成本)…

一个高级工程师的月薪应该差不多够改善硬件了吧?不够?那么整个研发部门的月薪呢?项目稳定性带来的业务市场直接价值和潜在价值呢?

架构设计时就要订好分库策略,否则一方面技术设计会没有预留,另一方面数据模型设计上也会缺乏预留。
分库有N种策略,比如:活跃库/历史库,读写分离,业务垂直分离 等等。
架构设计初期,订好未来将采用的分库策略,然后按该策略预留好技术支持和数据模型上的支持,比如:更新时间戳、数据属地信息、数据的业务分类信息等,就需要在多张表中都做冗余存储。
初期如果没有订好策略,等系统跑个3年后再来分库,会十分、相当、显著的痛苦。当然,如果坚持用个3~5年,然后跟客户说系统老了该掏点钱让我们升级换代了,也是个方案。

分库有的是用代理,这样的话之后分库不需要修改代码。但是如果是基于自身api的,分库后代码改动就很大,之前的单数据源就要变成多数据源。而且分库后很多查询操作也不能用原来的了。

设计数据库时我们一般会把常用、短类型字段放在主表(尽量打造成静态表),把不常用、长类型字段放在附表,最终2个或者多个表进行JOIN,附表中适当的维护使用冗余字段,也是不错的选择!

======================================================


单表单库--->分表分库--->主从复制--->读写分离--->增加缓存

在数据库前端增加缓存redis或memcached

初期单库单表

======================================================
单库单表已经达到业务忍受的极限,这时候不管在硬件还是在参数调优上都已经无法满足业务时,就分库分表

分库分表一共有两种方案:
1) 垂直拆分
2) 水平拆分


当数据库达到一定规模后(比如说大几千万以上),拆分是必须要考虑的。一般来说我们首先要进行垂直拆分,即按业务分割,比如说用户相关、订单相关、统计相关等等都可以单独成库。
但仅仅如此这是完全不够的,垂直拆分虽然剥离了一定的数据,但每个业务还是那个数量级,因此我们还得采取水平拆分进一步分散数据。

分库分表的优点相信上述两图都一目了然了,一个是专库专用,业务更集中,另一个是提升数据库服务的负载能力。
But there are always two sides to a coin。 从此以后你要接受你的系统复杂度将提升一个档次,迭代、迁移、运维等都不再容易。

垂直拆分在实现上就是一个多数据源的问题,没啥好讲的,基于 Sharding-JDBC 中间件

 

单表的数据量限制,当单表数据量到一定条数之后数据库性能会显著下降。数据多了之后,对数据库的读、写就会很多。
分库减少单台数据库的压力。

接触过几个分库分表的系统,都是通过主键进行散列分库分表的。这类数据比较特殊,主键就是唯一的获取该条信息的主要途径。比如:京东的订单、财付通的交易记录等。该类数据的用法,就是通过订单号、交易号来查询该笔订单、交易。

还有一类数据,比如用户信息,每个用户都有系统内部的一个userid,与userid对应的还有用户看到的登录名。那么如果分库分表的时候单纯通过userid进行散列分库,那么根据登录名来获取用户的信息,就无法知道该用户处于哪个数据库中。

或许有朋友会说,我们可以维护一个email----userid的映射关系,根据email先查询到userid,在根据userid的分库分表规则到对应库的对应表来获取用户的记录信息。这么做是可以的,但是这个映射关系的条数本身也是个瓶颈,原则上是没有减少单表内数据的条数,算是一个单点。并且要维护这个映射关系和用户信息的一致性(修改登录名、多登录名等其他特殊需求),最大一个原因,其实用户信息是一个读大于写的库,web2.0都是以用户为中心,所有信息都和用户信息相关联,所以对用户信息拆分还是有一定局限性的。
对于这类读大于写并且数据量增加不是很明显的数据库,推荐采用读写分离+缓存的模式,试想一下一个用户注册、修改用户信息、记录用户登录时间、记录用户登录IP、修改登录密码,这些是写操作。但是以上这些操作次数都是很小的,所以整个数据库的写压力是很小的。唯一一个比较大的就是记录用户登录时间、记录用户登录IP这类信息,只要把这些经常变动的信息排除在外,那么写操作可以忽略不计。所以读写分离首要解决的就是经常变化的数据的拆分,比如:用户登录时间、记录用户登录IP。这类信息可以单独独立出来,记录在持久化类的缓存中(可靠性要求并不高,登陆时间、IP丢了就丢了,下次来了就又来了)

以oracle为例,主库负责写数据、读数据。读库仅负责读数据。每次有写库操作,同步更新cache,每次读取先读cache再读DB。写库就一个,读库可以有多个,采用dataguard来负责主库和多个读库的数据同步。


======================================================

1、 垂直拆分
所谓垂直拆分,就是将单一数据库拆分成多个数据库,可以考虑的方案:
1) 根据业务逻辑进行拆分
2) 根据冷热数据进行拆分

这里以商品--订单--用户为例,当多种类商品存放于一个数据库和一张表中时,随着时间推移,数据量增大,单库单表查询能力下降,可以根据商品种类来进行拆分,比如公司的产品:高中、初中、小学,由单个表拆分成三个独立表。如果还大,继续拆分,高中拆分成高一、高二、高三。初中拆分成初一、初二、初三。小学拆分成小一、小二......小六。(所有的分库分表都源于生活中的逻辑)。
我们生产环境中针对数据进行了拆分,冷数据(采用MyISAM引擎),热数据(采用Innodb引擎),同时将MyISAM引擎的数据库的宿主机多采用redis或memcache进行缓存,Innodb引擎的数据库的宿主机针对业务情况分配计算型和内存型物理机,还是混合型,同时可以考虑使用redis做部分存储,用于缓解数据库压力。

垂直拆分优缺点:
优点:
1) 由单库单表拆分成多库多表,降低了数据库增删改查压力
2) 对冷热数据拆分,降低了成本并合理利用硬件
3) 对于垂直拆分的数据库,在设计数据库时就应当考虑好数据库架构的延展性,(否则会对后期的扩展造成很大的阻力)
4) 按照业务分库后,业务逻辑更加清晰,更方便运维管理。
缺点:
1) 对于联表查询,带来了不便,可以通过调用接口的方式来触发联表查询的操作,对整个系统而言,复杂度提高了。
2) 对于有些数据库,存在单库性能瓶颈影响整个业务情况。
3) 同时对于事务而言,复杂度提高了。
======================================================

2、 水平拆分
所谓水平拆分,简单来说就是将一张表中的数据按照行拆分成多份存储到不同的数据库的表中。比如user表有9000条数据,将user表拆成user1、user2、user3三张表存放于不同的数据库中,user1存储前3000条数据,中间3000条数据存储到user2表中,最后3000条数据存放到user3表中。
水平拆分的分片维度(有很多算法来决定采用哪种水平拆分的方案)
1) 按照哈希切片,对某个字段进行求哈希值,然后除以分片的数量,最后取模,取模相同的数据为一个分片,这就是哈希分片。
这种分片方式没有时效性。对数据分散比较均匀,缺点是如果需要查询则需要对数据进行聚合处理。
2) 按照时间序列算法切片,有的业务会有明显的季度波动,可以使用时间算法。这种算法就是数据分配不均。

生产环境中,我们部分业务采用的是哈希算法和时间序列算法的混合式
说到水平拆分,不得不提的是水平拆分的路由规则
设计数据库的时候,就要考虑到数据库中各种表的路由规则,同时还需要考虑数据表将来按照什么样的路由规则来进行分库分表。
比如,某个新用户注册,这个用户是如何分配到哪个库哪个表中?一般在注册的时候都会系统自动分配一个uid。根据这个uid可以按照某种算法来进行分配。比如uid%4,(这里将一张表分成4个表),如果是1 则分到第一张表中,以此类推。

水平拆分的优缺点:

优点:
1) 单库单表的数据量最大就那么大,有数据量的限制,我们可以根据业务情况来分配,刚好达到最大(这个量很难把握),既能提高该表的操作性能又节省了资源
2) 因为对表的结构改变非常少,对于开发而言更改代码量非常少,只需要增加路由规则。
3) 对整体系统的稳定性和负载都有大大的提高
缺点:
1) jion操作非常困难,尤其是跨库的联表查询
2) 有的拆分规则很难抽象出来
3) 分片的事务一致性比较难解决
4) 还有数据库的扩容和维护比较困难

======================================================
针对上述垂直拆分和水平拆分,都有以下缺点:
1) jion操作困难
2) 事务一致性困难
3) 多个数据源的管理变复杂。
如何解决事务一致性问题呢?(这个重要性排在第一位)
1) 采用本地事务的一致性(能用则用)
2) 分布式事务处理

分布式事务处理的解决方案:
1) 两阶段提交方案(最严格的方案,很少使用,因为是阻塞协议造成性能问题):分为准备(锁定资源)和提交(消费资源)两个阶段。这种方式依赖资源管理器。
2) 最大努力保证模式(最常用,极端情况需要实时补偿,将已提交的数据进行回滚)不依赖资源管理器
一共有两种操作:从消息队列中消费消息和更新数据库操作
开始消息事务开始数据库事务接收消息更新数据库提交数据库事务提交消息事务
当更新数据库时,突然中断,会进行回滚,恢复到初始状态,特殊情况,如果提交数据库事务成功,但是提交消息事务失败,就会造成消息再次消费的情况。这个可以通过消息幂等处理(有时候很多消息无法满足幂等性比如update操作,可以考虑增加一个消息应用状态表来记录消息消耗情况和数据库事务情况),出现上述极端情况,需要实时补偿。这种补偿机制类似于TCC模式Try-Confirm-Cancel(如果try都成功了,它会重复confirm或重复cancel,直到都成功),TCC要求Confirm/Cancel都必须是幂等操作。
3) 事务补偿机制
前面两种方案,都不是最好的,事务补偿机既能保证性能又能尽最大可能保证事务的一致性。说白了就是突破事务一失败就回滚的思想,指定在一定的时间内不断提交直到成功为止,如果超时则回滚。

======================================================
分库分表过程中带来的问题
1、 扩容和数据的迁移
数据已经分片,同时数据量已经快达到阈值,需要对集群进行扩容,都采用成倍扩容。
以下是5个扩容步骤:
1) 增加新的路由规则,对新的数据库采用新路由规则进行写,同时对旧数据库采用旧路由规则写。(双写,两套路由规则)
2) 将双写前的旧数据按照新规则写入到新数据库中(需要做大量的数据清洗工作)。
3) 将按照旧的分片规则的查询更改为按照新的分片规则查询。
4) 将双写的路由规则代码下线,只按照新规则写数据
5) 删除按照旧分片规则写入的历史数据。
2、 分库分表带来的联表查询问题。
举例:
卖家和买家,卖家需要查看该商品卖出情况,买家需要查看自己的交易情况,可以考虑使用两个表,一个以买家维度,记录买家商品交易情况,一个以卖家维度,记录卖家该商品交易情况。也就是查询交易和交易的数据是分别存储的,并从不同的系统提供接口。


=====================================================
主从复制

为什么要做主从?我们先来探讨以下这几个场景:

我们知道每台数据库服务器有他的最大连接数和 IOPS,若有一天他无法再满足我们的业务需求,那相比于在单台服务器上去做性能堆叠,是是否横向去扩展几台 Slave 去分担 Master 的压力更加合理。
如果服务对数据库的需求是 IO 密集型的,那可能会经常遇到行锁等待等问题,若要鱼与熊掌兼得,读写分离是否是更好的选择。
如果我们的系统需要做很多报表,或者统计和数据分析,这些业务往往相当地耗费资源但又不是很重要,那针对此,我们是否应该开几台 Slave,让他们去小黑屋里慢慢执行,别来影响我处理核心业务的效率。

Master 的主责为写,并将数据同步至 Slave,Slave 主要提供查询功能。

=====================================================
读写分离

基于 Sharding-JDBC 的读写分离实现非常简单,改一下配置文件,其余几乎是无感知的,application.yml:

 

posted on 2019-06-27 17:26  阳光-源泉  阅读(423)  评论(0编辑  收藏  举报

导航