《Remus: High Availability via Asychronous Virtual Machine Replication》译文

通过异步虚拟机复制实现高可用性

涉及资料

推测性执行的概念:https://zhuanlan.zhihu.com/p/33145828

Xen的概念:https://baike.baidu.com/item/Xen

系统脏页及页缓存的概念https://juejin.im/post/5d59638c518825291e3dd77f

Hypervisorhttps://zhuanlan.zhihu.com/p/34320333

 

摘要

让应用程序在硬件故障中幸存下来是一项代价很高的工作,通常涉重新设计软件以包含复杂的恢复逻辑以及部署专用硬件。这对提高大型或传统应用程序的可靠性构成了严重的障碍。我们将描述一种通用且透明的高可用性服务的架构,该架构可以保护现有软件,并且不需要修改,防止其运行所在的物理计算机发生故障。 Remus提供了极高的容错能力,当出现故障时,运行中的系统可以无缝切换到备用物理主机上执行,而停机时间只有几秒钟,同时完全保留了网络连接等其他状态连接。我们的方法将受保护的软件封装在虚拟机中,以每秒四十次的频率异步将更改的状态传播到备份主机,并使用推测性执行,使活动的虚拟机状态稍微领先于复制的虚拟机状态。

1 Introduction 介绍

高度可用的系统是非常宽泛的概念。 但是却很难实现可靠性,尤其在资源有限的系统中。 不幸的是,高可用性很难实现–它要求系统由冗余组件构成,这些组件必须能够在发生故障时进行维护并切换到备份。 旨在保护现代服务器的商业高可用性系统通常使用专用硬件,定制软件或同时使用两者(例如[12])。 在每种情况下,透明地解决故障的能力既复杂又昂贵,足以阻止在普通服务器上进行部署

 

本文介绍了Remus,它是一种在普通硬件上提供与操作系统和应用序无关的高可用性的软件系统。我们的方法利用虚拟化功能,实现在物理主机之间热迁移正在运行的虚拟机,并扩展了该技术以非常高的频率(每25ms间隔两次)复制整个正在运行的虚拟机实例的快照。使用此技术,我们的系统将虚拟机的执行离散化为一系列的快照。在复制完成虚拟机系统状态之前,不会释放外部输出,特别是传输的网络数据包。

 

虚拟化可以创建正在运行的计算机的副本,但不能保证该过程的率。同步地传输每一次状态的改变是不现实的,因为复制操作会占用网络设备大量的带宽。不如让一个主机以推测方式执行,然后设置检查点并异步复制其状态。在提交检查点之前,系统状态不会在外部可见,我们通过在过去数十毫秒内有效地运行系统来实现高速复制性能。

本文的贡献是实用的。整个系统复制是一种众所周知的提供高可用的方法。但是,通常认为它比仅复制相关数据的特定于应用程序的检查点技术要昂贵得多[15]。我们的方法可用于将HA作为虚拟机的平台服务“推向大众”。尽管它在操作上受到硬件和软件的限制,但该系统可以提供和商用解决方案同样甚至更好的保护。许多现有系统仅对持久性存储做镜像,要求应用程序从崩溃一致的持久状态中执行恢复。相比之下,Remus确保无论主节点何时发生故障,都不会丢失任何外部可见状态。

 

1.1 Goals 目标

Remus旨在使中低端系统获得高可用性。通过简化设置并允许将多服务器整合到较少的物理主机上,虚拟化使这些系统更受欢迎。但是,合并带来好处的同时增加了出现硬件故障的可能性。 Remus通过将高可用性作为虚拟化平台本身提供的服务进行商品化来解决,为虚拟机的管理员提供了检测风险的工具。

 

Remus的设计基于以下总体目标:

Generality 通用性 定制一个应用程序以支持高可用性可能会非常贵,更不用说组织可能依赖的各种软件。为了解决此问题,应将高可用性作为底层服务提供,并以通用的机制出现,而不关注受保护的应用程序或运行该应用程序的硬件如何。

Transparency 透明度 在许多环境中,可能无法修改操作系统和应程序。为了尽可能支持广泛的应用程序,高可用性不应要求修改OS或应用程序代码以支持诸如故障检测或状态恢复之类的功能。

Seamless failure recovery 无缝故障恢复 在单主机故障的情况下任何外部可见状态都不应丢失。此外,故障恢复应该足够快地进行,从外部用户的角度来看只是暂时的数据包丢失。已建立的TCP连接不应丢失或重置。

 

这些是崇高难以实现的目标,需要提供远远超出普通HA系统所提供保护水平,普通HA系统基于异步存储镜像,随后是特定于某些应用程序的恢复代码。此外,想要在不修改虚拟机内代码的情况下实现这种可用性级别,需要采用非常粗粒度的方法来解决此问题。该系统的最终最终目标是实现这些目标,同时提供可部署的性能水平,即使面对当今服务器硬件上常见的SMP硬件也是如此。

 

1.2 途径

 

Remus运行以active-passive模式运行的成对的服务器中。为了克服该方法有关的困难,我们采用了三种主要技术。 首先,我们基于虚拟化的基础架构来构建我们的系统,以实现整个系统的复制。 第二我们通过推测执行来提高系统性能,该执行将外部输出与同步点解耦。这允许主服务器保持生产效率,而与复制服务器的同步是异步执行的。图1给出了Remus中基本的操作阶段。

 

 

图1:remus中的推测执行和异步操作1:检查点,2:传输,3:同步,4:发布

 

基于VM的整个系统复制 系统管理程序已用于构建HA系统。在这项作中,虚拟化用于锁定步骤地运行一对系统,并且添加了额外的支持以确保一对物理主机上的虚拟机遵循确定的执行路径,为主虚拟机和备份虚拟机注入相同的外部事件,以使其状态相同。强制执行这种操作会出现两个问题。首先,要求它是高度稳定的结构,从而系统对正在执行的指令集和外部事件有相同的结果。其次,在多处理器系统中应用时,这会导致不可接受的开销,在多处理器系统中,必须共享处理器之间的内存来实现精确处理[8]。

 

推测执行 复制可以通过复制系统状态或确定性重新执行输入来实现我们认为后者对于实时操作是不切实际的,尤其是在多处理器环境中。因此,Remus不会尝试使计算确定化,如果将系统回滚到该检查点并重放其输入,则存在很大的可能性,即系统在给定检查点之后产生的输出会有所不同。但是,仅当主输出在外部可见时,才需要将副本的状态与主数据库同步。可以让输出缓冲到更合适的时间,而不是让正常的输出流必须何时发生同步,在同步点之前进行推测性的计算。这样可以在输出延迟和运行时间之间做出有利的权衡,其程度可以由管理员控制。

 

异步复制  在主服务器上缓冲外部输出,使复制可以异步执行。 主务器可以在捕获其计算机状态的那一刻后恢复执行,而无需等待远程备份端的确认。 将正常执行与复制过程重叠会产生实质性的性能优势。 即使间隔数十毫秒的检查点时,也可以进行高效的操作。

 

 

 

2 设计和实现

图2显示了我们系统的高级视图。首先,将要保护的机器封装在虚拟机中。我们的实现基于Xen虚拟机监视器[2],并扩展了Xen对实时热迁移的支持,以提供细粒度的检查点。我们一部分检查点支持的代码已并入Xen源代码。

 

Remus通过对主服务器频繁做检查点,并传播到备份物理主机来实现高可用性。在备份主机上虚拟机映像驻留在内存中,如果检测到主服务器系统的故障,则可以立即开始执行。由于备份仅与主数据库定期保持一致,因此必须缓冲所有网络输出,直到备份上的状态同步为止。当收到完整,一致的主机映像后,此缓冲区才释放给外部客户端。检查点,缓冲区和释放周期非常频繁,我们以高达每秒40次的频率提供基准测试结果,代表每25毫秒包含网络和磁盘状态的整个机器检查点。

 

与传输的网络传输不同,磁盘状态在外部不可见。但是,必须将其为完整且一致的快照传播到远程主机。为了对磁盘进行复制,所有对主磁盘的写操作均会异步传输到备份主机上,然后将其缓存在RAM中,直到相应的内存检查点到达。此时,将向主数据库确认完整的检查点,然后释放出站网络数据,并将缓冲的磁盘写操作写入备份磁盘。

 

值得强调的是,虚拟机直到发生故障才真正在备份主机上执行。它是充当活动虚拟机检查点的容器。这会消耗相对较少的备份主机资源,从而可以以N对1的配置同时保护在多个物理主机上运行的主服务器。这样的配置为管理员提供了高度的自由度,可以在冗余度和资源成本之间取得平衡。

 

  图2:remus:高层体系结构

2.1 故障模型

Remus提供以下特性:

1. 任何单个主机的故障都是可以容忍的。

2. 如果主要主机和备用主机同时发生故障,则受保护系统的数据将处于崩溃一致状态。

3. 在系统状态已提交给副本之前,不会有外部可见的输出

我们的目标是为单个物理主机的故障停止,提供完全透明的恢复。该系统的引人注目的方面是可以轻松地将高可用性改装到在商用硬件上运行的现有软件上。它使用一对商用主机通过冗余千兆位以太网连接,并且可以从任何组件的故障中恢复。通过结合块设备的状态复制协议,它避免了需要昂贵,共享的网络附加存储来存储磁盘映像。

我们的目标不是从软件错误或不间断故障中恢复。正如在[5]中观察到的那样,remus可以提供完整的系统状态捕获和复制,因此会将应用程序错误传播到备份中。这是提供透明度和通用性的必要结果。

 

我们的故障模型与商用HA产品相同,它们为当今的虚拟机器提供了保护[31,30]。但是,这些产品提供的保护程度远小于Remus提供的保护程度:现有的商业产品通过简单地从崩溃一致的磁盘状态重新启动另一台主机上的虚拟机,从而对物理主机的故障做出了响应。我们的方法可以在与实时迁移类似的时间范围内避免故障,并使虚拟机运行并保持网络连接完整。不会丢失外部暴露状态,也不会损坏磁盘。

 

 

2.2 管道检查点

每秒多次对运行中的虚拟机进行检查点,对主机系统提出了极高的要求。 Remus通过流水化检查点操作来解决此问题。我们使用epoch-based的系统,在该系统中,活动的虚拟机受到短暂的暂停,在执行过程中自动捕获更改的状态,并且在状态已传播到备份时释放外部输出。再次参考图1,该过程可以分为四个阶段:

  1. 每个时期暂停一次正在运行的虚拟机,并将任何已更改的状态复制到缓冲区中。这个过程实际上是实时迁移的“stop-and-copy阶段,但是正如本节稍后所述,它已经针对高频检查点进行了优化。将状态更改保留在缓冲区中后,虚拟机便会继续执行。
  2. 缓冲状态被传输并存储在备份主机的内存中。
  3. 备份主机收到完整的状态,检查点就会被告知主数据库。
  4. 释放缓冲的网络输出。

这种方法的结果是在检查点边界的执行有效地离散化了。备份触发对完成的检查点的确认,将释放已缓冲的网络输出,是什么呢?并表示进入新的过渡过程。

 

2.3 Memory and CPU

在Xen现有的机器上实现了检查点,以进行实时迁移[6]。实时迁移是一种技术,通过该技术可以将虚拟机重新定位到另一台物理主机,而只需要短暂的中断服务即可。为此,将内存复制到备份主机,同时虚拟机继续在旧主机运行。在实时迁移过程中,将拦截对内存的写入,并且将脏页整顿复制到备份主机。在指定的时间间隔后,或者由于虚拟机写入内存的速度至少达到迁移过程中复制的速度而未进行任何向前处理时,虚拟机将被挂起,剩余的脏内存将被复制出去以及当前的CPU状态。此时,备份主机上的镜像被激活。总停机时间取决于虚拟机挂起时要复制的剩余内存量,但通常不超过100毫秒。总迁移时间是客户机使用的内存量及其可写工作集有关。

 

Xen提供了使用称为影子页表(shadow page table)的技术来跟踪虚拟机对内存的写入的情况。启用此操作模式后,VMM会维护虚拟机页表的私有(“影子”)版本,并将其公开给硬件MMU。页面保护用于捕获虚拟机对其页面表内部版本的访问,从而允许虚拟机管理程序跟踪更新,并在适当时传播到影子版本

 

对于实时迁移,此技术已扩展为透明地(对虚拟机而言)将所有虚拟机内存标记为只读。然后,管理程序可以捕获虚拟机对内存的所有写操作,并维护自上一轮以来已被弄脏的页面映射。每一轮迁移过程都会自动读取并重置此映射,而迭代迁移过程将逐个追踪脏页面,直到不再产生为止。如上所述,实时迁移过程最终会暂停虚拟机的执行,并进入最后的"stop-and-copy"阶段,在此回传所有剩余的页面,并在目标主机上恢复执行。

 Remus将实现检查点作为实时迁移的最后阶段来重复执行:每个时期,虚拟机都将暂停,而更改的内存和CPU状态将被复制到缓冲区。然后,虚拟机在当前主机上恢复执行而不是在目标主机。确保始终在远程位置提供一致的映像,需要对迁移过程进行一些修改,并提供足够的性能。这些在下面描述。

 

迁移增强 

      在实时迁移中,虚拟机内存会迭代复制好几回,并且可能会消耗几分钟的执行时间。 而由stop-and-copy造成的服务中断造成的损耗则不是很大。 但在捕获频繁的虚拟机检查点时不是这种情况:每个检查点只是迁移的最后进入stop-and-copy阶段,因此这代表了减少检查点开销的优化的关键点。 对Xen检查点代码的检查显示,虚拟机处于挂起状态时所花费的大部分时间都浪费在了调度上,这主要是该守护程序,在虚拟机和domain 0 之间提供管理通信的xenstore守护程序的实现效率低下。

 

Remus通过两种方式优化检查点:

首先,它减少了挂起和恢复虚拟机所需的进程间请求的数量。

其次,它将xenstore完全从挂起/恢复过程中删除。

在原始代码中,当迁移过程希望挂起虚拟机时,它向虚拟机管理守护程序xend发送了一条消息。 Xend依次向xenstore写了一条消息,该消息通过事件通道(虚拟中断)警告虚拟机应暂停执行。虚拟机在挂起之前的最后一个动作是进行hyper callhypercall对域进行了调度,并导致Xen向xenstore发送通知,然后xenstore发送了一个中断到xend,最终将控制权返回给迁移过程。这个复杂的过程要花费的时间无法确定-典型的测量延迟为30到40毫秒,但是在某些情况下,我们看到的延迟长达500毫秒。

       Remus优化的方案通过在虚拟机中创建一个专门用于接收暂停请求的事件通道来简化此过程,迁移过程可以直接调用该通道。此外,提供了新的hypercall,以允许进程注册事件通道以进行回调,以通知其虚拟机挂起完成。总之,这两个通知机制使将虚拟机挂起所需的时间减少到大约100微秒-与以前的实现相比提高了两个或多个数量级。

       除了这些交互方式更改之外,我们还提高了内存复制过程的效率。首先,我们快速从内存扫描中筛选出干净的页面,因为在高频率设置检查点下,大多数内存是不变的。其次,我们在虚拟机开始时将虚拟机的整个物理内存映射到复制过程中,而不是在每个时期都映射和取消映射脏页面-我们发现映射foreign pages与复制它们大约需要相同的时间

 

检查点支持  

      在Xen中提供检查点支持需要对现有的“suspend-to-disk和实时迁移代码两项进行更改。

首先,支持对虚拟机暂停后继续执行。 Xen以前不允许“活动检查点”,而是在将其状态写出后销毁了VM。

其次,将挂起过程由单次变为重复执行,挂起程序转换为守护进程。 这允许在第一个之后的检查点回合仅复制新产生的脏内存。

       支持恢复需要两个基本更改。 第一个是新的hypercall,用于将域再次标记为可调度的(Xen从调度考虑中删除了挂起的域,因为以前它们总是在复制其状态后被销毁)。 为了在xenstore中重新监视,必须执行类似的操作

 

异步传输  

      为了允许虚拟机尽快恢复操作,需要对迁移过程进行修改,将脏的页面复制到暂存缓冲区,而不是在域暂停时将它们直接传递到网络。 这导致吞吐量显着增加:第3.3节中讨论的内核构建基准测试所需的时间在每秒20个检查点时减少了大约10%。

 

外部修改  

      如上所述,Xen中的准虚拟客户机包含一个挂起处理程序,该处理程序会在收到挂起请求后清理设备状态。 除了本节前面所述的通知优化之外,还对挂起请求处理程序进行了修改,以减少在挂起之前完成的工作量。 在原始代码中,挂起要求断开所有设备的连接,并拔出除CPU之外所有的电源。 在修改之后这项工作被推迟到在另一台主机上重新运行之后执行, 这些修改在3.1.0版的Xen中可用。

      这些更改并不能提高正确性,但是它们确实可以显着提高检查点的性能,并且涉及对虚拟机内核的非常底层的修改。 在半虚拟暂挂处理程序中,总更改少于100行代码。 如前所述,对于非半虚拟化的虚拟机,无需进行这些修改。

 

2.4 网络缓冲

不能指望大多数网络提供可靠的数据。因此,网络应用程序必须接受数据包丢失、复制和重新排序,或者使用高级协议,如提供服务保证的TCP。这一事实大大简化了网络缓冲问题:传输的数据包不需要复制,因为它们的丢失将显示为暂时的网络故障,并且不会影响受保护状态的正确性。 但是,排队等待传输的数据包要等到生成它们的检查点状态提交给备份为止。 如果主节点失败,则这些生成的数据包也会丢失。

 

图3描述了我们防止推测性网络状态释放的机制。 入站流量会立即传递到受保护的主机,但是将自上一个检查点以来生成的出站数据包将排队,直到当前状态已被检查点备份且站点已确认该检查点为止。 我们已将此缓冲区作为一种Linux排队规则来实现,该规则适用于域中的虚拟机的网络设备,该域响应两个RT-netlink消息。 在允许访客在检查点之后恢复执行之前,网络缓冲区会收到CHECKPOINT消息,这会导致它在出站队列中插入一个障碍,防止释放任何后续数据包,直到收到相应的重新发布消息为止。 备份确认了访客检查点后,缓冲区将收到RELEASE消息,此后它开始将流量从队列中释放

 

在此实现中有两个小问题。首先是在linux中,排队规则只对传出流量起作用。在Xen下,访客网络接口由访客中的前端设备和域0中的相应后端设备组成。来自访客的出站流量在域0中的后端设备上显示为入站流量。因此,为了将流量排队,我们通过称为中间排队设备[16]的特殊设备路由入站流量,将入站流量转换为出站流量。该模块旨在通过iptables [27]在IP层工作,但将其扩展到在我们用于实现VM网络访问的桥接层工作并不困难。

第二点起因是Xen虚拟网络设备的实现。为了提高性能,出站网络流量使用的内存不会在来宾域和域0之间复制,而是共享。但是,一次只能共享少量页面。如果消息仅在短时间内在来宾和域0之间传输,则此限制是不可能的。不幸的是,网络输出缓冲区可能导致消息在运行中持续大量时间,这导致用户网络设备在发送了非常少量的流量之后被阻塞。因此,在对消息进行排队时,我们首先将它们复制到本地内存中,然后将本地映射释放到共享数据

 

2.5磁盘缓冲

磁盘所面临的挑战与网络接口所面临的挑战截然不同,主要是因为磁盘可以提供更强大的可靠性保证。尤其是,当磁盘已确认写入后,即使在确认后立即发生电源故障,应用程序(或文件系统)也有望恢复该数据。尽管Remus旨在从单个主机故障中恢复,但即使两个主机都发生故障,它也必须保持崩溃的一致性。

此外,提供通用系统的目的是,排除使用为HA应用程序设计的昂贵的镜像存储硬件。因此,Remus会在备份主机上维护活动VM磁盘的完整镜像。在使用保护系统之前,主磁盘上磁盘的当前状态会镜像到备份主机。一旦采取了保护措施,对持久性存储的写操作将被跟踪和检查,类似于对内存的更新。图4概述了磁盘复制机制

 

 

与第2.3节中描述的内存复制子系统一样,从活动虚拟机写入磁盘的操作被视为直写(write-through):它们将立即写入于主磁盘映像,并异步镜像到备份虚拟机上的内存缓冲区。这种方法提供了两个直接的好处:首先,它确保活动磁盘映像始终保持崩溃一致。如果两个主机均发生故障,则主磁盘将保存发生故障时外部可见虚拟机的崩溃状态(如果主虚拟机未发生故障,或者备份主机在激活之前也失败,则外部可见状态驻留在主虚拟机上,否则(主虚拟机发生故障,而备注主机未发生故障)它就位于备份主机中)。其次,直接写入磁盘可准确说明物理设备的延时和吞吐量特性。这种显而易见的特性具有相当大的价值:准确地磁盘响应能力是一个微妙的问题,因为我们自己经历了较早版本的磁盘缓冲区,该缓冲区将写请求保存在主虚拟机的内存中,直到提交检查点为止。这种方法要么缓冲写操作,要么不足以表示将数据提交到磁盘所需的时间,并且使虚拟机在执行过程中过于频繁,要么保守地高估了写延迟,从而导致性能损失。模型化磁盘访问时间非常困难[28],我们的实现是通过保留从磁盘到其客户端虚拟机的直接反馈来避免该问题。

 

 

(在备份主机在接收到主虚拟机传输的最新检查点之前,不可以修改当前备份主机磁盘上已存储的旧检查点,若此时备份主机发生故障,最新检查点的传输将失败,而磁盘中的检查点也因为发生修改而不具有完整性)

 

 

 

在备份确认已收到检查点时,磁盘更新将完全存放在内存中。在接收到整个检查点之前,不得更改磁盘状态,因为这将阻碍备份回滚到最新的完整检查点。一旦确认了检查点,就可以将磁盘请求缓冲区应用于磁盘。

如果主虚拟机发生故障(刚好在传输完检查点之后发生故障),备份主机将等到所有缓冲区内容均已写入磁盘后再恢复执行(可以让外部去访问)。尽管备份可以使用请求缓冲区作为物理磁盘上的覆盖立即开始执行,但这会违反呈现给受保护虚拟机的磁盘语义:如果备份在激活后失败,但数据还没有完全刷新到磁盘,则其启动-磁盘状态可能不是崩溃一致的。

 

在任何给定时间,Remus管理的两个磁盘镜像中只有一个实际上是有效的(因为只有一台虚拟机接收外部访问)。从多主机崩溃中恢复时,这一点至关重要。通过在备份磁盘上使用激活记录(activation record)可以实现此属性,该记录是在将最新的磁盘缓冲区完全刷新到磁盘之后且在备份虚拟机开始执行之前写入的。从多个主机故障中恢复时,此记录可用于标识磁盘的有效,和崩溃一致的版本。

 

磁盘缓冲区被实现为Xen的block tap模块[32]。区块分接头是一种设备,它允许特权域(privileged domain)中的进程将其自身有效地置于提供给客户虚拟机的前端访问磁盘设备和实际为请求提供服务的后端处理设备之间。缓冲模块记录来自受保护虚拟机的磁盘写请求,并将它们镜像到备份上的相应模块,该模块执行上述检查点协议,然后在主虚拟机出现故障时,在执行备份之前从磁盘请求路径中删除自身。

 

2.6检测失败

Remus的目标是可以使用普通硬件,以常规和透明的方式提供高可用性,而无需修改受保护的应用程序。当前,我们使用直接集成在检查点流中的简单故障检测器:备份对提交请求的响应超时,主虚拟机将认为备份已崩溃并禁用保护。同样,从主数据库传输新检查点的超时,将导致备份虚拟机假设主虚拟机已崩溃,并从最近的检查点恢复执行。

系统配置为使用一对绑定的网络接口,并且两个物理主机使用保护NIC上的一对以太网交叉电缆(或独立的交换机)进行连接。如果这两个网络路径均失败,则Remus当前不会提供隔离执行的机制。解决分区的传统技术(即仲裁协议)很难在两个主机配置中应用。我们认为,在这种情况下,我们已经将Remus设计为商品硬件所能达到的极限。

 

3 评估

 

设计Remus的主要目的是使高可用性具有足够的通用性和透明性,以便可以将其部署在现在的商品硬件上。在本节中,我们将描述我们的方法针对各种不同的工作负载所产生的消耗,以便回答两个问题:(1)该系统是否可实际部署? (2)哪种工作负载最适合我们的方法?

在评估性能影响之前,我们必须确定系统正常运行。我们可以实现通过在复制协议的每个阶段注入网络故障,同时在受保护的系统上增加大量磁盘,网络和CPU负载。我们发现,在每种情况下,备份将在大约一秒钟内接替丢失的主数据库,并保留所有外部可见的状态,包括活动的网络连接。然后,我们评估非常不同的工作负载上的系统性能开销。我们发现,诸如内核编译之类的通用任务在每秒检查点20次时会导致大约50%的性能损失,而以SPECweb表示的依赖网络的工作负载则以大约四分之一以上的本机速度运行。这种情况下的额外开销主要是由于网络接口上的输出提交延迟。根据此分析,我们得出结论,尽管设计Remus的主要目的是使高可用性具有足够的通用性和透明性,以便可以将其部署在当今的商品硬件上。在本节中,我们将刻画我们的方法针对各种不同的工作负载所产生的间接费用,以便两个回答两个问题:(1)该系统是否可实际部署? (2)哪种工作量最适合我们的方法?在评估性能影响之前,我们必须确定系统正常运行。我们可以实现通过在复制协议的每个阶段注入网络故障,同时在受保护的系统上增加大量磁盘,网络和CPU的负载。我们发现,在每种情况下,备份将在大约一秒钟内接替丢失的主虚拟机,并保留所有外部可见的状态,包括活动的网络连接。然后,我们评估不同的工作负载上的系统性能开销。我们发现,诸如内核编译之类的通用任务在每秒检查点20次时会导致大约50%的性能损失,而以SPECweb表示的依赖网络的工作负载则以大约四分之一以上的本机速度运行。这种情况下的额外开销主要是由于网络接口上的输出提交延迟。

基于此分析,我们得出的结论是,尽管Remus在状态复制方面很有效,但它的确引入了严重的网络延迟,特别是对于在内存写入比较差的局部性应用程序。因此,对网络延迟非常敏感的应用程序可能不太适合这种类型的高可用性服务(尽管有一些优化有可能显著减少网络延迟,我们将在基准测试结果之后更详细地讨论其中一些优化),根据基准测试结果获取更多详细信息。我们进行评估时比较保守,使用基准驱动的工作负载,这比典型的虚拟化系统预期的要密集得多。由于系统负载是可变的,因此这种环境带来的合并机会特别有吸引力。

 

3.1测试环境

 

除非另有说明,否则所有测试均在IBM eS-erver x306服务器上运行,该服务器由一个启用超线程的3.2 GHz Pentium 4核处理器,1 GB RAM,3个Intel e1000 GbE网络接口和80 GB SATA硬盘驱动器组成。 系统管理程序是Xen 3.1.2,按第2.3节中的描述,修改所有虚拟机的操作系统为Xen 3.1.2,linux 2.6.18发行版本。 受保护的VM分配了512 MB的总RAM。 为了最大程度地减少来自VMM的调度影响,将 domain 0的VCPU固定到第一个超线程。 一个物理网络接口桥接到guest虚拟接口并用于应用程序流量,一个物理网络接口用于管理访问,最后一个用于复制(我们没有绑定用于复制的接口,这与我们测试无关)。 虚拟磁盘由SATA驱动器上的磁盘映像提供,并使用tapdisk AIO驱动程序导出到客户机。

 

3.2 正确性验证

 

如第2.2节中所述,Remus的复制协议在四个不同的阶段运行:(1)检查点更改状态并增加网络和磁盘请求流的时期,(2)复制系统状态,(3)完整的内存检查点和 接收到相应的磁盘请求集,从备份发送检查点确认(4)收到确认后,释放在上一个时期排队的出站网络数据包。 为了验证我们的系统是否按预期工作,我们在每个阶段测试了故意导致的网络故障。 对于每个测试,受保护系统都会执行内核编译过程,以生成磁盘,内存和CPU负载。 为了验证网络缓冲区,我们同时执行了一个图形密集型X11客户端(glxgears),该客户端连接到外部X11服务器。 Remus已配置为接受检查点每25毫秒一次。 每个单独的测试重复两次。 在每个故障点,备份都成功接管了受保护系统的执行,而在备份检测到故障并激活复制的系统时,只有微小的网络延迟(大约一秒钟)才被注意到。 glxgears客户端在短暂暂停后继续运行,并且内核编译任务继续成功完成。 然后,我们正常关闭虚拟机并在备份磁盘映像上执行强制文件系统检查,该报告未报告任何不一致情况。

3.3 基准

在以下部分中,我们使用各种宏基准来评估系统的性能,这些宏基准可以代表一系列实际混合的工作负载。我们运行的主要工作负载是内核编译测试,SPECweb2005基准测试和Postmark磁盘基准测试。内核编译是一个平衡的工作负载,它会强调虚拟内存系统,磁盘和CPU,SPECweb主要强调网络性能和内存吞吐量,而Postmark则专注于磁盘性能。

为了更好地理解以下度量,我们执行了一个微基准测试,用于测量复制来宾状态所花费的时间(来宾被暂停时)以及将数据发送到备份所花费的时间(相对于自上一个检查点以来已更改的页数)。我们编写了一个应用程序,以重复更改设置的页面数的第一个字节,并在1000次迭代中测量了时间。图5展示了在检查点和复制阶段花费的平均,最小和最大记录时间,置信区间为95%。它表明检查点频率的瓶颈是传输时间。

 

 

 

内核编译 内核编译测试使用默认配置和bz-Image目标来测量构建linux内核2.6.18所需的时钟时间。编译使用GCC版本4.1.2,并创建版本3.81。这是测试CPU,内存和磁盘性能的平衡工作负载。图6显示了与未保护虚拟机中的基准编译相比,以每秒10、20、30和40次的速度将数据配置到检查点时的保护开销。每个频率的总测量开销分别为31%、52%、80%和103%。在我们测试的速率范围内,开销与检查点频率呈线性比例关系。我们认为,在这组测试中测量的开销对于通用系统也是合理的,即使在每秒40次检查点频率的情况也是这样。

 

SPECweb2005  SPECweb基准测试至少由三个独立的系统组成:Web服务器,应用程序服务器和一个或多个Web客户端模拟器。我们将它们配置为不同物理机上的三个VM。在1024 MB的总可用RAM中,为应用程序服务器和客户端配置了640 MB。 Web服务器和备份配备了2048 MB的RAM,其中1024分配给了Web服务器虚拟机(即正在测试的系统)。我们在本节中提到的SPECweb分数是使用SPECweb“电子商务”测试所取得的最高成绩,保持了95%的“良好”和99%的“可接受”时间

 

 

图7显示了相对于不受保护的服务器,SPECweb在各种检查点频率下的性能。这些分数主要是服务器和客户端之间的网络缓冲区所造成的延迟的函数。尽管将它们配置为在一定频率范围内运行,但SPECweb足够快的接触内存,以至于传输在检查点之间的内存所需的时间有时会超过100毫秒,而不必考虑检查点频率如何。由于在确认检查点状态之前无法释放网络缓冲区,因此有效网络延迟可能高于配置的检查点间隔。 Remus确保在每个epoch开始时都将VM挂起,但是无法确保当前每个epoch要复制的状态总量不超过在配置的epoch长度内可用的带宽。由于有效检查点频率低于配置的频率,并且网络延迟在SPECweb得分中占主导地位,因此在配置的频率范围内性能相对较平坦。在配置的每秒10、20、30和40个检查点速率下,获得的平均检查点速率分别为9.98、16.38、20.25和23.34,或者平均延迟分别为100ms,61ms,49ms和43ms。

 SPECweb是一个需要大量RAM的工作负载,对网络延迟也非常敏感。这使得它不适合我们当前的实现,该实现将网络延迟换为内存吞吐量。图8展示了客户端VM和Web服务器在SPECweb上的巨大效果延迟。我们使用Linux netem [19]排队规则为Web服务器(虚拟化但未在Remus下运行)的出站链接添加不同程度的延迟。为了进行比较,图7还显示了禁用网络缓冲时的保护开销,以便更好地将网络延迟与其他形式的检查点开销隔离开(同样,平面的配置文件是由于有效的检查点速率低于配置的速率) 。第3.4节中讨论的截止时间调度和页面压缩是减少检查点延迟和传输时间的两种可能的技术。两者之一或两者都会减少检查点延迟,因此可能会大大提高SPECweb性能

 

Postmark 前面的部分描述了受保护的网络和内存性能,但是所使用的基准仅在磁盘子系统上施加了中等负载。 为了更好地了解磁盘缓冲机制的效果,我们运行了Postmark磁盘基准测试(版本1.51)。 该基准测试对吞吐量和磁盘响应时间均很敏感。 为了隔离磁盘复制的成本,我们在这些测试期间没有使用内存或网络保护。 配置与不受保护的系统相同,除了虚拟磁盘由Tapdisk复制模块提供。 图9显示了执行10000个邮戳事务所需的总时间,这些事务不需要磁盘复制,并且复制的磁盘以每秒10、20、30和40次的频率提交。结果表明复制对磁盘性能没有显著影响。

 

3.4 Potential optimizations 潜在的优化

尽管我们相信本节前面显示的性能开销对于它们所提供的功能是合理的,但我们仍希望进一步降低它们,尤其是对于延迟敏感的工作负载。除了对现有代码进行更仔细的调整之外,我们认为以下技术有可能极大地提高性能。时限式调度法 当前执行检查点所需的时间是可变的,具体取决于要复制的内存量。尽管Remus确保在每个时期的开始都将受保护的VM挂起,但当前它并未尝试控制可能在各个时期之间改变的状态量。为了提供更严格的调度保证,可以根据检查脏页的数量,在检查点之间显着降低访客操作的速度[10]。优先考虑延迟而不是吞吐量的应用程序(例如,第3.3节中讨论的SPECweb基准测试所建模的应用程序)可以启用此限制以提高性能。对于这种操作,可以扩展影子页表句柄,以便在脏页数超过某个高水位线时调用回调,或者配置为直接暂停虚拟机

 

页面压缩 已经观察到,磁盘写操作通常仅改变数据块的5–20%[35]。如果RAM具有类似的属性,则可以通过仅发送同一页先前传输的增量来利用RAM,以减少需要复制的状态量

 

为了评估压缩复制流的潜在好处,我们设计了一个基本压缩引擎原型在传输一个页面之前,该系统检查它是否存在于先前传输页面的地址索引LRU缓存中在缓存命中时,页面将与以前的版本进行XOR处理,差异将以运行长度编码。当页面写入不更改大部分页面时,这提供了显着的压缩。尽管对于大多数数据流来说都是如此,但仍有相当一部分页面已被修改到XOR压缩无效的地步。在这些情况下,通用算法(例如gzip使用的算法)可以实现更高程度的压缩。

 

我们发现,通过使用一种混合方法,其中每个页面优先进行XOR压缩,但是如果XOR压缩比低于5:1或缓存中不存在上一页,则退回到gzip压缩,我们可以观察复制流上的典型压缩比为10:1。图10显示了第3.3节中描述的内核编译基准测试60秒内以MBps消耗的带宽。缓存大小为8192页,平均缓存命中率为99%。

 

压缩复制流可能会消耗复制主机上的额外内存和CPU资源,但是诸如XOR压缩技术之类的轻量级方案应该通过减少复制所需的带宽以及随之而来的网络减少来收回成本缓冲延迟。

 

写时复制检查点 当前的实现将域在每个检查点暂停一段时间,时间长度与自上一个检查点以来已被弄脏的页面数成线性关系。通过将脏页标记为写时复制并立即恢复域,可以减轻这种开销。这样可以将域必须暂停的时间减少到与访客可用的固定比例的RAM总量。我们打算通过为Xen影子分页系统提供一个用户空间映射的缓冲区来实现写时复制,在恢复读写访问权限之前,它可以将触摸的页面复制到该缓冲区中。复制过程然后可以从COW缓冲区中提取所有标记为已复制的页面,而不是直接从guest虚拟机读取它们。复制页完成后,Xen COW模块可以将其在缓冲区中的空间标记为可重复使用。如果缓冲区将满,则可以简单地暂停guest虚拟机,从而导致服务从COW正常降级到停止并复制操作

4 Related Work 相关工作

状态复制可以在几个级别上执行,每个级别在效率和通用性上有不同的平衡。在最低级别上,基于硬件的复制可能是最可靠的解决方案。但是,与软件相比,硬件的开发成本要高得多,因此,硬件复制存在明显的经济劣势。虚拟化层的复制具有硬件方法的许多优点,由于它是通过软件实现的,因此成本较低。但是,像硬件一样,虚拟化层对其复制的操作系统和应用程序状态没有语义上的了解。结果,它的灵活性不如操作系统,应用程序库或应用程序本身中的进程检查点,因为它必须复制整个系统而不是单个进程。它也可能效率较低,因为它可能会复制不必要的状态。然而,这些高级方法面临的挑战是,组成检查点的状态元素之间的相互依赖性在不知不觉中很难识别并从系统的其他部分中分离出来,因此这些检查点机制比虚拟化中的检查点要复杂得多。

虚拟机迁移。如前所述,Remus建立在Xen对实时迁移的支持之上[6],并进行了显着扩展以支持频繁的远程检查点。Bradford等人,扩展了Xen的实时迁移支持的方向:将持久性状态与guest一起迁移,以便可以在不与原始系统共享网络存储的远程节点上重新启动它[3]。

 

与Remus一样,其他项目也使用虚拟机来提供高可用性。与我们的工作最接近的是Bressoud和Schneider的著作[4]。他们使用虚拟机监视器将主要系统看到的输入事件转发到备份系统,在备份系统中按确定的方式重放它们,以复制主要状态。确定性重放比简单的虚拟化对目标体系结构的约束要严格得多,并且需要VMM中特定于体系结构的实现。

 Bressoud和Schneider的工作表明,确定性重播的另一个重大缺点是,它很难扩展到多核CPU。问题是确定核心访问共享内存的顺序是必要的,但也很困难。有人尝试解决该问题。例如,飞行数据记录器[34]是一个硬件模块,它嗅探高速缓存的一致性流量,以便记录多个处理器访问共享内存的顺序。同样,Dunlap引入了一种软件方法,其中CREW协议(并发读取,互斥写入)通过页面保护[8]应用于共享内存。尽管这些方法确实使SMP具有确定性的重放成为可能,但由于开销高(至少随并发程度线性增加),因此尚不清楚它们是否可行。我们的工作完全避开了这个问题,因为它不需要确定性重放。

 

虚拟机日志记录和重播 虚拟机日志记录已用于高可用性以外的其他用途。例如,在ReVirt [9]中,虚拟化被用来为记录目标系统中的状态变化提供一个安全层,以便为入侵检测系统提供更好的取证证据。重播的系统是原始系统的只读副本,除非为了重新创建系统损坏中所涉及的事件,否则不打算运行该副本。日志记录也已用于构建时间旅行的调试器[13],与ReVirt一样,该回放器仅出于取证目的而重放系统。

 

操作系统复制 有许多操作系统,例如Accent [25],Amoeba [18],MOSIX [1]和Sprite [23],它们支持进程迁移,主要用于负载平衡。使用流程迁移进行故障恢复的主要挑战是,迁移的进程通常会将剩余依赖项保留在从中迁移它们的系统上为了容忍主主机的故障,消除这些依赖关系是必要的,但是由于系统的复杂性和这些依赖关系的结构,解决方案是难以捉摸的。

已经进行了一些尝试在操作系统级别上复制应用程序。 Zap [22]尝试在Linux内核中引入虚拟化层。必须为每个操作系统重新构建此方法,并在各个版本之间仔细维护。

图书馆方法 一些应用程序库支持进程迁移和检查点。这个支持通常用于并行应用程序框架,如CoCheck[29]。通常,进程迁移用于负载平衡,检查点用于在出现故障时恢复整个分布式应用程序

复制存储。在用于灾难恢复和取证的可检查点存储方面也进行了大量工作。 Linux逻辑卷管理器[14]提供了一种有限形式的块存储的写时复制快照视差[33],通过在块级别提供有限的轻量级写时复制快照,显著改进了此设计水平视图文件系统[11]允许给定卷一次存在一个快照。其他方法包括RSnapshot,它运行在文件系统之上,以通过一系列硬链接和各种备份软件来创建快照。 DRBD [26]是块设备上的软件,可透明地将其复制到另一台服务器。

 

投机执行。使用推测性执行将I / O处理与计算隔离开来的方法已经被其他系统采用。特别是,SpecNFS [20]和Rethink the Sync [21]以与我们类似的方式使用推测,以使I / O处理异步。 Remus与这些系统的不同之处在于,来自客户机的块I / O的指令集始终保持不变:它们立即应用于本地物理磁盘。取而代之的是,我们的系统缓冲生成的网络流量,以隔离推测执行的外部可见效果,直到相关状态被完全复制为止。

 

5 Future work

本节简要讨论了我们打算探索的一些方向,以改善和扩展Remus。正如我们在上一节中所演示的那样,高可用性服务所带来的开销并不是不合理的。但是,本文描述的实现还很年轻。一些潜在的优化领域仍有待探索。完成第3.4节中讨论的目标优化后,我们打算研究更通用的扩展,例如以下所述。

 

内省优化 目前,Remus传播的状态超出了严格的要求。例如,缓冲区高速缓存页面不需要复制,因为它们可以简单地从备份上的持久性存储中读取。为了利用这一点,虚拟磁盘设备可以记录为磁盘读取提供给它的缓冲区的地址,以及相关的磁盘地址。如果在磁盘读取完成后未修改这些页面,则内存复制过程可能会跳过这些页面。远端将负责从其磁盘副本中重新发出读取内容,以填充丢失的页面。对于繁重的磁盘工作负载,这应该可以大大减少状态传播时间。

 

硬件虚拟化支持 由于我们实验室在开发时缺乏支持硬件虚拟化的设备,我们只对半虚拟化的客户虚拟机提供了完善的支持。但是,我们已经研究了支持完全虚拟化环境所需的代码,并且前景十分广阔。实际上,由于支持虚拟化的硬件提供了更好的封装,因此它可能比半虚拟实现更简单。

 

集群复制 扩展系统以保护多个相互连接的主机是很有用。当每个主机都可以独立地受到保护时,协调保护将使内部网络通信能够在不进行缓冲的情况下继续进行。这有可能显著提高分布式应用程序的吞吐量,包括托管主机环境中流行的三层web应用程序配置。集群复制的支持可以通过分布式检查点协议来提供,例如我们的同事Gang Peng的硕士论文[24]中所描述的,该协议使用了Remus提供的检查点基础结构的早期版本。

 

灾难恢复。 Remus是Second-Site [7]项目的产品,该项目的目的是提供运行系统的地理多样性镜像,以便在物理灾难中生存。为了试验这种配置,我们正在计划Remus的多站点部署。在长距离部署中,网络延迟将更大关注。另外,需要重新配置网络以相应地重定向Internet流量

 

日志结构化数据中心  我们正在扩展Remus已经捕获和保留受保护VM的完整执行历史,而不仅仅是最新的检查点。 通过将客户内存映射到Parallax [17],这是专为高频快照设计的虚拟块存储,我们希望能够以非常精细的粒度有效地存储大量持久性和瞬态状态。 这些数据在构建高级调试和取证工具时非常有用。它还可以提供从状态损坏中恢复的方便机制,无论是由operatorerror还是由恶意代理(病毒等)引入的

 

6 Conclusion 结论

Remus是一种新颖的系统,用于在现有商品上运行的软件上改造高可用性硬件系统使用虚拟化来封装受保护的虚拟机,并执行频繁的整个系统检查点以异步复制相对执行的单个特定虚拟机的状态。

 

提供高可用性是一项艰巨的任务,并且传统上需要大量的成本和工程上的努力。 Remus通过在虚拟化平台层将高可用性呈现为一项服务来实现商品化:对于特定的虚拟机,可以简单地“打开” HA。与任何高可用性系统一样,保护也不是没有代价的:为确保一致的复制而需要的网络缓冲给要求非常低延迟的应用程序带来了性能开销。管理员还必须部署额外硬件,这些硬件可以在N对1配置中使用,使用一个备份来保护多个活动的主机,作为这种开销的交换。Remus完全省去了修改单个应用程序以提供HA功能的任务,而且它不需要特殊用途的硬件。

 

Remus代表了现代服务器HA设计空间中一个未经探索的地方。该系统允许按一下按钮即可简单地将保护动态地提供给正在运行的VM。我们认为这种模式对希望为客户提供差异化服务的托管提供商特别有吸引力。

 

 

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 、4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、下载 4使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合;、 4下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.m或d论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 、1资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。、资源 5来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。、资 5源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值