分布式-数据库治理

一、分布式数据库

1.选择什么样的分布式数据库?

分布式架构下的数据应用场景远比集中式架构复杂,会产生很多数据相关的问题。谈到数据,首先就是要选择合适的分布式数据库。

分布式数据库大多采用数据多副本的方式,实现数据访问的高性能、多活和容灾。

目前主要有三种不同的分布式数据库解决方案。它们的主要差异是数据多副本的处理方式和数据库中间件。

1. 一体化分布式数据库方案

它支持数据多副本、高可用。多采用 Paxos 协议,一次写入多数据副本,多数副本写入成

功即算成功。代表产品是 OceanBase 和高斯数据库。

2. 集中式数据库 + 数据库中间件方案

它是集中式数据库与数据库中间件结合的方案,通过数据库中间件实现数据路由和全局数据管理。

数据库中间件和数据库独立部署,采用数据库自身的同步机制实现主副本数据的一致性。

集中式数据库主要有 MySQL 和 PostgreSQL 数据库,基于这两种数据库衍生出了很

多的解决方案,比如开源数据库中间件 MyCat+MySQL 方案,TBase(基于PostgreSQL,但做了比较大的封装和改动)等方案。

3. 集中式数据库 + 分库类库方案

它是一种轻量级的数据库中间件方案,分库类库实际上是一个基础 JAR 包,与应用软件部署在一起,实现数据路由和数据归集。

它适合比较简单的读写交易场景,在强一致性和聚合分析查询方面相对较弱。典型分库基础组件有 ShardingSphere。

4.总结

这三种方案实施成本不一样,业务支持能力差异也比较大。

一体化分布式数据库主要由互联网大厂开发,具有超强的数据处理能力,大多需要云计算底座,实施成本和技术能力要求比较高。

集中式数据库 + 数据库中间件方案,实施成本和技术能力要求适中,可满足中大型企业业务要求。

第三种分库类库的方案可处理简单的业务场景,成本和技能要求相对较低。

在选择数据库的时候,我们要考虑自身能力、成本以及业务需要,从而选择合适的方案。

2、如何设计数据库分库主键?

选择了分布式数据库,第二步就要考虑数据分库,这时分库主键的设计就很关键了。

与客户接触的关键业务,我建议你以客户 ID 作为分库主键。这样可以确保同一个客户的数据分布在同一个数据单元内,避免出现跨数据单元的频繁数据访问。跨数据中心的频繁服务

调用或跨数据单元的查询,会对系统性能造成致命的影响。

将客户的所有数据放在同一个数据单元,对客户来说也更容易提供客户一致性服务。而对企

业来说,“以客户为中心”的业务能力,首先就要做到数据上的“以客户为中心”。

当然,你也可以根据业务需要用其它的业务属性作为分库主键,比如机构、用户等。

3、数据库的数据同步和复制

在微服务架构中,数据被进一步分割。为了实现数据的整合,数据库之间批量数据同步与复制是必不可少的。

数据同步与复制主要用于数据库之间的数据同步,实现业务数据迁移、数据备份、不同渠道核心业务数据向数据平台或数据中台的数据复制、以及不同主题数据的整合等。

传统的数据传输方式有 ETL 工具和定时提数程序,但数据在时效性方面存在短板。

分布式架构一般采用基于数据库逻辑日志增量数据捕获(CDC)技术,它可以实现准实时的数据复制和传输,实现数据处理与应用逻辑解耦,使用起来更加简单便捷。如监听数据库binlog进行实时同步。

现在主流的 PostgreSQL 和 MySQL 数据库外围,有很多数据库日志捕获技术组件。CDC也可以用在领域事件驱动设计中,作为领域事件增量数据的获取技术。

4、跨库关联查询如何处理?

跨库关联查询是分布式数据库的一个短板,会影响查询性能。在领域建模时,很多实体会分散到不同的微服务中,但很多时候会因为业务需求,它们之间需要关联查询。

关联查询的业务场景包括两类:第一类是基于某一维度或某一主题域的数据查询,比如基于客户全业务视图的数据查询,这种查询会跨多个业务线的微服务;第二类是表与表之间的关联查询,比如机构表与业务表的联表查询,但机构表和业务表分散在不同的微服务。

如何解决这两类关联查询呢?

对于第一类场景,由于数据分散在不同微服务里,我们无法跨多个微服务来统计这些数据。

你可以建立面向主题的分布式数据库,它的数据来源于不同业务的微服务。

采用数据库日志捕获技术

采用数据库日志捕获技术,从各业务端微服务将数据准实时汇集到主题数据库。在数据汇集时,提前做好数据关联(如将多表数据合并为一个宽表)或者建立数据模型。面向主题数据库建设查询微服务。这样一次查询你就可以获取客户所有维度的业务数据了。你还可以根据主题或场景设计合适的分库主键,提高查询效率。

借助大数据平台

由于大数据平台会抽离各个业务的数据库模型,可以在大数据平台加上定时任务用于计算业务模型,然后在讲计算结果推送到业务平台

对于第二类场景,对于不在同一个数据库的表与表之间的关联查询场景,你可以采用小表广

播,在业务库中增加一张冗余的代码副表。当主表数据发生变化时,你可以通过消息发布和

订阅的领域事件驱动模式,异步刷新所有副表数据。这样既可以解决表与表的关联查询,还

可以提高数据的查询效率。

二、数据中台与企业级数据集成

分布式微服务架构虽然提升了应用弹性和高可用能力,但原来集中的数据会随着微服务拆分

而形成很多数据孤岛,增加数据集成和企业级数据使用的难度。你可以通过数据中台来实现

数据融合,解决分布式架构下的数据应用和集成问题。

你可以分三步来建设数据中台。

第一,按照统一数据标准,完成不同微服务和渠道业务数据的汇集和存储,解决数据孤岛和

初级数据共享的问题。

第二,建立主题数据模型,按照不同主题和场景对数据进行加工处理,建立面向不同主题的

数据视图,比如客户统一视图、代理人视图和渠道视图等。

第三,建立业务需求驱动的数据体系,支持业务和商业模式创新。

数据中台不仅限于分析场景,也适用于交易型场景。你可以建立在数据仓库和数据平台上,

将数据平台化之后提供给前台业务使用,为交易场景提供支持。

三、单进程多数据源解决方案

1.为什么建议一个服务只要一个数据源

1.共享事务问题

对多数据源写操作时,如何保证数据的一致性,完整性?即如果要保证双写的一致性,可能会引入分布式事务的解决方案,这样直接增加了系统的复杂度。

一种理论可行的方案是,直接让各个服务共享数据库连接。同一个应用进程中的不同持久化工具(JDBC、ORM、JMS 等)共享数据库连接并不困难,一些中间件服务器(比如 WebSphere),就内置了“可共享连接”功能来专门支持共享数据库的连接。

但这种“共享”的前提是,数据源的使用者都在同一个进程内。由于数据库连接的基础是网络连接,它是与 IP 地址和端口号绑定的,字面意义上的“不同服务节点共享数据库连接”很难做到。

所以,为了实现共享事务,就必须新增一个中间角色,也就是交易服务器。无论是用户服务、商家服务还是仓库服务,它们都要通过同一台交易服务器来与数据库打交道。

如果将交易服务器的对外接口实现为满足 JDBC 规范,那它完全可以看作一个独立于各个服务的远程数据库连接池,或者直接作为数据库代理来看待。此时,三个服务所发出的交易请求就有可能做到,由交易服务器上的同一个数据库连接,通过本地事务的方式完成。

比如,交易服务器根据不同服务节点传来的同一个事务 ID,使用同一个数据库连接来处理跨越多个服务的交易事务。

之所以强调理论可行,是因为这个方案,其实是与实际生产系统中的压力方向相悖的。一个服务集群里,数据库才是压力最大、最不容易伸缩拓展的重灾区。所以,现实中只有类似ProxySQL和MaxScale这样用于对多个数据库实例做负载均衡的数据库代理,而几乎没有反过来代理一个数据库为多个应用提供事务协调的交易服务代理。

这也是为什么说它更有可能是个伪需求的原因。如果你有充足理由让多个微服务去共享数据库,那就必须找到更加站得住脚的理由,来向团队解释拆分微服务的目的是什么。

在实际应用中并不常见,也几乎没有相应的成功案例,能够查到的资料几乎都来源于十多年前 Spring 的核心开发者Dave Syer的文章“Distributed Transactions in Spring, with and without XA”。

2.使用和切换问题

这么多数据源如何使用?如何切换?ORM框架只允许使用一个数据源,如何和ORM框架集成?即使集成成功了,也会比单一数据源增加开发难度和排查解决问题难度。

3.数据库连接资源管理问题

比如我要对我所在的服务的表执行ddl,我去查看表的事务执行情况,我不知道这个长事务是由哪个系统带来了,我就得挨着去问我的兄弟团队,可能搞了半天我才能执行表的ddl.那你的业务方不把你给打死。

什么你不知道为什么要先看是否有长事务或者表的并发访问情况?那么你先去看看线上执行ddl引起锁表的系列问题吧。

2.多数据源解决方案

让我们自己实现多数据源访问,应该考虑哪些点呢?大家都好好想想,一定要动脑筋思考下!让我做的话,需要解决3个问题,分别是:

配置来源问题:多数据源的配置存储在什么位置?yml文件,配置中心,还是缓存?

创建和管理问题:多个数据源如何创建?如何管理?用什么存储?

使用和切换问题:这么多数据源如何使用?如何切换?ORM框架只允许使用一个数据源,如何和ORM框架集成?

思考完后,不一定非要自己开发,你遇到的问题,别人早就遇到了,并且都有现成的解决方案,可以拿来主义。

业界多数据源实现方案

业界有2种实现方案,分别为:

AOP + ThreadLocal ,如:Mybatis-plus的多数据源(dynamic-datasource);

语义解析,如:客户端侧:ShardingSphere-Jdbc,服务端侧:ShardingSphere-Proxy,阿里云、腾讯云proxy。

3.Mybatis-plus解决方案

业界多数据源方案有很多种,咱们这次主要对Mybatis-plus多数据源(dynamic-datasource)进行一次深度剖析,首先来看它有哪些特性,然后带着这些特性去看源码。

特性

支持数据源分组,2种负载均衡策略:轮询和随机

支持对JDBC连接的url,username,password加密 ENC()

支持无数据源启动,动态增加删除数据源

支持每个数据库独立初始化表结构schema和数据库database。

支持数据源延迟初始化

提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成

提供 自定义数据源来源 方案(如全从数据库加载)

支持 多层数据源嵌套切换 。

提供 本地多数据源事务方案

提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。

  1. 使用案例

yml文件添加多数据源配置

spring:
  application:
    name: demo
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://127.0.0.1:3306/test?useSSL=true&charset=utf8mb4&serverTimezone=Hongkong
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
        order:
          url: jdbc:mysql://127.0.0.1:3306/only_db_0?useSSL=true&charset=utf8mb4&serverTimezone=Hongkong
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver

service类:


@Slf4j
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements TOrderService {

    @Autowired
    private TOrderMapper tOrderMapper;

    @DS("master")
    @Override
    public Boolean insertMaster(TOrder tOrder) {
        int result = baseMapper.insert(tOrder);
        return result > 0 ? true : false;
    }

    @DS("order")
    @Override
    public Boolean insertOrder(TOrder tOrder) {
        int result = baseMapper.insert(tOrder);
        return result > 0 ? true : false;
    }
}

参考资料

Mybatis-plus多数据源深度剖析
Mybatis-plus多数据源深度剖析 - 知乎

四、跨系统实时数据同步

在大厂中,对于海量数据的处理原则,都是根据业务对数据查询的需求,反过来确定选择什么数据库、如何组织数据结构、如何分片数据,这样才能达到最优的查询性能。

同样一份订单数据,除了在订单库保存一份用于在线交易以外,还会在各种数据库中,以各种各样的组织方式存储,用于满足不同业务系统的查询需求。

像 BAT 这种大厂,它的核心业务数据,存个几十上百份是非常正常的。

1.传统ETL工具定时同步数据

早期大数据刚刚兴起的时候,大多数系统还做不到异构数据库实时同步,那个时候普遍的做法是,使用 ETL 工具定时同步数据,在 T+1 时刻去同步上一个周期的数据,然后再做后续的计算和分析。

定时 ETL 对于一些需要实时查询数据的业务需求就无能为力了。所以,这种定时同步的方式,基本上都被实时同步的方式给取代了。

2.Canal实时数据同步

怎么来做这么大数据量、这么多个异构数据库的实时同步呢?

我们以 MySQL to Redis 同步为例,利用 Canal 把自己伪装成一个 MySQL 的从库,从 MySQL 实时接收 Binlog 然后写入 Redis 中。

把这个方法稍微改进一下,就可以用来做异构数据库的同步了,那这样难道不会有什么问题吗?

为了能够支撑下游众多的数据库,从 Canal 出来的 Binlog 数据肯定不能直接去写下游那么多数据库,一是写不过来,二是对于每个下游数据库,它可能还有一些数据转换和过滤的工作要做。

对于下游业务各种个性化和复杂的的业务需求,我们还需要一个MQ 来解耦上下游。

所以我这边总结一下增加mq的几点原因

1. Canal接受的Binlog往多个下游数据库写,那么如果区间有增加数据库和减少数据库,那就得改canal这层的服务,和业务有强耦合,所以需要MQ 来解耦上下游,让下游自己决定是否消费binlog和如何消费binlog

2.降低主从延迟,一旦Binlog写入的数据过快,忘下游写入的速度跟不上就会出现主从延迟,而接入mq利用其分区有序性进行并行写入能增加下游的消费速度,降低主从延迟。

3.Canal+MQ 实时数据同步

1.架构设计

Canal 从 MySQL 收到 Binlog 并解析成结构化数据之后,直接写入到 MQ 的一个订单Binlog 主题中,然后每一个需要同步订单数据的业务方,都去订阅这个 MQ 中的订单Binlog 主题,消费解析后的 Binlog 数据。

在每个消费者自己的同步程序中,它既可以直接入库,也可以做一些数据转换、过滤或者计算之后再入库,这样就比较灵活了。

2.同步延迟解决方案

1.为何要降低同步延迟?

有些接收 Binlog 消息的下游业务,对数据的实时性要求比较高,不能容忍太高的同步时延。比如说,每个电商在大促的时候,都会有一个大屏幕,实时显示现在有多少笔交易,交易额是多少。这个东西都是给老板们看的,如果说大促的时候,你让老板们半小时之后才看到数字,那估计你就得走人了。

大促的时候,数据量大、并发高、数据库中的数据变动频繁,同步的 Binlog 流量也非常大。为了保证这个同步的实时性,整个数据同步链条上的任何一个环节,它的处理速度都必须得跟得上才行。

2.为何会出现同步延迟?

我们一步一步分析可能会出现性能瓶颈的环节。 

顺着数据流向往下看,Canal 和MQ 这两个环节,由于没什么业务逻辑,性能都非常好。

所以,一般容易成为性能瓶颈的就是消费 MQ 的同步程序,因为这些同步程序里面一般都会有一些业务逻辑,而且如果下游的数据库写性能跟不上,表象也是这个同步程序处理性能上不来,消息积压在 MQ 里面,这样就好存在大量的同步延迟问题

3.数据顺序保证

那我们能不能多加一些同步程序的实例数,或者增加线程数,通过增加并发来提升处理能力呢?

这个地方的并发数,还真不是随便说扩容就可以就扩容的,我来跟你讲一下为什么。

我们知道,MySQL 主从同步 Binlog,是一个单线程的同步过程。为什么是单线程?

原因很简单,在从库执行 Binlog 的时候,必须按顺序执行,才能保证数据和主库是一样的。为了确保数据一致性,Binlog 的顺序很重要,是绝对不能乱序的。

严格来说,对于每一个MySQL 实例,整个处理链条都必须是单线程串行执行,MQ 的主题也必须设置为只有 1 个分区(队列),这样才能保证数据同步过程中的 Binlog 是严格有序的,写到目标数据库的数据才能是正确的。

那单线程处理速度上不去,消息越积压越多,这不无解了吗?其实办法还是有的,但是必须

得和业务结合起来解决。

还是拿订单库来说啊,其实我们并不需要对订单库所有的更新操作都严格有序地执行,比如说 A 和 B 两个订单号不同的订单,这两个订单谁先更新谁后更新并不影响数据的一致性,因为这两个订单完全没有任何关系。但是同一个订单,如果更新的 Binlog 执行顺序错了,那同步出来的订单数据真的就错了。

也就是说,我们只要保证每个订单的更新操作日志的顺序别乱就可以了,这种一致性要求称为因果一致性(Causal Consistency),有因果关系的数据之间必须要严格地保证顺序,没有因果关系的数据之间的顺序是无所谓的。

说到这里我想你一定已经想道了对应的解决方案了,没错,那就是利用mq的分区有序性解决同步延迟问题。

4.mq分区有序性

首先根据下游同步程序的消费能力,计算出需要多少并发;然后设置 MQ 中主题的分区(队列)数量和并发数一致。

因为 MQ 是可以保证同一分区内,消息是不会乱序的,所以我们需要把具有因果关系的 Binlog 都放到相同的分区中去,就可以保证同步数据的因果一致性。对应到订单库就是,相同订单号的 Binlog 必须发到同一个分区上。

Canal 自带的分区策略就支持按照指定的 Key,把 Binlog 哈希到下游的 MQ 中去,具体的配置可以看一下Canal 接入 MQ 的文档。

五、数据库平稳切换解决方案

1.背景

随着我们的系统规模逐渐增长,总会遇到需要更换数据库的问题。我们来说几种常见的情况。

  • 对 MySQL 做了分库分表之后,需要从原来的单实例数据库迁移到新的数据库集群上。
  • 系统从传统部署方式向云上迁移的时候,也需要从自建的数据库迁移到云数据库上。
  • 一些在线分析类的系统,MySQL 性能不够用的时候,就需要更换成一些专门的分析类数据库,比如说 HBase。

更换数据库这个事儿,是一个非常大的技术挑战,因为我们需要保证整个迁移过程中,既不能长时间停服,也不能丢数据。

2.架构设计

设计在线切换数据库的技术方案,首先要保证安全性,确保每一个步骤一旦失败,都可以快速回滚。此外,还要确保迁移过程中不丢数据,这主要是依靠实时同步程序和对比补偿程序来实现。

我把这个复杂的切换过程的要点,按照顺序总结成下面这个列表,供你参考:

  1. 上线同步程序,从旧库中复制数据到新库中,并实时保持同步;

  2. 上线双写订单服务,只读写旧库;

  3. 开启双写,同时停止同步程序;

  4. 开启对比和补偿程序,确保新旧数据库数据完全一样;

  5. 逐步切量读请求到新库上;

  6. 下线对比补偿程序,关闭双写,读写都切换到新库上;

  7. 下线旧库和订单服务的双写功能

接下来我们还是以订单库为例子,说一下这个迁移方案应该如何来设计。

1.新旧实时数据同步

首先要做的就是,把旧库的数据复制到新库中。因为旧库还在服务线上业务,所以不断会有订单数据写入旧库,我们不仅要往新库复制数据,还要保证新旧两个库的数据是实时同步的。

所以,我们需要用一个同步程序来实现新旧两个数据库实时同步。

怎么来实现两个异构数据库之间的数据实时同步,这个方法我们刚刚讲过,我们可以使用 Binlog 实时同步数据。如果源库不是 MySQL 的话,就麻烦一点儿,但也可以参考复制状态机理论来实现。

这一步不需要回滚,原因是,只增加了一个新库和一个同步程序,对系统的旧库和程序都没有任何改变。即使新上线的同步程序影响到了旧库,只要停掉同步程序就可以了。

而且如果只是同步旧数据的话,这一步可以在上线前凌晨时候同步上一日的数据即可。

上线新版的订单服务,这个时候订单服务仍然是只读写旧库,不读写新库。让这个新版的订单服务需要稳定运行至少一到二周的时间,期间除了验证新版订单服务的稳定性以外,还要验证新旧两个订单库中的数据是否是一致的。这个过程中,如果新版订单服务有问题,可以立即下线新版订单服务,修正相关问题后再重新进行实时同步。

2.新旧双写

然后,我们需要改造一下订单服务,业务逻辑部分不需要变,DAO 层需要做如下改造:

1. 支持双写新旧两个库,并且预留热切换开关,能通过开关控制三种写状态:只写旧库、只写新库和同步双写。

2. 支持读新旧两个库,同样预留热切换开关,控制读旧库还是新库。

稳定一段时间之后,就可以开启订单服务的双写开关了,开启双写开关的同时,需要停掉同步程序。

这里面有一个问题需要注意一下,就是这个双写的业务逻辑,一定是先写旧库,再写新库,并且以写旧库的结果为准

旧库写成功,新库写失败,返回写成功,但这个时候要记录日志,后续我们会用到这个日志来验证新库是否还有问题。旧库写失败,直接返回失败,就不写新库了。这么做的原因是,不能让新库影响到现有业务的可用性和数据准确性。上面这个过程如果出现问题,可以关闭双写,回到只读写旧库的状态。

3.新旧数据核对服务

切换到双写之后,新库与旧库的数据可能会存在不一致的情况,原因有两个:

1.停止同步程序和开启双写,这两个过程很难做到无缝衔接,

2.双写的策略也不保证新旧库强一致

这时候我们需要上线一个对比和补偿的程序,这个程序对比旧库最近的数据变更,然后检查新库中的数据是否一致,如果不一致,还要进行补偿。

开启双写后,还需要至少稳定运行至少几周的时间,并且期间我们要不断地检查,确保不能有旧库写成功,新库写失败的情况出现。对比程序也没有发现新旧两个库的数据有不一致的情况,这个时候,我们就可以认为,新旧两个库的数据是一直保持同步的。

4.新旧双读

接下来就可以用类似灰度发布的方式,把读请求一点儿一点儿地切到新库上。同样,期间如果出问题的话,可以再切回旧库。

全部读请求都切换到新库上之后,这个时候其实读写请求就已经都切换到新库上了,实际的切换已经完成了,但还有后续的收尾步骤。

5.旧库下线

再稳定一段时间之后,就可以停掉对比程序,把订单服务的写状态改为只写新库,到这里,旧库就可以下线了。

注意,整个迁移过程中,只有这个步骤是不可逆的。但是,这步的主要操作就是摘掉已经不再使用的旧库,对于在用的新库并没有什么改变,实际出问题的可能性已经非常小了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值