首页 > 其他分享 >Yarn Scheduler调度器解析

Yarn Scheduler调度器解析

时间:2023-10-16 22:32:37浏览次数:32  
标签:node capacity scheduler 队列 调度 yarn Yarn Scheduler 解析

1. 背景

在Yarn中,资源调度是最核心的功能。在https://blog.51cto.com/u_15327484/7835300文章中,介绍了Yarn调度的核心接口ResourceScheduler通过nodeUpdate方法调度资源,通过allocate方法获取调度结果。

ResourceScheduler常用的ResourceScheduler实现就是FairScheduler和CapacityScheduler,它们的调度流程都非常长。本文会围绕源码介绍调度流程,一些细节会忽略。

2. 调度器结构

  1. 队列:在FairScheduler和CapacityScheduler中,都维护了队列这一结构。每个队列拥有指定的资源,可以限制指定用户将作业运行在该队列之上。
  2. 应用:每个队列之上,有多个应用请求,这些应用请求中包含已经获得的资源以及需要分配的资源。
  3. 容器:每个应用下的资源请求以容器进行封装:

当resourcemanager接收到nodeUpdate请求后,会依次选择合适的队列、应用、容器进行调度。结构如下:

Untitled.png

3. CapacityScheduler调度流程

对于CapacityScheduler而言,它设计的初衷就是优先分配资源给使用率最低的队列。调度步骤如下:

  1. 选择队列。从根队列开始,使用深度优先遍历算法,从根队列开始,依次遍历子队列找出资源占用率最小的子队列,以促使队列的使用率都处于平衡状态,即每个队列都有作业在运行。若子队列为叶子队列,则选择该队列;若子队列为非叶子队列,则以该子队列为根队列重复前面的过程直到找到一个资源使用率最小的叶子队列为止。
  2. 选择应用。在Step1中选好了叶子队列后,取该队列中最早提交的应用程序(实际排序时用的 Application ID,提交时间越早的应用程序,Application ID 越小)。
  3. 选择 Container。在 Step2中选好应用程序之后,选择该应用程序中优先级最高的 Container。对于优先级相同的 Containers,优选选择满足本地性的 Container,会依次选择 node local、rack local、no local。

CapacityScheduler服务端处理NODE_UPDATE调度事件,如果没有开启异步调度,就接收心跳时进行调度:

public void handle(SchedulerEvent event) {
    switch(event.getType()) {
    case NODE_UPDATE:
    {
      NodeUpdateSchedulerEvent nodeUpdatedEvent = (NodeUpdateSchedulerEvent)event;
      RMNode node = nodeUpdatedEvent.getRMNode();
      nodeUpdate(node);
      //是否开启异步调度,如果没有开启,就在接收心跳时进行调度,否则在异步线程中进行调度
      if (!scheduleAsynchronously) {
        //开始调度
        allocateContainersToNode(getNode(node.getNodeID()));
      }
    }
}

在allocateContainersToNode方法中,开始进行父队列的资源分配:

if (node.getReservedContainer() == null) {
      //计算资源量是否大于0,否则说明集群资源已经用满了
      if (calculator.computeAvailableContainers(node.getAvailableResource(),
        minimumAllocation) > 0) {
        if (LOG.isDebugEnabled()) {
          LOG.debug("Trying to schedule on node: " + node.getNodeName() +
              ", available: " + node.getAvailableResource());
        }
        //父队列开始分配资源
        root.assignContainers(clusterResource, node, false);
      }
    } else {
      LOG.info("Skipping scheduling since node " + node.getNodeID() + 
          " is reserved by application " + 
          node.getReservedContainer().getContainerId().getApplicationAttemptId()
          );
    }

其中,资源计算calculator由以下配置指定,DominantResourceCalculator使用cpu和内存计算资源。默认配置DefaultResourceCalculator只使用内存计算资源:

<property>
        <name>yarn.scheduler.capacity.resource-calculator</name>
        <value>org.apache.hadoop.yarn.util.resource.DominantResourceCalculator</value>
    </property>

步入正题,此时要从root队列中选择合适的子队列进行调度了。此时调用ParentQueue#assignContainers方法进行调度,该方法中,调用assignContainersToChildQueues方法,将决定调度哪个子队列。它遍历子队列,即队列中越前的子队列,越优先进行调度:

//遍历子队列,即队列中越前的子队列,越优先进行调度
for (Iterator<CSQueue> iter=childQueues.iterator(); iter.hasNext();) {
      CSQueue childQueue = iter.next();
      if(LOG.isDebugEnabled()) {
        LOG.debug("Trying to assign to queue: " + childQueue.getQueuePath()
          + " stats: " + childQueue);
      }
      //子队列开始调度资源
      assignment = childQueue.assignContainers(cluster, node, needToUnreserve);
      if(LOG.isDebugEnabled()) {
        LOG.debug("Assigned to queue: " + childQueue.getQueuePath() +
          " stats: " + childQueue + " --> " + 
          assignment.getResource() + ", " + assignment.getType());
      }

      // If we do assign, remove the queue and re-insert in-order to re-sort
      if (Resources.greaterThan(
              resourceCalculator, cluster, 
              assignment.getResource(), Resources.none())) {
        // Remove and re-insert to sort
        iter.remove();
        LOG.info("Re-sorting assigned queue: " + childQueue.getQueuePath() + 
            " stats: " + childQueue);
        childQueues.add(childQueue);
        if (LOG.isDebugEnabled()) {
          printChildQueues();
        }
        break;
      }
    }

而子队列是TreeSet类型,它基于队列的使用大小,对于使用量最小的队列,越排在队列最前,越优先执行:

protected final Set<CSQueue> childQueues;
this.childQueues = new TreeSet<CSQueue>(queueComparator);

static final Comparator<CSQueue> queueComparator = new Comparator<CSQueue>() {
    @Override
    public int compare(CSQueue q1, CSQueue q2) {
      if (q1.getUsedCapacity() < q2.getUsedCapacity()) {
        return -1;
      } else if (q1.getUsedCapacity() > q2.getUsedCapacity()) {
        return 1;
      }

      return q1.getQueuePath().compareTo(q2.getQueuePath());
    }
  };

选择好合适的子队列后,最终选择到了叶子节点,叶子节点要选择合适的应用分配资源。叶子节点的实现类是LeafQueue,如下所示,它会遍历应用,依次进行优先级最高的请求进行调度,调用assignContainers方法:

//在队列中,应用越前,越先执行
for (FiCaSchedulerApp application : activeApplications) {
      
      synchronized (application) {
        // Check if this resource is on the blacklist
        if (SchedulerAppUtils.isBlacklisted(application, node, LOG)) {
          continue;
        }
        
        //获取应用中每个资源请求的不同优先级,优先级越高越先执行
        // Schedule in priority order
        for (Priority priority : application.getPriorities()) {
          ResourceRequest anyRequest =
              application.getResourceRequest(priority, ResourceRequest.ANY);
          if (null == anyRequest) {
            continue;
          }
          
          // Required resource
          Resource required = anyRequest.getCapability();

          // Do we need containers at this 'priority'?
          if (application.getTotalRequiredResources(priority) <= 0) {
            continue;
          }
          if (!this.reservationsContinueLooking) {
            if (!needContainers(application, priority, required)) {
              if (LOG.isDebugEnabled()) {
                LOG.debug("doesn't need containers based on reservation algo!");
              }
              continue;
            }
          }
          
          Set<String> requestedNodeLabels =
              getRequestLabelSetByExpression(anyRequest
                  .getNodeLabelExpression());

          // Compute user-limit & set headroom
          // Note: We compute both user-limit & headroom with the highest 
          //       priority request as the target. 
          //       This works since we never assign lower priority requests
          //       before all higher priority ones are serviced.
          Resource userLimit = 
              computeUserLimitAndSetHeadroom(application, clusterResource, 
                  required, requestedNodeLabels);          
          
          // Check queue max-capacity limit
          if (!canAssignToThisQueue(clusterResource, required,
              labelManager.getLabelsOnNode(node.getNodeID()), application, true)) {
            return NULL_ASSIGNMENT;
          }

          // Check user limit
          if (!assignToUser(clusterResource, application.getUser(), userLimit,
              application, true, requestedNodeLabels)) {
            break;
          }

          // Inform the application it is about to get a scheduling opportunity
          application.addSchedulingOpportunity(priority);
          
          // Try to schedule
          //进行调度
          CSAssignment assignment =  
            assignContainersOnNode(clusterResource, node, application, priority, 
                null, needToUnreserve);
          //省略
    }

优先级的定义如下,优先级越高,资源请求越前,越先执行:

final Set<Priority> priorities = new ConcurrentSkipListSet<Priority>(
    new org.apache.hadoop.yarn.server.resourcemanager.resource.Priority.Comparator());

public static class Comparator 
  implements java.util.Comparator<org.apache.hadoop.yarn.api.records.Priority> {
    @Override
    public int compare(org.apache.hadoop.yarn.api.records.Priority o1, org.apache.hadoop.yarn.api.records.Priority o2) {
      return o1.getPriority() - o2.getPriority();
    }
  }

应用的实现也是TreeSet,当appId越小,排序越前,先调度。appId越小,提交时间越早,越先执行:

this.activeApplications = new TreeSet<FiCaSchedulerApp>(applicationComparator);

static final Comparator<FiCaSchedulerApp> applicationComparator = 
    new Comparator<FiCaSchedulerApp>() {
    @Override
    public int compare(FiCaSchedulerApp a1, FiCaSchedulerApp a2) {
      return a1.getApplicationId().compareTo(a2.getApplicationId());
    }
  };

选择好合适的应用后,就开始选择合适的container了。如下所示,LeafQueue按优先级分别调度本地、同机架、其他机架的container请求:

private CSAssignment assignContainersOnNode(Resource clusterResource,
      FiCaSchedulerNode node, FiCaSchedulerApp application, Priority priority,
      RMContainer reservedContainer, boolean needToUnreserve) {
    Resource assigned = Resources.none();

    // Data-local
    ResourceRequest nodeLocalResourceRequest =
        application.getResourceRequest(priority, node.getNodeName());
    if (nodeLocalResourceRequest != null) {
      assigned = 
          assignNodeLocalContainers(clusterResource, nodeLocalResourceRequest, 
              node, application, priority, reservedContainer, needToUnreserve); 
      if (Resources.greaterThan(resourceCalculator, clusterResource, 
          assigned, Resources.none())) {
        return new CSAssignment(assigned, NodeType.NODE_LOCAL);
      }
    }

    // Rack-local
    ResourceRequest rackLocalResourceRequest =
        application.getResourceRequest(priority, node.getRackName());
    if (rackLocalResourceRequest != null) {
      if (!rackLocalResourceRequest.getRelaxLocality()) {
        return SKIP_ASSIGNMENT;
      }
      
      assigned = 
          assignRackLocalContainers(clusterResource, rackLocalResourceRequest, 
              node, application, priority, reservedContainer, needToUnreserve);
      if (Resources.greaterThan(resourceCalculator, clusterResource, 
          assigned, Resources.none())) {
        return new CSAssignment(assigned, NodeType.RACK_LOCAL);
      }
    }
    
    // Off-switch
    ResourceRequest offSwitchResourceRequest =
        application.getResourceRequest(priority, ResourceRequest.ANY);
    if (offSwitchResourceRequest != null) {
      if (!offSwitchResourceRequest.getRelaxLocality()) {
        return SKIP_ASSIGNMENT;
      }

      return new CSAssignment(
          assignOffSwitchContainers(clusterResource, offSwitchResourceRequest,
              node, application, priority, reservedContainer, needToUnreserve), 
              NodeType.OFF_SWITCH);
    }
    
    return SKIP_ASSIGNMENT;
  }

在container的资源分配完后,会执行FiCaSchedulerApp#allocate方法:

RMContainer allocatedContainer = 
          application.allocate(type, node, priority, request, container);

保存已经分配了资源的contianer信息:

newlyAllocatedContainers.add(rmContainer);
liveContainers.put(container.getId(), rmContainer);

当appMaster通过心跳获取资源分配结果时,就会从FiCaSchedulerApp#getAllocation中,获取这些容器,并和nodemanager通信启动容器。

4. FairScheduler调度流程

FairScheduler也是进行队列、应用、容器三个层次的调度,但是在排序方法上有所不同。

由FSParentQueue#assignContainer选择子队列。不同子队列的排序比较器是FairShareComparator:

private static final FairShareComparator COMPARATOR =
          new FairShareComparator();

TreeSet<FSQueue> sortedChildQueues = new TreeSet<>(policy.getComparator());
    readLock.lock();
    try {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Node " + node.getNodeName() + " offered to parent queue: " +
            getName() + " visiting " + childQueues.size() + " children");
      }
      sortedChildQueues.addAll(childQueues);
      for (FSQueue child : sortedChildQueues) {
        assigned = child.assignContainer(node);
        if (!Resources.equals(assigned, Resources.none())) {
          break;
        }
      }
    }

对于FairShareComparator,它只计算内存,不计算CPU资源。为保持公平起见,它的排序策略如下:

  1. 一个队列需要资源, 另外一个队列不需要资源, 则需要资源的排前面。
  2. minResource空间使用占比上的排在前面。若都需要资源的话, 对比使用的内存占minShare的比例, 比例小的排前面, (即尽量保证达到minShare)。对于队列而言minShare就是minResource,即队列必须预留的资源量。
  3. 队列资源使用占比少的排在前面。若比例相同的话, 计算出使用量与队列权重的比例, 小的排前面, 即权重大的优先, 使用量小的优先。
  4. 若还是相同, 提交时间早的优先, app id小的排前面。
private static class FairShareComparator implements Comparator<Schedulable>,
      Serializable {
    private static final long serialVersionUID = 5564969375856699313L;
 
    @Override
    public int compare(Schedulable s1, Schedulable s2) {
      int res = compareDemand(s1, s2);
 
      // Share resource usages to avoid duplicate calculation
      Resource resourceUsage1 = null;
      Resource resourceUsage2 = null;
 
      if (res == 0) {
        //资源使用量
        resourceUsage1 = s1.getResourceUsage();
        resourceUsage2 = s2.getResourceUsage();
        //资源使用量 % min resource
        res = compareMinShareUsage(s1, s2, resourceUsage1, resourceUsage2);
      }
 
      if (res == 0) {
        //资源使用量 % resources consumed
        res = compareFairShareUsage(s1, s2, resourceUsage1, resourceUsage2);
      }
 
      // Break the tie by submit time
      if (res == 0) {
        res = (int) Math.signum(s1.getStartTime() - s2.getStartTime());
      }
 
      // Break the tie by job name
      if (res == 0) {
        res = s1.getName().compareTo(s2.getName());
      }
 
      return res;
    }
  }

相对于CapcityScheduler只考虑使用队列资源最小值,FairScheduler更为复杂,它考虑的是使用量与队列规格的比值。

了解了FairScheduler队列排序后,再看下应用的排序,它也是FairShareComparator进行排序的:

private TreeSet<FSAppAttempt> fetchAppsWithDemand(boolean assignment) {
    TreeSet<FSAppAttempt> pendingForResourceApps =
        new TreeSet<>(policy.getComparator());
    readLock.lock();
    try {
      for (FSAppAttempt app : runnableApps) {
        if (!Resources.isNone(app.getPendingDemand()) &&
            (assignment || app.shouldCheckForStarvation())) {
          pendingForResourceApps.add(app);
        }
      }
    } finally {
      readLock.unlock();
    }
    return pendingForResourceApps;
  }

对于应用的排序规则如下:

  1. 一个作业需要资源, 另外一个作业不需要资源, 则需要资源的排前面。
  2. 在应用的实现类FSAppAttempt中,minShare为Resources.none(),为0,即应用没有minShare的概念。实际上已使用内存量越低的应用排序越前。
  3. 当两个应用的内存使用量相同时。在应用的实现类FSAppAttempt中,weights的实现是应用的优先级,内存使用量/优先级越小,越先执行。
  4. 若还是相同, 提交时间早的优先, app id小的排前面。

对于容器的优先级,也是本地、同机架、其他机架的顺序,这里不展开。

5. FairScheduler和CapacityScheduler对比

如下,Capacity在队列排序中,基于资源利用率进行排序;在应用排序中,使用FIFO。在Hadoop3.3.5版本中,应用排序支持Fair了,它对所有应用按资源使用比例从小到大、提交时间从前往后的顺序排序。

FairScheduler在队列排序和应用排序都使用FairShareComparator进行排序(当然也可以选择其他排序器):

Untitled 1.png

本文只描述两个最核心的区别,其他重点后续再看。

  • 备注:CapacityScheduler处理心跳调度,还可以异步调度。FairScheduler则可以连续调度。但是社区反馈连续调度当nm节点数超过100,会严重影响调度性能:https://issues.apache.org/jira/browse/YARN-6487,处于废弃状态。

6. FairScheduler相关配置

FairScheduler配置 CapacityScheduler配置 配置说明
minResources yarn.scheduler.capacity.<queue-path>.capacity 队列最小资源
maxResources yarn.scheduler.capacity.<queue-path>.maximum-capacity 队列最大资源
aclSubmitApps yarn.scheduler.capacity.<queue-path>.acl_submit_applications 队列提交授权
aclAdministerApps yarn.scheduler.capacity.<queue-path>.acl_administer_queue 队列管理授权
maxAMShare yarn.scheduler.capacity.<queue-path>.maximum-am-resource-percent AM资源限额管理
queueMaxAppsDefault yarn.scheduler.capacity.<queue-path>.maximum-applications 最大apps限制
maxRunningApps yarn.scheduler.capacity.<queue-path>.max-parallel-apps 最大运行apps限制
maxContainerAllocation (“X mb, Y vcores”) yarn.scheduler.capacity.<queue-path>.maximum-allocation-mb yarn.scheduler.capacity.<queue-path>.maximum-allocation-vcores container最大资源
yarn.scheduler.fair.preemption yarn.resourcemanager.scheduler.monitor.enable 是否开启抢占,默认值为false
yarn.scheduler.fair.allow-undeclared-pools yarn.scheduler.capacity.<queue-path>.auto-create-child-queue.enabled 是否允许自动创建队列,FS默认为true,CS默认为false
yarn.scheduler.fair.user-as-default-queue yarn.scheduler.capacity.queue-mappings 用户队列映射,FS默认为true

fairscheduler配置示例:

    <pool name="quque1">
        <minSharePreemptionTimeout>600</minSharePreemptionTimeout>
        <maxResources>2400000 mb,1200 vcores</maxResources>
        <aclAdministerApps>quque1</aclAdministerApps>
        <aclSubmitApps>quque1</aclSubmitApps>
        <minResources>16000 mb,8 vcores</minResources>
      </pool>

webUI示例:

Untitled 2.png

capacityscheduler配置示例:

  <property>
    <name>yarn.scheduler.capacity.root.gzsushixuan.maximum-applications</name>
    <value>10000</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.gzsushixuan.minimum-user-limit-percent</name>
    <value>100</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.gzsushixuan.acl_administer_queue</name>
    <value>gzsushixuan</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.gzsushixuan.capacity</name>
    <value>[memory=16000,vcores=8]</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.gzsushixuan.user-limit-enable</name>
    <value>false</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.gzsushixuan.ordering-policy</name>
    <value>fair</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.gzsushixuan.acl_submit_applications</name>
    <value>gzsushixuan</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.gzsushixuan.user-limit-factor</name>
    <value>10000</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.gzsushixuan.maximum-capacity</name>
    <value>[memory=2400000,vcores=1200]</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.gzsushixuan.state</name>
    <value>RUNNING</value>
  </property>
  <property>
    <name>yarn.scheduler.capacity.root.gzsushixuan.maximum-am-resource-percent</name>
    <value>300.0</value>
  </property>

webUI示例:

Untitled 3.png

capacityScheduler配置解释:

  • yarn.scheduler.capacity.<queue-path>.capacity:最小资源量,对应webUI的Absolute Configured Capacity。
  • yarn.scheduler.capacity.<queue-path>.maximum-capacity:最大资源量,对应webUI的Absolute Configured Max Capacity。
  • yarn.scheduler.capacity.<queue-path>.user-limit-factor:单个用户最多能够使用占比,如果已用户命令队列,可以设置100%。
  • yarn.scheduler.capacity.resource-calculator: org.apache.hadoop.yarn.util.resource.DefaultResourseCalculator,它只会计算内存。DominantResourceCalculator则会计算内存和CPU。
  • 如果队列配置的是绝对资源,当所有队列的capacity总和大于集群总资源时,队列的最小有效资源(Effective Capacity)= 队列最小资源占所有队列资源占比 * 集群总资源。
  • 如果队列配置的是绝对资源,当所有队列的capacity总和大于集群总资源时,队列的最大有效资源(Effective Max Capacity)= 队列最大资源占所有队列资源占比 * 集群总资源。
  • 当队列配置的是绝对资源时,叶子队列设置的maximum-applications无效,会被重新计算,计算方式如下:队列的maximum-applications = 队列资源占比 * yarn.scheduler.capacity.maximum-applications(默认为10000)。

标签:node,capacity,scheduler,队列,调度,yarn,Yarn,Scheduler,解析
From: https://blog.51cto.com/u_15327484/7894282

相关文章

  • Nest.js Controller 解析:探索路由和请求处理的强大功能
    Controller 它主要是负责特定路由请求处理并将响应结果返回给客户端。每个控制器它会有多个路由,不同路由对应不同的业务请求处理。在Nest 中,创建一个控制器,应该使用类和装饰器,装饰器会使类相关联的数据的关联起来,将请求绑定到相应的控制器。可以使用Nest-cli提供的......
  • 计算机网络的分组转发算法例题解析
    例题展示例题解决将题目中要求的ip地址与相对应的子网掩码进行二进制上面的相与即可,若是与目的ip地址一致,那么就直接跳转到其对应的那个接口;否则就直接跳转到默认接口;本题答案为R2;......
  • Python爬虫:抖音 JS XB逆向解析
    哈喽兄弟们,抖音现在有JS加密,以前的方法爬不了饿了,今天来实现一下某音短视频的JS逆向解析。知识点动态数据抓包`在这里插入代码片`requests发送请求X-Bogus 参数逆向环境模块python 3.8               运行代码pycharm 2022.3           辅......
  • yarn的常用命令
    yarn的常用命令:yarn-v//查看yarn版本yarnconfiglist//查看yarn配置yarnconfiggetregistry//查看当前yarn源//修改yarn源(此处为淘宝的源)yarnconfigsetregistryhttps://registry.npm.taobao.org//yarn安装依赖yarnadd包名//局部安......
  • yarn和npm对比
    Yarn和npm都是JavaScript的包管理工具,由不同的公司开发和维护。它们之间有一些相似之处,但也存在一些显著的区别。在功能上,npm和Yarn有许多共同的特性,如安装依赖、全局安装等。然而,它们在处理依赖关系的方式上存在差异。对于npm和yarn,它们将为每个项目的node_modules文件......
  • 开关电源三大基础拓扑解析:BUCK/BOOST/BUCK-BOOST
    1、BUCK拓扑电路Buck电路是一个降压电路,Vi=Vls+Vo。因Vi>Vo,故具有降压作用。(1)开关管S导通阶段 当开关闭合时,续流二极管D是截止的,由于输入电压Vi与储能电感Ls接通,因此输入-输出压差(Vi-Vo)就加在Ls上,使通过Ls上的电流线性地增加。在此阶段,除向负载供电外,还有一部分电能储存......
  • Spark入门指南:从基础概念到实践应用全解析
    本文已收录至GitHub,推荐阅读......
  • Redis持久化深度解析
    本文已收录至GitHub,推荐阅读......
  • 解析“字符指针变量,数组指针变量,二维数组”
    1.字符指针变量字符指针变量是存放地址的charch='w'; char*pc=&ch; *pc='w';表达式的两个属性:【值属性】计算后的值是多少【类型属性】类型是什么注:hello是常量字符串,不能被修改,是连续存放的,可用printf("%s\n",p);打印字符串。常量字符串指的是在程序中声明的一个不......
  • 网络规划设计师真题解析--HDLC(帧类型)
    HDLC协议通信过程如下图所示,其中属于U帧的是(13)。(2021)A.仅SABME          B.SABME和UA C.SABME、UA和REJ,1    D.SABME、UA和I,0,0答案:B解析:HDLC帧类型如图:bit01234567I帧0N(S)发送帧序号3bit,取值23(0-7)P/FN(R)下一个预期要接收帧的序号3bit,取值23(0-7)S帧10S......