今天跟大家分享一下我第一次入职公司发布项目时遇到的一个问题。是数据库读写分离的坑。前言事情是这样的。刚入职的时候接到过这样一个业务需求:每个支付通道在支付失败的时候都会返回一个特定的错误码。业务需要将通道特定的错误代码转换为内部错误代码。这样我们就可以统一向外界返回自己的错误码了。这个要求其实并不难。当时设计的系统架构如下:添加新规则的过程简单分为三步:业务人员通过管理后台对映射规则库进行增删改查,对缓存进行增删改查。这里之所以加缓存是因为每次支付都需要用到这个场景,使用缓存可以避免每次都从数据库中读取,提高读取速度。后续的支付请求业务流程如下:数据库读写分离-用户操作当缓存中的映射规则不存在时,会查询数据库,然后加载到缓存中。如果缓存中的映射规则已经存在,则直接使用缓存中的映射规则。这个业务流程其实比较简单。当时在测试环境测试没有问题,但是在后续发布线上环境的时候遇到了奇怪的问题。“添加新规则后,映射规则有一段时间没有生效,查看日志,发现查询数据库时,没有数据。”这很奇怪。日志显示添加成功,但是查询没有数据。但是过了一会儿,查询又有数据了。检查代码后发现没有问题。第二天上班的时候问了同事才知道问题的原因:原来线上数据库采用主从架构,数据读写分离,数据查询使用从库.数据写入直接在主库上操作,然后同步到从库。“由于数据库同步有延迟,这样会导致在数据同步期间,数据不同步,主从数据不一致,从库查询不到最新的数据。”如果你之前的数据库系统架构是单库或者主备结构,当你第一次切换到数据读写分离架构时,大概率会踩到这个坑。数据库系统架构的开发我们先来了解一下数据库系统架构,最后是如何解决主从同步延迟导致的数据不一致。主备架构业务发展初期,数据访问量较小。这时候我们可以直接采用单库架构。但是,我们一般不会使用上面的架构,因为存在单点问题。如果数据库出现故障,这段时间业务将不可用。除了等待重启,我们没有其他解决办法。所以我们会添加一个备库来实时同步主库的数据。主备架构一旦“主库”出现故障,手动将“主机”踢下线,将“备”改为“主机”继续提供服务。这种架构易于部署和维护,业务开发不需要任何修改。但是,缺点也很明显。备库只有在主库出现问题时才会启用,存在一定的资源浪费。主从架构随着业务的发展,请求量不断增加,数据量也不断增加,业务变得越来越复杂,数据很快就会达到瓶颈。由于大部分业务读多写少,数据库读取最容易成为系统瓶颈。这时候,我们可以提高阅读的表现。这时候我们可以采用的方案是增加slave实例,主从同步,数据读写分离。可以看出这种架构和主从架构没有区别。主要区别在于,在主从架构下,从库和主库一样,需要一直工作。主库提供写服务,从库只提供读服务。如果后续的阅读压力还是太大,我们也可以通过增加从库的数量来横向扩展阅读能力。主从架构虽然帮助我们解决了读取的瓶颈,但是由于主从之间需要进行数据同步,自然存在一定的延迟。在这个延迟窗口期间,从库中读取只能读取到一个旧数据,这就是上述案例中问题的真正原因。接下来,让我们看看如何优化这种情况。主从缓解忍大法第一个解法很简单,没有别人,不管你是谁,不看也没关系。此时业务不需要做任何改造,hello,me,orher~如果业务对数据一致性要求不高,可以采用这种方案。数据同步写入方案主从数据同步方案一般采用异步方式同步到备库。我们可以修改成同步方案,主从同步完成,主库上的写就可以返回了。业务系统发起写操作,向主库写入数据的写请求需要等待主从同步完成后,再将数据返回给读从库。在该方案中,主从同步完成后,即可读取到最新的数据。我们只需要修改数据库之间的同步配置即可,业务层不需要修改,比较简单。“但是,由于主库需要等待主从完成,写请求的延迟会增加,吞吐量会下降。”这对于目前的在线业务来说可能是无法接受的。Selectivemandatoryreadmaster对于需要强一致性的场景,我们可以对所有的读请求都操作master库,这样“读写都在master库”,不会出现不一致的情况。这类方案的业务层需要改造,而且是强制读master,改造难度相对较小。但是这种方案浪费了另外一个数据库,增加了主库的压力。中间件选择路由方式该方案需要使用一个中间件,所有的数据库操作都先发送给中间件,再由中间件分发到相应的数据库。此时流程如下:写请求,中间件会发给主库,记录此时写请求的key(操作表加主键等)。读请求,如果此时key存在,则路由到主库一段时间后(经验值),中间件认为主从同步完成,删除key,后续读会读从库。该方案可以保持数据读写的一致性。但是在系统架构中增加了一个中间件,整体复杂度变高,业务开发变得更加复杂,学习成本也比较高。缓存路由方式和中间件方案的过程类似,但是改造成本比较低,不需要中间件。此时的流程是这样的:向主库发送写请求,同时缓存操作的key。缓存过期时间设置为主从延时读请求。首先判断缓存是否存在。如果主库不存在,说明最近没有发生写操作。从库中读取请求操作的成本比中间件方案要低,但是我们此时引入了一个缓存组件,所有的读写之间会多一些。一步缓存操作。综上所述,我们引入主从架构,数据读写分离。目的是为了解决业务快速发展、请求量增大、并发量增大带来的数据库读取瓶颈。然而,当引入一种新的架构来解决问题时,必然会带来另一个问题。数据库读写分离后,主从延迟会导致数据不一致,为了解决主从延迟和数据不一致的情况,我们可以采用如下解决方案:忍大法数据库同步写方案选择性强读主中间件选择路由方式缓存路由大法以上方案各有优势,当然也有相应的缺点。我们需要根据自己的业务情况选择相应的解决方案。
