单库读写转变读写分离的血泪历程

读写分离原理

读写分离原理:让主数据库(master)处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库(slave)处理SELECT查询操作。
读写分离实现方式:使用主数据的binlog日志,在从数据库再执行一遍

缘由

2020年教育行业的大考

在2020年初一场有新冠病毒引起的肺炎在中国境内流行,同时工厂停摆,学校放假,所有学生全部线上上课,所有的教育系统满负荷提供各种线上教育的支持,我们系统主要提供**平台的线上教育的支持。由于平时系统并不会有太大的并发,所以我们一直采用的使用单库读写的方式,并且在代码层面也没有考虑过读写分离可能出现什么问题。万万没想到这次疫情导致平台的用户直接增长到百万级别,这时候单库读写就不够看了,我们直接就上了6台数据库,做了一主6从的架构。

读写分离代码实现

我们把数据架构搭起来之后,我们突然发现我们的应用层并没有支持读写分离的模式。这时领导把这个艰巨的任务交给了我,我当时也一脸懵逼,这玩意咋写那?我们本身是使用基于thinkPHP的框架,但是没有读写分离的实现代码,我就把THINKPHP的读写分离的代码翻看了一下,发现这也不难啊!然后我就直接动手改代码,按照如下思想
(1)读的时候随机在从库中选取一个库进行连接
(2)把连接放在进程池里,以便下次获取的时候可以直接从连接池中获取
(3)然后把最底层的query方法和insert方法 根据读写分离进行相关读库或者写库的分离实现
半个小时,改代码加测试,很快就实现了。找了同时review了一下就上线了

同事提出质疑

query方法和execute方法混用

有同事提出质疑,你这样的实现方法,对于以前代码不规范的情况,比如query方法使用的insert update delete方法怎么办??? 虽然我当时一阵***飞过,然后查了一下代码发现还真的有人这样用,那只能解决了。
解决思路也很简单,粗暴,代码会很难看,但是当时不可能去一点点改代码,风险和代价都太高了
(1) 在quer方法中截断前6个字符和前4个字符
(2)把字符统一转化成大写
(3)判断字符是否等于 show和select
(4)如果判断成功则直接把调用调整到execte方法

噩梦开始

因为明天就要正式使用留给我的时间并不多,当天晚上经过简单的测试就上线。但是噩梦也就随之而来

事务未处理

上线之后有很多的作业提交不上,用户很快就反馈回来了。我当时吓出一身冷汗,我快速回想作业只改了读写分离的代码,我迅速翻出代码和日志来进行排查,结果发现在提交接口很简单,就是先查一下判断有没有数据就进行提交。我看代码没问题,我就看还有什么东西会影响,随后我发现了在接口查询之前开启了一个事务。然后我就追了下底层代码发现开启事务的时候有可能会在多个数据库上开启。出了问题咋办?改呗!
思路也是简单粗暴
(1)开启事务、回滚和提交事务直接启用主库连接,保证只在主库上启用事务
(2)在执行query的时候检测是否存在事务,如果存在事务那查询语句全都启用主库连接

插入立即查询的接口

上线之后有一个上传资源你的接口,用户说我上传的了怎么显示没上传那,我立刻就去查了日志,发现那条记录是存在的啊。我仔细看了下代码,代码是这样写的 addData 然后在输出的时候getData。然后我结合读写分离的特性发现如果我插入数据马上进行查询发现由于同步数据的问题有可能从库上没有数据这个时候去查就会出现查不到的情况。
出了问题咋办?改呗!
思路也是简单粗暴
分析问题原因:出问题的查询大多数出现在这样的情况下“数据刚插入数据库,立刻从数据库中读取一份数据做判断”这个时候从库中可能没有数据,但是主库中有,
(1)对于“插入之后马上查询”的代码使用主库查询,不使用从库查询了
(2)开启主库查询 M()->connectMaster();
(3)关闭主库查询 M()->disconnectMaster();

离线脚本的雪崩

我们系统对于一些实时性不高,并且处理速度慢的任务全都放到了离线服务,让离线服务去慢慢跑那些慢的数据。但是只从上了读写分离之后,发现离线脚本总是无缘无故的掉线。虽然重启能解决问题但是还是会要解决不是。我仔细翻看代码和日志,发现数据库的进程每次查询完没有释放掉,时间长了就被mysql杀掉了,但是代码里是有释放的代码的。没办法又去追底层了,去到底层一看我明白了,由于原来的单库读写所以固定释放一个就可以了,现在是读写分离,需要遍历数组然后挨个释放。
思路
(1)把数组中的连接全部释放掉

优化

针对插入后立即查询的代码进行优化

代码就这样上线运行了一段时间,但是我们发现对于这种插入完立马进行查询的代码我们需要打补丁的似的发现一处动一处,这让我们处在很不利的条件。
我在思考了整个工作流程和特点整理出来一个针对这种问题的解决方案,思路如下
(1)对于插入时的表名放入一个数组中
(2)在查询的时候检查该数组中是否存在本次查询的临时数组中是否有该字段
(3)存在改字段直接去到主库进行查询
整理之后即为:一次接口请求的进程中,如果一张表进行过更新操作,则改更改以后的针对这张表的查询操作都从主库查询中走。

存在问题

对于接口中的查询同步仍然没有解决

端上在进行添加接口之后,马上进行下次详情接口的请求,这时候仍然可能出现查不出数据的问题,如果大家能有解决问题的方案可以进行讨论

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值