电商系统:读写分离那档子事

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 大家好,我是Leo,目前在常州从事Java后端工程师。上篇文章我们介绍了主库,从库,从库延迟,主库挂了,从库谋权篡位那点事情。从上述中延伸了并行复制策略的发展史,切换策略等。今天我们介绍一下读写分离那些问题,主要从概念,目的,单到多的演变,安全性演变以及六个解决方案。

介绍


大家好,我是Leo,目前在常州从事Java后端工程师。上篇文章我们介绍了主库,从库,从库延迟,主库挂了,从库谋权篡位那点事情。从上述中延伸了并行复制策略的发展史,切换策略等。

今天我们介绍一下读写分离那些问题,主要从概念,目的,单到多的演变,安全性演变以及六个解决方案。


思路


根据读者和用户的反馈,画了一个写作思路图。通过此图可以更好的分析出当前文章的写作知识点。可以更快的帮助读者在最短时间内判断是否为有效文章!

image.png

案例


首先就是介绍一下我个人全权负责的电商系统。这个是私活,巴西那边的线上订货平台。

image.png

这个就是客户端,用户的下单界面。

image.png

言归正传,为什么要举这个例子呢? 就是想通过电商系统,来介绍读写分离的问题。以及业务间的考量!根据业务的轻重程序选择相应的解决方案。

读写分离是什么

读写分离就是为了提升性能压力做的一种方案。这种方案的优点与目标就是。主写,从读。专业的库做专业的事情。这样可以提供查询性能,也可以提升写的性能。同时缓解服务器的压力。目的:缓解主库的压力

单到多的裂变

这个大家还是比较熟悉的,以前的单应用时代。因为数据量不大,访问量也不大。往往一台服务器一个数据库就足矣支持日常是使用了。随着大数据时代的到来,用户量,访问量暴增,导致单库无法满足日常需求。所以数据库进行扩展。如下图就是从单库到多库裂变的示例图

image.png

安全性的裂变

随着后面科技的发展。数据的安全性能,也是人家关心的一个重点。所以代理层的概念出来了

image.png

proxy代理层优缺点

  • 有代理层:中间多了一层过滤,查询性能比没有代理层的慢一些,整体结构相对变的复杂一些。对客户端,服务端比较友好,但是对开发团队的能力要求更高。
  • 无代理层:没有代理层,中间直连,查询性能比有代理层略好一些。整体架构简单,排查问题也方便。但是在部署的时候比较繁琐,会出现主备切换,库迁移等问题。都需要动数据库连接配置。

抉择: 剩下的抉择就取决于各个公司的科技实力了!

问题

既然考虑到主从库的问题,那么必要会遇到,主库,从库数据一致性的问题。除了数据同步的一致性问题,还有应用时的一致性问题。

数据同步的一致性问题,前几篇文章我们已经讲过了。这里简单回顾一下,就不做详细介绍了。数据同步主要通过binlog完成。深入到细节的话,可以深挖binlog的三种格式,row格式,statement格式,mixed格式。三种格式各有各的优点。

  1. row:数据比较详细,但是如果数据量非常大的时候比如delete from history 这个时候要记录,删除的每一条记录,所以比较占内存
  2. statement:数据不够详细,容易导致,数据不一致 (索引引起的查询方式的不同,limit取值会不一致)
  3. mixed:上面的融合版。他中间会有一个判断,判断是否会引起数据不一致这个问题,如果是采用row,如果不会采用statement

应用时的一致性问题,最上面的电商的例子。我在管理端添加一个数据的时候,我们肯定是要第一时间查询是否添加成功的。那么我们在数据库中操作的流程是这样的。

  1. 写入数据到主库
  2. 等待主库数据同步到从库

我们在查询的时候是查的从库,所以会有一段时间的不一致问题!我们称之为 过期读 !那么我们如何处理?


解决方案


强制走主库

第一种方式就是强制走主库,强制走主库又分两条路线。不可能全部的请求都打到主库上。所以这里会有一个判断过滤。

这个判断主要会处理看当前的请求是否是及时查看的数据,就比如上述的电商商品一样,添加完商品我们肯定是要查看是否添加成功的。如果是这样的话,那肯定是要强制走主库查询的,

如果是查询一些历史数据,比如说几个月开外的数据,那么肯定不能走主库查询。所以这个就可以不强制走主库。就算晚几秒中看到也是情有可原的。用户还会自动帮我们强刷一下界面呢

sleep方案

这个方案的处理方式就是读从库之前先sleep一下。类似于select sleep(1) 。因为大概的主从库的延迟一般都是1秒,所以我们这里也是给他睡眠1秒。

直接睡一秒严格意义上是影响性能的! 重点来了

我们采用的不是睡完一秒之后,再去从库查询,而且通过前端缓存的方式。以管理端发布商品为例,直接把用户输入的新商品显示在界面上,而不是真的去数据库中查询。等下次刷新的时候也就过了sleep的时间了。主从库的数据也就同步过来了。


自然不是那么简单!

如果主从同步的时间,也就是延迟超过了1秒。那么还会出现过期读的情况

如果查询数据的时间是0.3秒,那么用户还需要等1秒。

判断主从延迟

sleep方案显然是解决不了真实的需求的。但是可以解决大部分场景,对要求比较高的公司还是无法入手。

第一种方式 就是判断主从的延迟情况。前几篇文章我们提到了使用seconds_behind_master 参数,来衡量主从库延迟的长短。

所以,我们在查询的时候可以先判断一下这个参数是否已经等于0。如果不等于0,那就必须等他等于0才可以继续操作。

第二种方式就是判断当前日志的读取位点。这里介绍两个参数。Master_Log_FileRead_Master_Log_Pos  表示的是读到主库的最新位点。以及Relay_Master_Log_FileExec_Master_Log_Pos,表示的是从库执行的最新位点。有了主库的最新位点与从库的最新位点。如果完全相同,就说明已经同步完成了。

第三种方式是对GTID的判断

谈到GTID,我们要聊到它的三个参数,第一个是是否使用协议,第二个是日志的集合,第三个是已经执行完的集合

Auto_Position=1 ,表示这对主从关系使用了 GTID 协议。

Retrieved_Gtid_Set,是从库收到的所有日志的 GTID 集合;

Executed_Gtid_Set,是从库所有已经执行完成的 GTID 集合。

如果集合相同表示同步完成

Semi-sync

seim-sync这个也是MySQL的半同步复制,基于默认的异步复制和完全同步复制之间,它是在master在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个slave收到并写到relay log中才返回给客户端。相对于异步复制,semisync提高了数据的安全性,但是又比完全同步性能好,所以master和slave之间的时间一定要一致,以免造成semisync失败。

这里semi-sync概念来自于 www.linuxidc.com/Linux/2017-…

流程是什么呢

  1. 事务提交的时候,主库会给从库发一个binlog日志
  2. 从库接收到binlog之后,会给主库回一下ACK包,表示已经收到了
  3. 主库收到ACK之后就表示事务完成

也就是说,只要启动了semi-sync。就表示所有给客户端发送过确认的事务,都确保了备库已经收到了这个日志。

semi-synv配置前面关于位点的判断,就能确定在从库上执行的查询请求可以避免过期读。但是无法解决一主多从的问题。

有的小伙伴可能就会问了,为什么

根据上述流程我们讨论一下。主库给从库发送binlog的时候,从库收到就会给主库发ack,主库收到就完成了。那么谁管你是几个从库? 答案不就出来了嘛!

有哪些问题

  1. 一主多从的时候,在某些从库执行查询请求会存在过期读的现象;
  2. 在持续延迟的情况下,可能出现过度等待的问题。(GTID集合的位点与主库的位点永远不一致)

等主库位点方案

理解这种方案,我们要先了解一个指令

select master_pos_wait(file, pos[, timeout]);

这条指令是在从库中执行的。第一个参数 file是文件的名称,第二个参数pos是位置信息,第三个参数是表示这个函数最多可以等待多少秒。

执行完成之后,会返回一个正整数和一些结果。

正整数: 表示从开始应用执行到最后一共执行了多少事务。

结果: 如果执行期间,备库同步线程发生异常,则返回 NULL。如果等待超过 N 秒,就返回 -1。如果刚开始执行的时候,就发现已经执行过这个位置了,则返回 0。

我们可以试想一下这种方法应用之后的流程。

  • 事务执行之后,执行 show master status 获取主库上的file文件和pos位置。
  • 选定一个从库,在从库上执行 select master_pos_wait(file, pos[, timeout]); 查询返回结果
  • 如果返回的结果>0 ,说明从库已经执行过了事务的同步。可以从主库上查询数据
  • 否则就从主库,因为从库此时没有更新数据,要从主库上查。


GTID 方案

既然有了GTID模式,那必然也是有GTID方案。

下面我们就来聊一下这个方案。

这个方案比上一个等主库位点方案做了一些优化性的处理。主要优化在show master status 上。

首先介绍一下指令

select wait_for_executed_gtid_set(gtid_set, 1);
  • 逻辑就是,等待直到这个库执行的事务中包含传入的gtid_set,返回0
  • 否则就是超时返回1

在主库位点方案中,我们要多做一次查询 show master status ,而GTID帮我们省略了这一次查询。

GTID逻辑流程

  1. 事务A更新完成之后,从这个返回包中取这个事务的GTID,我们先记为gtid1
  2. 选定一个从库执行查询语句
  3. 在从库上执行上述的指令select wait_for_executed_gtid_set(gtid_set, 1);
  4. 如果返回0表示,表示从从库上查询数据,否则就从主库中(超时了)。

我们再来分析一下流程中的一些问题。返回包中取GTID,那么如何把GTID放进去呢?

只需要将参数 session_track_gtids 设置为 OWN_GTID,然后通过 API 接口 mysql_session_track_get_first 从返回包解析出 GTID 的值即可。


总结


今天介绍了读写分离的一些相关概念,以及发展史,安全问题。最核心的还是读写分离给我们带来的问题,以及这个问题对应的几种方案的比较!根据特定的业务场景选择合适的方案


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
4月前
|
消息中间件 算法 数据库
架构设计篇问题之商城系统高并发写的问题如何解决
架构设计篇问题之商城系统高并发写的问题如何解决
|
4月前
|
消息中间件 缓存 监控
如何设计一个秒杀系统,(高并发高可用分布式集群)
【7月更文挑战第4天】设计一个高并发、高可用的分布式秒杀系统是一个非常具有挑战性的任务,需要从架构、数据库、缓存、并发控制、降级限流等多个维度进行考虑。
128 1
|
6月前
|
消息中间件 缓存 运维
秒杀系统之高可用
秒杀系统之高可用
50 0
120分布式电商项目 - 读写分离(一主多从的实现)
120分布式电商项目 - 读写分离(一主多从的实现)
52 0
|
运维 中间件 关系型数据库
117分布式电商项目 - 读写分离(方案)
117分布式电商项目 - 读写分离(方案)
58 0
|
存储 缓存 NoSQL
110分布式电商项目 - Redis集群(主从复制)
110分布式电商项目 - Redis集群(主从复制)
69 0
|
消息中间件 缓存 负载均衡
秒杀系统设计:高并发下的架构考虑
随着互联网的快速发展,电商平台上的秒杀活动越来越受欢迎。然而,高并发的情况下,如何保证秒杀系统的稳定性和可扩展性成为一个非常具有挑战性的问题。在本文中,我们将讨论如何设计一个高效、可靠的秒杀系统。
220 1
|
存储 缓存 负载均衡
【秒杀购物商城业务服务】「分布式架构服务」盘点中间件服务的高可用模式及集群技术的方案分析
【秒杀购物商城业务服务】「分布式架构服务」盘点中间件服务的高可用模式及集群技术的方案分析
119 4
【秒杀购物商城业务服务】「分布式架构服务」盘点中间件服务的高可用模式及集群技术的方案分析
|
关系型数据库 MySQL 中间件
Mycat中间件综合部署高可用-读写分离-分库分表(1.6)
Mycat中间件综合部署高可用-读写分离-分库分表(1.6)
134 0
|
缓存 NoSQL 关系型数据库
实现高并发,高可用,分布式支付系统
实现高并发,高可用,分布式支付系统
700 1
实现高并发,高可用,分布式支付系统