Fair Scheduler与Capacity Scheduler比较

1、Fair Scheduler

Facebook开发的适合共享环境的调度器,支持多用户多分组管理,每个分组可以配置资源量,也可限制每个用户和每个分组中的并发运行作业数量;每个用户的作业有优先级,优先级越高分配的资源越多。

2、Capacity Scheduler

Yahoo开发的适合共享环境的调度器,支持多用户多队列管理,每个队列可以配置资源量,也可限制每个用户和每个队列的并发运行作业数量,也可限制每个作业使用的内存量;每个用户的作业有优先级,在单个队列中,作业按照先来先服务(实际上是先按照优先级,优先级相同的再按照作业提交时间)的原则进行调度。

3、相同点

[1]均支持多用户多队列,即都适用于多用户共享集群的应用环境

[2]单个队列均支持优先级和FIFO调度方式

[3]均支持资源共享,即某个queue中的资源有剩余时,可共享给其他缺资源的queue

4、不同点

[1]核心调度策略不同。计算能力调度器的调度策略是,先选择资源利用率低的queue,然后在queue中同时考虑FIFO和memory constraint因素;而公平调度器仅考虑公平,而公平是通过作业缺额体现的,调度器每次选择缺额最大的job(queue的资源量,job优先级等仅用于计算作业缺额)

 

[2]内存约束。计算能力调度器调度job时会考虑作业的内存限制,为了满足某些特殊job的特殊内存需求,可能会为该job分配多个slot;而公平调度器对这种特殊的job无能为力,只能杀掉这种task

ps:个人认为,capacityScheduler和FairScheduler最大的区别是:

capacityScheduler使用百分比定义队列资源,FairScheduler使用固定最大内存大小和core定义队列资源。

capacityScheduler的好处是队列资源可以随着集群规模的变化而动态变化,够灵活;坏处是队列资源最大值不是固定的,当集群中有新的节点加入时,队列资源随着增大,但是业务上,该队列可能不需要更大的资源。

FairScheduler的好处是,队列资源的最大大小是固定可控的。

==============================

Capacity Scheduler - vs - Fair Scheduler

Yarn 自带了两个支持多用户、多队列的调度器,分别是 Capacity Scheduler(容量调度器) 和 Fair Scheduler(公平调度器),前文YARN Capacity Scheduler(容量调度器)对 Capacity Scheduler 进行了介绍,本文通过将通过比较 Fair Scheduler 与 Capacity Scheduler 进行比较的方式来介绍 Fair Scheduler 并说明两者的异同点。

capacityScheduler支持使用yarn.scheduler.capacity.maximum-applications / yarn.scheduler.capacity.<queue-path>.maximum-applications参数,来确定集群和每个队列提交应用的最大数量,超过该数量拒绝提交。fairScheduler支持以下方式确定每个队列提交应用的最大数量。

 <maxRunningApps>50</maxRunningApps>

capacityScheduler也可以通过以下参数置是否抢占式调度。

  • yarn.resourcemanager.scheduler.monitor.enable设置为true,默认为false。
  • yarn.resourcemanager.scheduler.monitor.policies参数设置为ProportionalCapacityPreemptionPolic,它也是默认值。
  • yarn.scheduler.capacity.<queue-path>.disable_preemption参数设置为false,默认为false。

 

上面这张表展示了Capacity Scheduler 和 Fair Scheduler 在各个特性上的差异,下面我们主要对两者的资源分配策略进行进一步说明。

Capacity Scheduler 资源分配策略

资源分配策略也就是当出现空闲资源时,应该在哪个队列给哪个 Application 分配资源。Capacity Scheduler 采用三级资源分配策略,它会一次选择队列、应用程序和 Container 使用该资源。如下图:

Step1:选择队列
从根队列开始,使用深度优先遍历算法,从根队列开始,依次遍历子队列找出资源占用率最小的子队列。若子队列为叶子队列,则选择该队列;若子队列为非叶子队列,则以该子队列为根队列重复前面的过程直到找到一个资源使用率最小的叶子队列为止

Step2:选择应用
在Step1中选好了叶子队列后,取该队列中最早提交的应用程序(实际排序时用的 Application ID,提交时间越早的应用程序,Application ID 越小)

Step2:选择 Container
在 Step2中选好应用程序之后,选择该应用程序中优先级最高的 Container。对于优先级相同的 Containers,优选选择满足本地性的 Container,会依次选择 node local、rack local、no local

在 Capacity Scheduler 中,在比较资源占用率时,不同的资源比较器对资源定义是不一样的。默认的是 DefaultResourceCalculator,它只考虑内存资源。另外一种是 DominantResourceCalculator,采用了 DRF 比较算法,同时考虑内存和 cpu 两种资源。通过参数 yarn.scheduler.capacity.resource-calculator 来设置。

Fair Scheduler 资源分配策略

Fair Scheduler 与 Capacity Scheduler 一样也是依次选择队列、应用,最后选择 Container,其中选择队列和应用策略相同,采用了 FairShareComparator 比较器对队列或者应用程序进行排序,然后从头从头开始选择。

在介绍具体实现前还需介绍以下两个定义:

  • 最小共享量:管理员可给每个队列配置一个最小共享量,调度器在分配资源时,需要保证每个队列中的作业至少获取该数目的资源。一个常见的应用场景是,对产品队列设置最小共享量,而测试队列不设置,这样,当可用资源有限时时,优先保证产品队列有资源可用。
  • 公平共享量:当集群中存在多个队列时,某些队列中的资源可能用不了,这时候调度器会自动将这些队列中剩余的资源共享给其他需要的队列,其他这些队列获取的共享资源多少主要由其队列 weight决定,队列 weight越大,获取的资源越多。 一个队列的最小共享量加上其获取的共享资源数目,就是公平共享量。

比较的具体实现如下:

    @Override
    public int compare(Schedulable s1, Schedulable s2) {
      double minShareRatio1, minShareRatio2;
      double useToWeightRatio1, useToWeightRatio2;
      Resource minShare1 = Resources.min(RESOURCE_CALCULATOR, null,
          s1.getMinShare(), s1.getDemand());
      Resource minShare2 = Resources.min(RESOURCE_CALCULATOR, null,
          s2.getMinShare(), s2.getDemand());
      boolean s1Needy = Resources.lessThan(RESOURCE_CALCULATOR, null,
          s1.getResourceUsage(), minShare1);
      boolean s2Needy = Resources.lessThan(RESOURCE_CALCULATOR, null,
          s2.getResourceUsage(), minShare2);
      minShareRatio1 = (double) s1.getResourceUsage().getMemory()
          / Resources.max(RESOURCE_CALCULATOR, null, minShare1, ONE).getMemory();
      minShareRatio2 = (double) s2.getResourceUsage().getMemory()
          / Resources.max(RESOURCE_CALCULATOR, null, minShare2, ONE).getMemory();
      useToWeightRatio1 = s1.getResourceUsage().getMemory() /
          s1.getWeights().getWeight(ResourceType.MEMORY);
      useToWeightRatio2 = s2.getResourceUsage().getMemory() /
          s2.getWeights().getWeight(ResourceType.MEMORY);
      int res = 0;
      if (s1Needy && !s2Needy)
        res = -1;
      else if (s2Needy && !s1Needy)
        res = 1;
      else if (s1Needy && s2Needy)
        res = (int) Math.signum(minShareRatio1 - minShareRatio2);
      else
        // Neither schedulable is needy
        res = (int) Math.signum(useToWeightRatio1 - useToWeightRatio2);
      if (res == 0) {
        // Apps are tied in fairness ratio. Break the tie by submit time and job
        // name to get a deterministic ordering, which is useful for unit tests.
        res = (int) Math.signum(s1.getStartTime() - s2.getStartTime());
        if (res == 0)
          res = s1.getName().compareTo(s2.getName());
      }
      return res;
    }
  }

阅读以上代码,我们可以知道对于两个可调度项(队列和应用程序均是可调度项),假设为 s1,s2,公平调度算法流程如下:

  1. 若 s1紧需资源(使用资源量小于最小资源保证即紧需资源),s2 不紧需资源,把资源给 s1
  2. 若 s2紧需资源,s1 不紧需资源,把资源给 s2
  3. 若 s1、s2 都紧需资源,把资源给 minShareRatio 更小的,minShareRatio1 = 已使用内存/ 最小资源保证
  4. 若 s1、s2 都不紧需资源, 把资源给 useToWeightRatio 更小的, useToWeightRatio = 已使用内存 / 权重
  5. 若 s1、s2 都不紧需资源且useToWeightRatio相同,把资源给更早启动的

知道了比较算法,也就知道如何选出队列和应用程序了。选出应用程序之后,选择 Container 的逻辑与Capacity Scheduler 一致。

转载自:简书.牛肉圆粉不加葱. Capacity Scheduler - vs - Fair Scheduler

========================================================

常见调度器

前文说过,调度器的两个主要作用:1.决定如何划分队列;2.决定如何分配资源,这里又分两种情况:为队列分配资源和为单个application分配资源。从这两方面看下常见的调度器,重点分析下FairScheduler。

FIFO

最简单、也是默认的调度器。只有一个队列,所有用户共享。
资源分配的过程也非常简单,先到先得,所以很容易出现一个用户占满集群所有资源的情况。
可以设置ACL,但不能设置各个用户的优先级。

优点是简单好理解,缺点是无法控制每个用户的资源使用。
一般不能用于生产环境中。

CapacityScheduler

在FIFO的基础上,增加多用户支持,最大化集群吞吐量和利用率。它基于一个很朴素的思想:每个用户都可以使用特定量的资源,但集群空闲时,也可以使用整个集群的资源。也就是说,单用户的情况下,和FIFO差不多。
这种设计是为了提高整个集群的利用率,避免集群有资源但不能提交任务的情况。 (根本原因是capacityScheduler使用百分比定义队列资源)

特点:

  • 划分队列使用xml文件配置,每个队列可以使用特定百分比的资源
  • 队列可以是树状结构,子队列资源之和不能超过父队列。所有叶子节点的资源之和必须是100%,只有叶子节点能提交任务
  • 可以为每个队列设置ACL,哪些用户可以提交任务,哪些用户有admin权限。ACL可以继承
  • 队列资源可以动态变化。最多可以占用100%的资源。管理员也可以手动设置上限。
  • 配置可以动态加载,但只能添加队列,不能删除
  • 可以限制整个集群或每个队列的并发任务数量
  • 可以限定AM使用的资源比例,避免所有资源用来执行AM而只能无限期等待的情况

当选择队列分配资源时,CapacityScheduler会优先选择资源用量在规定量以下的。
当选择一个队列中的application分配资源时,CapacityScheduler默认使用FIFO的规则,也可以设定每个app最多占用队列资源的百分比。

关于CapacityScheduler一个比较重要问题就是百分比是如何计算的。默认的算法是DefaultResourceCalculator类的ratio方法,只考虑了内存。也就是说CapacityScheduler调度时是只考虑内存的。管理员也可以手动设置选择其他算法。

优点:灵活、集群的利用率高
缺点:也是灵活。某个用户的程序最多可以占用100%的资源,如果他一直不释放,其他用户只能等待,因为CapacityScheduler不支持抢占式调度,必须等上一个任务主动释放资源。

FairScheduler

我们一直在用的调度器。设计思路和CapacityScheduler不同,优先保证“公平”,每个用户只有特定数量的资源可以用,不可能超出这个限制,即使集群整体很空闲。(根本原因是FairScheduler使用固定数量大小定义队列资源)

特点:

  • 使用xml文件配置,每个队列可以使用特定数量的内存和CPU
  • 队列是树状结构。只有叶子节点能提交任务
  • 可以为每个队列设置ACL
  • 可以设置每个队列的权重
  • 配置可以动态加载
  • 可以限制集群、队列、用户的并发任务数量
  • 支持抢占式调度

优点:稳定、管理方便、运维成本低
缺点:相对CapacityScheduler,牺牲了灵活性。经常出现某个队列资源用满,但集群整体还有空闲(其它队列还有空闲资源)的情况。整体的资源利用率不高。

下面重点看下资源分配的算法。

Max-min fairness算法

FairScheduler主要关注“公平”,那么在一个共享的集群中,怎样分配资源才算公平?
常用的是max-min fairness算法:wiki。这种策略会最大化系统中一个用户收到的最小分配。如果每一个用户都有足够地请求,会给予每个用户一份均等的资源。尽量不让任何用户被“饿死”。

一个例子:资源总量是10,有3个用户A/B/C,需要的资源分别是5/4/3,应该怎样分配资源?
第一轮:10个资源分成3份,每个用户得到3.33
第二轮:3.33超出了用户C的需求,超出了0.33,将这多余的0.33平均分给A和B,每个用户得0.16
所以最后的分配结果是A=3.49,B=3.49,C=3

上面的例子没有考虑权重,如果3个用户的权重分别是0.5/1/0.8,又应该如何分配资源?
第一轮:将权重标准化,3个用户的权重比是5:10:8。将所有资源分成5+10+8=23份,按比例分配给各个用户。A得到10*5/23=2.17,B得到10*10/23=4.35,C得到10*8/23=3.48
第二轮:B和C的资源超出需求了,B超过0.25,C超过0.48。将多出资源分配给A。
最后的分配结果是A=2.9,B=4,C=3
由于进位的问题会有些误差。

更多用户的情况下同理。

DRF

Max-min fairness解决了单一资源下,多用户的公平分配。这个算法以前主要用来分配网络流量。但在现代的资源管理系统中,往往不只有一种资源。比如YARN,包含CPU和内存两种资源。多种资源的情况下,如何公平分配?Berkeley的大牛们提出了DRF算法

DRF的很多细节不提了。核心概念在于让所有application的“主要资源占比”尽量均等。比如集群总共X内存,Y CPU。app1和app2是CPU密集型的,app1每次请求3个CPU,app2每次请求4个CPU;app3和app4是内存密集型的,app3每次请求10G内存,app4每次请求20G内存。设分给app1、app2、app3、app4的请求数分别是a、b、c、d。DRF算法就是希望找到一组abcd,使得3a/X=4b/X=10c/Y=20d/Y。
如何判断CPU/内存密集型?如果任务需要的CPU资源/集群总的CPU资源 > 需要的内存资源/集群总的内存资源,就是CPU密集型,反之是内存密集型。
实际应用中一般没有最优解,而是一个不断动态调整的过程。和max-min fairness一样,也要经过多轮分配,才能达到一个公平的状态。

如果考虑权重的话,算法会更复杂一点。另外在单一资源的情况下,DRF会退化为max-min fairness。

资源分配过程

了解了一些基本的算法,接下来看看FairScheduler的资源分配过程。
前文说过NM的心跳会触发一个NODE_UPDATE事件,scheduler同时也是这个事件的handler,会尝试在对应的节点上分配container。
在FairScheduler中,所有的queue、application都继承了Scheduable,构成一个树状结构。资源分配的过程就是从这颗树的根节点开始查找,直到找到一个合适的Scheduable对象的过程。

以上图为例,共有3种对象:ParentQueue(root是一个特殊的ParentQueue)、LeafQueue、Application,只有LeafQueue才能提交app。
每个Queue可以定义自己的SchedulingPolicy,这个policy主要用于Scheduable对象的排序。目前共有3种SchedulingPolicy的实现:FifoPolicy、FairSharePolicy、DominantResourceFairnessPolicy,FIFO只能用于LeafQueue,其他两种Policy可以用于任意Queue。默认是FairSharePolicy。

分配Container是一次深度优先搜索:从root节点开始,首先检查当前节点资源是否用满,是则直接返回(这里会同时考虑CPU和内存)。如果当前节点是ParentQueue,就将所有子节点排序(SchedulingPolicy决定了这里的顺序),依次尝试在每个子节点上分配container;如果当前节点是LeafQueue,就将下面的app排序(也是SchedulingPolicy决定,但加入了一些app特有的判断条件),依次尝试为每个app分配资源;如果当前节点是app,会比较当前app的资源需求与节点的剩余资源,如果能满足,才真正分配container。至此整个资源分配过程才完成。如果找不到合适的app,最后会返回null。

从上面的过程可以看出,每次NM心跳时,都会触发一次资源分配,而且只能分配一个container。所以NM的心跳频率会影响到整个集群的吞吐量。另外可以配置参数yarn.scheduler.fair.assignmultiple让一次心跳分配多个container,默认是false。

下面看下默认的FairSharePolicy是如何排序的。这个policy只考虑内存资源,但跟max-min failness不太一样。max-min fairness关注整体资源的公平分配,而FairSharePolicy目的在于公平分配“被调度的机会”,所以最终的资源分配可能不是算法上的最优解。但目的是一样的,都是让所有app有机会运行,不会被饿死。

每个Schedulable对象都有minShare、maxShare、fairShare 3个属性,其中minShare、maxShare用于排序,fairShare用于抢占式调度,后文会讲到。此外还有权重属性(weight),也会用于排序。对于queue而言,minShare、maxShare就是fair-scheduler.xml里配置的minResource和maxResource,weight也是直接配置的。对于application而言minResource直接返回0,maxResource直接返回Integer.MAX_VALUE,weight如果没有配置yarn.scheduler.fair.sizebasedweight=true就直接返回1.0,意味着所有app的权重是相同的。

FairSharePolicy在比较两个Schedulable对象时,先看是否有已分配资源小于minShare的,如果是说明当前Scheduable处于饥饿状态,应该被优先满足。如果两个Schedulable都处于饥饿状态,就看谁占minShare的比例更小(谁更饿)。如果没有饥饿状态的,就比较两个Schedulable已用资源与权重的比例,这个比例越大,说明占用了越多的资源,为了公平,应该给另一个Schedulable分配资源。

DominantResourceFairnessPolicy是YARN中DRF算法的实现,会考虑内存和CPU两种资源,排序逻辑会更复杂些,这里略过。

任务分配过程

任务分配过程决定一个app被分到哪个队列。相对于资源分配过程,这个过程简单的多。因为app在提交的时候一般会指定队列名。

第一步:检查提交的app是否指定了队列名。如果没有指定,检查是否存在和用户同名的队列。如果还不存在,就提交到default队列。default队列可以在配置文件中指定,也可以在调度器初始化时默认创建。
第二步:检查ACL,当前用户是否有向指定队列提交任务的权限。
第三步:如果通过ACL检查,发出一个APP_ACCEPTED事件。app加入LeafQueue的children,开始等待资源分配。

FairScheduler的一个特点是客户端可以动态创建队列,即指定一个不存在的队列。但生产环境中这一般是不允许的。

抢占式调度

FairScheduler特有的功能。当某个队列资源不足时,调度器会杀死其他队列的container以释放资源,分给这个队列。这个特性默认是关闭的。
关键点有两个:1.启动抢占式调度的条件?2.选择哪些container去杀掉?

前文说过每个Schedulable对象都有minShare、fairShare属性。这两个属性是抢占式调度的阈值。当一个Schedulable使用的资源小于fairShare*0.5、或者小于minShare,并且持续超过一定时间(这两种情况的超时时间不同,可以设置),就会开始抢占式调度。

Schedulabe的fairShare是会不断变化的(minShare一般不会变化)。如果队列的minResource、maxResource、权重等属性变化,fairShare都要重新计算。application开始或结束,也都要重新计算fairShare。FairScheduler中有一个线程UpdateThread,默认每0.5秒调用一次update方法,就会重新计算fairShare。
计算fairShare的过程就是将“上层”Schedulable的fairShare,“公平”的分配给下层的Schedulable。计算过程从root queue开始。root queue的fairShare就是整个集群的可用资源。怎样才算公平?要综合考虑各个Schedulable的权重、minShare、maxShare,算法也是由SchedulingPolicy决定的。默认是FairSharePolicy。这个计算逻辑跟max-min fairness类似。

当FairScheduler决定开始抢占时,首先会计算要抢得的资源量。对于使用资源量小于minShare的,要恢复到minShare;对于使用量小于fairShare*0.5的,需要恢复到fairShare。将所有要恢复的资源量相加,得出要抢的的资源总量。然后遍历所有LeafQueue,找到所有资源用量大于fairShare的app,将他们在运行的container加入一个List,按优先级升序排列。然后遍历,优先杀死优先级低的container。当释放足够的资源后,抢占停止。

如何确定container的优先级?这是由AM在申请资源的时候决定的。用一个整数表示,数字越大优先级越低。以MapReduce为例,AM Container是0,Reduce Container是10,Map Contaienr是20。意味着一个map任务更容易被杀死。

抢占式调度可以一定程度上保证公平,但不可控因素比较多。如果用户的长时间任务因此失败,是不可接受的。所以生产环境一般关闭这个特性。

计算本地性

从MapReduce时代开始,“移动计算比移动数据更经济”的概念就深入人心。在YARN中,当然也继承了这一传统。这一特性主要是用来配合HDFS的,因为HDFS的多副本,任务应该尽量在选择block所在的机器上执行,可以减少网络传输的消耗。如果开启了Short-Circuit Read特性,还可以直接读本地文件,提高效率。

本地性有3个级别:NODE_LOCAL、RACK_LOCAL、OFF_SWITCH,分别代表同节点、同机架、跨机架。计算效率会依次递减。

根据前文所述,Container在申请时可以指定节点,但这不是强制的。只有NM心跳的时候才会分配资源,所以container无法一般确定自己在那个节点上执行,基本是随机的。scheduler能做的只是尽量满足NODE_LOCAL,尽量避免OFF_SWITCH。计算本地性更多的要AM端配合,当AM拿到资源后,优先分配给NODE_LOCAL的任务。

但FairScheduler中,允许一个app错过若干次调度机会,以便能分到一个NODE_LOCAL的节点。由yarn.scheduler.fair.locality.threshold.node控制。这个参数是一个百分比,表示相对整个集群的节点数目而言,一个app可以错过多少次机会。
比如yarn.scheduler.fair.locality.threshold.node为0.2,集群节点数为10。那么FairScheduler分配这个资源时,发现当前发来心跳的NM不能满足这个app的NODE_LOCAL要求,就会跳过,继续寻找下一个APP。相当于这个app错过一次调度机会,最多可以错过2次。
对RACK_LOCAL而言,有一个参数yarn.scheduler.fair.locality.threshold.rack,作用差不多。

转载自:YARN资源调度策略

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YARN调度器是用于分配和管理集群资源的组件。YARN支持多种调度器,包括Fair Scheduler(公平调度器)、Capacity Scheduler(容量调度器)和FIFO Scheduler(先进先出调度器)。 Fair Scheduler是一种公平调度器,它根据预先配置的规则将资源分配给正在运行的应用程序。它允许大任务和小任务在提交的同时获得一定的系统资源,避免了大任务阻塞小任务的情况。\[2\] Capacity Scheduler是一种容量调度器,它允许对集群资源进行细粒度的划分和管理。每个队列都被分配了一定的资源,并且可以限制每个队列执行的作业数量。这使得不同的应用程序可以共享集群资源,提高了资源利用率。\[2\] FIFO Scheduler是一种先进先出调度器,它按照应用程序提交的顺序将其排成一个队列,并按照顺序分配资源。这是最简单和最容易理解的调度器,但不适用于共享集群,因为大任务可能会阻塞其他任务的执行。\[2\] 根据你提供的引用内容,Fair Scheduler的配置可以在YARN配置文件中的yarn.resourcemanager.scheduler.class属性中指定为org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler。\[1\] 总之,YARN调度器根据不同的需求和场景,提供了多种调度策略,以便更好地管理和分配集群资源。 #### 引用[.reference_title] - *1* [yarn 的三种 scheduler](https://blog.csdn.net/qq_34077611/article/details/79893977)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [YARN调度器(Scheduler)详解](https://blog.csdn.net/lovedieya/article/details/107447102)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [YARN的调度器Scheduler](https://blog.csdn.net/CyAurora/article/details/119277073)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值