【58沈剑架构系列】细聊冗余表数据一致性

本文主要讨论四个问题:

(1)为什么会有冗余表的需求

(2)如何实现冗余表

(3)正反冗余表谁先执行

(4)冗余表如何保证数据的一致性

 

一、需求缘起

互联网很多业务场景的数据量很大,此时数据库架构要进行水平切分,水平切分会有一个patition key,通过patition key的查询能够直接定位到库,但是非patition key上的查询可能就需要扫描多个库了。

例如订单表,业务上对用户和商家都有订单查询需求:

Order(oid, info_detail)

T(buyer_id, seller_id, oid)

如果用buyer_id来分库,seller_id的查询就需要扫描多库。

如果用seller_id来分库,buyer_id的查询就需要扫描多库。

这类需求,为了做到高吞吐量低延时的查询,往往使用“数据冗余”的方式来实现,就是文章标题里说的“冗余表”

T1(buyer_id, seller_id, oid)

T2(seller_id, buyer_id, oid)

同一个数据,冗余两份,一份以buyer_id来分库,满足买家的查询需求;

一份以seller_id来分库,满足卖家的查询需求。

二、冗余表的实现方案

【方法一:服务同步写】


顾名思义,由服务层同步写冗余数据,如上图1-4流程:

(1)业务方调用服务,新增数据

(2)服务先插入T1数据

(3)服务再插入T2数据

(4)服务返回业务方新增数据成功

优点

(1)不复杂,服务层由单次写,变两次写

(2)数据一致性相对较高(因为双写成功才返回)

缺点

(1)请求的处理时间增加(要插入次,时间加倍)

(2)数据仍可能不一致,例如第二步写入T1完成后服务重启,则数据不会写入T2

如果系统对处理时间比较敏感,引出常用的第二种方案

【方法二:服务异步写】


数据的双写并不再由服务来完成,服务层异步发出一个消息,通过消息总线发送给一个专门的数据复制服务来写入冗余数据,如上图1-6流程:

(1)业务方调用服务,新增数据

(2)服务先插入T1数据

(3)服务向消息总线发送一个异步消息(发出即可,不用等返回,通常很快就能完成)

(4)服务返回业务方新增数据成功

(5)消息总线将消息投递给数据同步中心

(6)数据同步中心插入T2数据

优点

(1)请求处理时间短(只插入1次)

缺点

(1)系统的复杂性增加了,多引入了一个组件(消息总线)和一个服务(专用的数据复制服务)

(2)因为返回业务线数据插入成功时,数据还不一定插入到T2中,因此数据有一个不一致时间窗口(这个窗口很短,最终是一致的)

(3)在消息总线丢失消息时,冗余表数据会不一致

如果想解除“数据冗余”对系统的耦合,引出常用的第三种方案

【方法三:线下异步写】


数据的双写不再由服务层来完成,而是由线下的一个服务或者任务来完成,如上图1-6流程:

(1)业务方调用服务,新增数据

(2)服务先插入T1数据

(3)服务返回业务方新增数据成功

(4)数据会被写入到数据库的log中

(5)线下服务或者任务读取数据库的log

(6)线下服务或者任务插入T2数据

优点

(1)数据双写与业务完全解耦

(2)请求处理时间短(只插入1次)

缺点

(1)返回业务线数据插入成功时,数据还不一定插入到T2中,因此数据有一个不一致时间窗口(这个窗口很短,最终是一致的)

(2)数据的一致性依赖于线下服务或者任务的可靠性

上述三种方案各有优缺点,但不管哪种方案,都会面临“究竟先写T1还是先写T2”的问题?这该怎么办呢?

三、究竟先写正表还是反表

对于一个不能保证事务性的操作,一定涉及“哪个任务先做,哪个任务后做”的问题,解决这个问题的方向是:

【如果出现不一致】,谁先做对业务的影响较小,就谁先执行。

以上文的订单生成业务为例,buyer和seller冗余表都需要插入数据:

T1(buyer_id, seller_id, oid)

T2(seller_id, buyer_id, oid)

用户下单时,如果“先插入buyer表T1,再插入seller冗余表T2”,当第一步成功、第二步失败时,出现的业务影响是“买家能看到自己的订单,卖家看不到推送的订单”

相反,如果“先插入seller表T2,再插入buyer冗余表T1”,当第一步成功、第二步失败时,出现的业务影响是“卖家能看到推送的订单,卖家看不到自己的订单”

由于这个生成订单的动作是买家发起的,买家如果看不到订单,会觉得非常奇怪,并且无法支付以推动订单状态的流转,此时即使卖家看到有人下单也是没有意义的。

因此,在此例中,应该先插入buyer表T1,再插入seller表T2。

however,记住结论:如果出现不一致】,谁先做对业务的影响较小,就谁先执行。

四、如何保证数据的一致性

从二节和第三节的讨论可以看到,不管哪种方案,因为两步操作不能保证原子性,总有出现数据不一致的可能,那如何解决呢?

【方法一:线下扫面正反冗余表全部数据】


如上图所示,线下启动一个离线的扫描工具,不停的比对正表T1和反表T2,如果发现数据不一致,就进行补偿修复。

优点

(1)比较简单,开发代价小

(2)线上服务无需修改,修复工具与线上服务解耦

缺点

(1)扫描效率低,会扫描大量的“已经能够保证一致”的数据

(2)由于扫描的数据量大,扫描一轮的时间比较长,即数据如果不一致,不一致的时间窗口比较长

有没有只扫描“可能存在不一致可能性”的数据,而不是每次扫描全部数据,以提高效率的优化方法呢?

【方法二:线下扫描增量数据】


每次只扫描增量的日志数据,就能够极大提高效率,缩短数据不一致的时间窗口,如上图1-4流程所示:

(1)写入正表T1

(2)第一步成功后,写入日志log1

(3)写入反表T2

(4)第二步成功后,写入日志log2

当然,我们还是需要一个离线的扫描工具,不停的比对日志log1和日志log2,如果发现数据不一致,就进行补偿修复

优点

(1)虽比方法一复杂,但仍然是比较简单的

(2)数据扫描效率高,只扫描增量数据

缺点

(1)线上服务略有修改(代价不高,多写了2条日志)

(2)虽然比方法一更实时,但时效性还是不高,不一致窗口取决于扫描的周期

有没有实时检测一致性并进行修复的方法呢?

【方法三:实时线上“消息对”检测】


这次不是写日志了,而是向消息总线发送消息,如上图1-4流程所示:

(1)写入正表T1

(2)第一步成功后,发送消息msg1

(3)写入反表T2

(4)第二步成功后,发送消息msg2

这次不是需要一个周期扫描的离线工具了,而是一个实时订阅消息的服务不停的收消息。

假设正常情况下,msg1和msg2的接收时间应该在3s以内,如果检测服务在收到msg1后没有收到msg2,就尝试检测数据的一致性,不一致时进行补偿修复

优点

(1)效率高

(2)实时性高

缺点

(1)方案比较复杂,上线引入了消息总线这个组件

(2)线下多了一个订阅总线的检测服务

however,技术方案本身就是一个投入产出比的折衷,可以根据业务对一致性的需求程度决定使用哪一种方法。我这边有过好友数据正反表的业务,使用的就是方法二。

【文章转载自微信公众号“架构师之路”】

原文地址:https://www.cnblogs.com/codeon/p/8287570.html

时间: 2024-10-03 23:32:45

【58沈剑架构系列】细聊冗余表数据一致性的相关文章

【58沈剑架构系列】一分钟了解负载均衡的一切

什么是负载均衡 负载均衡(Load Balance)是分布式系统架构设计中必须考虑的因素之一,它通常是指,将请求/数据[均匀]分摊到多个操作单元上执行,负载均衡的关键在于[均匀]. 常见的负载均衡方案 常见互联网分布式架构如上,分为客户端层.反向代理nginx层.站点层.服务层.数据层.可以看到,每一个下游都有多个上游调用,只需要做到,每一个上游都均匀访问每一个下游,就能实现“将请求/数据[均匀]分摊到多个操作单元上执行”. [客户端层->反向代理层]的负载均衡 [客户端层]到[反向代理层]的负

【58沈剑架构系列】缓存架构设计细节二三事

本文主要讨论这么几个问题: (1)“缓存与数据库”需求缘起 (2)“淘汰缓存”还是“更新缓存” (3)缓存和数据库的操作时序 (4)缓存和数据库架构简析   一.需求缘起 场景介绍 缓存是一种提高系统读性能的常见技术,对于读多写少的应用场景,我们经常使用缓存来进行优化. 例如对于用户的余额信息表account(uid, money),业务上的需求是: (1)查询用户的余额,SELECT money FROM account WHERE uid=XXX,占99%的请求 (2)更改用户余额,UPDA

细聊冗余表数据一致性

本文主要讨论四个问题: (1)为什么会有冗余表的需求 (2)如何实现冗余表 (3)正反冗余表谁先执行 (4)冗余表如何保证数据的一致性   一.需求缘起 互联网很多业务场景的数据量很大,此时数据库架构要进行水平切分,水平切分会有一个patition key,通过patition key的查询能够直接定位到库,但是非patition key上的查询可能就需要扫描多个库了. 例如订单表,业务上对用户和商家都有订单查询需求: Order(oid, info_detail) T(buyer_id, se

【58沈剑架构系列】lvs为何不能完全替代DNS轮询

上一篇文章“一分钟了解负载均衡的一切”引起了不少同学的关注,评论中大家争论的比较多的一个技术点是接入层负载均衡技术,部分同学持这样的观点: 1)nginx前端加入lvs和keepalived可以替代“DNS轮询” 2)F5能搞定接入层高可用.扩展性.负载均衡,可以替代“DNS轮询” “DNS轮询”究竟是不是过时的技术,是不是可以被其他方案替代,接入层架构技术演进,是本文将要细致讨论的内容. 一.问题域 nginx.lvs.keepalived.f5.DNS轮询,每每提到这些技术,往往讨论的是接入

【58沈剑架构系列】互联网架构,如何进行容量设计?

一,需求缘起 互联网公司,这样的场景是否似曾相识: 场景一:pm要做一个很大的运营活动,技术老大杀过来,问了两个问题: (1)机器能抗住么? (2)如果扛不住,需要加多少台机器? 场景二:系统设计阶段,技术老大杀过来,又问了两个问题: (1)数据库需要分库么? (2)如果需要分库,需要分几个库? 技术上来说,这些都是系统容量预估的问题,容量设计是架构师必备的技能之一.常见的容量评估包括数据量.并发量.带宽.CPU/MEM/DISK等,今天分享的内容,就以[并发量]为例,看看如何回答好这两个问题.

【58沈剑架构系列】秒杀系统架构优化思路

一.秒杀业务为什么难做 1)im系统,例如qq或者微博,每个人都读自己的数据(好友列表.群列表.个人信息): 2)微博系统,每个人读你关注的人的数据,一个人读多个人的数据: 3)秒杀系统,库存只有一份,所有人会在集中的时间读和写这些数据,多个人读一个数据. 例如:小米手机每周二的秒杀,可能手机只有1万部,但瞬时进入的流量可能是几百几千万. 又例如:12306抢票,票是有限的,库存一份,瞬时流量非常多,都读相同的库存.读写冲突,锁非常严重,这是秒杀业务难的地方.那我们怎么优化秒杀业务的架构呢? 二

【58沈剑架构系列】DB主从一致性架构优化4种方法

需求缘起 大部分互联网的业务都是“读多写少”的场景,数据库层面,读性能往往成为瓶颈.如下图:业界通常采用“一主多从,读写分离,冗余多个读库”的数据库架构来提升数据库的读性能. 这种架构的一个潜在缺点是,业务方有可能读取到并不是最新的旧数据: (1)系统先对DB-master进行了一个写操作,写主库 (2)很短的时间内并发进行了一个读操作,读从库,此时主从同步没有完成,故读取到了一个旧数据 (3)主从同步完成 有没有办法解决或者缓解这类“由于主从延时导致读取到旧数据”的问题呢,这是本文要集中讨论的

【58沈剑架构系列】如何实施异构服务器的负载均衡及过载保护?

零.需求缘起 第一篇文章“一分钟了解负载均衡”和大家share了互联网架构中反向代理层.站点层.服务层.数据层的常用负载均衡方法. 第二篇文章“lvs为何不能完全代替DNS轮询”和大家share了互联网接入层负载均衡需要解决的问题及架构演进. 在这两篇文章中,都强调了“负载均衡是指,将请求/数据[均匀]分摊到多个操作单元上执行,负载均衡的关键在于[均匀]”. 然而,后端的service有可能部署在硬件条件不同的服务器上: 1)如果对标最低配的服务器“均匀”分摊负载,高配的服务器的利用率不足: 2

【58沈剑架构系列】线程数究竟设多少合理

一.需求缘起 Web-Server通常有个配置,最大工作线程数,后端服务一般也有个配置,工作线程池的线程数量,这个线程数的配置不同的业务架构师有不同的经验值,有些业务设置为CPU核数的2倍,有些业务设置为CPU核数的8倍,有些业务设置为CPU核数的32倍. “工作线程数”的设置依据是什么,到底设置为多少能够最大化CPU性能,是本文要讨论的问题. 二.一些共性认知 在进行进一步深入讨论之前,先以提问的方式就一些共性认知达成一致. 提问:工作线程数是不是设置的越大越好? 回答:肯定不是的 1)一来服