文章目录
什么是读写分离?
顾名思义,读写分离主要是为了将对数据库的读写操作分散到不同的数据库节点上,请为提升写性能,大幅度提升读性能。
一般我们会选择一主多从,一个数据库负责写,其他的数据库负责读,主从数据库之间会进行数据同步,以保证数据库中的一致性,符合系统的写少读多的特点。
如何实现读写分离?
实现方案:
- 上面提到的一台主数据库,其他的一台或者多台作为从数据库
- 主从数据库之间的数据实时同步,这个过程也就我们熟悉的主从复制
- 系统将写请求交给主数据库处理,读请求交给从数据库处理
可以在应用和数据中间加一个代理才能。应用程序所有的数据请求都交给代理层处理,代理层负责分离读写请求,将他们路由对应到数据库中。
提供此功能的中间件有MySQL Router(官方, MySQL Proxy 的替代方案)、Atlas(基于 MySQL Proxy)、MaxScale、MyCat
在之前写的项目中,我比较推荐的方法是用sharding-jdbc来帮助我们管理读写请求,减少了都很多的运维成本。
主从复制的原理是什么?
Mysqlbinlog 主要记录了 Mysql 数据库中数据所有的变化,因此我们跟主库的 binlog 日志就能够将主库的数据同步到从库中去。
- 主库将数据库中数据的变化写入到 binlog
- 从库连接主库
- 从库会创建一个 I/O 线程向主库请求更新的 binlog
- 主库会创建一个 binlog dump 线程来发送 binlog,从库的 I/O 线程负责接收
- 从库的 I/O 线程将接收的 binlog 写入到 relay log 中
- 从库的 SQL 线程读取 relay log 同步数据到本地(也就是再执行一次 SQL)
一般看到 binlog 就要想到主从复制。当然,除了主从复制之外,binlog 还能帮助我们实现数据恢复。
常见的同步 Mysql 数据到其他数据源的工具比如阿里开源的 canal 的底层一般也是用 binlog
如何避免主从延迟?
读写分离对于提升数据库的并发非常有效,但是同时也会带来一个问题:主库和从库的数据存在延迟,比如说你写完主库之后,同步到从库是需要一定的时间的,这个时间差就导致了主库和从库的数据不一致,这就是主从同步延迟
入股我们的业务场景无法容忍组从同步的延迟的话,那应该如何避免?
强制将读请求路由到主库处理
很简单的一个道理,既然从库同步需要时间,那么我们直接在主库读取,但是会增加主库的压力,是我了解到使用比较多的方式
比如 Sharding-JDBC
就是采用的这种方案。通过使用 Sharding-JDBC 的 HintManager
分片键值管理器,我们可以强制使用主库。
HintManager hintManager = HintManager.getInstance();
hintManager.setMasterRouteOnly();
// 继续JDBC操作
对于这种方案,你可以将那些必须获取最新数据的读请求都交给主库处理。
延迟读取
这种方法的适用场景就比较少了,原理就是既然从库同步需要时间,那么我们直接延迟之后再读取,虽然这种方式很方便,但是业务里面一般不会出现允许出现这种代码
但是如果你是一些对数据比较敏感的场景,你可以在完成写请求之后,避免立即进行请求操作。比如你支付成功之后,跳转到一个支付成功的页面,当你点击返回之后才返回自己的账户。
什么情况会出现主从延迟?
-
从库硬件性能比主库差:从库接受 binlog 并且写入 relay log 以及执行 SQL 语句的速度会比较慢,导致延迟
解决方式:用更好的机器作为从库或者从库进行性能优化,比如调整参数,增加缓存,使用 SSD
-
从库处理的读请求过多:从库需要同步主库所有的写请求,还要执行读请求,请求过多会占用从库的 CPU、内存和网络资源,影响从库复制的效率
解决方式:
- 引入缓存
- 部署更多的从库,将请求分散,减轻压力
- 引入其他系统来提供查询能力(Hadoop、Elasticsearch)
-
大事务:运行时间比较长,长时间未提交的事务就可以称为大事务。由于大事务执行时间长,并且从库上的大事务会比主库上的大事务花费更多的时间和资源,因此非常容易造成主从延迟
解决方式:解决办法是避免大批量修改数据,尽量分批进行。类似的情况还有执行时间较长的慢 SQL ,实际项目遇到慢 SQL 应该进行优化。
-
从库过多:主库需要将 binlog 同步到所有的从库,如果从库数量太多,会增加同步的时间和开销
解决方式:减少从库的数量,或者将从库分为不同的层级,让上层的从库再同步给下层的从库,减少主库的压力。
-
网络延迟
-
单线程复制:MySQL5.5 及之前,只支持单线程复制。为了优化复制性能,MySQL 5.6 引入了 多线程复制,MySQL 5.7 还进一步完善了多线程复制。
-
复制模式:MySQL 5.5 开始,MySQL 以插件的形式支持 semi-sync 半同步复制。并且,MySQL 5.7 引入了 增强半同步复制 。
分库分表
读写分离主要是应对数据库读并发,没有解决数据库的存储问题
分库分表可以帮我们缓解 Mysql 的存储压力
什么是分库?
垂直分库
就是把单一的数据库暗号业务进行划分,不同的业务使用不同的数据库,进而将一个数据库压力分担到多个数据库
例如:数据库中的用户表,订单表和商品标分别单独拆分用户数据库,订单数据库和商品数据库
水平分库
把同一个表按照一定规则拆分到不同的数据库中,每个库可以位于不同的服务器上
例如:订单表数据量太大,你对订单表进行了水平切分(水平分表),然后将切分后的 2 张订单表分别放在两个不同的数据库。
什么是分表?
分表 就是对单表的数据进行拆分,可以是垂直拆分,也可以是水平拆分。
垂直分表 是对数据表列的拆分,把一张列比较多的表拆分为多张表。
举个例子:我们可以将用户信息表中的一些列单独抽出来作为一个表。
水平分表 是对数据表行的拆分,把一张行比较多的表拆分为多张表,可以解决单一表数据量过大的问题。
举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。
水平拆分只能解决单表数据量大的问题,为了提升性能,我们通常会选择将拆分后的多张表放在不同的数据库中。也就是说,水平分表通常和水平分库同时出现。
什么情况需要分库分表?
1. 数据量过大
当单个表的数据量达到数千万或数亿条记录时,单个数据库实例可能无法有效地处理这些数据。这时需要通过分表(将数据水平切分到多个表)或分库(将数据水平切分到多个数据库实例)来减小单个表或库的负担,提高查询和写入性能。
2. 访问量过高
高并发访问会导致数据库的读写性能下降,甚至引发数据库瓶颈。分库分表可以将数据分布到多个数据库实例上,通过负载均衡来分散访问压力,从而提升系统的整体性能和响应速度。
3. 数据库的 I/O 瓶颈
当数据库服务器的磁盘 I/O 成为瓶颈,无法满足应用需求时,可以通过分库分表,将数据分布到多个服务器上,减少单个服务器的 I/O 压力。
4. 单表操作锁冲突严重
在高并发写入场景下,单表操作可能会出现锁冲突,导致性能下降。分表可以有效减少锁冲突,提升写入性能。
5. 备份和恢复时间过长
当单个数据库实例的数据量过大时,备份和恢复的时间会非常长。分库分表可以将数据分散到多个实例上,缩短单个实例的备份和恢复时间,提高系统的可用性。
6. 单库容量限制
不同的数据库系统对单库的容量有限制,当数据量接近或超过这些限制时,需要考虑分库分表。
7. 地理分布需求
如果应用系统需要跨地域部署,为了减少跨地域的网络延迟和提高访问速度,可以根据地域进行分库分表。
8. 业务逻辑上的隔离需求
有些场景下,业务逻辑上需要对不同的数据进行隔离,例如多租户系统中不同租户的数据需要隔离,这时可以通过分库分表来实现。
不过分库分表的成本太高,非必要不采用
常见的分片算法
哈希分片:求指定分片键的哈希,然后根据哈希值确定数据应被放置在哪个表中。哈希分片比较适合随机读写的场景,不太适合经常需要范围查询的场景。哈希分片可以使每个表的数据分布相对均匀,但对动态伸缩(例如新增一个表或者库)不友好。
范围分片:按照特定的范围区间(比如时间区间、ID 区间)来分配数据,比如 将 id
为 1~299999
的记录分到第一个表, 300000~599999
的分到第二个表。范围分片适合需要经常进行范围查找且数据分布均匀的场景,不太适合随机读写的场景(数据未被分散,容易出现热点数据的问题)。
映射表分片:使用一个单独的表(称为映射表)来存储分片键和分片位置的对应关系。映射表分片策略可以支持任何类型的分片算法,如哈希分片、范围分片等。映射表分片策略是可以灵活地调整分片规则,不需要修改应用程序代码或重新分布数据。不过,这种方式需要维护额外的表,还增加了查询的开销和复杂度。
一致性哈希分片:将哈希空间组织成一个环形结构,将分片键和节点(数据库或表)都映射到这个环上,然后根据顺时针的规则确定数据或请求应该分配到哪个节点上,解决了传统哈希对动态伸缩不友好的问题。
地理位置分片:很多 NewSQL 数据库都支持地理位置分片算法,也就是根据地理位置(如城市、地域)来分配数据。
融合算法分片:灵活组合多种分片算法,比如将哈希分片和范围分片组合。
1. 复杂性增加
问题:分库分表后,系统的架构变得更加复杂,开发和维护的难度增加。
解决方案:使用成熟的分库分表中间件(如ShardingSphere、Mycat等)来管理分库分表策略,简化开发工作。
2. 事务一致性
问题:分库分表后,跨库事务无法使用传统的数据库事务机制,分布式事务的管理变得复杂。
解决方案:使用分布式事务管理框架(如Seata)或者采用最终一致性方案(如消息队列、补偿机制)来保证数据一致性。
3. 分片键选择
问题:选择不当的分片键会导致数据分布不均匀,造成某些数据库负载过重。
解决方案:根据业务特点选择合适的分片键,并定期监控和调整分片策略,保证数据均匀分布。
4. 跨分片查询
问题:涉及多个分片的查询变得复杂且性能可能下降,例如跨分片的聚合查询、排序和分页等操作。
解决方案:尽量避免跨分片查询,必要时可以在应用层进行数据的合并和处理,或者使用分布式查询中间件来优化查询性能。
5. 数据迁移
问题:随着业务的发展,数据分片方案可能需要调整,数据迁移变得复杂且风险较高。
解决方案:设计灵活的分片策略,使用分布式数据迁移工具(如DataX、DTS)来安全高效地进行数据迁移。
6. 全局唯一ID
问题:分库分表后,需要解决全局唯一ID的问题,以避免不同分片间的主键冲突。
解决方案:使用全局唯一ID生成方案,如UUID、Snowflake算法、数据库序列等。
7. 运维成本
问题:分库分表后,数据库实例增多,运维管理的复杂度和成本增加。
解决方案:使用自动化运维工具和平台,提高运维效率,降低人工干预。
8. 数据一致性校验
问题:数据分散在多个库中,数据一致性的校验和修复变得更加困难。
解决方案:定期进行数据一致性校验,使用数据校验和修复工具,保证数据的一致性。
9. 读写分离
问题:分库分表后的读写分离策略需要重新设计和实现。
解决方案:使用读写分离中间件,并结合缓存技术,提升读写性能。
10.跨库聚合查询问题
分库分表会导致常规聚合查询操作,如 group by,order by 等变得异常复杂。这是因为这些操作需要在多个分片上进行数据汇总和排序,而不是在单个数据库上进行。为了实现这些操作,需要编写复杂的业务代码,或者使用中间件来协调分片间的通信和数据传输。这样会增加开发和维护的成本,以及影响查询的性能和可扩展性
推荐方案
Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。
ShardingSphere 项目(包括 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar)是当当捐入 Apache 的,目前主要由京东数科的一些巨佬维护。
ShardingSphere 绝对可以说是当前分库分表的首选!ShardingSphere 的功能完善,除了支持读写分离和分库分表,还提供分布式事务、数据库治理、影子库、数据加密和脱敏等功能。
ShardingSphere 提供的功能如下:
分表分库后,数据怎么迁移呢?
停机迁移
原理
- 停机:系统停机,停止所有新数据写入。
- 全量数据迁移:将所有数据从旧库迁移到新库。
- 配置更新:更新应用程序的数据库配置,指向新库。
- 测试验证:验证数据一致性和系统功能正常。
- 重新上线:重新启动系统。
优点
- 简单易行。
- 数据一致性有保障。
缺点
- 系统不可用时间较长。
- 对大数据量系统不友好。
双写迁移
原理
- 双写改造:应用程序同时向旧库和新库写入数据。
- 全量数据迁移:在系统运行中,将旧库数据迁移到新库。
- 数据校验:校验新旧库数据一致性。
- 切换读库:将读操作切换到新库,停止旧库写入。
- 降级双写:取消旧库写入逻辑,完成迁移。
优点
- 无需停机,系统持续服务。
- 多次校验,数据一致性高。
缺点
- 实现复杂,需要改造应用程序。
- 处理双写中的一致性问题。