PolarDB MySQL提供了最终一致性、会话读一致性以及全局一致性三种一致性级别,满足您在不同场景下对一致性级别的要求。

PolarDB架构

PolarDB是一个由多个节点构成的数据库集群,一个主节点,多个读节点。对外默认提供两个地址,一个是集群地址,一个是主地址,推荐使用集群地址,因为它具备读写分离功能可以把所有节点的资源整合到一起对外提供服务。

1

MySQL读写分离解决和引入

用过MySQL的都知道,MySQL的主从复制简单易用,非常流行,通过把主库的Binlog异步地传输到备库并实时应用,一方面可以实现高可用,另一方面备库也可以提供查询,来减轻对主库的压力。

2

虽然备库可以提供查询,但存在两个问题:

一是主库和备库一般提供两个不同的访问地址,应用程序端需要选择使用哪一个,对应用有侵入。

二是MySQL的复制是异步的,即使是半同步也没办法做到100%强同步,因此备库的数据并不是最新的,有延迟,无法保证查询的一致性。

为了解决第一个问题,我们引入了读写分离代理。一般的实现是,代理会伪造成MySQL与应用程序建立好连接,解析发送进来的每一条SQL,如果是UPDATE、DELETE、INSERT、CREATE等写操作则直接发往主库,如果是SELECT则发送到备库。

3

但是第二个问题,延迟导致的查询不一致,还是没有解决,使用时,就不可避免地会遇到备库SELECT查询数据不一致的现象(因为主备有延迟)。MySQL负载低的时候延迟可以控制在5秒内,但当负载很高时,尤其是对大表做DDL(比如加字段)或者大批量插入的时候,延迟会非常严重。

PolarDB最终一致性、会话读一致性以及全局一致性

  • 最终一致性:PolarDB采用异步物理复制方式在主库和只读库间做数据同步, 在主库更新后,相关的更新会apply到只读库,具体的延迟时间与写入压力有关, 一般在毫秒级别, 通过异步复制的方式实现主库和只读库之间的最终数据一致。
  • 会话读一致性:为了解决最终一致性会出现的查询不一致,PolarDB利用自身物理复制速度快的优点,将查询发给已经更新了数据的只读节点,详细原理请参见实现原理
  • 全局一致性:部分应用场景,除了会话内部有逻辑上的因果依赖关系,会话之间也有依赖关系,会话读一致性无法满足需求,就需要使用全局一致性

PolarDB读写分离的会话读一致性

PolarDB是读写分离的架构,传统的读写分离都只提供最终一致性的保证,主从复制延迟会导致从不同节点查询到的结果不同,比如一个会话内连续执行以下查询:
INSERT INTO t1(id, price) VALUES(111, 96);
UPDATE t1 SET price = 100 WHERE id=111;
SELECT price FROM t1;

在读写分离的下,最后一个查询的结果是不确定的,因为读会发到只读库,在执行SELECT时之前的更新是否同步到了只读库时不确定的,因此结果也是不确定的;因为有这个问题,所以就要求应用程序去适应最终一致性,而一般的解决方法是: 将业务做拆分,有高一致性要求的请求直连到主库,可以接受最终一致性的部分走读写分离;显然这样会增加应用开发的负担,还会增大主库的压力,影响读写分离的效果。

为了解决这个问题,在PolarDB中我们提供了会话一致性或者说因果一致性的保证,会话一致性即保证同一个会话内,后面的请求一定能够看到此前更新所产生版本的数据或者比这个版本更新的数据,保证单调性,就很好的解决了上面这个例子里的问题。

实现原理

4

在PolarDB的链路中间层做读写分离的同时,中间层会跟踪各个节点已经应用了的redo日志位点即LSN,同时每次更新时会记录此次更新的位点为Session LSN, 当有新请求到来时我们会比较Session LSN和当前各个节点的LSN,仅将请求发往LSN大于或等于Session LSN的节点,从而保证了会话一致性;表面上看该方案可能导致主库压力大,但是因为PolarDB是物理复制,速度极快,在上述场景中,当更新完成后,返回客户端结果时复制就同步在进行,而当下一个读请求到来时主从极有可能已经完成,然后大多数应用场景都是读多写少,所以经验证在该机制下即保证了会话一致性,也保证了读写分离负载均衡的效果。

PolarDB全局一致性

PolarDB当前的架构是一写多读的分布式架构,该架构下只读节点和读写节点之间存在延迟,会导致一些一致性问题的出现,例如刚执行完的更新,立即查询查不到。为此PolarDB的读写分离模块实现了会话一致的功能,保证同一个会话内的请求能保证因果一致,但是有一些应用场景,除了会话内部有逻辑上的因果依赖关系,会话之间也有依赖关系,那么会话一致就不够用了,因此就需要本文提供的全局一致的功能。

会话间存在的依赖关系

例如使用连接池的场景下,同一个线程的请求有可能通过不同连接发出去,在数据库侧看来这些请求属于不同会话,但是业务逻辑上这些请求有前后依赖关系,那么就需要全局一致来保证正确性。

说明 全局一致行是通过跟踪复制位点来实现的,在主从延迟较高时,可能会导致更多的请求路由到主库,造成主库压力大,同时业务延迟也可能增加,此功能适用于读多写少的场景。

一致性级别选择最佳实践

PolarDB一致性级别越高,集群性能越低, 推荐使用会话一致性,该级别对性能影响很小而且能满足绝大多数应用场景的需求。

对于有不同会话间一致性需求的可以选择以下方案:

  • 使用Hint将特定查询强制发到主库。
    eg: /*FORCE_MASTER*/ select * from user;
  • 选择全局一致性。