高性能架构模式
高性能架构模式
官网:https://shardingsphere.apache.org/index_zh.html
互联网业务兴起之后,海量用户加上海量数据的特点,单个数据库服务器已经难以满足业务需要,必须考虑数据库集群的方式来提升性能。高性能数据库集群的第一种方式是读写分离,第二种方式是数据库分片
Apache ShardingSphere 是一款分布式的数据库生态系统,可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强
Apache ShardingSphere 设计哲学为 Database Plus,旨在构建异构数据库上层的标准和生态。它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。它站在数据库的上层视角,关注它们之间的协作多于数据库自身
相关信息
阿里巴巴开发手册 -> MySQL 数据库 -> 建表规约:
单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表
如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。实际上在数据量比较少的情况下强行进行分库和分表这种方案会让系统性能下降,因为在这个过程中要解决分布式事务的问题,要解决跨库关联的问题以及数据库成本的问题
读写分离架构
读写分离是指将数据库的读操作和写操作分别分配到不同的服务器上,以提高数据库的并发负载能力。主从配置是指使用主服务器(Master)和从服务器(Slave)来实现数据的同步和备份
一般来说,主从配置和读写分离是相结合的,因为这样可以实现以下优点:
主服务器只负责写操作,从服务器只负责读操作,可以减轻主服务器的压力,提高系统性能
从服务器可以作为主服务器的备份,如果主服务器出现故障,可以快速切换到从服务器,保证系统高可用性
从服务器可以根据业务需求进行水平扩展,增加多个从服务器来提供更多的读服务
如图所示,一台 MySQL 主服务器带两台 MySQL 从服务器做数据复制,业务服务器在进行数据库写操作时对主服务器进行操作,在进行数据库读操作时对两台从服务器进行操作,这样会大量减轻对主服务器的压力
![](https://img.sherry4869.com/blog/it/java/intermediate/sharding-sphere/architecture/img.png)
下图展示了根据业务需要,将用户表的写操作和读操路由到不同数据库的方案
![](https://img.sherry4869.com/blog/it/java/intermediate/sharding-sphere/architecture/img_1.png)
CAP 理论
CAP 定理(CAP theorem)又被称作布鲁尔定理(Brewer's theorem),是加州大学伯克利分校的计算机科学家埃里克·布鲁尔(Eric Brewer)在 2000 年的 ACM PODC 上提出的一个猜想。对于设计分布式系统的架构师来说,CAP 是必须掌握的理论
在一个分布式系统中,当涉及读写操作时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(Partition Tolerance)三者中的两个,另外一个必须被牺牲
C 一致性(Consistency):等同于所有节点访问同一份最新的数据副本。在某个写操作完成之后的任何读操作都必须返回该写操作写入的值,或者再之后的写操作写入的值。即各个数据备份的数据内容要保持一致且都为最新数据,强调的是数据正确
A 可用性(Availability):等同于每次请求都能获取到非错误的响应,但是不保证获取的数据为最新数据。非故障的节点(不能宕机)在合理的时间内返回合理地响应(不是错误和超时的响应)。对访问本系统的客户的一种承诺:我一定会给你返回数据,但不保证数据最新,强调的是不出错
P 分区容忍性(Partition Tolerance):等同于系统在网络分区或消息丢失时,仍能继续运行。由于分布式系统通过网络进行通信,网络是不可靠的。当任意数量的消息丢失或延迟到达时,系统仍会继续提供服务,不会挂掉。对访问本系统的客户端的一种承诺:系统会一直运行,哪怕是给用户返回一个响应超时的错误提示信息,或者是旧的数据。强调的是不挂掉
如果从服务器设置为只读模式,那么它在复制完数据之前是不能被写入的,但是可以被读取。如果从服务器设置为读写模式,那么它在复制完数据之前既可以被写入也可以被读取,但是可能会造成数据不一致的问题
网络分区或故障会导致分布式系统中的节点之间无法通信或通信延迟,这就会影响系统的一致性和可用性。例如:如果一个节点向 CA 申请证书,但是网络分区导致 CA 无法收到申请或者无法及时回复,那么这个节点就无法获取证书,也就无法访问其他服务。这就降低了系统的可用性;另一方面,如果一个节点向 CA 申请撤销证书,但是网络分区导致其他节点无法及时更新证书状态,那么这些节点就可能接收到已经失效的证书,这就破坏了系统数据的一致性
假设有一个网上银行系统,它使用 CA 架构来保证用户和服务器之间的安全通信。用户需要向 CA 申请数字证书,然后用这个证书来登录银行网站进行转账、查询等操作。如果发生网络分区或故障,可能会出现以下影响系统的一致性和可用性的情况:
用户无法向 CA 申请或更新证书,导致无法登录或操作
CA 无法向用户或服务器发送证书撤销列表(CRL),导致无法及时识别已经失效的证书
用户或服务器收到了过期或被篡改的证书,导致信息泄露或被攻击
总结:分区容忍性(P)是分布式系统的基本要求,因为网络分区是一种不可避免的错误,如果不考虑分区容忍性(P),那么 CA 架构的系统就相当于传统的单机数据库,无法实现数据的水平扩展
CP
CP 架构是指在 CAP 架构中,选择保证一致性和分区容错性的系统。这种系统牺牲了可用性,即在网络分区或故障的情况下,可能无法对外提供服务。CP 架构适合对数据一致性要求高的场景:例如银行转账、电商订单等
一个典型的 CP 架构的例子是 Zookeeper,它作为微服务注册中心时,保证了数据的一致性,但是在网络故障时可能无法正常工作
如下图所示,为了保证数据一致性,当发生网络分区现象后,N1 节点上的数据已经更新到 y,但由于 N1 和 N2 之间的复制通道中断,数据 y 无法同步到 N2,N2 节点上的数据还是 x。这时客户端 C 访问 N2 时,N2 需要返回 Error,提示客户端 C:“系统故障中”,如果要用户访问系统时就绝对要返回一致的数据话,只能给用户返回错误信息,这种处理方式违背了可用性的要求,因此 CAP 三者只能满足 CP
![](https://img.sherry4869.com/blog/it/java/intermediate/sharding-sphere/architecture/img_2.png)
AP
AP 架构是指在分布式系统中,优先保证可用性和分区容错性,而牺牲一致性的架构。AP 架构适合那些对数据一致性要求不高,但对服务可用性要求高的场景,例如社交网络等。AP 架构的优点是可以提高系统的可扩展性、容错性和响应速度,缺点是可能导致数据不一致或丢失
AP 架构的例子是 Eureka,它是一个基于 REST 的服务注册和发现组件,用于构建微服务架构。Eureka 在设计时就放弃了强一致性,而选择了最终一致性,这样可以提高系统的可用性和容错性。Eureka 还提供了自我保护机制,当网络分区发生时,它不会剔除注册表中的服务实例,而是保持当前状态,以防止误删
如下图所示,为了保证可用性,当发生分区现象后,N1 节点上的数据已经更新到 y,但由于 N1 和 N2 之间的复制通道中断,数据 y 无法同步到 N2,N2 节点上的数据还是 x。这时客户端 C 访问 N2 时,N2 将当前自己拥有的数据 x 返回给客户端 C 了,而实际上当前最新的数据已经是 y 了,这就不满足一致性的要求了,因此 CAP 三者只能满足 AP。注意:这里 N2 节点返回 x,虽然不是一个“正确”的结果,但是一个“合理”的结果,因为 x 是旧的数据,并不是一个错乱的值,只是不是最新的数据而已
![](https://img.sherry4869.com/blog/it/java/intermediate/sharding-sphere/architecture/img_3.png)
相关信息
Zookeeper 和 Eureka 都是服务注册和发现的组件,但是它们有以下区别:
Zookeeper 遵循 CP 原则,即保证数据的一致性和分区容错性,但牺牲了可用性。Eureka 遵循 AP 原则,即保证服务的可用性和分区容错性,但牺牲了一致性
Zookeeper 在网络分区时会剔除不可达的节点,导致注册服务不可用。Eureka 在网络分区时会保留所有节点,启动自我保护机制,防止误删
Zookeeper 使用 ZAB 协议来同步数据,需要半数以上节点正常工作才能提供服务。Eureka 使用异步复制来同步数据,每个节点都可以独立提供服务
Zookeeper 适合对数据一致性要求高的场景,例如配置中心、分布式锁等。Eureka 适合对服务可用性要求高的场景,例如微服务架构、负载均衡等
BASE 理论
BASE 理论是由 eBay 工程师提出,是对可用性和一致性的权衡。BASE 是由 Basically Available(基本可用),Soft State(软状态),和 Eventually Consistent(最终一致性)三个短语的缩写。BASE 是对 CAP 中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于 CAP 定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)
举例来说,电商网站的购物车功能就可以使用 BASE 理论来实现。用户在不同的设备上添加或删除购物车中的商品时,可能会看到不同的结果,但这并不影响用户的基本使用体验。购物车中的数据会在后台进行异步同步,最终保证用户在结算时能够看到正确的商品信息
基本可用
指的是在分布式系统出现故障或者网络延迟时,系统仍然可以提供基本的服务功能,但是可能会降低部分性能或者舍弃部分非核心功能。例如,在电商网站中,如果出现了服务器故障或者网络拥塞,那么用户仍然可以浏览商品和下单购买,但是可能会无法查看评论或者推荐等非核心功能
软状态
指的是在分布式系统中允许数据存在中间状态,并且该中间状态不会影响系统整体可用性。例如在电商网站中,如果用户修改了收货地址或者取消了订单,那么这些数据可能会在不同的副本之间存在不一致的情况(即软状态),但是这些不一致并不会影响用户继续使用网站的其他功能。再例如微信发朋友圈,只有我自己看到,其余人隔了 1 分钟后才看到
最终一致性
指的是在分布式系统中,在经过一段时间或者经过一定条件后,所有副本之间能够达到数据一致的状态。例如,在电商网站中,如果用户修改了收货地址或者取消了订单(即软状态),那么这些数据最终会通过某种机制(如异步通知、定时同步等)使得所有副本都更新为最新的值(即最终一致性)
数据一致性模式
强一致性
强一致性是指分布式系统中所有节点在同一时间看到的数据是相同的,也就是说,更新操作成功并返回客户端后,所有节点的数据完全一致。这种一致性模式要求复制是同步的,即主库在写入成功后需要等待从库的确认,确保从库已经收到写入操作
强一致性可以保证数据的准确性和可靠性,但是会降低系统的可用性和吞吐量,因为每次写入操作都需要等待所有节点的响应。强一致性适合对数据敏感度高、实时性要求强的场景,例如银行转账、电商支付等
两阶段提交、三阶段提交和 Paxos 算法都是分布式系统中实现强一致性的协议,它们的主要区别如下:
两阶段提交(2PC)的流程分为两个阶段:第一阶段是协调者向所有参与者发送事务内容,并等待参与者的响应;第二阶段是协调者根据参与者的反馈决定是否执行事务提交,并通知所有参与者
一个例子是:假设有一个分布式系统,其中有一个协调者节点 C 和三个参与者节点 A、B、D 要执行一个转账事务。那么 2PC 的流程如下:
第一阶段:C 向 A、B、D 发送转账事务的内容,并询问它们是否可以执行该事务。A、B、D 在收到请求后,各自执行转账操作,并将结果返回给 C
第二阶段:C 在收到所有节点的响应后,如果都是成功的,则向 A、B、D 发送提交消息,让它们正式提交转账操作;如果有任何一个失败的,则向 A、B、D 发送回滚消息,让它们撤销转账操作
优点:简单易实现,保证了强一致性
缺点:同步阻塞,性能低;单点故障,协调者挂了会导致参与者处于不确定状态;网络分区时无法保证一致性
场景:适用于对数据一致性要求高、事务执行时间短、参与者数量少的场合
三阶段提交(3PC)的流程分为三个阶段:第一阶段是协调者向所有参与者发送事务内容,并等待参与者的响应;第二阶段是协调者在收到所有参与者的同意后,再次向所有参与者发送预提交消息,并等待参与者的确认;第三阶段是协调者根据参与者的反馈决定是否执行事务提交,并通知所有参与者
一个例子是:假设有一个分布式系统,其中有一个协调者节点 C 和三个参与者节点 A、B、D 要执行一个转账事务。那么 3PC 的流程如下:
第一阶段:C 向 A、B、D 发送转账事务的内容,并询问它们是否可以执行该事务。A、B、D 在收到请求后,各自准备好转账操作,并将结果返回给 C
第二阶段:C 在收到所有节点的同意后,向 A、B、D 发送预提交消息,并等待它们的确认。A、B、D 在收到预提交消息后,锁定资源并准备提交或回滚操作,并将确认结果返回给 C
第三阶段:C 在收到所有节点的确认后,如果都是成功的,则向 A、B、D 发送提交消息,让它们正式提交转账操作;如果有任何一个失败或超时的,则向 A、B、D 发送回滚消息,让它们撤销转账操作
优点:相比 2PC,减少了同步阻塞时间,提高了性能;避免了单点故障导致的不确定状态
缺点:仍然存在网络分区时无法保证一致性的问题
场景:适用于对数据一致性要求高、事务执行时间短、参与者数量少且网络稳定的场合
Paxos 算法是一种基于消息传递和投票机制来达成共识(即选择某个值) 的分布式一致性算法。可以在存在部分节点故障或消息丢失的情况下达成一致。Paxos 算法涉及三种角色:提议人(Proposer)、接受人(Acceptor)和学习人(Learner)。Paxos 算法可以分为两个过程:Prepare 过程和 Accept 过程
一个例子是:在某个分布式系统中有五个节点 P1-P5,在这些节点中要选出领导人(Leader)。那么 Paxos 算法可以描述如下:
Prepare 过程
P1 作为提议人发起一个编号为 N 的提案,向其他节点发送 Prepare(N) 消息,请求它们承认自己是领导人
其他节点收到 Prepare(N) 消息后,如果 N 大于它们之前看到的任何提案编号,则回复 Promise(N),表示承诺不再接受任何编号小于 N 的提案;否则,拒绝回复
P1 收到多数节点的 Promise(N) 消息后,进入 Accept 过程;否则放弃或重新发起一个新的提案
Accept 过程
P1 向所有承诺了自己的节点发送 Accept(N,V) 消息,其中 V 是自己作为领导人的值
其他节点收到 Accept(N,V) 消息后,如果没有承诺过比 N 更大的提案编号,则回复 Accepted(N,V),表示接受了该提案;否则,拒绝回复
P1 收到多数节点的 Accepted(N,V) 消息后,完成共识,并通知所有学习者该值;否则放弃或重新发起一个新的提案
如果有多个提议人,那么可能会出现冲突的情况,即不同的提议人发起了不同编号或者不同值的提案。Paxos 算法通过以下机制来解决这个问题:
提议人在发起提案时,必须保证每次使用的编号都是唯一且递增的
接受人在回复 Promise 或 Accepted 消息时,必须遵守以下规则:
如果收到了编号更大的 Prepare 消息,那么拒绝回复之前承诺过的任何编号更小的 Accept 消息
如果收到了编号相同但值不同的 Accept 消息,那么只接受第一个收到的 Accept 消息
提议人在收到多数节点的 Promise 消息后,必须选择其中最大编号且已经被接受过的值作为自己提案的值;如果没有这样的值则可以任意选择一个值
优点:容错性强,可以在任意多数节点正常工作时保证一致性;可扩展性好,可以动态增加或删除节点
缺点:复杂难懂,实现困难;效率低,在网络延迟大或消息冲突多时需要多次重试才能达成共识
场景:适用于对数据一致性要求高、事务执行时间长、参与者数量多且网络不稳定的场合
弱一致性
弱一致性是指系统中的某个数据被更新后,后续对该数据的读取操作可能得到更新后的值,也可能是更新前的值。弱一致性根据不同的业务场景,又可以分解为更细分的模型,不同一致性模型又有不同的应用场景。以下是一些常见的弱一致性细分模型
指的是一个进程从一个副本中写入数据后,再从同一个副本中读取数据,必须能够读到刚才写入的数据。它保证了一个用户在提交了一个更新后,可以立刻读到自己的更新。它不会对其他用户的写入做出承诺,其他用户的更新可能稍等才会看到
例如在社交网络中,当你修改了个人资料或发表了动态后,你希望能够立即看到自己做出的改变。但其他人可能需要等待几秒或几分钟才能看到你更新后的信息
还有一个关于数据库中使用读写一致性的例子是 MySQL 的主从复制。在这种架构中,主数据库负责写入操作,从数据库负责读取操作。主数据库会将写入的数据同步到从数据库,从而保证数据的一致性。但是如果用户在主数据库上更新了数据,然后立刻去从数据库上读取,可能会发现数据还没有同步过来。这就是弱一致性的问题
为了解决这个问题,可以使用读写一致性的模型,让用户在提交了更新后,可以立刻从主数据库上读取自己的更新。这样就保证了用户自己视角的数据一致性。读写一致性的缺点是会影响系统性能和资源消耗,因为每次写入都需要通知所有节点或者中间件来保证读取最新数据
读写一致性的两种常见方案:
一种是让所有请求(读写)都走一遍日志复制(Log replication)这样可以保证所有节点都有相同的数据,但是会降低系统性能和可用性
另一种是使用 ReadIndex 的方法。ReadIndex 是一种实现线性一致读的机制,它的原理是这样的:
当主数据库收到一个读请求时,它会记录当前已提交的索引为 ReadIndex,并将这个 ReadIndex 发送给从数据库
从数据库收到 ReadIndex 后,会回复主数据库自己是否已经应用了这个索引
主数据库等待多数从数据库的回复,如果都已经应用了 ReadIndex ,那么主数据库就认为自己还是有效的主数据库,并且可以处理读请求
主数据库等待自己的应用索引达到 ReadIndex ,然后从状态机中读取最新数据,并返回给客户端
这样就保证了客户端可以读到最新的数据,并且不会受到主从切换的影响。这种模型可以保证每个副本都有最新的数据,但是也会增加网络通信和同步的开销,适用于对实时性要求高,需要保持用户自己视角的数据一致性的场景,以及实时性要求较高的场景,如电商库存、订单
单调读的具体使用场景是指在分布式系统中,需要保证同一个用户或进程在多次读取数据时,不会遇到时光倒流的情况
一个例子是邮件系统,当用户收到了一封邮件后,就不能再看到这封邮件之前收到的邮件
如果一个用户先从一个副本中读取了某个值,然后又从另一个副本中读取了同一个值,那么他应该看到相同或更新的值,而不是更旧的值。这样可以保证用户看到的数据是单调递增的,不会出现逻辑上的错误或混乱。例如,在电商网站中,当你浏览了某个商品后,你希望在下次浏览时能够看到相同或更高的价格;但如果价格降低了,你可能会感觉被欺骗了
实现单调读一致性的一种方式是确保每个客户端(或进程)都只从一个副本读取数据,这样就可以避免读到不同副本上的不同版本的数据。另一种方式是在每次写入数据时,都附上一个时间戳或者递增的序号,然后在每次读取数据时都检查这个标识符是否比之前读到得更大,如果不是则拒绝接受这个数据
单调读的优点是保证了数据的顺序和逻辑性,缺点是需要记录每个节点的读取历史和版本号
这种模型可以保证每个进程看到的数据是单调递增的,但是也会导致不同进程看到不同版本的数据,适用于对顺序要求高的场景,比如消息队列、日志等。这样可以创建很多从库,并将读请求分散到所有的从库上去,减小主库的负载,并允许向最近的节点发送读请求。但是这只适用于异步复制,如果尝试同步复制,则单个节点故障将使整个系统无法写入
单调写一致性是一种以客户端为中心的一致性模型,它要求同一个客户端(或进程)的写操作在所有副本上都以同样的顺序执行,即保证客户端的写操作是串行的
举例来说,如果一个用户在游戏中杀死了两个怪物,分别获得了 100 和 200 经验值,那么他在任何副本上查看自己的经验值时,都应该是 300 或者更高。如果不满足单调写一致性,有可能出现用户在某个副本上看到自己的经验值只有 100 或者 200 ,这会影响用户的体验和信任
实现单调写一致性的一种方式是确保每个客户端(或进程)都有一个唯一的标识符,例如时间戳或者递增的序号,然后在每次写入数据时都附上这个标识符。这样所有副本就可以根据这个标识符来判断数据的更新顺序,并且拒绝接受比当前版本更旧的数据
单调写的优点是可以避免时光倒流的现象,即先前写入较新的数据,后续读取不会得到更旧的数据。单调写也可以保证 FIFO 一致性,即先发生的写操作先被其他客户端看到。缺点是可能会降低系统的吞吐量和可用性,因为需要在多个副本之间同步数据和顺序。单调写也不能保证强一致性或因果一致性,即不同客户端之间可能看到不同版本或顺序的数据
单调写一致性适用于那些对数据更新顺序敏感,但不需要实时反馈给其他用户的场景。例如在社交网络中,用户修改了自己的个人信息或者发布了新的动态,他希望能够立即看到自己的修改或者发布结果,但不介意其他用户稍后才能看到
会话一致性是一种分布式系统的一致性模型,它保证同一个会话内,后面的请求一定能够看到此前更新所产生版本的数据或者比这个版本更新的数据。也就是说执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值
会话一致性适用于需要保证“读己之所写”的场景,例如在线购物、社交网络、电子邮件等这些场景中,客户端对自己刚刚执行的操作有实时反馈的需求,但不要求其他客户端也能立即看到最新数据
会话一致性可以通过以下几种方式实现:
使用客户端存储方式:Cookie、JWT 等来做登录状态验证
使用公共 Session 存储服务:Redis 或者 Memcached 等存储 Session
使用 nginx-sticky-module 来保持会话
使用会话令牌来跟踪数据版本
指的是如果两个操作之间存在因果关系(例如先写后读),那么所有进程都必须按照因果关系来观察它们;如果两个操作之间没有因果关系(例如并发写),那么所有进程可以以任意顺序来观察它们
因果一致性是指捕获系统中操作之间的因果关系的模型,并保证每个过程可以以共同的因果顺序观察那些因果相关的操作。也就是说,如果一个操作 A 在因果上先于另一个操作 B ,那么所有节点都应该按照 A -> B 的顺序看到这两个操作。但如果两个操作没有因果关系,也就是并发的,那么不同节点可以看到不同的顺序
举个例子,如果用户 A 在社交网络上发了一条动态,然后用户 B 对这条动态进行了评论。那么这两个操作就有因果关系,用户 A 的动态必须先于用户 B 的评论被其他用户看到。但如果用户 C 和用户 D 同时对这条动态进行了评论,那么这两个评论就没有因果关系,不同用户可以看到不同的评论顺序
因果一致性的实现方法是在每个数据对象上附加一个版本向量,记录该对象被哪些节点修改过,以及每个节点对该对象的最新版本号。这样就可以根据因果关系来判断哪些数据更新应该被可见,哪些应该被隐藏
这种模型可以保证有依赖关系的操作被正确执行,但是也会牺牲部分并发性能,适用于对逻辑正确性要求高,不需要全局写入顺序,但需要保持用户之间交互的顺序的场景,比如社交网络,论坛等
数据库分片架构
读写分离的问题:读写分离分散了数据库读写操作的压力,但没有分散存储压力,为了满足业务数据存储的需求,就需要将存储分散到多台数据库服务器上
数据分片:将存放在单一数据库中的数据分散地存放至多个数据库或表中,以达到提升性能瓶颈以及可用性的效果。数据分片的有效手段是对关系型数据库进行分库和分表。数据分片的拆分方式又分为垂直分片和水平分片
垂直分片
按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用。垂直分库和垂直分表是垂直分片的两种具体方式,它们都是按照业务维度进行拆分,但有不同的粒度和目的
垂直分库
垂直分库是将一个数据库中的多个表按照业务模块划分到不同的数据库中,每个数据库专门负责一个业务模块。例如,一个电商系统可以将用户相关的表放在一个数据库里,订单相关的表放在一个数据库里
用户相关的表包括:用户基本信息表,用户账户信息表,用户登录日志表等等
商品相关的表包括:商品类别表,商品表等等
订单相关的表包括:订单数据表,支付记录表等等
在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将压力分散至不同的数据库
![](https://img.sherry4869.com/blog/it/java/intermediate/sharding-sphere/architecture/img_4.png)
下图展示了根据业务需要,将用户表和订单表垂直分片到不同的数据库的方案:
![](https://img.sherry4869.com/blog/it/java/intermediate/sharding-sphere/architecture/img_5.png)
垂直分库的优点是:
可以减少数据冗余,提高数据一致性
可以根据业务特点选择合适的数据库类型和配置
可以降低单个数据库的负载,提高性能和稳定性
垂直分库的缺点是:
增加了跨库查询和事务处理的复杂度
难以实现负载均衡,可能存在某些业务模块访问量过大而导致瓶颈
难以进行水平扩展,当某个业务模块数据量过大时,需要再次进行拆分
垂直拆分可以缓解数据量和访问量带来的问题,但无法根治。如果垂直拆分之后,表中的数据量依然超过单节点所能承载的阈值,则需要水平分片来进一步处理
垂直分表
单表数据量过大有两种情况,第一种就是数据记录不多,但磁盘空间的占用比较大,有可能是单条记录的数据内容比较多:表中的列很多/表中有好多的字段都存储了特别多的内容
垂直分表是将一个表中的多个字段按照使用频率或长度划分到不同的表中,每个表只存储部分字段。例如,一个用户表可以将基本信息字段放在一张主表里,将扩展信息字段放在一张副表里
适用场景举例:
当数据表中有很多字段,但只有部分字段经常被访问时,可以将这些字段拆分到一个单独的表中,减少数据传输量和内存占用
当数据表中有很多字段,但只有部分字段需要高并发访问时,可以将这些字段拆分到一个单独的表中,提高查询效率和响应速度
当数据表中有很多字段,但只有部分字段需要高安全性保障时,可以将这些字段拆分到一个单独的表中,增强数据保密性和隔离性
假设我们开发一个婚恋网站,用户在筛选其他用户的时候,主要是用 age 和 sex 两个字段进行查询,而 nickname 和 description 两个字段主要用于展示,一般不会在业务查询中用到。description 本身又比较长,因此我们可以将这两个字段独立到另外一张表中,这样在查询 age 和 sex 时,就能带来一定的性能提升
垂直分表引入的复杂性主要体现在表操作的数量要增加。例如:原来只要一次查询就可以获取 name、age、sex、nickname、description,现在需要两次查询,一次查询获取 name、age、sex,另外一次查询获取 nickname、description
![](https://img.sherry4869.com/blog/it/java/intermediate/sharding-sphere/architecture/img_6.png)
垂直分表的优点是:
可以减少单条记录占用空间,提高查询效率和缓存命中率
可以避免跨页问题,MySQL底层是通过数据页存储的,一条记录占用空间过大会导致跨页读取
可以方便地进行水平扩展,在水平切分时可以只对主表进行切割
垂直分表的缺点是:
增加了跨表查询和事务处理的复杂度
需要维护主副表之间的关联关系
需要修改原有代码逻辑来适应新的数据结构
水平分片
水平分片又称为横向拆分。相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分
水平分库和水平分表是两种数据库优化的方法,它们都是根据数据的内在逻辑关系,将同一个表或库按照不同的条件分散到多个数据库或多个表中,从而减少单个表或库的数据量,提高查询效率,实现分布式存储
水平分表
水平分表是指将一个表的数据(按数据行)分到多个同一个数据库的多张表中,每个表只有这个表的部分数据。水平分表可以小幅提升性能,它仅仅作为水平分库的一个补充优化。水平分表适用于单张表过大导致查询慢或索引失效的场景
单表切分为多表后,新的表即使在同一个数据库服务器中,也可能带来可观的性能提升,如果性能能够满足业务要求,可以不拆分到多台数据库服务器,毕竟业务分库也会引入很多复杂性
假设你有一个用户表 user,里面有很多字段,如 id,name,age,gender,email 等。这个表的数据量非常大,导致查询很慢。如果你采用水平分表的方法,你可以根据 id 的范围将 user 表切分成多个子表,比如 user_1 存储 id 为 1-1000 的用户数据,user_2 存储 id 为 1001-2000 的用户数据,以此类推。这样每个子表的数据量就变小了,查询也会更快。但是这些子表都在同一个数据库中
水平分表的优点是可以减少单张表扫描行数和索引大小,缺点是需要解决跨表联合查询、主键冲突等问题
水平分库
水平分库是指将单张表的数据切分到多个服务器上去,每个服务器具有相应的库与表,只是表中数据集合不同。水平分库能够有效地缓解单机和单库的性能瓶颈和压力,突破网络IO、连接数、硬件资源等的限制。水平分库适用于数据量极大且持续增长的场景,例如电商订单、社交动态等
假设你有一个用户表 user,里面有很多字段,如 id,name,age,gender,email 等。这个表的数据量非常大,导致查询很慢。如果你采用水平分库的方法,你可以根据 id 的模数将 user 表切分成多个子库,并部署在不同的服务器上。比如 user_0 存储 id 取模 2 为 1 的用户数据,并部署在服务器 A 上;user_1 存储 id 取模 2 为 0 的用户数据,并部署在服务器 B 上;以此类推。这样每个子库的数据量也变小了,并且可以利用多台服务器的资源和网络带宽来提高并发能力
水平分库的优点是可以扩展存储容量和提升并发能力,缺点是需要解决跨库事务一致性、数据迁移、负载均衡等问题
![](https://img.sherry4869.com/blog/it/java/intermediate/sharding-sphere/architecture/img_7.png)
读写分离和数据分片架构
下图展现了将数据分片与读写分离一同使用时,应用程序与数据库集群之间的复杂拓扑关系
![](https://img.sherry4869.com/blog/it/java/intermediate/sharding-sphere/architecture/img_8.png)
程序代码封装
程序代码封装指在代码中抽象一个数据访问层(或中间层封装),实现读写操作分离和数据库服务器连接的管理
![以读写分离为例](https://img.sherry4869.com/blog/it/java/intermediate/sharding-sphere/architecture/img_9.png)
中间件封装
中间件封装指的是独立一套系统出来,实现读写操作分离和数据库服务器连接的管理。对于业务服务器来说,访问中间件和访问数据库没有区别,在业务服务器看来,中间件就是一个数据库服务器
![以读写分离为例](https://img.sherry4869.com/blog/it/java/intermediate/sharding-sphere/architecture/img_10.png)
常用解决方案
Apache ShardingSphere(程序级别和中间件级别)
MyCat(数据库中间件)