当前位置: 首页 > article >正文

分库分表 相关问题

问题:分库后,就有多个数据源需要,dbproxy 对机器做代理,一般需要lvs/f5 等手段来实现流量的负载均衡,跨机房可能需要dns分发,例如 mycat 阿里的主键。

就这个问题通过一问一答的方式解答

什么是 dbproxy,为什么要用 dbproxy?

dbproxy(数据库代理)可以理解为在应用层与数据库之间插入的一层代理服务,它的主要作用是“屏蔽后端数据库的复杂性”,对上层提供一个统一的访问接口。

屏蔽数据分片(sharding)的细节:
当数据库做了分库分表(sharding)之后,后端往往会出现多台数据库实例、多套数据源。
如果没有代理层,应用需要自己知道每条 SQL 或者每个主键应该访问哪一台数据库实例。
这样应用层会变得非常复杂,而且维护成本高。

统一的连接管理:
在没有代理的情况下,应用可能需要和多台数据库都建立连接,而且应用可能会开启大量连接,会导致数据库压力上升或者网络开销变大。引入代理层后,可以集中管理连接池,并对数据库连接进行复用、路由等操作。

读写分离、负载均衡:
如果有读写分离需求(主库写、从库读),dbproxy 可以帮我们根据 SQL 类型(读/写)自动路由到不同的数据库实例。此外,如果有多台从库,需要做负载均衡时,也可以在代理层实现。

此外,MyCat、阿里 DRDS(Alibaba 的分布式关系数据库服务)、ShardingSphere 等其实都是常见的分库分表中间件或数据库代理的一些解决方案。

为什么分库就需要多个数据源?

所谓“分库”,可以理解成将原本在单一数据库中的数据,按照一定的规则(比如按照用户 ID 范围、订单 ID 哈希等)拆分到多个数据库实例中。这样做的目的是:

水平扩展:单库的容量或性能达到瓶颈后,将数据分到多台机器可以提高整体吞吐量。
分摊压力:多台数据库服务器分担请求,能够缓解单库/单机的压力。

但是分库带来的一个直接后果就是后端出现了多个数据源:

数据源 1(库 A)
数据源 2(库 B)

数据源 N(库 N)

对应用来说,就不是只连一个“数据库”,而是多个。所以应用需要知道——“我这条 SQL 到底要去哪一个库执行?”。这时如果缺少一个统一的代理或中间件,应用就得自己写很多逻辑来进行路由判断:

if (用户ID % 2 == 0) {
走 数据源A
} else {
走 数据源B
}

而且当分库策略或者分库结构发生改变时,又得改业务逻辑。
所以大部分情况下,会加一个 dbproxy 或类似的中间件,把多库对应用‘伪装’成一个逻辑库,由它来做路由、聚合操作等。

为什么需要 LVS / F5 等做负载均衡?

假设你的 dbproxy 部署了多台实例,或者你的数据库实例本身也有多台,需要对外提供统一的访问地址。这时一般会需要一个负载均衡层,比如:
LVS(Linux Virtual Server):内核层级的负载均衡转发,性能高;
F5:硬件负载均衡设备,很多大公司可能用来做专业的流量调度。

它们可以提供一个虚拟 IP 或者域名,对外只暴露一个地址,而内部则把请求按一定的策略分发到真正的 dbproxy 或数据库后端。这样:

如果某个实例挂了,负载均衡可以自动摘除,保证服务的高可用。
可以让多个实例并行,达到横向扩展的效果。

为什么跨机房可能需要 DNS 分发?

当你的应用或服务需要跨机房(甚至跨地域)部署时,网络拓扑就更加复杂。常见有两种层面的流量调度手段:

在网络或四层/七层负载均衡上做流量转发:

比如同一个业务在北京、上海、广州都有机房,那么应用流量先走到一个全局的负载均衡设备或 GSLB(Global Server Load Balancing),再根据 IP 或者距离、延迟等策略把流量引导到就近的机房。

DNS 级别的负载均衡(域名解析层面):

比如你配置一个域名 db.xxx.com,通过 DNS 轮询或者智能 DNS 的方式,自动把请求解析到离用户最近或健康的机房 IP。
常见的做法叫做 GSLB + DNS 方案,比如一些云厂商的 “智能 DNS” 或者 Akamai、Cloudflare 的 GSLB 服务,都能帮你做全局流量调度。
好处是:不需要手动去改 IP,用户通过域名访问,不同区域的解析得到不同的 IP,可以就近接入,提高访问速度,降低延迟,而且当某个机房出现故障时,DNS 解析可以切换到其他机房 IP。

所以,跨机房的负载均衡往往涉及到 DNS 层面的全局调度(GSLB),并配合一定的健康检查或路由策略,使得应用或客户端能够快速接入最近且健康的机房。

关于主键冲突等问题?

分库分表之后,为了保证每台库中的主键(或 ID)全局唯一,通常需要在代理层或者ID 生成服务进行处理。例如:

雪花算法(Snowflake),保证在分布式场景中生成的 ID 全局唯一,而且还能保证有一定的递增趋势。

或者 MyCat/DRDS 自带一些主键策略,像 全局自增主键、多列联合主键 等方式,避免分片数据库中的主键冲突。

这类中间件/代理层也会在一定程度上统一管理或生成主键,屏蔽后面多数据源的冲突问题。

常见的全局唯一主键生成方案?

数据库自增序列/表

通过某个中心数据库表或者序列来获取全局递增的 ID。
缺点:中心化依赖,性能和可用性受限,单点故障风险。

UUID

系统随机生成 128 bit(通常存储为 36 字符的字符串)的标识。
优点:本地生成,无中心依赖。
缺点:存储空间大(影响索引性能),不适合有序索引,难以根据 ID 做路由分片。

Snowflake/雪花算法(Twitter 提出的经典算法)

64 位长整型ID,通常结构是时间戳 + 机器ID + 序列号。
优点:按时间递增,大多是本地生成(需正确配置机器ID、时钟等),性能好,索引有序。
缺点:需要分配 workerId,时钟回拨(时间不同步)会带来复杂度。

号段(Segment)/号段缓存(Leaf)方案

利用数据库的自增列或序列,一次分配一段区间(比如 1000 个ID的区间)给某个节点,节点本地生成 ID。
优点:减少数据库频繁访问,提升性能。
缺点:依赖号段中心库,遇到数据库故障时要有兜底策略。

Redis 原子自增

借助 Redis 的 INCR 命令进行分布式递增;
优点:性能高、实现简单。
缺点:Redis 挂了影响可用性,需要持久化或主从架构保证可靠性。

分布式协调(Zookeeper)

通过 ZK 的顺序节点特性生成全局有序 ID。
缺点:并发高时性能瓶颈明显,不是高并发场景的最佳选择。

实际生产中,雪花算法和号段方案是非常常见的选择。

具体实现细节:以雪花算法为例

雪花算法基础
Snowflake 算法生成的 64 位长整型,通常格式如下(不同实现可能略有差异):

| 1 bit 符号位 | 41 bit 时间戳(毫秒)| 10 bit 工作机器ID | 12 bit 序列号 |

符号位 (1 bit):通常是 0,不用。
时间戳 (41 bit):可以支撑约 69 年的毫秒级时间。
机器ID (10 bit):可以表示 1024 台机器/进程。
序列号 (12 bit):同一毫秒内可生成 4096 个 ID。

生成过程:

取当前时间戳(毫秒级),减去一个自定义起始时间(epoch),得到相对毫秒数。
拼接机器ID 和当毫秒内的序列号。
得到 64 位整形数字(在 Java 中通常是 long 类型)。

机器ID分配
通常会在配置中心或注册中心(Zookeeper、Nacos、Apollo 等)配置每台实例的唯一机器ID。
避免重复机器ID,否则会造成 ID 冲突。

处理时间回拨
如果因为服务器时间突然往回跳,会导致 ID 重复。
一种常见策略:
如果检测到当前时间小于上一次生成 ID 的时间,则等待或直接抛异常,让调用方进行重试;
或在机器ID 上再加一层备用标记,用以覆盖临时回拨(但实现较复杂)。

ID 的数据类型、在数据库如何存储?

无论是雪花算法、号段方案还是其他方式,最终在数据库里通常会用BIGINT类型(MySQL 等常见数据库都是 64 位整型)。为什么?

足够大:64 位可以容纳相当大的数字(9,223,372,036,854,775,807)。
索引效率:数字类型做主键索引效率更高(相比字符串),也有天然顺序(Snowflake 生成的ID大体单调递增)。
通用性好:大多数语言里都有 64 位整型类型(long 类型等)

CREATE TABLE user_order (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2),
create_time TIMESTAMP,

);

在插入数据时,应用侧(或中间件)已经生成好了全局唯一的 ID,直接插入即可。

分库分表/跨机房场景下的路由分片?

基于 ID 的分库分表

常见做法是基于 ID 取模(Hash)或区间进行路由。例如:

Hash 路由:库索引 = (id % 库数),表索引 = (id % 表数) 等;
区间路由:如 0 ~ 9999999 存在库0,10000000 ~ 19999999 存在库1,等等。

如果使用雪花算法,往往取其中的低位来做 id % n,这样分散度较好(也要注意避免某些位都是 0 导致不均衡)。

注意:如果将来要扩容分库数,从 2 库扩展到 4 库,ID 取模算法也要做迁移或映射,否则会导致数据不在预期的库中。可以考虑使用一致性哈希或中间件提供的线上扩容功能。

基于 ID 分配机房(跨机房存储)

如果每个机房都存储不相交的数据片,通常也可以通过取模或区间来决定某条数据应该进哪个机房。

例如:id % 2 == 0 存在北京机房,id % 2 == 1 存在上海机房。
或者更精细的分段,比如 ID 前 X 位代表机房编号,这在定制的雪花算法实现里也可以做到(将机器ID 部分的一部分位数当作“机房ID”+“机器ID”)。

但是这种做法会导致“跨机房 JOIN/查询”难度较大。如果一个用户的数据需要经常跨机房访问,就要考虑多机房同步或“同城多活”方案,复杂度会增加。

具体举例:MyCat / ShardingSphere / DRDS?

一些常见的分库分表中间件内部也会内置 ID 生成器,或者可以集成第三方 ID 生成服务,以实现全局主键:

MyCat

提供了多种主键策略,比如自增 ID、UUID、前缀自增等,但很多团队会结合雪花算法自己生成,然后写到 MyCat 路由。
路由规则可自定义(Groovy 表达式等)。

ShardingSphere

可以配置内置的雪花算法生成器 (Snowflake) 或者你自定义的算法;
在 sharding 配置中指定 keyGenerator 的实现类;
还可以定义分片算法(standard/inline/hint 等),在 inline 中写 t_order_$->{order_id % 4} 之类的表达式。

阿里 DRDS / PolarDB-X

本身也提供全局自增 ID和雪花算法两种模式,可以在控制台上进行配置。

如果使用雪花算法,ID 会不会重复?

只要每台机器的 workerId 唯一,时间戳不回拨,序列号在同毫秒内不溢出,就不会重复。
时间回拨可以通过“等待”或“抛异常重试”来处理。

ID 是不是严格递增?

雪花算法是趋势递增,同一毫秒内生成多个 ID,后者序列号更大,毫秒切换后则跳到新的时间区间。但由于多线程并发或多机并发,全局的顺序性不一定 100% 按数值递增,只有单机单毫秒内是顺序的。
如果要绝对顺序,可以用数据库序列或 ZK 顺序节点,但要考虑性能。

ID 占用多少存储空间?

64 位整型在 MySQL 中一般是 BIGINT (8 字节),相比 32 位INT 多 4 个字节,但是在实际生产中一般都能接受。

修改分库分表策略,ID 取模规则怎么变?

需要迁移数据或使用中间件提供的在线扩容功能;
在早期设计就要考虑可扩容,比如使用 “虚拟桶” 思路(先对 ID 取模 128,再把桶映射到实际库表),扩容时只需改变桶与库表的映射关系,减少大规模数据迁移。

跨机房多活场景,主键还会冲突吗?

如果每个机房都用各自的雪花算法,但机器ID 没做好区分,可能出现冲突;
需要为不同机房分配不同机房ID段,然后再在机房内部用机器ID + 序列号;
或者用统一的中心化 ID 服务(注意多活容灾)

订单表量很大,会不会因为主键太大影响性能?

通常使用雪花算法或递增 ID,都能让主键在InnoDB聚簇索引中大致有序插入,插入性能较好;
如果索引随机性大(如 UUID) 反而会导致数据页分裂、性能下降。

在分布式事务下 ID 是怎么处理的?

通常先拿 ID(本地生成或分布式 ID 服务),再进行插入,这样不依赖全局事务;
避免了“插入时再获取自增ID”,减少锁竞争和分布式事务带来的性能问题

公司订单量爆炸增长,单库性能不足,如何拆分数据库?

先做读写分离,缓解读压力。
如果还是不够,进一步垂直拆分(用户、订单等分不同数据库),再进行水平拆分(根据订单ID或用户ID哈希拆分)。
引入分库分表中间件(MyCat、ShardingSphere)或云厂商 DRDS/PolarDB-X,实现自动路由、主键生成。
同时考虑搜索、统计类场景,可能要引入ES、大数据平台等。

“为什么要用 MyCat / ShardingSphere,而不是自己在应用里写路由逻辑?”

避免业务代码与分库分表策略耦合,后期维护成本高。
中间件往往提供成熟的功能,如全局主键生成、分片路由、读写分离、分布式事务支持等。
便于在线扩容、迁移,减少对应用的改动。

问道 谈到多数据源 / 分库分表 / 负载均衡 / 跨机房 / DNS 分发 ?

业务背景:为什么要分库/分表,为什么要做负载均衡,为什么要跨机房。

技术实现:
分库分表中间件的核心功能(路由、读写分离、主键生成);
负载均衡(LVS/F5/HAProxy)的工作原理和如何部署;
DNS/GSLB 进行跨机房流量调度的思路。

关键挑战:分布式事务、数据一致性、主键冲突、迁移扩容、故障切换、DNS 缓存等。

实践经验:
是否有线上规模案例?遇到哪些坑,比如 SQL 分片错误、热点数据、跨机房延迟等。
如何进行监控与报警,比如 metrics、日志、慢 SQL。

扩展思考:
会不会继续往微服务、消息队列、大数据分析方向演进?如何结合 Spring Cloud 或云平台服务?

项目实践或线上真实数据(QPS、TPS、库规模、节点数、机房分布)

深挖底层原理,可以谈一些LVS 的四层转发机制 (DR/NAT 模式)、F5 处理 SSL 卸载、DB 中间件如何做 SQL 解析和执行计划等底层知识。

场景设计,你可以尝试描述如何保证当机房A挂了以后,机房B能够无缝顶上,涉及到数据同步、应用配置、DNS 切换、监控报警等全链路内容。


http://www.kler.cn/a/525517.html

相关文章:

  • 蓝牙技术在物联网中的应用有哪些
  • 网站如何正式上线(运维详解)
  • 四.3 Redis 五大数据类型/结构的详细说明/详细使用( hash 哈希表数据类型详解和使用)
  • 免杀国内主流杀软的恶意样本分析
  • RPC是什么?和HTTP区别?
  • 51单片机开发:定时器中断
  • 3.目录操作
  • 软件工程概论试题二
  • “深入浅出”系列之算法篇:(5)AIGC
  • 面试经典150题——图的广度优先搜索
  • 保姆级讲解 python之zip()方法实现矩阵行列转置
  • 【Leetcode 热题 100】32. 最长有效括号
  • 深入探讨:服务器如何响应前端请求及后端如何查看前端提交的数据
  • 大模型知识蒸馏技术(2)——蒸馏技术发展简史
  • vscode软件操作界面UI布局@各个功能区域划分及其名称称呼
  • 留学生scratch计算机haskell函数ocaml编程ruby语言prolog作业VB
  • Java实现.env文件读取敏感数据
  • Flutter 新春第一弹,Dart 宏功能推进暂停,后续专注定制数据处理支持
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.26 统计圣殿:从描述统计到推断检验
  • 安卓(android)订餐菜单【Android移动开发基础案例教程(第2版)黑马程序员】
  • arkts bridge使用示例
  • [Python学习日记-80] 用 socket 实现文件传输功能(上传下载)
  • 设计模式 - 行为模式_Template Method Pattern模板方法模式在数据处理中的应用
  • C#方法作用
  • Java基础知识总结(二十八)--可变参数(...)、静态导入、枚举
  • JMeter插件 Arrivals Thread Group 源码解析:实现原理与性能测试中的应用