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

背景信息

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

2

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

  • 主库和备库一般提供两个不同的访问地址,应用程序端需要选择使用哪一个,对应用有侵入。
  • MySQL的复制是异步的,即使是半同步也没办法做到100%强同步,因此备库的数据并不是最新的而是有延迟的,无法保证查询的一致性。

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

3

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

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

  • 最终一致性PolarDB采用了异步物理复制方式在主库和只读库间做数据同步。主库的数据更新后,相关的更新会应用到只读库,具体的延迟时间与写入压力有关(一般在毫秒级别),通过异步复制的方式实现主库和只读库之间的最终数据一致。
  • 会话读一致性:为了解决最终一致性出现的查询不一致问题,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日志位点,即日志序号(Log Sequence Number,简称LSN)。同时每次数据更新时PolarDB会记录此次更新的位点为Session LSN。当有新请求到来时,PolarDB会比较Session LSN和当前各个节点的LSN,仅将请求发往LSN大于或等于Session LSN的节点,从而保证了会话一致性。表面上看该方案可能导致主库压力大,但是因为PolarDB是物理复制,速度极快。在上述场景中,当更新完成后,返回客户端结果时复制就同步在进行,而当下一个读请求到来时,主节点和只读节点之间的数据复制极有可能已经完成。且大多数应用场景都是读多写少,所以经验证在该机制下既保证了会话一致性,又保证了读写分离负载均衡的效果。

PolarDB全局一致性

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

会话间存在的依赖关系

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

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

一致性级别选择最佳实践

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

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

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