深入解析SQL Server高可用镜像实现原理

达芬奇密码2018-08-15 11:48

SQL Server 是windows平台.NET架构下标配数据库解决方案,与Oracle、MySQL共同构成了DB-Engines Ranking的第一阵营,在国内外企业市场中有着广泛的应用。Mirroring是SQL Server 最常用的高可用解决方案,具有自动故障转移,高安全模式下具有数据“零”丢失,对客户端透明等优势,目前AWS、阿里云、腾讯云均采用该技术实现云端SQL Server 高可用部署。今天我们就来聊聊SQL Server 高可用镜像实现原理;


数据副本

镜像技术实现了位于不同物理服务器上的两个SQL Server实例数据同步,在镜像集群中,SQL Server实例具有三种角色;

  • Principal:具有完整的数据副本,对外提供数据库读写服务;
  • Mirror:具有完整的数据副本,本身不提供读写服务,通过接收来自Principal的更新日志实现数据同步,允许创建快照实现报表;
  • Witness: 本身不存储数据,只负责在高安全运行模式下提供自动故障切换的能力,确保两个SQL Server实例只有一个对外提供服务,避免脑裂情况出现;

在镜像集群中,principal和mirror的数据同步是依靠事务日志来实现的,与Oracle和MySQL 不同,SQL Server的事务日志是Database级别的,不是实例级别的,每个Database都单独的事务日志,这也就使得SQL Server的镜像是可以基于Database层面实现。一个SQL Server实例中的两个Database,一个可以作为principal,一个可以作为mirror,分别与其他SQL Server实例组建镜像关系;另外,SQL Server 一个Database只能有一个mirror节点,一个mirror的database不可以再有mirror节点,这点与MySQL级联复制不同;

SQL Server的事务日志是物理记录级别的,记录了对数据库某个页的某行记录(slot)的操作,principal创建镜像后,会启动一个单独的事务日志发送线程,维护一个虚拟的发送队列,然后读取事务日志,将其进行压缩,根据官方公布的测试数据,压缩比不低于12.5%,然后发送给mirror节点,mirror节点接收到以后,会将其写入本地在磁盘上的一个重做队列文件中,然后再通过另外的一个线程异步的方式,从重做队列中获取事务日志,然后分发给应用线程(process unit)进行回放。

不同于MySQL Binlog,由于SQL Server的事务日志在principal上事务执行过程中就已经源源不断的写入(MySQL Binlog仅在事务提交阶段生成),在principal事务执行的同时事务日志就已经传递给了mirror,所以基于事务日志的SQL Server的复制性能会更理想,同时,在mirror回放的过程中,可以基于页(page)级别进行并发更新(redo parallel),在SQL Server的标准版中,仅提供一个线程进行事务日志回放(roll forward),在企业版中,如果当前服务器CPU大于5核,则每4个核,增加一个并行线程,如果低于5个核,则仍然使用单线程回放。同时,基于页的事务日志的并发执行,还有一个好处就是对于同一个页面的更新可以合并刷新,减少脏页刷新数量;


运行模式

镜像集群提供了三种运行模式:

  • 高性能模式:principal与mirror之间数据异步传输,principal上的事务提交无需等待mirror的响应,principal 宕机后,存在数据更新丢失的可能,不支持自动故障转移,可以通过强制服务的方式使得mirror提供服务,适合对数据可靠性要求不高,性能要求较高的业务场景,与MySQL 的异步复制模式,Oracle DataGuard 最大性能模式相近;
  • 不带故障转移的高安全模式:principal上所有的事务提交,都必须要确认该事务涉及的事务日志均已经传送到的mirror上,并写入mirror的重做队列中,持久化 (是否持久化到外存设备还与windows操作系统写入缓存策略相关),mirror返回确认后,才可提交,可以实现principal宕机下数据“零”丢失,不支持自动故障转移,可以通过手动转移或者强制服务方式使得mirror提供服务,与MySQL 5.7 Loss-less replication、Oracle DataGuard 最大可用模式相近;
  • 带故障转移的高安全模式:与不带故障转移的高安全模式相比,增加了witness(见证服务器),可以实现自动的故障转移,通过witness,可以确保只有一个节点成为principal,对外提供服务,实际上witness最重要的一个作用就是选主;


故障转移

镜像集群故障转移最复杂场景就是带见证服务器的支持自动故障转移的高安全模式,所以我们重点讨论该模式下的故障处理流程。

初始状态下,witness、principal和mirror三个节点两两之间均保持长连接会话,现在讨论其中一方连接中断情况:

  • principal 与 witness 连接中断:

    此时witness与 mirror连接正常,触发自动故障恢复流程,principal丢失witness连接会话,如果principal 仍在运行状态,则将状态标记为DISCONNECTED,表示失去与mirror连接,切断所有客户端连接, 停止读写服务,等待故障切换。为了防止网络抖动引起不必要的切换,会话超时默认时间为10秒;witness和mirror将principal标记为不可用,等待mirror上的重做队列中的事务日志回放(roll forward)完成后,mirror成为新的principal,开始对外提供读写服务。

最后mirror会通过后台线程,将未提交的事务回滚(基于binlog的MySQL复制由于binlog是在事务提交阶段生成的,所以不存在事务回滚的阶段)。

从整个故障切换的流程来看,故障切换时间主要包括三个部分:检测到principal宕机的时间、mirror上重做队列中事务日志的回放时间以及回滚未提交事务的时间,其中前面两段时间服务不可用,尤其时重做队列的回放时间,直接决定了服务不可用时间。

  • mirror 与 witness 连接中断:

    此时,principal与witness连接正常,principal状态变为Disconected,表示终止与mirror连接,mirror状态变为suspend,principal不再向mirror发送事务日志,等待mirror重新建立到witness链接后,principal才会恢复与mirror进行数据同步;

  • principal 与 mirror 连接中断: principal与mirror同时保持witness的连接会话,但是principal与mirror之间会话中断,witness会通知mirror,principal依然保持连接状态,不会触发故障切换;此时principal由于保持有witness的连接会话,服务正常;

下面来考虑三方会话两个会话同时中断情况:

  • principal 与所有节点会话中断

    只要mirror与witness会话正常,即可完成正常的故障转移;如果mirror与witness连接也中断,则无法完成,即便是后来mirror与witness的会话优先恢复,则也无法故障切换,因为已然不确定mirror是否拥有全部principal的数据,此时即便principal处于运行状态,也无法提供服务,等待principal与任意节点会话恢复正常,即可恢复读写服务;

  • mirror 与 所有节点会话中断

    不会触发故障切换,principal切入公开运行模式(异步),即不会再向mirror发送事务日志,也不再需要等待mirror的响应,直到mirror重新恢复会话。

  • witness 与 所有节点会话中断

    不会触发故障切换,principal继续提供读写服务,与mirror数据继续同步,镜像集群丧失自动故障转移能力,退化为不带故障转移的高安全模式;

    如果三方会话同时连接中断,则principal无法提供服务,直到principal与任意节点通信恢复正常。

    场景1中,初始状态实例A、B和witness保持会话连接,其后,A实例宕机,失去与其他成员的会话,实例B与witness保持会话,触发故障切换,实例B升级为principal。其后实例B也宕机,服务停止。然后实例A恢复,但是由于此时实例A已经是mirror,不能确定实例A拥有实例B的所有更新数据,所以无法故障切换,最后实例B恢复,服务恢复正常。

    场景2中,实例A和B同时失去了与witness的会话,但是服务已然正常,A与B之间数据继续同步,其后A与B同时宕机,服务停止。最后A恢复服务,与witness恢复会话,A继续提供服务。


透明切换

一个完整的高可用机制除了后端节点的切换选主,还包括发生故障转移后,客户端如何能够快速的连接到冗余节点,继续业务读写。常用的实现,包括Driver层解决方案,例如mongoDB replication set,也有通过二层内的广播方式实现vip,例如keepalived,当然还包括proxy,以及三层DNS的实现。

SQL Server的实现采用了Driver层处理的方式,开发者要实现自动的故障转移,在连接数据库时,除了必须要指定初始节点的IP和端口,还要指定故障转移的节点的IP和端口。客户端首先尝试使用初始节点创建连接,如果初始节点指向的实例当前为principal,则连接会建立成功,可以正常的读写。当发生故障切换时,principal会切断所有已有客户端连接,然后客户端建立到初始节点连接也会失败,通过一定的重试策略失败后,会尝试连接之前指定的故障转移节点,从而实现服务入口的切换。

SQL Server常用的访问接口:OLE DB、ODBC、ADO均支持指定故障转移节点,格式如下:

Server=250.65.43.21,4734; Failover_Partner=250.65.43.22,4734;


集群监控

镜像集群的监控可以通过SQL Server Management stdio启动镜像监视器,或者系统内置的存储过程来实现,监控的主要指标包括:

  • 未发送日志:principal上未发送的日志超过指定的阈值,会在principal上生成一个警告,在高性能模式下,强制服务时可以作为评估principal上事务丢失数量的依据,同样也适用于在高安全模式切换成异步模式状态下(mirror失去连接)
  • 未还原日志:重做队列中的未被应用的事务日志数量(KB),超过阈值,会在mirror上生成一个警告,该值可以作为评估故障转移时间的主要因素。
  • 最早未被发送的事务:principal发送队列中,最早未被发送的事务的至今的时间,单位时分钟,超过阈值,会在principal上生成警告,与未发送日志量一起,从时间维度,衡量高性能模式下和高安全异步模式下,数据丢失数量;
  • 镜像提交开销:高安全模式下,principal上事务从提交到等到mirror响应的时间开销的平均值,如果超过阈值,则在principal上生成一个警告,在同步模式下,该值可以i衡量同步开销;


参考文档:

网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者郭忆授权发布。