异步推理与队列服务

更新时间: 2023-07-24 18:35:41

对于推理耗时相对较长的使用场景,同步等待结果会存在HTTP长连接断开、客户端超时、负载均衡及实例异常等问题。 针对以上问题,PAI提供了队列服务和异步推理功能,可以通过请求分发、订阅推送或定期查询结果来实现推理。本文为您介绍如何使用队列服务及异步推理。

背景信息

功能介绍

  • 异步推理

    对于实时性要求比较高的在线推理场景,通常使用同步推理,即客户端发送一个请求,同步等待结果返回。

    对于推理耗时比较长或者推理时间无法确定的场景,同步等待结果会带来HTTP长连接断开、客户端超时等诸多问题。通常需要使用异步推理来解决上述问题,即请求发送至服务端,客户端不再同步等待结果,而是选择定期去查询结果,或通过订阅的方式在请求计算完成后等待服务端的结果推送。

  • 队列服务

    对于准实时推理场景,比如短视频、视频流或语音流的处理、计算复杂度很高的图像处理等场景,不需要实时返回结果,但需要在指定时间内获取推理结果,该场景存在以下几类问题:

    • 负载均衡算法不能选择round robin算法,需要根据各个实例的实际负载情况进行请求的分发。

    • 实例异常,该实例上未计算完成的任务需要重新分配给其他实例进行计算。

    PAI推出了一套独立的队列服务框架,用来解决以上等请求分发的问题。

实现原理

实现原理
  • 异步推理功能依赖服务分组功能。在同分组内的推理服务和队列服务会自动组成订阅关系,即当在服务分组中创建队列服务后,相同服务分组内的推理服务会立即对队列服务进行订阅。

    重要
    • 处于公共资源组内的推理服务需要重启以发现队列服务的加入,如果队列服务先于公共资源组内的推理服务创建,则不需要。

    • 单个分组中只能创建一个队列服务,但是可以创建多个推理服务。这些推理服务都会自动订阅这个队列服务。

  • 每个队列服务会默认创建两个队列,即输入队列(默认)和输出队列(sink队列)。当队列服务和推理服务搭配使用时,推理服务中内置的服务引擎,作为同分组队列服务的客户端,会自动监听该队列服务中输入队列的数据并进行订阅处理,并将结果自动写入到输出队列中。因此,您只需要在服务部署时指定服务分组,并在该分组中创建一个队列服务,则该分组服务即可同时通过同步和异步两种方式进行访问。

  • 创建一个高可用的队列服务,用于接收客户端发送的请求。客户端实例根据自己所能承受的并发度来订阅指定个数的请求,队列服务会保证每个实例上处理的请求不会超过客户端实例订阅的窗口大小,通过该方式来保证不会存在实例过载,最终将订阅或查询的数据返回给客户端。

    说明

    比如每个实例只能处理5路语音流,则从队列中订阅消息时,将window size配置为5。当实例处理完一路语音流后将结果commit,队列服务会为实例重新推送一路新的语音流,保证实例上处理的语音流最多不超过5路。

  • 队列服务通过检测客户端的连接状态,对客户端进行健康检查,如果因客户端异常导致连接断开,队列服务会将该客户端实例标记为异常,已经分发给该实例进行处理的请求会重新推送给其他正常实例进行处理,以此来保证在异常情况下请求数据不会丢失。

创建服务进行异步推理

使用异步推理有两种方式,两者的原理和结构相同

  • 直接创建异步推理服务,该服务会同时自动管理一个队列子服务和一个预测子服务,两者的生命周期绑定。

  • 在服务分组内创建队列服务和推理服务,您可以像普通的服务那样管理这两个服务,主要满足分组中需要有多个推理服务,推理服务需要细致管理等较复杂的应用场景。

直接创建异步推理服务(推荐)

请参考异步推理服务一键化创建和管理推理服务与队列服务。

通过控制台方式分别创建队列服务和推理服务

  1. 创建推理服务和服务分组。

    1. 登录PAI控制台

    2. 在左侧导航栏单击工作空间列表,在工作空间列表页面中单击待操作的工作空间名称,进入对应工作空间内。

    3. 按照下图操作指引,进入服务群组页签。

      image.png
    4. 单击新建分组服务

    5. 部署服务页面,配置参数,并单击部署

      其中:

      • 服务名称:示例值为pmml_test。

      • 所属群组:选择新建群组,示例值为test_group。

      其他参数配置,详情请参见控制台上传部署

  2. 创建队列服务。

    1. 服务群组页签,单击分组服务操作列下的添加队列服务

    2. 添加队列服务配置面板,配置参数,并单击添加

      其中:

      • 模型部署占用资源:其中的实例数建议配置为2,通过主备实例来保证可靠性。

      • 服务名称:示例值为qservice。

      队列服务创建成功后,会在服务列表呈现,且当前版本队列服务image.png

      同时分组服务支持异步调用。image.png

  3. 准备服务的JSON配置文件service.json。

    重要

    如果队列服务要为推理服务提供输入源,并形成自动订阅关系,您需要通过group字段来指定服务分组,且队列服务和推理服务需要归属于同一分组。

    • 队列服务(qservice)的配置文件。

      {
        "metadata":{
           "name":"qservice",
           "group":"test_group",
           "instance":2,
           "role":"Queue"
        }
      }

      配置文件中的关键参数解释:

      • role:如果您需要创建队列服务,只需在metadata中增加"role": "Queue"字段即可,后台会自动执行队列服务的配置。

      • instance:实例个数建议配置为2~3,通过主备实例来保证服务可靠性。

      • resource:队列服务所使用的资源组,如果不填写将默认使用公共资源组。

    • 推理服务(pmml_test)的配置文件。该示例采用了默认配置,可配置项请参考队列服务参数配置。

      {
        "model_path": "http://eas-data.oss-cn-shanghai.aliyuncs.com/models/testpmml.pmml",
        "processor": "pmml",
        "metadata": {
           "name": "pmml_test",
           "group": "test_group"
        }
      }

    配置文件中的其他参数解释,详情请参见命令使用说明

  4. 创建服务。

    您可以登录eascmd客户端后使用create命令创建队列服务和推理服务,如何登录eascmd客户端,请参见下载并认证客户端,使用示例如下。

    $ eascmd create service.json

队列服务参数配置及说明

大多数情况下,队列服务使用默认配置即可正常使用。如果有特殊需求,您可以通过在JSON文件中最外层的queue配置块来配置队列服务。示例如下:

{  
  "queue": {
     "sink": {
        "memory_ratio": 0.3
     },
     "source": {
        "auto_evict": true,
     }
 }

下面介绍具体的配置项。

配置队列服务资源

当您直接使用异步推理服务时,其中的队列子服务的资源会默认按照metadata中的字段进行配置,但是有些使用场景下,您需要对队列子服务的资源进行单独配置,请参考本章节。

说明

当您选择单独创建队列服务时,队列服务的资源按照上文的配置方法进行配置即可,除了"重要"与"警告"的内容外,可略过本章节。

  • 通过queue.resource声明服务所使用的资源组。

    {
      "queue": {
        "resource": eas-r-slzkbq4tw0p6xdthqh  #默认跟随预测子服务资源组	
      }
    }
    • 队列子服务默认跟随推理子服务的资源组。

    • 当您需要使用公共资源组部署队列服务时,可以声明resource为空串(""),这在您的专属资源组CPU和内存不充足时非常有用。

      说明

      推荐使用公共资源组部署队列服务。

  • 通过queue.cpuqueue.memory声明服务每个实例所使用的CPU(单位:核数)和内存大小(单位:MB)。

    {
      "queue": {
         "cpu": 2,     #默认值为1
         "memory": 8000     #默认值为4000
      }
    }

    如果您没有进行资源配置,队列服务将按照1 CPU核、4 GB内存进行默认配置。这在大多数场景下能满足需要。

    重要

    如果您的订阅者(比如预测子服务的实例数量)数量超过200时,建议将CPU核数配置为2核以上。

    警告

    不建议在生产环境中缩小队列服务的内存配置。

  • 通过queue.min_replica配置异步服务中队列子服务最小实例数量。

    {
      "queue": {
         "min_replica": 3			#默认为1
      }
    }

    在使用异步服务时,队列子服务的实例数量将根据预测子服务的运行时实例数量自动调整,默认的实例的调整区间为[1, min{2, 预测子服务实例数量}]。特别地,如果配置异步服务的自动扩缩容规则并允许将实例数量缩小到0时,将自动保留1个队列服务实例。可以通过queue.min_replica调整最小保留的队列服务实例数量。

    说明

    增加队列服务实例数量可以提高可用性,但不能提高队列服务性能。

配置队列服务功能

队列服务拥有多项可配置的功能,您可通过本章节的配置方法进行调整。

  • 通过queue.sink.auto_evict或者queue.source.auto_evict分别配置输出/输入队列自动数据驱逐。

    {
      "queue": {
         "sink": {
            "auto_evict": true				#输出队列打开自动驱逐,默认false
          },
          "source": {
             "auto_evict": true				#输入队列打开自动驱逐,默认false
          }
      }
    }

    默认情况下队列的自动数据驱逐处于关闭状态,如果您的队列已满将无法继续输入数据。在有些场景下,如果您允许数据在队列中溢出,可以选择打开自动数据驱逐功能,队列将自动驱逐队列中最老的数据以允许新数据写入。

  • 通过queue.max_delivery配置最大投递次数。

    {
       "queue": {
          "max_delivery": 10						#最大投递次数为10,默认值:5。当配置为0时,最大投递次数关闭, 数据可以被无限次投递。
       }
    }

    当单条数据的投递次数超过该值之后数据将被认为无法进行处理,会被作为死信。请参考队列服务死信策略

  • 通过queue.max_idle配置数据最大处理时间。

    {
        "queue": {
          "max_idle": "1m"							#配置单条数据最大处理时长为1分钟,如果超过该时间将被投递给其它订阅者, 投递完毕后投递次数+1。
          															#默认值为0,即没有最大处理时长。
        }
    }

    示例中配置的时间长度为1分钟, 支持多种时间单位,如h(小时),m(分钟),s(秒)。如果单条数据处理的时间超过了这里配置的时长,则有两种可能:

    • 如果未超过queue.max_delivery设定的阈值,该条数据会被投递给其他订阅者;

    • 如果已超过queue.max_delivery设定的阈值,该条数据将会被执行死信策略;

  • 通过queue.dead_message_policy配置死信策略。

    {
        "queue": {
          "dead_message_policy":  "Rear" 		#枚举值为Rear或者Drop, Rear即为放入尾队列,Drop将该条数据删除。 
    																				#默认值为Rear。
        }
    }

配置队列最大长度或最大数据体积

队列服务的最大长度和最大数据体积是此消彼长的关系,计算关系如下所示:

image..png

队列服务实例内存是固定的,因此如果调整单条数据最大体积,则会导致该队列最大长度减小。

说明
  • 在4 GB内存的默认配置下,由于最大数据体积默认为8 KB,则输入输出队列均可以存放230399条数据,如果您需要在队列服务中存放更多数据项,可以参考上文中的内存配置,将内存大小按照需要提高。系统将占用总内存的10%。

  • 对于同一个队列,不能同时配置最大长度和最大数据体积。

  • 通过queue.sink.max_length或者queue.source.max_length分别配置输出队列/输入队列的最大长度。

    {
        "queue": {
           "sink": {
              "max_length": 8000					#配置输出队列最大长度为8000条数据
           },
           "source": {
              "max_length": 2000					#配置输入队列最大长度为2000条数据
           }
        }
    }
  • 通过queue.sink.max_payload_size_kb或者queue.source.max_payload_size_kb分别配置输出队列/输入队列单条数据最大数据体积。

    {
        "queue": {
           "sink": {
              "max_payload_size_kb": 10				#配置输出队列单条数据最大体积是10KB, 默认8kB
           },
           "source": {
              "max_payload_size_kb": 1024			#配置输入队列单条数据最大体积是1024Kb(1MB), 默认是8kB
           }
        }
    }

配置内存分配倾斜

  • 通过queue.sink.memory_ratio来调整输入输出两个队列占用的内存大小。

    {
        "queue": {
           "sink": {
              "memory_ratio": 0.9			#配置输出队列内存占比,默认值为0.5
           }
        }
    }
    说明

    默认配置下,输入队列和输出队列均分队列服务实例的内存。如果您的服务需要输入文本,输出图片,并期望在输出队列存放更多数据,则可以将queue.sink.memory_ratio相应提高;相反地,如果您期望输入图片输出文本,则可以将queue.sink.memory_ratio相应减小。

配置水平自动扩缩容

推理服务水平自动扩缩容配置方法,请参见自动扩缩容

阿里云首页 人工智能平台 PAI 相关技术圈