=那一刻,还是输给了现实=😔

基础认识

分库分表定义:

为了解决由于数据量过大而导致的数据库性能降低的问题,将原来独立的数据库拆分成若干数据库,把原来数据量大的表拆分成若干数据表,使得单一数据库、单一数据表的数据量变得足够小,从而达到提升数据库性能的效果。

在开发过程中,对于每个维度都可以采用两种拆分思路,即垂直拆分水平拆分

垂直拆分:在电商系统中,用户在打开首页时,往往会加载一些用户性别、地理位置等基础数据。对于用户表而言,这些位于首页的基础数据访问频率显然要比用户头像等数据更高。基于这两种数据的不同访问特性,可以把用户单表进行拆分,将访问频次低的用户头像等信息单独存放在一张表中,把访问频次较高的用户信息单独放在另一张表中:

垂直分表:

image-20240925215434373

垂直分表的处理方式就是将一个表按照字段分成多张表,每个表存储其中一部分字段

垂直分库:

image-20240925215507913

水平拆分:

image-20240925215554250

  • 取模算法,取模的方式有很多,比如前面介绍的按照用户 ID 进行取模,当然也可以通过表的一列或多列字段进行 hash 求值来取模;
  • 范围限定算法,范围限定也很常见,比如可以采用按年份、按时间等策略路由到目标数据库或表;
  • 预定义算法,是指事先规划好具体库或表的数量,然后直接路由到指定库或表中。

划分原则比较:

分类 水平分库分表 垂直分库分表
拆分维度 按行(记录)拆分 按列(字段)或表拆分
拆分依据 基于某个分片键(如用户 ID、订单 ID) 基于业务模块或表字段
适用场景 数据量大,查询频繁,需将同一表拆分到多个库 业务模块之间独立,或表字段过多需要拆分
优势 提高单表和单库的存储和访问性能,扩展性强 减少单库负载,隔离业务逻辑,优化复杂查询性能
劣势 跨库查询和事务处理较复杂,依赖分布式事务 多库间关联查询变复杂,可能需要 join 或冗余数据

分库分表的两种常见解决方案:客户端分片与代理服务器分片

分库分表是应对数据库性能瓶颈时常用的手段,主要通过将数据划分成多个部分(分片),分别存储在不同的数据库或数据表中,从而减少单个数据库的压力。在分库分表的实现上,主要有两种常见的解决方案:客户端分片代理服务器分片

客户端分片是指分片逻辑完全由客户端(应用层)来管理和执行。客户端负责根据分片规则决定某个 SQL 请求应该发送到哪个数据库或表中。简而言之,分片规则已经前置到客户端,分片逻辑嵌入到应用层,应用直接与多个数据库实例交互。

工作原理:

  • 分片逻辑封装在客户端:分片规则和逻辑直接在应用程序中实现,应用层根据 SQL 请求的相关信息(如主键、字段值)来决定将请求路由到哪个数据库或哪个表。
  • 透明的数据库操作:通过对 JDBC 协议的重写,客户端分片框架(如 ShardingSphere-JDBC)使得业务开发人员不用关心具体的分片逻辑。开发者依然使用标准的 JDBC 进行数据库操作,底层由中间件自动完成分片操作。

代理服务器分片是在应用层和数据库之间添加一个代理层,这个代理服务器负责管理分片逻辑和数据库连接。应用程序只需要与代理服务器通信,代理服务器根据分片规则,将 SQL 请求路由到具体的数据库实例或表中。

典型框架:

  • Cobar(阿里巴巴开源的代理分片中间件)
  • MyCat(开源的数据库分片中间件)
  • ShardingSphere-Proxy(ShardingSphere 的代理模式实现)

分库分表跨库事务问题

  • 两阶段提交(2PC) 和 三阶段提交(3PC):适合强一致性需求的场景,但性能开销较大。
  • TCC 模式:适合对性能要求较高的场景,通过业务逻辑实现补偿。
  • 消息队列 + 最终一致性:适合能够容忍短暂不一致的业务场景,使用消息队列和补偿机制保证最终一致性。
  • Seata:提供了一站式分布式事务解决方案,支持多种模式,适合大部分分布式系统。

雪花算法

  • 符号位(Sign)

    • 占 1 位,始终为 0,因为生成的 ID 是正数。
  • 时间戳(Timestamp)

    • 占 41 位,表示从自定义纪元(通常是 Unix 纪元 1970 年 1 月 1 日)到当前时间的毫秒数。
    • 41 位的时间戳能够表示的毫秒数约等于 69 年,因此可以使用雪花算法生成的 ID 长达 69 年。
  • 数据中心 ID(Data Center ID)

    • 占 5 位,用于区分不同的数据中心,最多可以支持 2^5 = 32 个数据中心。
  • 机器 ID(Machine ID)

    • 占 5 位,用于标识同一数据中心内的不同机器,最多可以支持 2^5 = 32 台机器。
  • 序列号(Sequence)

    • 占 12 位,用于区分同一毫秒内生成的不同 ID。12 位的序列号在同一毫秒内最多可以生成 4096 个唯一 ID。

    雪花算法(Snowflake Algorithm)生成的 ID 并不是连续的

分库分表方案中出现数据倾斜问题

解决方案 描述 优点 缺点
合理的分片键设计 选择合理的分片键,保证数据的均匀分布。避免使用低基数字段或时间字段,可以使用组合分片键。 通过合理的分片键设计,减少数据倾斜,提升系统性能。 如果数据特征变化较大,分片键可能需要不断调整。
哈希分片 对分片键进行哈希运算,然后基于哈希值进行分片,防止数据集中在某些节点。 分布均匀,能有效避免数据倾斜问题。 哈希分片后无法轻易按顺序查询,扩展时需要重新计算哈希值。
一致性哈希 在扩展节点时,通过一致性哈希只迁移部分数据,减少数据倾斜和数据迁移的量。 扩展性好,数据均匀分布,减少大规模的数据迁移。 需要结合虚拟节点等手段才能保证较好的数据均衡效果。
虚拟节点 为每个物理节点分配多个虚拟节点,进一步分散数据,减少单点的数据倾斜。 数据分布更加均匀,减少部分节点的负载压力。 虚拟节点的管理和映射相对复杂,可能增加系统复杂度。
范围分片 根据某个字段的值范围进行分片,如ID范围或日期范围。 容易管理,适合数据顺序性比较强的场景。 如果数据分布不均匀,某些范围的数据量可能会远远大于其他范围,导致倾斜。
热点数据缓存 对于访问频繁的热点数据,使用缓存(如Redis)减少数据库压力,避免热点集中引发的分片倾斜。 缓解数据库压力,提升读写性能。 需要设计缓存失效和同步策略,可能增加系统复杂度。
动态扩展分片 在分片数据量过大时,动态扩展分片数量,将大分片拆分为多个小分片。 解决数据量增长后的存储和查询压力,支持系统扩展。 需要额外的分片拆分和数据迁移逻辑,操作复杂。
分片迁移 当某个分片数据过多时,将该分片的数据部分迁移到其他分片,以平衡数据量。 动态调整分片数据,平衡存储和查询压力。 分片迁移的实现复杂,迁移过程中可能会影响系统性能。
冷热数据分离 将热数据和冷数据分离存储,热数据放在高性能存储节点,冷数据放在低成本存储节点。 热数据和冷数据分离后,减少热门数据的查询压力,优化系统性能。 冷热数据划分规则需要合理设计,且数据迁移操作复杂。
预分片 在系统初期预先分配多个分片,避免后期数据集中在某些分片上。 数据可以均匀分布到各个预先分配的分片中,减少后期的分片调整。 初期可能会有大量空分片,浪费资源。
Redis原子操作 使用 Redis 的 INCR 命令生成递增的序列号,结合分布式锁或集群操作,避免热点问题。 高并发场景下使用 Redis 实现分布式唯一ID生成,性能优越。 需要额外的缓存集群资源,可能增加系统复杂度。
UUID + Hash 使用 UUID 或基于 UUID 的哈希函数生成唯一的订单号,确保全局唯一性。 保证唯一性,无需依赖数据库或外部服务。 生成的订单号较长,不易读且无法保证顺序性。
分布式中间件 使用 Sharding-JDBC、MyCAT 等分布式中间件,自动进行分片和数据分布管理。 提供多种分片策略,适合复杂的分布式场景,减少开发工作量。 依赖中间件的性能和稳定性,学习成本较高。

分库分表面试必背

面试官:关于分库分表的面试问题,你了解多少?

ShardingSphere

ShardingSphere 是 Apache 基金会旗下的一个开源分布式数据库中间件生态系统,提供了分库分表、读写分离、数据加密、分布式事务等功能。其目标是帮助企业在不改变现有数据库系统的前提下,提供轻量级、可扩展的数据库分布式管理解决方案。在大规模高并发场景下,ShardingSphere 可以有效提高数据库的读写性能和扩展性,解决数据库单点瓶颈问题。

ShardingSphere 集成了 Spring 和 Spring Boot 这两款 Spring 家族的主流开发框架。

ShardingSphere

三个核心产品:

  1. ShardingSphere-JDBC:基于 JDBC 层的轻量级分库分表方案,适用于 Java 应用程序。
  2. ShardingSphere-Proxy:数据库代理服务,提供多语言支持,适合多语言应用场景。
  3. ShardingSphere-Sidecar(规划中):面向云原生环境的数据库解决方案,提供服务网格的管理能力。

功能架构

ShardingSphere 提供了以下几个重要功能:

  1. 数据分片(Sharding)
    • 水平分库分表:通过分片键(如订单ID、用户ID等)将数据分片到不同的数据库实例或表中,这样可以减轻单个数据库的压力。
    • 灵活的分片策略:支持多种分片策略,如范围分片、哈希分片、自定义分片等。开发人员可以根据业务需求灵活选择不同的分片方式。
    • 复杂查询支持:支持跨分片的复杂查询,如 JOINGROUP BYORDER BY 等操作,确保分片后的数据查询功能不受影响。
  2. 读写分离
    • 在主从数据库架构下,写操作发送到主库,读操作发送到从库。这种方式可以有效提高数据库的读操作性能,减轻主库的压力。
    • 支持读负载均衡策略,例如轮询、最少连接等。
  3. 分布式事务
    • 强一致性事务(2PC):支持跨库的分布式事务,保证数据的强一致性,使用两阶段提交协议(2PC)来确保事务的正确性。
    • 柔性事务:支持 BASE 理论下的柔性事务,确保在分布式环境中追求最终一致性。
  4. 数据加密
    • 提供透明的数据加密功能,能够对敏感数据(如用户的身份信息、信用卡号等)进行加密存储和解密查询,确保数据安全性。
  5. 弹性扩展
    • 提供动态扩展能力,能够在业务增长时,通过增加数据库实例来提高系统的容量和处理能力。
    • 通过高可用架构,支持数据节点的自动扩展和缩减,保证系统的灵活性。
  6. 影子库功能
    • 支持影子库,用于模拟真实流量进行灰度测试,确保新功能在生产环境下的稳定性。
  7. 统一治理中心(Governance Center)
    • 提供对数据分片、读写分离、分布式事务、数据加密等功能的统一配置和管理。通过 Zookeeper、Nacos 等注册中心实现集群配置的动态更新和监控。

ShardingSphere-JDBC

ShardingSphere 的前身是 Sharding-JDBC,所以这是整个框架中最为成熟的组件。Sharding-JDBC 的定位是一个轻量级 Java 框架,在 JDBC 层提供了扩展性服务。我们知道 JDBC 是一种开发规范,指定了 DataSource、Connection、Statement、PreparedStatement、ResultSet 等一系列接口。而各大数据库供应商通过实现这些接口提供了自身对 JDBC 规范的支持,使得 JDBC 规范成为 Java 领域中被广泛采用的数据库访问标准。

基于这一点,Sharding-JDBC 一开始的设计就完全兼容 JDBC 规范,Sharding-JDBC 对外暴露的一套分片操作接口与 JDBC 规范中所提供的接口完全一致。开发人员只需要了解 JDBC,就可以使用 Sharding-JDBC 来实现分库分表,Sharding-JDBC 内部屏蔽了所有的分片规则和处理逻辑的复杂性。显然,这种方案天生就是一种具有高度兼容性的方案,能够为开发人员提供最简单、最直接的开发支持。关于 Sharding-JDBC 与 JDBC 规范的兼容性话题,我们将会在下一课时中详细讨论。

image-20240925220607651

在实际开发过程中,Sharding-JDBC 以 JAR 包的形式提供服务。

image-20240925220528277

Sharding-Proxy

ShardingSphere-ProxyApache ShardingSphere 提供的一个数据库代理服务,它位于应用程序和实际数据库之间,作为数据库的代理层来处理分库分表、读写分离、分布式事务等操作。

通过 ShardingSphere-Proxy,应用程序与数据库的交互可以变得更加简单。应用程序只需要像往常一样发送 SQL 请求,而代理层会根据配置好的规则,将这些请求分发到不同的数据库或数据表,并处理复杂的事务、读写分离等操作。由于 ShardingSphere-Proxy 对外提供标准的数据库协议(如 MySQL、PostgreSQL 协议),因此应用程序无需修改任何代码即可使用。

ShardingSphere-Proxy 的工作流程如下:

  1. 应用程序 向 ShardingSphere-Proxy 发送标准 SQL 请求。
  2. ShardingSphere-Proxy 接收到 SQL 后,基于配置的分片规则、读写分离策略等,对 SQL 进行解析和路由。
  3. ShardingSphere-Proxy 根据业务逻辑将 SQL 请求路由到不同的数据库或表。
  4. ShardingSphere-Proxy 收集各个数据库返回的结果,并将结果整合后再返回给应用程序。

ShardingSphere-Sidecar

ShardingSphere-Sidecar 的目标是利用云原生技术(如 Kubernetes、Istio 等)来实现数据库层面的弹性扩展、动态配置和服务治理,是为云环境下的分布式数据库管理量身定制的解决方案。

数据分片

【shardingsphere】功能介绍-数据分片

在Java服务中实现高效的数据分片与路由

【Spring】SpringBoot整合ShardingSphere并实现多线程分批插入10000条数据(进行分库分表操作)。

举个栗子

假设我们有一个电商系统,其中的订单表 orders 数据量很大。为了提升系统性能,我们将订单表分为 4 个分片,分布在 2 个数据库实例中(DB1 和 DB2),具体如下:

  • DB1.orders_0 存储订单 ID 为 0-999 的订单。
  • DB1.orders_1 存储订单 ID 为 1000-1999 的订单。
  • DB2.orders_2 存储订单 ID 为 2000-2999 的订单。
  • DB2.orders_3 存储订单 ID 为 3000-3999 的订单。

分片规则(Sharding Strategy)

  • ShardingSphere 提供了灵活的分片规则,开发者可以基于订单 ID、用户 ID 等字段定义分片逻辑。比如可以根据订单 ID 取模(order_id % 4)来决定将订单数据放入哪个分片表。

例如,假设一个订单 ID 为 1537 的订单,ShardingSphere 会根据配置好的分片规则计算:

1
shard_table = order_id % 4 = 1537 % 4 = 1

这个订单会被路由到 DB1.orders_1 表中。

SQL 路由

  • 当应用程序发出一个查询,比如:

    1
    SELECT * FROM orders WHERE order_id = 1537;

    ShardingSphere 会解析 SQL,并根据分片规则(order_id % 4)判断数据在哪个库和表中存储。

  • 它将这个 SQL 路由到具体的数据库和表:

    1
    SELECT * FROM DB1.orders_1 WHERE order_id = 1537;

SQL 聚合和结果合并

  • 对于需要从多个分片表中汇总数据的操作,比如统计订单总数:

    1
    SELECT COUNT(*) FROM orders;

    ShardingSphere 会将这条 SQL 分发到所有分片表(orders_0 到 orders_3)上执行,并将各个表的结果汇总,最后返回给应用程序:

    1
    2
    3
    4
    SELECT COUNT(*) FROM DB1.orders_0;
    SELECT COUNT(*) FROM DB1.orders_1;
    SELECT COUNT(*) FROM DB2.orders_2;
    SELECT COUNT(*) FROM DB2.orders_3;

读写分离

读写分离,实际上就是将写操作路由到主数据库,而将读操作路由到从数据库

ShardingSphere实战(5)- 读写分离

分布式事务

在 ShardingSphere 中,除本地事务之外,还提供针对分布式事务的两种实现方案,分别是 XA 事务和柔性事务。这点可以从事务类型枚举值 TransactionType 中得到验证:

1
2
3
public enum TransactionType {
LOCAL, XA, BASE
}

XA 事务

[eXtended Architecture]

XA 事务提供基于两阶段提交协议的实现机制。所谓两阶段提交,顾名思义分成两个阶段,一个是准备阶段,一个是执行阶段。在准备阶段中,协调者发起一个提议,分别询问各参与者是否接受。在执行阶段,协调者根据参与者的反馈,提交或终止事务。如果参与者全部同意则提交,只要有一个参与者不同意就终止。

image-20240925221836310

XA 事务是典型的强一致性事务

BASE事务

BASE是与ACID(原子性、一致性、隔离性、持久性)相对的另一种事务处理理念。BASE是指基本可用(Basically Available)、软状态(Soft State)、最终一致性(Eventually Consistent)。相比于ACID,BASE更侧重于分布式系统的设计和实现。

举例说明:

假设有一个社交网络系统,用户可以发布动态并进行评论,系统需要保证用户发布的动态和评论在整个系统中是一致的,即保证最终一致性。

  • 当用户发布一条动态时,系统会首先将动态数据写入主数据库,并将动态数据复制到多个副本数据库中。
  • 同时,评论数据也会被写入主数据库和多个副本数据库。
  • 在用户发布动态后,在短时间内,不同用户在不同的副本数据库上查看该动态及评论可能会出现数据不一致的情况(软状态)。
  • 系统会异步地将数据进行同步,保证最终所有的副本数据库中的数据是一致的(最终一致性)。

数据脱敏

数据脱敏,是指对某些敏感信息通过脱敏规则进行数据转换,从而实现敏感隐私数据的可靠保护。

实现方式

  1. 配置规则:首先,需要在ShardingSphere的配置文件中定义数据脱敏规则,包括需要脱敏的字段、脱敏的方式(如替换、加密等)以及脱敏的规则。
  2. 数据处理:当数据库查询返回结果时,ShardingSphere会根据配置的规则对敏感数据进行脱敏处理,然后再将结果返回给用户。
  3. 保留原始数据:通常情况下,脱敏操作是在返回结果时进行的,原始数据仍然保存在数据库中,只有在返回给用户时才进行脱敏处理。

举例

假设有一个用户表(user)包含用户的姓名(name)、手机号(phone)、邮箱(email)等敏感信息,我们要对手机号和邮箱进行脱敏处理:

  • 配置规则:在ShardingSphere的配置文件中定义数据脱敏规则,指定对手机号和邮箱字段进行脱敏,可以选择使用星号遮蔽部分信息或者进行加密处理。
1
2
3
4
5
6
7
8
9
10
dataMasking:
rules:
- name: MaskPhone
columns:
- user.phone
encryptor: aes
- name: MaskEmail
columns:
- user.email
strategy: PARTIAL(2,'****',2)
  • 数据处理:当执行查询操作时,ShardingSphere会根据配置的规则对查询结果中的手机号和邮箱进行脱敏处理,然后返回给用户。

例如,原始数据:

1
2
3
| name   | phone         | email               |
|--------|---------------|---------------------|
| Alice | 12345678901 | alice@example.com |

经过ShardingSphere的数据脱敏处理后,返回给用户的结果可能是:

1
2
3
| name   | phone         | email               |
|--------|---------------|---------------------|
| Alice | 123****8901 | a****@example.com |

通过ShardingSphere的数据脱敏功能,可以有效保护用户的隐私数据,确保敏感信息在展示时不会泄露。

总结:常见的脱敏法

  1. 部分隐藏:部分隐藏是对敏感数据的部分内容进行遮蔽,常见的方式包括:
    • 部分脱敏:例如对手机号码只显示部分数字,如将中间几位数字用星号代替。
    • 部分替换:例如对邮件地址只显示部分字符,如用星号代替一部分字符。
    • 部分加密:对敏感数据进行部分加密,展示给用户时进行解密。
  2. 完全隐藏:完全隐藏是对整个敏感数据进行隐藏,不显示原始数据,常见的方式包括:
    • 完全脱敏:直接将敏感数据完全删除或替换为固定的值(如NULL)。
    • 完全加密:对整个敏感数据进行加密,展示给用户时进行解密。
  3. 数据扰动:数据扰动是对敏感数据进行混淆处理,以使原始数据难以被还原,常见的方式包括:
    • 数据乱序:对数据进行随机排序,打乱原始数据的顺序。
    • 数据混淆:对数据进行随机替换或加入噪音数据,使数据失去原始的含义。
  4. 数据掩码:数据掩码是对敏感数据进行掩盖处理,以保护数据的隐私,常见的方式包括:
    • 数据掩码:使用特定的掩码字符(如星号)来代替敏感数据,以隐藏真实数据。
    • 数据加密:对敏感数据进行加密处理,只有具有解密权限的用户才能查看原始数据。
  5. 数据一致性保护:在进行数据脱敏时,需要确保脱敏后的数据仍然保持一定的数据一致性,以避免数据泄漏。