我们接上期,4.3 基于RDMA 的日志传输
PolarDB-SCC 利用单边的RDMA 进行日志传输,减少网络开销节省CPU周期,如下图所示每个只读RO节点有一个日志的缓冲区,RW节点的日志缓冲区的日志数据将始终远程写入到RO节点日志的缓冲区中的相同的偏移量位置,RW节点为每个RO节点分配一个日志写入程序,负责通过RDMA 将日志写入到RO节点缓冲,RO节点会自动在RW节点中注册,然后相关的日志会写入到从节点中。
这里有一个问题,关于消费的问题,如果RO节点跟不上RW节点产生日志的速度,或者RO节点本身无法快速的应用日志,都会导致RW节点的日志传输到 RO节点的缓冲区后,被覆盖,导致日志丢失。所以在此处需要更详细的设计,下图中RW节点上每个日志写入程序都有两个线程局部的变量
Algorithm 1 The log write on the RW node
1: function LogWriteThread( )
2: Initialize( )
3: while True do
4: # wait for the new log
5: while !(#FA8CC4= >= !(#<0G do
6: Wait( )
7: end while
8: # start to remotely write log
9: BC0AC_;B= !(#FA8CC4=
10: 4=3_;B= Min(!(#<0G, BC0AC_;B= +, '3)⇢_!⇢# )
11: # check overwrite
12: if IfBufOverWritten(BC0AC_;B=) then
13: ReInitialize( )
14: continue
15: end if
16: # actually write log to RO node
17: RDMARemoteWriteBuf(BC0AC_;B=, 4=3_;B=)
18: # check overwrite again
19: if IfBufOverWritten(BC0AC_;B=) then
20: ReInitialize( )
21: continue
22: end if
23: # update !(#FA8CC4=
24: !(#FA8CC4= 4=3_;B=
25: # remotely write !(#FA8CC4= to !(#AF_FA8CC4=
26: RDMARemoteWriteWrittenLSN(BC0AC_FA8CC4=)
27: end while
28: end function
Algorithm 2 The log read on the RO node
1: function LogReadThread( )
2: while True do
3: # wait for the new log
4: while !(#<0G_A> >= !(#AF_FA8CC4= do
5: Wait( )
6: end while
7: # start to read log from BC0AC_;B= to 4=3_;B=
8: BC0AC_;B= !(#<0G_A>
9: 4=3_;B= !(#AF_FA8CC4=
10: if IfBufValid(BC0AC_;B=, 4=3_;B=) == False then
11: ;>6_30C0 ReadFromStorage(BC0AC_;B=, 4=3_;B=)
12: else if IfBufOverWritten(BC0AC_;B=) then
13: ;>6_30C0 ReadFromStorage(BC0AC_;B=, 4=3_;B=)
14: else
15: ;>6_30C0 ReadLogBuffer(BC0AC_;B=, 4=3_;B=)
16: if IfBufOverWritten(BC0AC_;B=) then
17: ;>6_30C0 ReadFromStorage(BC0AC_;B=, 4=3_;B=)
18: end if
19: end if
20: # parse the log data and put the parsed data to queue
21: ParseLog(log_data)
22: !(#<0G_A> 4=3_;B=
23: end while
24: end function
上面引入两段代码,在RW节点每个日志的写入有两个本地的线程变量,LSNstart , LSN written,介于 start 和 written 之间的日志说明写入到了从节点,这里有还有一个变量LSN max ,这个maximun LSN 表达已经写入到本地的缓存中最大的LSN数据的位置,基于设定在RW节点上数据已经被记录,介于written 和 max 之间,并且这些应该远程到了RO节点上,然后这些数据会被更新心的位置 包含 start 和 written, 所以RO节点上可以被应用的是 start 和 written 的之间的LSN数据,所以在RO节点上需要将 written和max_ro 之间的日志数据进行比对,看是否完成,并且 LSNstart 和 LSN written 都会被刷新,相应的只读节点可以通过检查是否有新的日志被写入其日志缓冲区。如在RDMA写入期间RW节点失败,只读节点上可能存在部分数据。但是,由于RW节点未更新,那么只读节点将不使用这些日志数据。只读节点将从共享存储中读取相应的日志数据。
上面第一段代码,展示RW节点如何将要换成日志写入到只读节点的日志缓冲区,日志写入程序首先进行初始化,并将他们远程写入到相应的只读节点,一点有新的日志附加到缓冲区,这些日志将复制到只读节点,(第9-10行),在写入日志之前和之后,日志写入程序必须检查日志缓冲区是否被覆盖(第11-22行)。由于它是一个环形缓冲区,它可以通过检查之间的差异是否小于缓冲区大小来判断是否被覆盖(第11行)。如果差异大于缓冲区大小,则表示日志缓冲区已被覆盖。如果是的话,它必须重置并重新启动写入过程(第14行)如果日志写入成功完成,将更新并将其远程写入到相应的只读节点的(第24-26行)。只读节点可以通过检查其来确定是否有新的日志被写入其日志缓冲区。如果在RDMA写入期间RW节点失败,则只读节点上可能存在部分数据。由于RW节点未更新其只读节点将不使用这些日志数据,只读节点将从共享存储中读取相应的日志数据。
上面的代码 2段,说明了只读节点如何从其日志缓冲区读取日志。只读节点维护了已从日志缓冲区或共享存储中读取的全局最大LSN一旦日志读取器开始从日志缓冲区中读取日志,在启动读取过程之前,必须检查相应的日志是否有效(第10行)。这是因为之前的日志将被视为无效。它还检查读取日志之前和之后是否被覆盖(第12-19行)。如果日志缓冲区无效或已被覆盖,它必须从共享云存储中读取恢复日志。读取后,只读节点可以解析被读取的日志并将解析后的日志条目注册到另一个队列进行日志应用程序(第21行)。最后,更新为已读取的最新LSN(第22行)。
4.4 读自写一致性
在读写事务中,必须确保只读节点上的读请求一定要读取与RW节点上发生的同一事务中的更新,这被称为读写一致性。PolarDB-SCC遵循类似的设计以确保读自写一致性。在PolarDB-SCC中,每次写操作都会生成相应的重做日志,并带有唯一的增量LSN。RW节点将LSN返回给代理节点以用于每个写请求。在向RO节点发送后续读请求之前,代理节点必须检查RO节点上的最大已应用LSN,以确定哪些RO节点可以为该读请求提供服务。如果有一些RO节点满足此要求,则代理节点将通过负载均衡器将读请求发送到其中一个RO节点。如果没有可用的节点,则将被阻塞等待LSN被应用于其中一个RO节点,或者在超时期后最终转发到RW节点。PolarDB-SCC为应用程序提供了处理的不同选择。若没有RO节点满足上述条件,代理可以直接将读请求转发给RW节点无需等待。另一个选择是将所有读请求发送到RW节点,而不检查在同一事务中这些读操作之前是否有更新。
4.5 高可用性和恢复
PolarDB-SCC仅通过现有的重做日志进行数据同步,而不对日志方案进行任何更改。基于RDMA的日志传输不会改变现有日志缓冲区的管理。在提交事务之前,PolarDB-SCC仍然按照通常的方式将相应的日志与共享云存储同步,以确保事务的可持久性。PolarDB-SCC对现有的恢复策略没有影响。如果一个RO节点失败,RW节点上对应的日志写入程序将发现与此RO节点的RDMA网络断开,并停止向此RO节点写入日志。一旦此RO节点重新启动并注册到RW节点,该日志写入程序将恢复工作。如果所需的日志不在其日志缓冲区中,则RO节点将从共享云存储中读取。如果RW节点失败,其中一个RO节点将被提升为新的RW节点,遵循与云原生数据库中相同的过程。
4.6 兼容性
这些系统可以按页单位组织物理数据。它们可以在RW节点上维护表/页的最新修改时间戳,甚至直接应用MTT设计,该设计也可应用于键值存储。RW节点可以记录KV对的修改时间戳,可以由RO节点获取。线性Lamport时间戳和基于RDMA的日志传输是更通用的设计,这些设计不是特定于数据库的,相关理论实现可以用于其他数据库或存储系统。
本文分享自 AustinDatabases 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!