Hadoop3.2.0 YARN Fair Scheduler

Hadoop:Fair Scheduler

目的

本文档描述了FairScheduler,它是Hadoop的可插拔调度程序,允许YARN应用程序公平地共享大型集群中的资源。

介绍

公平调度是一种为应用程序分配资源的方法,使得所有应用程序平均可以随时间获得相等的资源份额。Hadoop NextGen能够调度多种资源类型。默认情况下,公平调度程序仅基于内存来确定调度公平性决策。它可以配置为使用Ghodsi等人开发的Dominant Resource Fairness的概念同时调度内存和CPU。当有一个应用程序在运行时,该应用程序将使用整个群集。当提交其他应用程序时,将释放的资源分配给新应用程序,以便最终每个应用程序获得的资源大致相同。与构成应用程序队列的默认Hadoop调度程序不同,这可以让短应用程序在合理的时间内完成,同时不会使长期存在的应用程序挨饿。它也是在多个用户之间共享群集的合理方式。最后,公平共享还可以与应用优先级一起使用 - 优先级用作权重来确定每个应用应获得的总资源的比例。

调度程序将应用程序进一步组织为“队列”,并在这些队列之间公平地共享资源。默认情况下,所有用户共享一个名为“default”的队列。如果应用程序专门在容器资源请求中列出队列,则将请求提交给该队列。还可以通过配置基于请求中包含的用户名来分配队列。在每个队列中,调度策略用于在正在运行的应用程序之间共享资源。默认值是基于内存的公平共享,但也可以配置具有显性资源公平性的FIFO和多资源。可以按层次结构排列队列以划分资源,并使用权重配置以按特定比例共享群集。

除了提供公平共享之外,Fair Scheduler还允许为队列分配保证的最小份额,这对于确保某些用户,组或生产应用程序始终获得足够的资源非常有用。当队列包含应用程序时,它至少获得其最小份额,但是当队列不需要其完全保证共享时,超出部分将在其他正在运行的应用程序之间分配。这使得调度程序可以保证队列容量,同时在这些队列不包含应用程序时有效地利用资源。

Fair Scheduler允许所有应用程序默认运行,但也可以通过配置文件限制每个用户和每个队列运行的应用程序数量。当用户必须一次提交数百个应用程序时,这通常很有用,或者一般来说,如果同时运行太多应用程序会导致创建过多的中间数据或过多的上下文切换,则可以提高性能。限制应用程序不会导致任何后续提交的应用程序失败,只会在调度程序的队列中等待,直到某些用户的早期应用程序完成为止。

具有可插入策略的分层队列

公平调度程序支持分层队列。所有队列都来自名为“root”的队列。可用资源以典型的公平调度方式分布在根队列的子节点之间。然后,孩子们以相同的方式将分配给他们的资源分配给他们的孩子。应用程序只能安排在叶子队列上。通过将队列作为其父项的子元素放在公平调度程序分配文件中,可以将队列指定为其他队列的子项。

队列的名称以其父项的名称开头,句点为分隔符。因此,根队列下名为“queue1”的队列将被称为“root.queue1”,名为“parent1”的队列中名为“queue2”的队列将被称为“root.parent1.queue2”。当引用队列时,名称的根部分是可选的,因此queue1可以被称为“queue1”,而queue2可以被称为“parent1.queue2”。

此外,公平调度程序允许为每个队列设置不同的自定义策略,以允许以用户想要的任何方式共享队列的资源。可以通过扩展org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.SchedulingPolicy来构建自定义策略。FifoPolicy,FairSharePolicy(默认)和DominantResourceFairnessPolicy是内置的,可以很容易地使用。

原始(MR1)Fair Scheduler中不存在某些加载项。其中,使用自定义策略管理优先级“提升”某些应用程序。

自动将应用程序放入队列中

Fair Scheduler允许管理员配置自动将提交的应用程序放入适当队列的策略。放置可以取决于提交者的用户和组以及应用程序传递的请求队列。策略由一组规则组成,这些规则按顺序应用以对传入的应用程序进行分类。每个规则要么将应用程序放入队列,拒绝它,要么继续执行下一个规则。有关如何配置这些策略,请参阅下面的分配文件格式。

安装

要使用Fair Scheduler,首先在yarn-site.xml中分配适当的调度程序类:

<property>
  <name>yarn.resourcemanager.scheduler.class</name>
  <value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler</value>
</property>

配置

自定义Fair Scheduler通常涉及更改两个文件。首先,可以通过在现有配置目录的yarn-site.xml文件中添加配置属性来设置调度程序范围的选项。其次,在大多数情况下,用户需要创建一个分配文件,列出存在哪些队列以及它们各自的权重和容量。分配文件每10秒重新加载一次,允许动态更改。

可以放在yarn-site.xml中的属性

属性 描述
yarn.scheduler.fair.allocation.file 分配文件的路径。除了某些策略默认值之外,分配文件是描述队列及其属性的XML清单。此文件必须采用下一节中描述的XML格式。如果给出了相对路径,则在类路径(通常包括Hadoop conf目录)上搜索文件。默认为fair-scheduler.xml。
yarn.scheduler.fair.user-as-default-queue 在未指定队列名称的情况下,是否将与分配关联的用户名用作缺省队列名称。如果将其设置为“false”或未设置,则所有作业都有一个共享的默认队列,名为“default”。默认为true。如果在分配文件中给出了队列放置策略,则忽略此属性。
yarn.scheduler.fair.preemption 是否使用抢占。默认为false。
yarn.scheduler.fair.preemption.cluster-utilization-threshold 抢占后的利用率阈值。利用率计算为所有资源中使用量与容量的最大比率。默认为0.8f。
yarn.scheduler.fair.sizebasedweight 是否根据个人应用的大小为各个应用分配份额,而不是为所有应用提供相同的份额,无论大小。设置为true时,应用程序按自然对数加上应用程序的总请求内存加权,除以自然对数2.默认为false。
yarn.scheduler.fair.assignmultiple 是否允许在一个心跳中进行多个容器分配。默认为false。
yarn.scheduler.fair.dynamic.max.assign 如果assignmultiple为true,则是否动态确定可在一个心跳中分配的资源量。打开时,节点上大约一半未分配的资源将分配给单个心跳中的容器。默认为true。
yarn.scheduler.fair.max.assign 如果assignmultiple为true且dynamic.max.assign为false,则可以在一个心跳中分配的最大容器数量。默认为-1,不设置限制。
yarn.scheduler.fair.locality.threshold.node 对于在特定节点上请求容器的应用程序,自上次容器分配以来在接受另一个节点上的放置之前等待的调度机会的数量。表示为0到1之间的浮点数,它作为簇大小的一部分,是要传递的调度机会的数量。默认值-1.0表示不会调出任何调度机会。
yarn.scheduler.fair.locality.threshold.rack 对于在特定机架上请求容器的应用程序,自上次容器分配以来在接受另一个机架上的放置之前等待的调度机会数。表示为0到1之间的浮点数,它作为簇大小的一部分,是要传递的调度机会的数量。默认值-1.0表示不会调出任何调度机会。
yarn.scheduler.fair.allow-undeclared-pools 如果是这样,则可以在应用程序提交时创建新队列,无论是因为提交者将它们指定为应用程序的队列,还是因为user-as-default-queue属性将它们放在那里。如果这是错误的,则只要将应用程序放置在未在分配文件中指定的队列中,就会将其置于“默认”队列中。默认为true。如果在分配文件中给出了队列放置策略,则忽略此属性。
yarn.scheduler.fair.update-interval-ms 锁定调度程序并重新计算公平份额的间隔,重新计算需求,并检查是否有任何事项应该抢占。默认为500毫秒。
yarn.resource-types.memory-mb.increment-allocation fairscheduler以此值的增量授予内存。如果您提交的资源请求不是memory-mb.increment-allocation的倍数,则请求将向上舍入到最接近的增量。默认为1024 MB。
yarn.resource-types.vcores.increment-allocation fairscheduler以此值的增量授予vcores。如果您提交的资源请求不是vcores.increment-allocation的倍数,则请求将向上舍入到最接近的增量。默认为1。
yarn.resource-types.<resource>.increment-allocation fairscheduler 以此值的增量授予<resource>。如果您提交的资源请求不是<resource> .increment-allocation的倍数,则请求将向上舍入到最接近的增量。如果未为资源指定此属性,则不会应用增量舍入。如果未指定单位,则假定资源的默认单位。
yarn.scheduler.increment-allocation-mb 内存的分配增量。不再是首选。请改用yarn.resource-types.memory-mb.increment-allocation。默认为1024 MB。
yarn.scheduler.increment-allocation-vcores CPU vcores的分配增量。不再是首选。请改用yarn.resource-types.vcores.increment-allocation。默认为1。

分配文件格式

分配文件必须是XML格式。格式包含五种类型的元素:

  • 队列元素:代表队列。队列元素可以采用可选属性“type”,当设置为“parent" 时,它将使其成为父队列。当我们想要创建父队列而不配置任何叶队列时,这非常有用。每个队列元素可能包含以下属性:

    • minResources:队列有权获得的最小资源,格式为“X mb,Y vcores”或“vcores = X,memory-mb = Y”。指定内存和CPU以外的资源时需要后一种形式。对于单资源公平性策略,将忽略vcores值。如果不满足队列的最小份额,则将在同一父级下的任何其他队列之前提供可用资源。在单资源公平策略下,如果队列的内存使用率低于其最小内存份额,则认为队列不满意。在主导资源公平性下,如果队列的主要资源相对于群集容量的使用低于其对该资源的最小份额,则认为该队列不满意。如果在这种情况下多个队列不满意,资源以相关资源使用率与其最小值之间的最小比率进入队列。请注意,当应用程序提交到队列时,低于其最小值的队列可能不会立即达到最小值,因为已经运行的作业可能正在使用这些资源。

    • maxResources:队列将分配的最大资源,以“X%”,“X%cpu,Y%memory”,“X mb,Y vcores”或“vcores = X,memory-mb = Y”的形式表示。指定内存和CPU以外的资源时需要最后一种形式。在最后一种形式中,X和Y可以是没有单位的百分比或整数资源值。在后一种情况下,将从为该资源配置的默认单位推断出单位。不会为队列分配一个容器,该容器会将其总使用量超过此限制。

    • maxContainerAllocation:队列可以为单个容器分配的最大资源,以“X mb,Y vcores”或“vcores = X,memory-mb = Y”的形式表示。指定内存和CPU以外的资源时需要后一种形式。如果未设置该属性,则它的值将从父队列继承。它的默认值是yarn.scheduler.maximum-allocation-mb。不能高于maxResources。此属性对根队列无效。

    • maxChildResources:ad hoc子队列将分配的最大资源,以“X%”,“X%cpu,Y%memory”,“X mb,Y vcores”或“vcores = X,memory-mb =”的形式表示Y”。指定内存和CPU以外的资源时需要最后一种形式。在最后一种形式中,X和Y可以是没有单位的百分比或整数资源值。在后一种情况下,将从为该资源配置的默认单位推断出单位。将不会为ad hoc子队列分配一个容器,该容器会将其聚合使用量超过此限制。

    • maxRunningApps:限制队列中的应用程序数量一次运行

    • maxAMShare:限制可用于运行应用程序主服务器的队列公平共享的分数。此属性只能用于叶队列。例如,如果设置为1.0f,那么叶队列中的AM最多可占用内存和CPU公平份额的100%。-1.0f的值将禁用此功能,并且不会检查amShare。默认值为0.5f。

    • weight:与其他队列不成比例地共享群集。权重默认为1,权重为2的队列应该获得的资源大约是具有默认权重的队列的两倍。

    • schedulingPolicy:设置任意队列的调度策略。允许的值为“fifo”/“fair”/“drf”或任何扩展org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.SchedulingPolicy的类。默认为“合理”。如果“fifo”,具有较早提交时间的应用程序将优先考虑容器,但如果在满足早期应用程序的请求后群集上有剩余空间,则稍后提交的应用程序可以同时运行。

    • aclSubmitApps:可以将应用程序提交到队列的用户和/或组的列表。有关此列表的格式以及队列ACL的工作方式的详细信息,请参阅下面的ACL部分。

    • aclAdministerApps:可以管理队列的用户和/或组的列表。目前唯一的管理操作是查杀应用程序。有关此列表的格式以及队列ACL的工作方式的详细信息,请参阅下面的ACL部分。

    • minSharePreemptionTimeout:队列在尝试抢占容器以从其他队列获取资源之前,队列在其最小份额之下的秒数。如果未设置,队列将从其父队列继承该值。默认值为Long.MAX_VALUE,这意味着在设置有意义的值之前,它不会抢占容器。

    • fairSharePreemptionTimeout:队列在尝试抢占容器以从其他队列中获取资源之前,队列处于其公平共享阈值之下的秒数。如果未设置,队列将从其父队列继承该值。默认值为Long.MAX_VALUE,这意味着在设置有意义的值之前,它不会抢占容器。

    • fairSharePreemptionThreshold:队列的公平共享抢占阈值。如果队列在不接收fairSharePreemptionThreshold * fairShare资源的情况下等待fairSharePreemptionTimeout,则允许抢占容器以从其他队列获取资源。如果未设置,队列将从其父队列继承该值。默认值为0.5f。

    • allowPreemptionFrom:确定是否允许调度程序从队列中抢占资源。默认值为true。如果队列将此属性设置为false,则此属性将递归应用于所有子队列。

    • reservation:向ReservationSystem表示队列的资源可供用户保留。这仅适用于叶队列。如果未配置此属性,则无法保留叶队列。

  • 用户元素:表示管理各个用户行为的设置。它们可以包含单个属性:maxRunningApps,对特定用户正在运行的应用程序数量的限制。

  • userMaxAppsDefault元素:为未以其他方式指定限制的任何用户设置默认运行应用程序限制。

  • defaultFairSharePreemptionTimeout元素:设置根队列的公平共享抢占超时; 被根队列中的fairSharePreemptionTimeout元素覆盖。默认设置为Long.MAX_VALUE。

  • defaultMinSharePreemptionTimeout元素:设置根队列的最小共享抢占超时; 被根队列中的minSharePreemptionTimeout元素覆盖。默认设置为Long.MAX_VALUE。

  • defaultFairSharePreemptionThreshold元素:设置根队列的公平共享抢占阈值; 被根队列中的fairSharePreemptionThreshold元素覆盖。默认设置为0.5f。

  • queueMaxAppsDefault元素:设置队列的默认运行应用限制; 被每个队列中的maxRunningApps元素覆盖。

  • queueMaxResourcesDefault元素:设置队列的默认最大资源限制; 被每个队列中的maxResources元素覆盖。

  • queueMaxAMShareDefault元素:设置队列的默认AM资源限制; 被每个队列中的maxAMShare元素覆盖。

  • defaultQueueSchedulingPolicy元素:设置队列的默认调度策略; 如果指定,则由每个队列中的schedulingPolicy元素覆盖。默认为“合理”。

  • reservation-agent元素:设置ReservationAgent实现的类名,它试图将用户的预留请求放入Plan中。默认值为org.apache.hadoop.yarn.server.resourcemanager.reservation.planning.AlignedPlannerWithGreedy。

  • reservation-policy元素:设置SharingPolicy实现的类名,验证新保留是否违反任何不变量。默认值为org.apache.hadoop.yarn.server.resourcemanager.reservation.CapacityOverTimePolicy。

  • reservation-planner元素:设置Planner实现的类名,如果Plan容量低于(由于计划维护或节点故障)用户保留的资源,则调用该类名称。默认值为org.apache.hadoop.yarn.server.resourcemanager.reservation.planning.SimpleCapacityReplanner,它扫描计划并以相反的接受顺序(LIFO)贪婪地删除预留,直到预留资源在计划容量内。

  • queuePlacementPolicy元素:包含一系列规则元素,告诉调度程序如何将传入的应用程序放入队列。规则按列出的顺序应用。规则可能需要参数。所有规则都接受“create”参数,该参数指示规则是否可以创建新队列。“创建”默认为true; 如果设置为false并且规则将应用程序放入未在分配文件中配置的队列,我们​​将继续执行下一个规则。最后一条规则必须是永不发出继续的规则。有效规则是:

    • specified:将应用程序放入其请求的队列中。如果应用程序请求没有队列,即它指定“默认”,我们继续。如果应用程序请求以句点开头或结尾的队列名称,则将拒绝诸如“.q1”或“q1。”之类的名称。

    • user:将应用程序放入一个队列,其中包含提交它的用户的名称。用户名中的句点将替换为“_dot_”,即用户“first.last”的队列名称为“first_dot_last”。

    • primaryGroup:将应用程序放入一个队列,其中包含提交它的用户的主要组的名称。组名称中的句点将替换为“_dot_”,即组“one.two”的队列名称为“one_dot_two”。

    • secondaryGroupExistingQueue:将应用程序放入一个队列,其名称与提交它的用户的辅助组相匹配。将选择与已配置队列匹配的第一个辅助组。组名称中的句点将替换为“_dot_”,即如果存在这样的队列,则具有“one.two”作为其一个辅助组的用户将被置于“one_dot_two”队列中。

    • nestedUserQueue:将应用程序放入队列中,其中包含嵌套规则建议的队列下的用户名。这类似于'用户'规则,区别在于'nestedUserQueue'规则,用户队列可以在任何父队列下创建,而'user'规则仅在根队列下创建用户队列。请注意,仅当嵌套规则返回父队列时,才会应用nestedUserQueue规则。可以通过将队列的'type'属性设置为'parent'或通过在该队列下配置至少一个叶子使其成为父队列来配置父队列。请参阅示例用例的示例分配。

    • default:将应用程序放入默认规则的'queue'属性中指定的队列中。如果未指定“queue”属性,则将应用程序置于“root.default”队列中。

    • reject:该应用被拒绝。

    这里给出了一个示例分配文件:

<?xml version="1.0"?>
<allocations>
  <queue name="sample_queue">
    <minResources>10000 mb,0vcores</minResources>
    <maxResources>90000 mb,0vcores</maxResources>
    <maxRunningApps>50</maxRunningApps>
    <maxAMShare>0.1</maxAMShare>
    <weight>2.0</weight>
    <schedulingPolicy>fair</schedulingPolicy>
    <queue name="sample_sub_queue">
      <aclSubmitApps>charlie</aclSubmitApps>
      <minResources>5000 mb,0vcores</minResources>
    </queue>
    <queue name="sample_reservable_queue">
      <reservation></reservation>
    </queue>
  </queue>

  <queueMaxAMShareDefault>0.5</queueMaxAMShareDefault>
  <queueMaxResourcesDefault>40000 mb,0vcores</queueMaxResourcesDefault>

  <!-- Queue 'secondary_group_queue' is a parent queue and may have
       user queues under it -->
  <queue name="secondary_group_queue" type="parent">
  <weight>3.0</weight>
  <maxChildResources>4096 mb,4vcores</maxChildResources>
  </queue>

  <user name="sample_user">
    <maxRunningApps>30</maxRunningApps>
  </user>
  <userMaxAppsDefault>5</userMaxAppsDefault>

  <queuePlacementPolicy>
    <rule name="specified" />
    <rule name="primaryGroup" create="false" />
    <rule name="nestedUserQueue">
        <rule name="secondaryGroupExistingQueue" create="false" />
    </rule>
    <rule name="default" queue="sample_queue"/>
  </queuePlacementPolicy>
</allocations>

请注意,为了向后兼容原始的FairScheduler,“queue”元素可以改为命名为“pool”元素。

队列访问控制列表

队列访问控制列表(ACL)允许管理员控制谁可以对特定队列执行操作。它们配置了aclSubmitApps和aclAdministerApps属性,可以为每个队列设置。目前唯一受支持的管理操作是终止应用程序。管理员也可以向其提交申请。这些属性采用“user1,user2 group1,group2”或“group1,group2”等格式的值。如果用户/组是队列ACL的成员或任何队列的祖先的队列ACL的成员,则允许对队列执行操作。因此,如果queue2在queue1中,并且user1在queue1的ACL中,并且user2在queue2的ACL中,则两个用户都可以提交到queue2。

注意:分隔符是空格字符。要仅指定ACL组,请使用空格字符开始值。

默认情况下,根队列的ACL为“*”,因为ACL向下传递,意味着每个人都可以从每个队列提交并终止应用程序。要开始限制访问,请将根队列的ACL更改为“*”以外的其他值。

预订访问控制列表

预留访问控制列表(ACL)允许管理员控制可以对特定队列执行预留操作的人员。它们配置了aclAdministerReservations,aclListReservations和aclSubmitReservations属性,可以为每个队列设置。目前,支持的管理操作是更新和删除预留。管理员还可以提交并列出队列中的所有预留。这些属性采用“user1,user2 group1,group2”或“group1,group2”等格式的值。如果用户/组是预留ACL的成员,则允许对队列执行操作。请注意,任何用户都可以更新,删除或列出自己的预订。如果启用了预留ACL但未定义,则每个人都可以访问。

配置ReservationSystem

Fair Scheduler支持ReservationSystem,允许用户提前预留资源。应用程序可以通过在提交期间指定reservationId来在运行时请求保留资源。可以在yarn-site.xml中为ReservationSystem配置以下配置参数。

属性 描述
yarn.resourcemanager.reservation-system.enable 必需参数:在ResourceManager中启用ReservationSystem。预期的布尔值。默认值为false,即默认情况下不启用ReservationSystem。
yarn.resourcemanager.reservation-system.class 可选参数:ReservationSystem的类名。根据配置的调度程序选择默认值,即如果配置了FairScheduler,则它是FairReservationSystem。
yarn.resourcemanager.reservation-system.plan.follower 可选参数:在计时器上运行的PlanFollower的类名,并将FairScheduler与计划同步,反之亦然。根据配置的调度程序选择默认值,即如果配置了FairScheduler,则它是FairSchedulerPlanFollower。
yarn.resourcemanager.reservation-system.planfollower.time-step 可选参数:PlanFollower计时器的频率(以毫秒为单位)。期望值很高。默认值为1000

所述ReservationSystem集成了公平调度队列层级,并且可以配置为只为叶队列。详细说明在分配文件格式部分中。

管理

公平调度程序通过以下几种机制为运行时的管理提供支持:

在运行时修改配置

通过编辑分配文件,可以在运行时修改最小份额,限制,权重,抢占超时和队列调度策略。调度程序将在看到它被修改后10-15秒重新加载此文件。

通过Web UI进行监控

可以通过ResourceManager的Web界面在http://*ResourceManager URL*/cluster/scheduler. 中检查当前的应用程序,队列和公平共享。

可以在Web界面上的每个队列中看到以下字段:

  • Used Resources - 分配给队列中容器的资源总和。

  • Num Active Applications - 队列中至少收到一个容器的应用程序数。

  • Num Pending Applications - 队列中尚未收到任何容器的应用程序数。

  • 最小资源 - 保证队列的最低资源配置。

  • 最大资源 - 配置的最大资源允许进入队列。

  • 瞬时公平份额 - 队列的即时公平资源份额。这些共享仅考虑活动队列(具有正在运行的应用程序的队列),并用于调度决策。当其他队列不使用队列时,可以为队列分配超出其共享的资源。资源消耗等于或低于其瞬时公平份额的队列将永远不会抢占其容器。

  • 稳定的公平份额 - 队列稳定的公平份额资源。这些共享考虑了所有队列,无论它们是否处于活动状态(已运行应用程序)。这些计算频率较低,仅在配置或容量发生变化时才会更改。它们旨在提供用户可以预期的资源可见性,从而显示在Web UI中。

在队列之间移动应用程序

Fair Scheduler支持将正在运行的应用程序移动到不同的队列。这对于将重要应用程序移动到更高优先级队列或将不重要的应用程序移动到较低优先级队列非常有用。可以通过运行纱线应用程序移动应用程序-movetoqueue appID -queue targetQueueName。

当应用程序移动到队列时,其现有分配将使用新队列的分配而不是旧分配来计算,以确定公平性。如果将应用程序的资源添加到该队列将违反其maxRunningApps或maxResources约束,则尝试将应用程序移动到队列将失败。

倾倒公平调度程序状态

Fair Scheduler能够定期转储其状态。默认情况下禁用它。管理员可以通过将org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler.statedump日志记录级别设置为DEBUG 来启用它。

默认情况下,Fair Scheduler日志会转到Resource Manager日志文件。Fair Scheduler状态转储可能会生成大量日志数据。取消注释log4j.properties中的“公平调度程序状态转储”部分,以将状态转储到单独的文件中。

 

 

原文链接: https://hadoop.apache.org/docs/r3.2.0/

发布了295 篇原创文章 · 获赞 788 · 访问量 33万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 精致技术 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览