主从延迟读写不一致解决方案分析

news2024/11/29 12:48:04

具体业务场景方案分析

问题背景:

虽然强制走写库,避免了主从延迟带来的读库数据不一致问题,但是增加了写库的QPS,带来了巨大压力,所以通过限流来保护db,但这样会降低QPS。

业务上暂时不清楚,所以现在从技术层面讨论读库能分担压力的可行方案。

结论:

改走读库,但是

采用降低主从延迟 来尽量保证读的数据为最新

并使用 redis缓存标记法 ,来识别 是否走写库

问题分析:

走读库,无法避免的是数据不一致问题,对于这个问题,可以从两个方面并行解决:

具体方案选型参考下文

1.降低主从延迟

主从延迟降的越低,读的时候越不容易读不到最新数据。

降低的方案在下文详细介绍,具体方案分析需要结合业务来分析梳理,这里先不重要讨论

2.走从库,但兜底主库逻辑——即redis缓存标记法

这里重点讨论这个。

这个时候我们主要解决的问题是两个:

  1. 缓存 与写库db  数据一致性的问题
  2. 缓存的数据 与 读库db 的数据哪个是最新的判断问题

redis缓存标记法具体方案分析:

步骤:

写操作 :写redis、更新master

读操作:读redis,读slave

方案:

Aredis上记录标记格式(key=业务代号:数据库:表:主键ID; value = 过期时间)过期时间:预估的主库和从库同步延迟的时间

Bredis上记录标记格式(key=业务代号:数据库:表:主键ID; value = 更新时间)

Credis上记录标记格式(key=业务代号:数据库:表:主键ID; value = 自增ID)

方案A

由于过期时间:预估的主库和从库同步延迟的时间,这个无法准确预估,而现在我们是希望能准确判断读到的数据是否是延迟,因此不考虑。

方案B

对于方案B,我们在redis记录的是这条数据最新更新的时间Tredis_up,而数据库中数据更新的时候会生成数据更新的时间,那么通过比对slave 查出的 数据更新 Tslave_up

之间的大小,就可以判断 是否需要再走写库。

但这里有两个问题,先写redis,还是先写mysql,下面分别分析

B1 先写redis,后写mysql

具体操作:写redis的时候java生成当前的时间Tredis_up写进redis ,然后再更新数据库,这个时候数据库中的更新时间得用Tredis_up

分析:此操作可以保证redis中记录的就是最新的数据,防止写入redis失败的情况。

希望通过Tredis_u==Tslave_up来判断是否延迟,

即认为Tredis_u==Tslave_up代表没有延迟

数据一致性问题:此时其实无法通过判断Tslave_up ==Tredis_u来判断是否有延迟,是否需要走写库,

因为Tslave_up==Tredis_up 时候,可能是延迟(比如高并发场景,Tredis_up值和master数据库中的最新值不一样),也可能没有延迟(理想情况)

B2先写mysql,后写redis

具体操作:先更新数据库,再写redis的时候用这条数据的更新时间

分析:此操作不可以保证redis中记录的就是最新的数据,不能避免写入redis失败的情况。

希望通过Tredis_u==Tslave_up来判断是否延迟,

即认为Tredis_u==Tslave_up代表没有延迟

数据一致性问题:此时其实无法通过判断Tslave_up == Tredis_u来判断是否有延迟,是否需要走写库,

因为 Tslave_up==Tredis_up 时候,可能是延迟(比如redis更新晚了,或者更新失败),也可能没有延迟(理想情况)

方案C

以上方案的问题在于:

无法保证db,缓存数据一致性(就不用谈redis数据和slave数据的比较)

那么就有了方案C,可以设置value的值为自增版本号:versionId,同时存到这数据的字段中,那么就可以比对slave中的versionId和redis中的versionId是否相等即可。

写库具体操作:

1.先通过redis为这条数据生成一个自增字段versionId,并存下来

2.更新db数据,并写入versionId

3.比较新的versionId是否 大于原有的versionId——解决线程不安全问题、并发问题

4.如果新的versionId大于原有的versionId,那就顺利更新,否则重新在redis中生成版本号versionId,并更新

读库:

1.读slave

2.读redis

3.比较versionId:相等ok;不相等就再走写库

tip:这里需要注意一点的是我们的value不能是时间,而应该是redis生成的一个分布式自增Id

这样既解决redis与db数据一致性问题,也可以保证redis中value的值 与 读库db 的数据哪个是最新的判断问题。

问题保障:

1.redis生成分布式自增id——保证分布式环境下id不冲突

2.redis的单线程——保证单机高并发下线程安全

3.自增id——保证redis保存最新id

带来的问题:

1.由于引入了redis,所以吞吐量会降低一点,——但是考虑到大流量场景下,从库分担压力 带来效益会高于 redis引入带来的负面影响

其他待优化:~~~~~~~~~~~

综上,方案C可行,可以保证缓存,db一致性,且缓存的数据为最新的。因为版本号递增。

——————————————————————————————

主从延迟问题

在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。

这里补充实际延迟情况:比如多少QPS~延迟多久

1)、MySQL数据库主从同步延迟原理mysql主从同步原理:主库针对写操作,顺序写binlog,从库单线程去主库顺序读”写操作的binlog”,从库取到binlog在本地原样执行(随机写),来保证主从数据逻辑上一致。mysql的主从复制都是单线程的操作,主库对所有DDL和DML产生binlog,binlog是顺序写,所以效率很高,slave的Slave_IO_Running线程到主库取日志,效率比较高,下一步,问题来了,slave的Slave_SQL_Running线程将主库的DDL和DML操作在slave实施。DML和DDL的IO操作是随即的,不是顺序的,成本高很多,还可能可slave上的其他查询产生lock争用,由于Slave_SQL_Running也是单线程的,所以一个DDL卡主了,需要执行10分钟,那么所有之后的DDL会等待这个DDL执行完才会继续执行,这就导致了延时。有朋友会问:“主库上那个相同的DDL也需要执行10分,为什么slave会延时?”,答案是master可以并发,Slave_SQL_Running线程却不可以。

2)、MySQL数据库主从同步延迟是怎么产生的?当主库的TPS并发较高时,产生的DDL数量超过slave一个sql线程所能承受的范围,那么延时就产生了,当然还有就是可能与slave的大型query语句产生了锁等待。首要原因:数据库在业务上读写压力太大,CPU计算负荷大,网卡负荷大,硬盘随机IO太高次要原因:读写binlog带来的性能影响,网络传输延迟。

延迟的原因:

1、内存配置过小或者 iops 配置(这个指的是 io capacity,sas 盘和 ssd 盘配置有区别)不当。

2、主库 TPS 过高,主库写入频繁,从库压力跟不上导致延时

产生的DDL数量超过slave一个sql线程所能承受的范围

4、主库执行大事务导致延迟
比如在主库执行一个大的 update、delete、insert … select 的事务操作,产生大量的 binlog 传送到只读节点,只读节点需要花费与主库相同的时间来完成该事务操作,进而导致了只读节点的延迟。

解决方法:
拆分大事务:增加缓存,异步写入数据库,减少直接对db的大量写入,减少大事务

5.与slave的大型query语句产生了锁等待

数据库在业务上读写压力太大,CPU计算负荷大,网卡负荷大,硬盘随机IO太高次要原因:读写binlog带来的性能影响,网络传输延迟。

6、其它情况,如对无主键表的删除。
用户在删除数据的时候,由于表主键的缺少,同时删除条件没有索引,或者删除的条件过滤性极差,导致 slave 出现 hang 住,会严重的影响生产环境的稳定性。

只读实例出现延迟后的排查思路

  • 看只读节点 IOPS 定位是否存在资源瓶颈
  • 看只读节点的 binlog 增长量定位是否存在大事务
  • 看只读节点的 comdml 性能指标,对比主节点的 comdml 定位是否是主库写入压力过高导致
  • 看只读节点 show full processlist,判断是否有 Waiting for table metadata lock 和 alter,repair,create 等 ddl 操作。

主从延迟的解决方案:

1)、架构方面

1.业务的持久化层的实现采用分库架构,mysql服务可平行扩展,分散压力。

2.单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库。

3.服务的基础架构在业务和mysql之间加入memcache或者redis的cache层。降低mysql的读压力。

4.不同业务的mysql物理上放在不同机器,分散压力。

5.使用比主库更好的硬件设备作为slave总结,mysql压力小,延迟自然会变小。

2)、硬件方面

1.采用好服务器,比如4u比2u性能明显好,2u比1u性能明显好。

2.存储用ssd或者盘阵或者san,提升随机写的性能。

3.主从间保证处在同一个交换机下面,并且是万兆环境。

总结,硬件强劲,延迟自然会变小。一句话,缩小延迟的解决方案就是花钱和花时间。

3)、数据库自身特性

1、sync_binlog在slave端设置为0

2、–logs-slave-updates 从服务器从主服务器接收到的更新不记入它的二进制日志。

3、直接禁用slave端的binlog

4、slave端,如果使用的存储引擎是innodb,innodb_flush_log_at_trx_commit =2

1、sync_binlog=1 oMySQL提供一个sync_binlog参数来控制数据库的binlog刷到磁盘上去。默认,sync_binlog=0,表示MySQL不控制binlog的刷新,由文件系统自己控制它的缓存的刷新。这时候的性能是最好的,但是风险也是最大的。一旦系统Crash,在binlog_cache中的所有binlog信息都会被丢失。

如果sync_binlog>0,表示每sync_binlog次事务提交,MySQL调用文件系统的刷新操作将缓存刷下去。最安全的就是sync_binlog=1了,表示每次事务提交,MySQL都会把binlog刷下去,是最安全但是性能损耗最大的设置。这样的话,在数据库所在的主机操作系统损坏或者突然掉电的情况下,系统才有可能丢失1个事务的数据。但是binlog虽然是顺序IO,但是设置sync_binlog=1,多个事务同时提交,同样很大的影响MySQL和IO性能。虽然可以通过group commit的补丁缓解,但是刷新的频率过高对IO的影响也非常大。

对于高并发事务的系统来说,“sync_binlog”设置为0和设置为1的系统写入性能差距可能高达5倍甚至更多。所以很多MySQL DBA设置的sync_binlog并不是最安全的1,而是2或者是0。这样牺牲一定的一致性,可以获得更高的并发和性能。默认情况下,并不是每次写入时都将binlog与硬盘同步。因此如果操作系统或机器(不仅仅是MySQL服务器)崩溃,有可能binlog中最后的语句丢失了。要想防止这种情况,你可以使用sync_binlog全局变量(1是最安全的值,但也是最慢的),使binlog在每N次binlog写入后与硬盘同步。即使sync_binlog设置为1,出现崩溃时,也有可能表内容和binlog内容之间存在不一致性。

2、innodb_flush_log_at_trx_commit (这个很管用)抱怨Innodb比MyISAM慢 100倍?那么你大概是忘了调整这个值。默认值1的意思是每一次事务提交或事务外的指令都需要把日志写入(flush)硬盘,这是很费时的。特别是使用电池供电缓存(Battery backed up cache)时。设成2对于很多运用,特别是从MyISAM表转过来的是可以的,它的意思是不写入硬盘而是写入系统缓存。日志仍然会每秒flush到硬 盘,所以你一般不会丢失超过1-2秒的更新。设成0会更快一点,但安全方面比较差,即使MySQL挂了也可能会丢失事务的数据。而值2只会在整个操作系统 挂了时才可能丢数据。

5.设置主从同步方式为半同步(得至少一个slave响应) & 并行复制(多线程拉binlog)

缺点:写请求的时延将会增加,吞吐量将会降低,但因为是半同步,不是全同步,所以可以衡量一下影响

4)应用层面

1.大事务拆分成为小事务进行批量提交,这样只读节点就可以迅速的完成事务的执行,不会造成数据的延迟

2.分库分表降低数据库压力

3.mysql中的sql优化、锁优化、事务隔离级别优化,降低压力

读写分离数据不一致解决方案:

A降低主从延时方案

B延时无法改变时的方案:

1.避免插入后就马上读。

插入数据时立马查询可能查不到,重写代码。

2.业务能够接受可以不解决 ,sleep几毫秒再查

3.选择性强制读主

对于需要强一致的场景,我们可以将其的读请求都操作主库

4.借助redis 缓存标记法

1)A发起写请求,更新了主库,但在缓存中设置一个标记,代表此数据已经更新,标记格式(业务代号:数据库:表:主键ID)根据自己业务场景。

2)设置此标记,要加上过期时间,可以为预估的主库和从库同步延迟的时间

3)B发起读请求的时候,先判断此请求的业务在缓存中有没有更新标记

4)如果存在标记,走主库;如果没有走从库。

5.本地缓存标记

1)用户A发起写请求,更新了主库,并在客户端设置标记,过期时间,如:cookies

2)用户A再发起读请求时,带上这个本地标记在后端

3)后端在处理请求时,获取请求传过来的数据,看有没有这个标记(如:cookies)

4)有这个业务标记,走主库;没有走从库。

这个方案就保证了用户A的读请求肯定是数据一致的,而且没有性能问题,因为标记是本地客户端传过去的。

但其他用户在本地客户端是没有这个标记的,他们走的就是从库了。那其他用户不就看不到这个数据了吗?说的对,其他用户是看不到,但看不到的时间很短,过个1~10秒就能够看到。

但这个方案解决了当前用户的数据一致性的问题,如上面举的例子,写文章,然后到文章列表,本用户是能够看到的。其他用户暂时看不到是没有关系的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/26483.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]java毕业设计校园新闻管理系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

转铁蛋白修饰脂质体定制,Transferrin-Liposome

人转铁蛋白(Human Transferrin)主要在肝脏合成,是由位于同源N-端和C-端的两个叶片(Lobe)组成的一种单链糖蛋白。人转铁蛋白共含678个氨基酸残基,等电点为5.9、分子量为76kD。每分子的转铁蛋白能携带2个三价…

Mybatsi从入门到精通、从精通到卸载,这一篇就足够了【中篇】

资料下载 链接: https://pan.baidu.com/s/1i_D3hSkMElUyxBC0OJqRRg?pwdthg4提取码: thg4 简介 MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简…

深度学习入门(四十七)计算机视觉——SSD和YOLO简介

深度学习入门(四十七)计算机视觉——SSD和YOLO简介前言计算机视觉——SSD和YOLO简介课件(单发多框检测SSD)生成锚框SSD模型效果总结课件(YOLO)YOLO(你只看一次)效果前言 核心内容来…

nvm的下载,安装与使用详解

一、安装nodejs方式有很多种 第一种:官网下载  通过nodejs官网下载安装 ,但有个缺陷,不同版本的nodejs无法顺利的切换。 第二种: NVM安装  NVM可以帮助我们快速切换 node版本。 二、 下载nvm安装包 官方下载地址:http://​ h…

力扣(LeetCode)41. 缺失的第一个正数(C++)

类计数排序 小于 111 ,大于 nnn 的数,不会对答案造成影响。所以只要考虑 1——n1——n1——n 的数。 由于题目要求 O(1)O(1)O(1) 空间,参考计数排序思想,利用原数组 numsnumsnums 存储 1——n1——n1——n ,需要将 1…

网易有道三季报解读:转型“有道”,但依旧道阻且长

11月17日晚间,美股上市公司网易有道(以下简称“有道”)发布2022年第三季度财报,营收同比大幅增长,亏损大幅收窄,成绩喜人。有道去年三季度自7月24日“双减”政策出台后被迫开始转型,整整一年过去…

MySQL-Day02 数据库以及数据表的基本操作

目录 一、数据库的基本操作 1.1 创建数据库 1.2 删除数据库 1.3 使用创建的数据库 1.4 扩展知识:InnoDB表 面试题:InnoDB和MyISAM的区别? 二、数据表的基本操作 2.1 创建数据表 MySQL数据类型 2.2 表约束 2.2.1 主键约束 2.2.2 非…

求求了!这份GitHub 70K+的纯手写RabbitMQ 笔记都给我码住好吗!

说到消息中间件,大部分人的第一印象可能是Kafka。毕竟Kafka自问世以来,就顶着高并发,大流量的光环。当然了Kafka也不负众望,在大数据处理方面一直独领风骚。 这里想说说另一款同样优秀的消息中间件RabbitMQ。 选RabbitMQ还是Kaf…

一种多源信息融合方法及其应用(Matlab代码实现)

🍒🍒🍒欢迎关注🌈🌈🌈 📝个人主页:我爱Matlab 👍点赞➕评论➕收藏 养成习惯(一键三连)🌻🌻🌻 🍌希…

基于armv8的kvm实现分析(三)kvm初始化流程

本文基于以下软硬件假定: 架构:AARCH64 内核版本:5.14.0-rc5 1 kvm概述 kvm是基于linux内核实现的一种type 2虚拟化方案,它作为内核的一个模块负责虚拟化环境初始化,虚拟机和虚拟cpu模拟,以及IO捕获与转…

发布适用于 .NET 7 的 .NET MAUI

我们在六个月前向您介绍了 .NET 多平台应用程序 UI (MAUI),现在我们很高兴地宣布 .NET MAUI 在我们的下一个主要版本 .NET 7 中普遍可用。在此短的时间范围内,我们在 .NET MAUI 中的主要工作是解决您的主要反馈报告、改进 CollectionView 的性能&#xf…

P8842 [传智杯 #4 初赛] 小卡与质数2 垃圾筛

题目: 思路: 首先排除比较每一个比X小的数去看结果,因为一定会tle 然后考虑去和每一个比X小的数去看结果,去判定是否比它小,看起来是优秀了一些,但是 n以内的质数比例大约是1ln(n)\frac{1}{ln(n)}ln(n)1​…

MCE | 线粒体和能量代谢的关系

线粒体是细胞生命活动的能量工厂,是几乎所有真核生物都存在的一种细胞器。它的主要功能是进行氧化磷酸化 (OXPHOS) 合成 ATP,是糖类、脂肪和氨基酸等物质的最终氧化释放能量的场所。自带“内核”和“核心技术”的线粒体:线粒体内有一套独立的…

JAVA中使用最广泛的本地缓存?Ehcache的自信从何而来3 —— 解析Ehcache的各种集群方案,本地缓存如何变身分布式缓存

大家好,又见面了。 上一篇文章中,我们知晓了如何在项目中通过不同的方式来集成Ehcache并在业务逻辑中进行使用。作为JAVA本地缓存框架综合实力天花板级别的Ehcache,除了在本地缓存方面具有强悍的实力外,还具有一个其它对手所不具…

Metabase学习教程:提问-6

搜索表格和问题 了解如何使用筛选器和自定义表达式在SQL查询和简单问题中进行搜索。在表格中查找单词或短语现在比以往任何时候都容易。 添加过滤器给你的问题可以让你的问题搜索文本轻而易举。您可以使用示例数据库包含在每个Metabase安装中。 在问题中搜索 我们将从单击浏…

linux笔记(8):东山哪吒D1H移植lvgl(HDMI输出)

文章目录1. 下载,修改,编译源码1.1下载源码1.1.1新建一个lvgl目录,在该目录下下载源码1.1.2 在lvgl目录下再建一个myspace/lvgl_demo目录,把参与编译的文件拷贝到本目录1.2 修改源码1.3编译源码2.拷贝到东山哪吒开发板运行3.已移植…

BUUCTF Web 极客大挑战 2019 EasySQL

BUUCTF Web 极客大挑战 2019 EasySQL 文章目录BUUCTF Web 极客大挑战 2019 EasySQL1,输入万能密码:2,输入万能账号首先有点常识:正常SQL语句这样子写:select * from user where username XXX and password XXX&#x…

MySQL函数

函数的理解 什么是函数 函数在计算机语言的使用中贯穿始终,函数的作用是什么呢?它可以把我们经常使用的代码封装起来,需要的时候直接调用即可。这样既 提高了代码效率 ,又 提高了可维护性 。在 SQL 中我们也可以使用函数对检索出…

prompt(1) to win -xss学习

网址 https://prompt.ml/level 0 (闭合) function escape(input) {// warm up// script should be executed without user interactionreturn <input type"text" value" input ">; } 闭合前面的双引号 "><img src1 onerrorprompt(1)…