Distributed systems for fun and profit翻译-4. Replication

原文地址:http://book.mixu.net/distsys/replication.html

复制问题是分布式系统中的众多问题之一。我选择把重点放他而不是其他问题上,比如领导人选举、失败检测、互斥、共识和全局快照,因为他往往是人们最感兴趣的部分。例如,区分并行数据库的一种方法是根据它们的复制特性。此外,复制为许多子问题提供了上下文,如领导人选举、故障检测、共识和原子广播。

复制是一个组通信问题。什么样的安排和沟通模式能给我们提供我们想要的性能和可用性特征?面对网络分割和节点同时失效,如何保证容错性、持久性和不发散性?

同样,有很多方法可以实现复制。我将在这里采用的方法只是查看具有复制的系统可能采用的高级模式。这样有助于将讨论集中在整体模式上,而不是传递特定的消息。我在这里的目标是探索设计空间,而不是解释每个算法的细节。

让我们首先定义复制是什么。我们假设我们有一些初始数据库,并且客户机发出更改数据库状态的请求。

在这里插入图片描述
安排和通信模式可分为几个阶段:

  1. (请求)客户端向服务器发送请求。
  2. (同步)复制的同步部分发生在
  3. (响应)向客户端4返回响应。
  4. (异步)复制的异步部分发生

这个模型是基于这篇文章的。注意,在任务的每个部分中交换的消息的模式取决于特定的算法:我有意尝试在不讨论特定算法的情况下通过。

考虑到这些阶段,我们可以创建什么样的通信模式?我们选择的模式对性能和可用性有何影响?

同步复制

第一种模式是同步复制(也称为主动复制、立即复制、推送复制或悲观复制)。让我们画出它的样子:

在这里插入图片描述
在这里,我们可以看到三个不同的阶段:首先,客户端发送请求。接下来,我们称之为复制的同步部分发生了。这个术语指的是客户端被阻止-等待来自系统的回复。

在同步阶段,第一个服务器与其他两个服务器联系,并等待,直到它收到来自所有其他服务器的回复。最后,它向客户机发送一个响应,通知客户机结果(例如成功或失败)。

这一切似乎都很简单。如果不讨论同步阶段的算法细节,我们能对这种通信模式的具体安排说些什么呢?首先,请注意这是一种N对N的写方法:在返回响应之前,系统中的每个服务器都必须看到并确认它

从性能角度来看,这意味着系统的速度将与其中最慢的服务器一样快。系统还将对网络延迟的变化非常敏感,因为它要求每台服务器在继续之前进行回复。

考虑到N/N方法,系统不能容忍任何服务器的丢失。当服务器丢失时,系统无法再向所有节点写入数据,因此无法继续。它可能能够提供对数据的只读访问,但是在此设计中一个节点失效后不允许进行修改。

这种安排可以提供非常强的持久性保证:当返回响应时,客户端可以确定所有N个服务器都接收、存储和确认了请求。为了丢失一个已接受的更新,所有N个副本都需要丢失,这是一个尽可能好的保证。

异步复制

让我们将其与第二种模式——异步复制(亦称被动复制、拉式复制或延迟复制)进行对比。正如您可能已经猜到的,这与同步复制相反:
在这里插入图片描述
在这里,master(/leader/coordinator)立即向客户机发送一个响应。它最好将更新存储在本地,但不会同步执行任何重要的工作,也不会强迫客户机等待在服务器之间发生的更多轮的通信。

在稍后的某个阶段,将执行复制任务的异步部分。在这里,主服务器使用某种通信模式与其他服务器联系,其他服务器更新其数据副本。具体取决于使用的算法。

在不详细介绍算法的情况下,我们能对这种具体的安排说些什么呢?好吧,这是一种写1/N的方法:立即返回响应,稍后进行更新传播。

从性能的角度来看,这意味着系统是快速的:客户机不需要花费任何额外的时间等待系统的内部完成工作。系统对网络延迟的容忍度也更高,因为内部延迟的波动不会导致客户端的额外等待。

这种安排只能提供薄弱的,或概率的耐久性保证。如果没有出错,数据最终会复制到所有N台机器上。但是,如果包含数据的唯一服务器在这之前丢失,则数据将永久丢失。

考虑到1/N的方法,只要至少有一个节点启动,系统就可以保持可用(至少在理论上,尽管在实践中负载可能太高)。这样一种纯粹的惰性方法不能提供持久性或一致性保证;您可以向系统写入数据,但不能保证在发生任何错误时都能读回所写的数据。

最后,值得注意的是,被动复制不能确保系统中的所有节点始终包含相同的状态。如果您接受多个位置的写操作,并且不要求这些节点同步同意,那么您将面临分歧的风险:读取操作可能从不同位置返回不同的结果(特别是在节点失败和恢复之后),并且无法实施全局约束(需要与所有人通信)。

我并没有真正提到读(而不是写)期间的通信模式,因为读的模式实际上遵循写的模式:在读的过程中,您希望接触尽可能少的节点。我们将在quorums的背景下进一步讨论这个问题。

主要复制方法概述

在讨论了两种基本的复制方法:同步复制和异步复制之后,让我们看看主要的复制算法。

对复制技术进行分类的方法有很多种。我要介绍的第二个区别(在同步和异步之后)是:

  • 防止发散的复制方法(单拷贝系统)
  • 有发散风险的复制方法(多主系统)

第一组方法的特性是它们“表现得像一个单一的系统”。特别是,当发生部分故障时,系统确保只有一个系统副本处于活动状态。此外,系统还确保副本始终一致。这就是所谓的共识问题(consensus problem)。

如果几个进程(或计算机)都同意某个值,它们就可以达成共识。更正式地说:

  1. 约定:每一个正确的过程都必须约定相同的价值。
  2. 完整性:每个正确的过程至多决定一个值,如果它决定了某个值,那么它一定是由某个过程提出的。
  3. 终止:所有过程最终达成一个决定。
  4. 有效性:如果所有正确的过程都提出相同的值V,那么所有正确的过程都决定V。

互斥、领导人选举、多播和原子广播都是比较普遍的共识问题。保持单拷贝一致性的复制系统需要以某种方式解决一致性问题。

保持单副本一致性的复制算法包括:

  • 1n条消息(异步主/备份)
  • 2n条消息(同步主/备份)
  • 4n条消息(两阶段提交,多Paxos)
  • 6n条消息(三阶段提交,Paxos具有重复的leader选举)

这些算法的容错性各不相同(例如,它们可以容忍的故障类型)。我只是根据算法执行过程中交换的消息数对它们进行了分类,因为我认为尝试找到一个问题的答案是很有趣的:“我们用添加的消息交换来了什么?”

下图改编自谷歌的Ryan Barret,描述了不同选项的一些方面:
在这里插入图片描述
上图中的一致性、延迟、吞吐量、数据丢失和故障转移特性实际上可以追溯到两种不同的复制方法:同步复制(例如,在响应之前等待)和异步复制。当你等待时,你会得到更差的表现,但更强的保证。当我们讨论分区(和延迟)容忍度时,2PC和仲裁系统之间的吞吐量差异将变得明显。

在这个图中,执行弱(最终)一致性的算法被归为一类(“八卦”)。不过,我将更详细地讨论弱一致性的复制方法—八卦和(部分)仲裁系统。“transactions”行实际上更多地指的是全局谓词求值(global predicate evaluation),这在一致性较弱的系统中是不受支持的(尽管可以支持本地谓词求值)。

值得注意的是,执行弱一致性需求的系统具有较少的通用算法,并且可以选择性地应用更多的技术。由于不强制实现单拷贝一致性的系统可以像由多个节点组成的分布式系统一样自由地工作,因此要解决的明显目标较少,而重点更多地放在让人们了解他们所拥有的系统特性上。

例如:

  • 以客户机为中心的一致性模型试图提供更清晰的一致性保证,同时允许差异。
  • CRDTs(收敛和交换复制数据类型)利用某些基于状态和操作的数据类型的半格性质(结合性、交换性、幂等性)。
  • 合流分析(Confluence analysis)(如Bloom语言)使用关于计算单调性的信息来最大限度地利用无序。
  • PBS(概率有界陈旧性)利用从现实系统中收集的仿真和信息来描述部分群体系统的预期行为。

我将进一步讨论所有这些;首先,让我们看一下维护单副本一致性的复制算法。

主/备份复制(Primary/backup replication)

主/备份复制(也称为主副本复制、主从复制或日志传送)可能是最常用的复制方法,也是最基本的算法。所有更新都在主服务器上执行,并通过网络将操作日志(或更改)发送到备份副本。有两种变体:

  • 异步主/备份复制
  • 同步主/备份复制

同步版本需要两条消息(“更新”和“确认收到”),而异步版本只需要一条消息(“更新”)就可以运行。

P/B很常见。例如,默认情况下,MySQL复制使用异步变量。MongoDB也使用P/B(带有一些额外的故障转移过程)。所有操作都在一个主服务器上执行,主服务器将它们序列化为本地日志,然后异步复制到备份服务器。

正如我们前面在异步复制上下文中讨论的,任何异步复制算法只能提供弱的持久性保证。在MySQL复制中,这表现为复制延迟:异步备份总是至少在主备份之后执行一个操作。如果主服务器失败,则尚未发送到备份的更新将丢失。

主/备份复制的同步变体可确保在将写操作返回到客户端之前,将写操作存储在其他节点上,而代价是等待来自其他副本的响应。然而,值得注意的是,即使是这种变体也只能提供薄弱的保障。考虑以下简单的故障场景:

  • 主服务器接收一个写操作并将其发送到备份。
  • 备份将继续并确认该写操作,
  • 然后主服务器在将ACK发送到客户端之前失败

客户机现在假设提交失败,但是备份提交了它;如果备份升级为主备份,则它将不正确。可能需要手动清理来协调失败的主备份或不同的备份。

我当然在这里简化。虽然所有主/备份复制算法都遵循相同的一般消息传递模式,但它们在处理故障转移、副本长时间脱机等方面有所不同。然而,在该方案中,不可能对一次电源的不适当故障具有弹性。

在基于主/备份的方案中,关键是它们只能提供尽力而为的保证(例如,如果节点在不恰当的时间失败,它们很容易受到丢失更新或不正确更新的影响)。此外,P/B方案易受裂脑(split-brain)的影响,在这种情况下,由于临时网络问题而启动到备份的故障转移,并导致主备份和备份同时处于活动状态。

为了防止不恰当的失败导致违反一致性保证,我们需要添加另一轮消息传递,这将获得两阶段提交协议(2PC)。

两阶段提交协议(2PC)

两阶段提交(2PC)是许多经典关系数据库中使用的协议。例如,MySQL Cluster(不要与普通MySQL混淆)使用2PC提供同步复制,下图说明了消息流:

在这里插入图片描述
在第一阶段(投票),协调员将更新发送给所有参与者。每个参与者处理更新并投票决定是提交还是中止。投票提交时,参与者将更新存储到临时区域(预写日志)。在第二阶段完成之前,更新被认为是临时的。

在第二阶段(决定),协调员决定结果,并将结果通知每一位参与者。如果所有参与者都投票同意提交,则更新将从临时区域获取并永久化。

在提交被认为是永久性的之前准备好第二个阶段是有用的,因为它允许系统在节点失败时回滚更新。相反,在主/备份(“1PC”)中,没有步骤回滚在某些节点上失败而在其他节点上成功的操作,因此副本可能会分散。

2PC容易阻塞,因为单个节点故障(参与者或协调器)会阻塞进程,直到节点恢复。由于第二阶段,恢复通常是可能的,在第二阶段,其他节点会被告知系统状态。请注意,2PC假设每个节点的稳定存储中的数据永远不会丢失,并且没有节点永远崩溃。如果稳定存储器中的数据在崩溃中损坏,则仍有可能丢失数据。

节点故障期间的恢复过程的细节相当复杂,因此我将不详细介绍。主要任务是确保对磁盘的写入是持久的(例如刷新到磁盘而不是缓存),并确保做出正确的恢复决策(例如,学习循环的结果,然后在本地重新执行或撤消更新)。

正如我们在关于CAP的一章中了解到的,2PC是一个CA-它不允许分区。2PC地址不包括网络分区的故障模型;从节点故障中恢复的规定方法是等待网络分区恢复正常。如果一个协调员失败了,没有安全的方法来选出一个新的协调员,而是需要人工干预。2PC对延迟也相当敏感,因为它是一种写N/N的方法,在最慢的节点确认之前,写操作无法继续。

2PC在性能和容错性之间取得了不错的平衡,这就是为什么它在关系数据库中很流行的原因。然而,较新的系统通常使用分区容忍一致性算法,因为这样的算法可以提供从临时网络分区的自动恢复,以及更优雅地处理节点间增加的延迟。

接下来让我们看看分区容忍一致性算法。

分区容忍一致性算法

我们讨论的分区容忍一致性算法是保持单拷贝一致性的容错算法的一类。还有一类容错算法:容忍任意(拜占庭式)错误的算法;这些算法包括恶意行为导致的节点故障。这种算法很少在商业系统中使用,因为它们的运行成本更高,实现起来也更复杂,因此我将把它们排除在外。

在分区容忍一致性算法方面,最著名的算法是Paxos算法。然而,众所周知,它很难实现和解释,所以我将重点介绍Raft,这是一种最新的(2013年初)算法,旨在更容易教学和实现。让我们首先来看看网络分区和分区容忍一致性算法的一般特性。

什么是网络分区?

网络分区是指向一个或多个节点的网络链接失败。节点本身继续保持活动状态,它们甚至可以从位于网络分区一侧的客户端接收请求。正如我们之前所了解到的,在讨论CAP定理的过程中,网络分区确实发生了,并不是所有的系统都能很好地处理它们。

网络分区很棘手,因为在网络分区期间,不可能区分故障的远程节点和无法访问的节点。如果一个网络分区发生但没有节点失败,那么系统被分成两个同时处于活动状态的分区。下面的两个图说明了网络分区如何看起来像节点故障。

一个由两个节点组成的系统,故障与网络分区:

在这里插入图片描述
一个由3个节点组成的系统,故障与网络分区:

在这里插入图片描述
一个实施单一副本一致性的系统必须有某种打破对称性的方法:否则,它将分裂成两个独立的系统,这两个系统可以彼此分离,不再保持单一副本的假象。

对于实施单副本一致性的系统,网络分区容忍要求在网络分区期间,只有系统的一个分区保持活动状态,因为在网络分区期间不可能防止发散(例如CAP定理)。

多数决定(Majority decisions)

这就是为什么分区容忍一致性算法依赖多数票的原因。要求大多数节点(而不是所有节点(如2PC中所述))同意更新,这允许少数节点由于网络分区而关闭、速度慢或无法访问。

只要N个节点中有(N/2+1)个节点启动并可访问,系统就可以继续运行。分区容忍一致性算法使用奇数个节点(例如3、5或7)。由于只有两个节点,在失败后不可能有明显的多数。例如,如果节点数为3,则系统对一个节点故障具有弹性;对于5个节点,则系统对两个节点故障具有弹性。

当网络分区发生时,分区的行为是不对称的。一个分区将包含大多数节点。少数分区将停止处理操作以防止在网络分区期间出现分歧,但多数分区可以保持活动状态。这样可以确保只有一个系统状态副本保持活动状态。

多数也很有用,因为它们可以容忍分歧:如果存在干扰或失败,节点可能会进行不同的投票。然而,由于只能有一个多数决定,临时分歧最多可以阻止协议继续(放弃活性),但它不能违反单副本一致性标准(安全属性)。

角色

有两种方法可以构建系统:所有节点可能具有相同的职责,或者节点可能具有单独的、不同的角色。

用于复制的一致算法通常选择为每个节点具有不同的角色。拥有一个固定的领导或主服务器是一个优化,使系统更有效,因为我们知道,所有的更新必须通过该服务器。不是领队的节点只需要将其请求转发给领队。

请注意,具有不同的角色并不妨碍系统从领导者(或任何其他角色)的失败中恢复。仅仅因为角色在正常运行期间是固定的,并不意味着一个人不能通过在失败后重新分配角色(例如通过领导人选举阶段)从失败中恢复过来。节点可以重用领导者选举的结果,直到发生节点故障和/或网络分区。

Paxos和Raft都使用不同的节点角色。特别是,他们有一个领导节点(Paxos中的“提议者”),负责在正常运行期间的协调。在正常操作期间,其余的节点是追随者(Paxos中的“接受者”或“投票者”)。

Epochs(纪元)

在Paxos和Raft中,正常运行的每一个周期都被称为一个纪元(Raft中的“term”)。在每个纪元中,只有一个节点是指定的领导者(日本也使用类似的系统,纪元名称随着皇室继承而改变)。

在这里插入图片描述
在一次成功的选举之后,同一位领导人一直协调到新纪元的结束。如上图所示(摘自草稿纸),一些选举可能会失败,导致时代立即结束。

epoch充当一个逻辑时钟,允许其他节点识别过时节点何时开始通信-被分区或停止运行的节点的epoch数将比当前节点小,并且它们的命令将被忽略

决斗换领袖

在正常操作过程中,一种分区容忍一致性算法相当简单。如前所述,如果我们不关心容错性,我们可以使用2PC。大多数复杂性实际上来自于确保一旦达成一致的决策,它不会丢失,并且协议可以处理由于网络或节点故障而导致的领导者更改。

所有节点开始时都是跟随者;一个节点在开始时被选为领导者。在正常操作期间,leader保持一个心跳,允许跟随者检测leader是否失败或被分区。

当一个节点检测到一个领导者变得没有响应(或者,在初始情况下,没有领导者存在)时,它会切换到一个中间状态(Raft中称为“候选者”),在该状态中,它将term/epoch值增加一个,发起一次领导者选举,并竞争成为新的领导者。

要当选领导人,一个节点必须获得多数票。一种分配选票的方法是简单地在先到先得的基础上分配选票;这样,一位领导人最终将当选。在尝试当选之间添加随机的等待时间将减少同时尝试当选的节点数。

一个纪元(epoch)内有编号的提案(proposal)

在每一个时代,领导人每次提出一个值供表决。在每个时代,每个提案都有一个唯一的严格递增的数字。追随者(投票者/接受者)接受他们收到的第一个特定提案编号的提案。

正常运行

正常运行时,所有方案都经过领导节点。当客户端提交建议(例如更新操作)时,leader将联系仲裁中的所有节点。如果不存在相互竞争的提议(基于追随者的回应),领导者会提出价值。如果大多数追随者接受这个价值,那么这个价值就被认为是被接受的。

由于有可能另一个节点也在试图充当领导者,我们需要确保一旦一个提议被接受,它的价值就永远不会改变。否则,一个已经被接受的提议可能会被一个竞争的领导人推翻。Lamport 这样描述:

P2:如果选择值为v的方案,则所选的每个编号较高的方案都具有值v

要确保此属性有效,要求跟随者和提议者都受到算法的约束,以避免更改已被大多数人接受的值。请注意,“值永远不能更改”是指协议的单个执行(或运行/实例/决策)的值。一个典型的复制算法会运行多个算法的执行,但大多数算法的讨论都集中在一次运行上,以保持简单。我们希望防止更改或覆盖决策历史记录。

为了强制执行此属性,提议者必须首先向跟随者询问他们(编号最高)接受的提议和价值。如果提议者发现一个提议已经存在,那么它必须简单地完成协议的执行,而不是提出自己的提议。Lamport这样描述:

P2b.如果选择值为v的提案,则任何提案人发布的每个编号较高的提案都具有值v。

更具体地说:

P2c.对于任何v和n,如果值为v且编号为n的提案是由[领导]发布的,则有一组S由大多数接受方[跟随者]组成,使得(a)S中的任何接受方都没有接受任何编号小于n的提案,或(b)v是在S中被跟随者接受的编号小于n的所有提案中编号最高的提案的值。

这是Paxos算法的核心,也是从Paxos算法派生出来的算法的核心。直到协议的第二阶段才选择要建议的值。有时,投标人必须简单地重新传输先前作出的决定,以确保安全(如P2c中的条款b),直到他们知道他们可以自由提出值(如条款a)

如果存在多个以前的建议,则建议使用编号最高的建议值。只有在完全没有竞争性提案的情况下,提案人才能试图提出值。

为了确保在提议者向每个接受者询问其最近的价值时不会出现竞争性提案,提议者要求跟随者不要接受提案编号低于当前提案编号的提案。将各个部分组合起来,使用Paxos做出决定需要两轮沟通:

在这里插入图片描述
准备阶段允许提议者了解任何竞争或先前的提议。第二阶段是提出新值或先前接受的值。在某些情况下,例如,如果两个提议者同时处于活动状态(决斗);如果消息丢失;或者如果大多数节点都失败了,那么大多数人都不会接受任何提议。但这是可以接受的,因为建议的值的决策规则收敛于单个值(在上一次尝试中提案编号最高的值)。

事实上,根据FLP不可能性结果,这是我们所能做的最好的:当关于消息传递边界的保证不成立时,解决一致性问题的算法必须要么放弃安全性,要么放弃活性。Paxos放弃了活力:它可能不得不无限期地推迟决策,直到某个时间点没有竞争的领导者,并且大多数节点接受一个提议。这比违反安全保证要好。

当然,实现这个算法要比听起来困难得多。有许多小问题,即使在专家的手中,也会产生相当多的代码。这些问题包括:

  • 实际优化:
  • -避免通过领导租约(而不是心跳)重复选举领导人
  • -避免在领导人身份不变的稳定状态下重复提出建议信息
  • 确保跟随者和提议者不会丢失稳定存储中的项目,并确保稳定存储中存储的结果不会受到细微损坏(例如磁盘损坏)
  • 使群集成员资格能够以安全的方式更改(例如,基本Paxos基于大多数成员总是在一个节点中相交的事实,如果成员资格可以任意更改,则不适用)
  • 在崩溃、磁盘丢失或配置新节点后以安全有效的方式更新新副本
  • 快照过程和垃圾处理在一段合理时间后收集了需要确保安全的数据(例如平衡存储要求和容错要求)

Google的 Paxos Made Live详细说明了这些挑战。

分区容忍一致性算法:Paxos、Raft、ZAB

希望这让您了解了分区容忍一致性算法是如何工作的。我鼓励您阅读进一步阅读部分中的一篇文章,以了解不同算法的细节。

Paxos是编写强一致分区容忍复制系统时最重要的算法之一。它被用在谷歌的许多系统中,包括BigTable/Megastore使用的Chubby lock manager、谷歌文件系统以及Spanner。

Paxos是以希腊帕克索斯岛命名的,最初由莱斯利·兰波特在1998年发表的一篇名为《The Part-Time Parliament》的论文中提出。它通常被认为很难实现,并且有一系列来自具有相当分布式系统专业知识的公司的论文解释了进一步的实际细节(请参阅进一步阅读)。你可能想看看Lamport对这个问题的评论。

这些问题大多涉及到这样一个事实,即Paxos是用一轮协商一致的决策来描述的,但实际的工作实现通常希望高效地运行多轮协商一致。这导致在核心协议上开发了许多扩展,任何对构建基于Paxos的系统感兴趣的人仍然需要消化这些扩展。此外,还有其他一些实际挑战,例如如何促进集群成员的变化。

ZAB。Zookeeper原子广播协议在apache zookeeper中使用。Zookeeper是一个为分布式系统提供协调原语的系统,被许多以Hadoop为中心的分布式系统(如HBase、Storm、Kafka)所使用。Zookeeper基本上是开源社区的Chubby版本。从技术上讲,原子广播是一个不同于纯共识的问题,但它仍然属于确保强一致性的分区容忍算法的范畴。

Raft是最近(2013年)添加到这一系列算法中的一个。它被设计成比Paxos更容易教学,同时提供同样的保证。特别地,算法的不同部分被分离得更加清晰,文中还描述了一种改变聚类成员的机制。它最近在etcd中被采用,灵感来自ZooKeeper。

强一致性的复制方法

在本章中,我们研究了强制实现强一致性的复制方法。从同步工作和异步工作的对比开始,我们逐步发展到能够容忍日益复杂的故障的算法。以下是每种算法的一些关键特性:

主从备份:

  • 单个静态主服务器
  • 复制日志,从服务器不参与执行操作
  • 复制延迟没有限制
  • 不允许分区
  • 手动/临时故障转移,不允许容错,“热备份”

2PC:

  • 一致投票:提交或中止
  • 静态主服务器
  • 2PC不能在提交期间协调器和节点同时发生故障时生存
  • 不允许分区,尾延迟敏感

Paxos:

  • 多数决定
  • 动态主服务器
  • 容忍N/2-1的同时失败
  • 对尾延迟不敏感

Further reading


Primary-backup and 2PC

  • Replication techniques for availability - Robbert van Renesse &
    Rachid Guerraoui,2010
  • Concurrency Control and Recovery in Database Systems

Paxos


Raft and ZAB

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值