跳转到: 导航, 搜索

Zaqar/specs/api/v1.1

Zaqar API v1.1

笔记

欢迎对 v1.1 规范提出反馈(在 #openstack-zaqar 中找到我们,建议可在 etherpad 上跟踪)。我们还开始收集着眼于扩展和 API v2 的反馈,请访问:Zaqar/specs/api/next

TODO

  • 文档版本发现

概述

Zaqar 提供了一个基于 HTTP 的 API,遵循 REST 架构风格

本指南假定读者熟悉 REST、HTTP/1.1 和 JSON。URI 模板使用与 RFC 6570 相同的语法。

注:错误响应枚举在一个单独的页面上:Marconi/specs/api/v1.1/errors

v1.0 的变化

破坏性变化(BREAKING)

  • 队列存在性检查端点(通过 HEAD 和 GET)不再存在。
  • 现在每个请求都必需包含 X-Project-Id 头部。这是为了应对身份验证允许单个用户管理多个项目的情况,在这种情况下,我们将需要知道用户请求的是哪个项目(而不是假设 auth token 和项目之间存在 1:1 映射)。它还允许在 LB 上进行速率限制。
  • 现在每个请求都必需包含 Client-ID 头部。
  • 响应中不再设置 Content-Location 头部。
  • 在消息发布请求的响应中不再包含 partial 字段。当使用官方 SQL 和 MongoDB 驱动程序时,Zaqar 要么成功将提交批次中的所有消息入队,要么全部失败。任何支持批量消息发布的后端驱动程序都需要保证整个批次的原子插入(API v2.0 将把某些操作定义为可选,例如批量消息发布)。
  • 当请求集合资源(即 queues、messages)且集合为空时,现在响应正文中返回一个空的 JSON 数组,而不是 HTTP 204 No Content。
  • 响应正文中的所有顶级 JSON 数组现在都封装在一个对象中。这可以减轻我们稍后实现对直接从 Web 应用程序(JavaScript)访问 Zaqar 的支持时可能遇到的安全风险。
  • 当发布一条或多条消息时,消息列表现在封装在一个 JSON 对象类型中。这样做是为了与响应正文保持一致。
  • 健康检查现在是仅限管理员的端点,并返回给定节点的运营统计信息。添加了一个新的“ping”端点供负载均衡器使用。


非破坏性变化(NON-BREAKING)

  • 除了 JSON 之外,现在还支持 MessagePack。
  • 从相对 URI 中删除了多余的路径元素。
  • 现在通过访问根 URI 路径可以进行版本发现。
  • Location 头部(如果存在)现在指定完整的(即非相对的)URI。
  • 消息中添加了 claim 字段。如果消息未被认领,则省略该字段。
  • 消息中添加了 id 字段。
  • 如果消息被认领,消息的 href 现在始终包含查询字符串中的 claim_id
  • 当发布消息或创建认领时,可以省略 ttlgrace 字段。如果客户端未提供,Zaqar 将默认为云操作员配置的最大值。
  • 新的 pools API 和功能。
  • 添加了新的“pop”语义,用于在单个请求中认领和删除消息。请注意,只有当应用程序可以接受工作进程崩溃时丢失消息的风险时才应使用此功能。
  • 如果队列不存在,服务器将在发布消息时自动创建队列。
  • 对不存在队列的消息和统计信息的查询现在返回空列表等,而不是 404。

通用 API 元素

客户端

  • 客户端应遵循 HTTP 重定向
  • 客户端应声明 gzip 支持
  • 客户端应在 UserAgent 请求头中标识自己;例如,“User-Agent: python/2.7 cloudthing/1.2”
  • 客户端不应硬编码 URI 路径或模板,因为它们可能会随时间而改变。相反,客户端应缓存 API 提供的链接和链接模板。


API 版本控制

Zaqar API 使用 URI 版本控制方案。路径的第一个元素包含目标版本标识符,例如

  https://marconi.example.com/v1.1

URI 版本只会在容纳无法向后兼容的主要新功能或 API 重新设计时才会递增。发布新 API 版本时,旧版本将被弃用。Zaqar 维护人员将与开发人员和合作伙伴合作,确保有足够的时间迁移到新版本,然后才能停止使用旧版本。

由于版本只会在容纳主要 API 修订时才会递增,因此 API 的子版本化将很少见。

资源媒体类型

API 支持 `application/json`,编码为 UTF-8。还支持 MessagePack ('application/x-msgpack'),建议将其作为客户端库的默认设置。

从服务器接收到的无法识别的协议元素应直接忽略。这包括 JSON 对象属性、链接关系类型、媒体类型等。

身份验证

可以由中间件或反向代理实现。如果实现,则适用以下内容

所有对 API 的请求只能由经过身份验证的代理执行。

为了进行身份验证,代理向 Keystone Identity Service 端点发出身份验证请求。

为响应有效的凭据,Keystone 返回一个 auth token 和一个服务目录,其中包含给定 token 可用的所有服务和端点列表。可能会为 Zaqar 返回多个端点,具体取决于不同部署的物理位置和性能/可用性特征。

通常,Keystone 中间件根据 Zaqar 客户端提交的 auth token 提供 X-Project-Id 头部。为此,客户端必须在每次请求 Zaqar API 时在 `X-Auth-Token` 头部中指定有效的 auth token。API 在处理每个请求之前会根据 Keystone 验证 auth token。

如果未启用身份验证,客户端必须自行提供 X-Project-Id 头部。

授权

可以由中间件或反向代理实现。如果实现,则适用以下内容

API 需要根据提供的身份验证令牌(RBAC)验证对所有端点的读/写访问权限。ACL 应与令牌一起缓存。


端点

客户端可以选择与从 Keystone 检索到的任何服务端点进行交互。代理使用给定的端点作为主文档 href,从中发现所有其他 API 请求的链接。端点可以按区域组织

一个示例端点

https://marconi.example.com/v1.1

错误

如果任何请求导致错误,服务器将返回适当的 4xx 或 5xx HTTP 状态代码,并可选地在主体中返回以下信息

  • 标题
  • 描述
  • 内部代码
  • 更多信息的链接


示例

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "title": "Unsupported limit",
  "description": "The given limit cannot be negative, and cannot be greater than 50.",
  "code": 1092,
  "link": {
    "rel": "help",
    "href": "http://docs.example.com/messages#limit",
    "text": "API documentation for the limit parameter"
  }
}

通用头部

对 API 的每个请求都必须包含某些标准和扩展的 HTTP 头部。这些头部向服务器提供主机、代理、身份验证和其他相关信息。

头部 描述
Host API 的主机名,如 Keystone 服务目录中所引用
日期 当前日期和时间,使用标准的 RFC 1123 HTTP 日期格式
Accept 所需的媒体类型;目前仅支持 application/json
Accept-Encoding 指定代理接受 gzip 编码的响应正文。
Content-Length 对于 POST 或 PUT 请求,正在提交的消息文档的字节数
X-Auth-Token Keystone auth token,启用 auth 时每个请求都必需。
X-Project-Id 授予 X-Auth-Token 值访问权限的项目 ID。队列将在该项目下创建。在多租户部署中,每个请求都必需包含此头部。
Client-ID 一个 UUID,用于区分使用该服务的每个客户端。UUID 必须以其规范形式提交(例如,3381af92-2b9e-11e3-b191-71861300734c)。在 Zaqar 中,这用于避免将发送方的消息回显给同一实例,并且服务器可能会记录它以提供诊断和统计信息。应生成一次并在客户端重新启动之间保留,以避免客户端发送消息、使用不同的 ID 重新启动然后接收到自己回显的消息的竞态条件。

HTTP 响应代码

请参阅 Response Codes

示例 API 请求

GET /v1.1/queues/fizbat/messages?marker=1355-237242-783&limit=10 HTTP/1.1
Host: marconi.example.com
User-Agent: python/2.7 killer-rabbit/1.2
Date: Wed, 28 Nov 2012 21:14:19 GMT
Accept: application/json
Accept-Encoding: gzip
X-Auth-Token: 7d2f63fd-4dcc-4752-8e9b-1d08f989cc00
X-Project-Id: 518b51ea133c4facadae42c328d6b77b
Client-ID: 30387f00-39a0-11e2-be4d-a8d15f34bae2

端点概要

# Base endpoints
GET /v1.1
GET /v1.1/health
GET /v1.1/ping

# Queues
GET /v1.1/queues{?marker,limit,detailed}
PUT /v1.1/queues/{name}
DELETE /v1.1/queues/{name}

# Queue stats
GET /v1.1/queues/{queue_name}/stats

# Messages
GET /v1.1/queues/{queue_name}/messages{?marker,limit,echo,include_claimed}
POST /v1.1/queues/{queue_name}/messages
DELETE /v1.1/queues/{queue_name}/messages/{message_id}{?claim_id}
DELETE /v1.1/queues/{queue_name}/messages{?ids}
DELETE /v1.1/queues/{queue_name}/messages{?pop}

# Claims
POST /v1.1/queues/{queue_name}/claims{?limit}
GET /v1.1/queues/{queue_name}/claims/{claim_id}
PATCH /v1.1/queues/{queue_name}/claims/{claim_id}
DELETE /v1.1/queues/{queue_name}/claims/{claim_id}

# Pools
GET /v1.1/pools?detailed=False&marker=None&limit=10
GET /v1.1/pools/{pool}?detailed=False
PUT /v1.1/pools/{pool}
DELETE /v1.1/pools/{pool}
PATCH /v1.1/pools/{pool}

基本端点

获取主文档

模板

GET /v1.1


请求

GET /v1.1 HTTP/1.1
Host: marconi.example.com

...


响应

HTTP/1.0 200 OK
Cache-Control: max-age=86400
Content-Length: 4345
Content-Type: application/json-home
Date: Tue, 06 Aug 2013 16:31:48 GMT
Server: WSGIServer/0.1 Python/2.7.3

{
    "resources": {
        "rel/queue-stats": {
            "href-template": "/v1.1/queues/{queue_name}/stats",
            "href-vars": {
                "queue_name": "param/queue_name"
            },
            "hints": {
                "allow": [
                    "GET"
                ],
                "formats": {
                    "application/json": {}
                }
            }
        },
        "rel/post-messages": {
            "href-template": "/v1.1/queues/{queue_name}/messages",
            "href-vars": {
                "queue_name": "param/queue_name"
            },
            "hints": {
                "accept-post": [
                    "application/json"
                ],
                "allow": [
                    "POST"
                ],
                "formats": {
                    "application/json": {}
                }
            }
        },
        "rel/queue": {
            "href-template": "/v1.1/queues/{queue_name}",
            "href-vars": {
                "queue_name": "param/queue_name"
            },
            "hints": {
                "allow": [
                    "PUT",
                    "DELETE"
                ],
                "formats": {
                    "application/json": {}
                }
            }
        },
        "rel/queues": {
            "href-template": "/v1.1/queues{?marker,limit,detailed}",
            "href-vars": {
                "marker": "param/marker",
                "detailed": "param/detailed",
                "limit": "param/queue_limit"
            },
            "hints": {
                "allow": [
                    "GET"
                ],
                "formats": {
                    "application/json": {}
                }
            }
        },
        "rel/messages": {
            "href-template": "/v1.1/queues/{queue_name}/messages{?marker,limit,echo,include_claimed}",
            "href-vars": {
                "marker": "param/marker",
                "include_claimed": "param/include_claimed",
                "queue_name": "param/queue_name",
                "limit": "param/messages_limit",
                "echo": "param/echo"
            },
            "hints": {
                "allow": [
                    "GET"
                ],
                "formats": {
                    "application/json": {}
                }
            }
        },
        "rel/messages-delete": {
            "href-template": "/v1.1/queues/{queue_name}/messages{?ids,pop}",
            "href-vars": {
                "ids": "param/ids",
                "pop": "param/pop",
            },
            "hints": {
                "allow": [
                    "DELETE"
                ],
                "formats": {
                    "application/json": {}
                }
            }
        },
        "rel/claim": {
            "href-template": "/v1.1/queues/{queue_name}/claims{?limit}",
            "href-vars": {
                "queue_name": "param/queue_name",
                "limit": "param/claim_limit"
            },
            "hints": {
                "accept-post": [
                    "application/json"
                ],
                "allow": [
                    "POST"
                ],
                "formats": {
                    "application/json": {}
                }
            }
        }
    }
}


讨论

整个 Zaqar API 可以从一个起点发现;代理无需知道除此 URI 之外的任何内容即可探索整个 API。

此文档是可缓存的(应由代理和客户端缓存)。

另请参阅:http://tools.ietf.org/html/draft-nottingham-json-home-03

检查节点健康状况

模板

GET /v1.1/health

或者

HEAD /v1.1/health


请求

GET /v1.1/health HTTP/1.1
Host: example.marconi.com

...


响应

{
    "mongo_pool_1": {
        "message_volume": {
            "claimed": 0,
            "total": 0,
            "free": 0
        },
        "storage_reachable": true,
        "operation_status": {
            "create_queue": {
                "seconds": 0.0021300315856933594,
                "ref": null,
                "succeeded": true
            },
            "post_messages": {
                "seconds": 0.033502817153930664,
                "ref": null,
                "succeeded": true
            },
            "list_messages": {
                "seconds": 0.000013113021850585938,
                "ref": null,
                "succeeded": true
            },
            "claim_messages": {
                "seconds": 0.0013759136199951172,
                "ref": "3f515f37-58a0-4c81-8214-3e92979b82e7",
                "succeeded": false
            },
            "delete_queue": {
                "seconds": 0.0030739307403564453,
                "ref": null,
                "succeeded": true
            }
        }
    },
    "mongo_pool_2": {
        "message_volume": {
            "claimed": 0,
            "total": 0,
            "free": 0
        },
        "storage_reachable": true,
        "operation_status": {
            "create_queue": {
                "seconds": 0.0011799335479736328,
                "ref": null,
                "succeeded": true
            },
            "post_messages": {
                "seconds": 0.024316072463989258,
                "ref": null,
                "succeeded": true
            },
            "list_messages": {
                "seconds": 0.000008106231689453125,
                "ref": null,
                "succeeded": true
            },
            "claim_messages": {
                "seconds": 0.000576019287109375,
                "ref": "68629fda-b4ce-4cf9-978a-df0df8df36a7",
                "succeeded": false
            },
            "delete_queue": {
                "seconds": 0.003300905227661133,
                "ref": null,
                "succeeded": true
            }
        }
    },
    "catalog_reachable": true
}

讨论

这是一个操作员端点,不应在面向最终用户的客户端库中实现。除非请求用户是管理员,否则也不应将其包含在 json-home 文档中。


使用此请求获取特定 Zaqar 节点的详细运营统计信息。

Ping 节点

模板

GET /v1.1/ping

或者

HEAD /v1.1/ping


请求

GET /v1.1/ping HTTP/1.1
Host: example.marconi.com

...


响应

HTTP/1.1 204 No Content

或者

HTTP/1.1 503 Service Unavailable


讨论

这是一个操作员端点,不应在面向最终用户的客户端库中实现。除非请求用户是管理员,否则也不应将其包含在 json-home 文档中。

使用此资源检查给定 Zaqar 节点是否在线。如果节点关闭,则无法访问此端点,并将作为负载均衡器将节点从轮换中移除的信号。或者,端点可能可访问,但由于存储驱动程序故障或其他错误,服务暂时不可用。

只有当存在 X-Forwarded-For 头部时,才能访问健康端点。在这种情况下,它不需要身份验证,允许负载均衡器查询给定节点的健康状况。

对于未通过 LB 发送的请求,Zaqar 将返回 404 Not Found,因为此端点不应由最终用户访问,并且没有必要向全世界暴露另一个潜在的 DDoS 向量。

队列

获取队列

模板

GET /v1.1/queues/{queue_name}


请求

GET /v1.1/queues/fizbit HTTP/1.1
Host: marconi.example.com

...


响应 HTTP/1.1 200 OK

{
   "key": {
       "key2": "value",
       "key3": [1, 2, 3, 4, 5]
    }
}


讨论

返回队列元数据。

创建队列

模板

PUT /v1.1/queues/{queue_name}

{
   ...
}

请求

PUT /v1.1/queues/fizbat HTTP/1.1
Host: marconi.example.com


响应

HTTP/1.1 201 Created
Location: https://marconi.example.com/v1.1/queues/fizbit


讨论

创建队列。

请求正文是一个任意文档,允许存储有关特定应用程序应如何与队列交互的上下文信息。此正文的大小(以字符为单位,包括空格)必须 <= 64 KiB(可配置)。文档必须是有效的 JSON。

queue_name 是要赋予队列的名称。名称长度不得超过 64 个字节,并且仅限于 US-ASCII 字母、数字、下划线和连字符。

列出队列

模板

GET /v1.1/queues{?marker,limit,detailed}


请求

GET /v1.1/queues?marker=baz&detailed=true HTTP/1.1
Host: marconi.example.com

...


响应

HTTP/1.1 200 OK

{
  "links": [
    {
      "rel": "next",
      "href": "queues?marker=kooleo&detailed=true"
    }
  ],
  "queues": [
    { "name": "boomerang", "href": "queues/boomerang" },
    { "name": "fizbit", "href": "queues/fizbit" },

    ...

    { "name": "kooleo",  "href": "queues/kooleo" }
  ]
}

或者,如果不存在队列

HTTP/1.1 200 OK

{
  "links": [
    {
      "rel": "next",
      "href": "queues?marker=baz&detailed=true"
    }
  ],
  "queues": []
}

讨论

查询参数定义如下

marker 上一个响应中返回的最后一个队列的名称,用于检索下一页结果。注意:客户端通常应只跟随“next”链接,而不是尝试手动构建 URI 路径。

limit 要返回的队列记录的最大数量。不能超过云操作员配置的硬最大值(默认为 20)。如果未给定,则限制默认为 10。

detailed 设置为“true”可在列表中内联队列元数据(默认为 false)。

删除队列

模板

DELETE /v1.1/queues/{queue_name}


请求

DELETE /v1.1/queues/fizbat HTTP/1.1
Host: marconi.example.com


响应

HTTP/1.1 204 No Content


讨论

使用此操作立即删除队列及其所有消息(如果有)。

队列统计信息

模板

GET /v1.1/queues/{queue_name}/stats


请求

GET /v1.1/queues/fizbit/stats HTTP/1.1
Host: marconi.example.com

...


响应 HTTP/1.1 200 OK

{
  "messages": {
    "free": 146929,
    "claimed": 2409,
    "total": 149338,
    "oldest": {
        "href": "queues/fizbit/messages/50b68a50d6f5b8c8a7c62b01",
        "age": 63,
        "created": "2013-08-12T20:44:55Z"
    },
    "newest": {
        "href": "queues/fizbit/messages/50b68a50d6f5b8c8a7c62b01",
        "age": 12,
        "created": "2013-08-12T20:45:46Z"
    }
  }
}


讨论

返回队列统计信息,包括队列中的消息数量,按状态细分。

注意:如果总数为 0,则不包含“最旧”和“最新”消息统计信息。

消息

所有消息相关操作都需要在头部包含 Client-Id。这是为了确保消息不会回显到发布它们的客户端,除非客户端明确请求这样做。

列出消息

模板

GET /v1.1/queues/{queue_name}/messages{?marker,limit,echo,include_claimed}


请求

GET /v1.1/queues/fizbit/messages?marker=1355-237242-783&limit=10 HTTP/1.1
Host: marconi.example.com

...


响应

HTTP/1.1 200 OK

...

{
  "links": [
    {
      "rel": "next",
      "href": "messages?marker=6244-244224-783&limit=10"
    }
  ],
  "messages": [
    {
      "href": "messages/50b68a50d6f5b8c8a7c62b01",
      "id": "50b68a50d6f5b8c8a7c62b01",
      "ttl": 120,
      "age": 53,
      "body": {
        "event": "ActivateAccount",
        "mode": "active"
      }
    },
    {
      "href": "messages/50b68a50d6f5b8c8a7c62b02?claim_id=06ef2372-6746-11e3-b311-b3adcbe406e9",
      "id": "50b68a50d6f5b8c8a7c62b01",
      "ttl": 800,
      "age": 790,
      "body": {
        "event": "CreateInvoice",
        "customer_id": "90fd8734-6746-11e3-be3c-7e45c531c7ca"
      }
    }
  ]
}

或者,如果没有可用的消息

HTTP/1.1 200 OK

...

{
  "links": [
    {
      "rel": "next",
      "href": "messages?marker=6244-244224-783&limit=10"
    }
  ],
  "messages": []
}


讨论

消息 ID 和标记是不透明字符串;客户端不应假定它们的格式或长度。此外,客户端应假定标记和消息 ID 之间没有关系(即,一个不能从另一个推断出来)。这允许各种存储驱动程序实现。

结果按年龄排序,最旧的消息在前。

limit 指定要返回的消息数量最多为 20 个(可配置)。如果未指定,则限制默认为 10。当可用消息多于可以一次请求返回的消息时,客户端可以通过简单地在从上一个调用返回的 URI 模板参数中使用的“next”字段(TBD)来获取下一批。

marker 是一个不透明字符串,客户端可以使用它来请求下一批消息。它将客户端已接收的消息告知服务器。

注意:如果未指定 marker,API 将返回队列头部的所有消息(最多限制)。

echo 是一个布尔值(即“true”或“false”),用于确定 API 是否返回客户端自己的消息,由 Client-ID 头部的 uuid 部分确定。如果未指定,则 echo 默认为“false”。

include_claimed 是一个布尔值(即“true”或“false”),用于确定 API 是否返回已声明的消息以及未声明的消息。如果未指定,则默认为“false”(仅返回未声明的消息)。

获取特定消息

模板

GET /v1.1/queues/{queue_name}/messages/{message_id}


请求

GET /v1.1/queues/fizbat/messages/50b68a50d6f5b8c8a7c62b01 HTTP/1.1
Host: marconi.example.com

...


响应

HTTP/1.1 200 OK
Content-Location: /v1.1/queues/fizbat/messages/50b68a50d6f5b8c8a7c62b01

...

{
  "href": "/v1.1/queues/fizbit/messages/50b68a50d6f5b8c8a7c62b01",
  "id": "50b68a50d6f5b8c8a7c62b01",
  "ttl": 800,
  "age": 790,
  "body": {
    "event": "ActivateAccount",
    "mode": "active"
  }
}


讨论

消息字段定义如下

href 是一个不透明的相对 URI,客户端可以使用它来唯一标识消息资源并与其交互。

ttl 是在发布消息时设置的 ttl。消息将在 (ttl - age) 秒后过期。

age 相对于服务器时钟的 ts 以来的秒数。

body 提交的消息的任意文档。

如果消息 ID 或声明 ID 格式不正确或不存在,则不返回任何消息。

按 ID 获取一组消息

模板

GET /v1.1/queues/{queue_name}/messages{?ids}


请求

GET /v1.1/queues/fizbat/messages?ids=50b68a50d6f5b8c8a7c62b01,f5b8c8a7c62b0150b68a50d6 HTTP/1.1
Host: marconi.example.com

...


响应

HTTP/1.1 200 OK
Content-Location: /v1.1/queues/fizbat/messages?ids=50b68a50d6f5b8c8a7c62b01,f5b8c8a7c62b0150b68a50d6

...

{
  "messages": [
    {
      "href": "/v1.1/queues/fizbit/messages/50b68a50d6f5b8c8a7c62b01",
      "id": "50b68a50d6f5b8c8a7c62b01",
      "ttl": 800,
      "age": 32,
      "body": {
        "cmd": "EncodeVideo",
        "jobid": 58229 
      }
    },
    {
      "href": "/v1.1/queues/fizbit/messages/f5b8c8a7c62b0150b68a50d6",
      "id": "f5b8c8a7c62b0150b68a50d6",
      "ttl": 800,
      "age": 790,
      "body": {
        "cmd": "EncodeAudio",
        "jobid": 58201 
      }
    }
  ]
}


讨论

与消息列表不同,在此方法中始终返回客户端自己的消息。ID 列表不得超过 20 个(可配置,与一次可以发布的消息数量的设置共享)。

此方法返回所有具有匹配 ID 的消息。如果提供格式不正确的 ID 或不存在的消息 ID,则将其忽略并返回其余消息。

发布消息

模板

POST /v1.1/queues/{queue_name}/messages


请求

POST /v1.1/queues/fizbit/messages HTTP/1.1
Host: marconi.example.com

...


{
  "messages": [
    {
      "ttl": 300,
      "body": {
        "event": "BackupStarted",
        "backup_id": "c378813c-3f0b-11e2-ad92-7823d2b0f3ce"
      }
    },
    {
      "body": {
        "event": "BackupProgress",
        "current_bytes": "0",
        "total_bytes": "99614720"
      }
    }
  ]
}


响应

提交单个消息时

HTTP/1.1 201 Created
Location: https://marconi.example.com/v1.1/queues/fizbit/messages?ids=50b68a50d6f5b8c8a7c62b01
Content-Type: application/json

{
    "links": [
        {
            "rel": "rel/message",
            "href": "messages/50b68a50d6f5b8c8a7c62b01"
        }
    ]
}

提交多个消息时

HTTP/1.1 201 Created
Location: https://marconi.example.com/v1.1/queues/fizbit/messages?ids=50b68a50d6f5b8c8a7c62b01,50b68a50d6f5b8c8a7c62b05
Content-Type: application/json

{
    "links": [
        {
            "rel": "rel/message",
            "href": "messages/50b68a50d6f5b8c8a7c62b01"
        },
        {
            "rel": "rel/message",
            "href": "messages/50b68a50d6f5b8c8a7c62b05"
        }
    ]
}

讨论

单个请求中最多可提交 20 条消息(默认值,但可配置),但必须始终封装在集合容器中(例如,JSON 中的数组)。如果需要,可以使用 Location 头部或响应正文的最终值来检索创建的消息以供进一步处理。

客户端仅指定消息的主体和 ttl;服务器将插入 ID 和年龄等元数据。

注意:如果指定的队列尚不存在,服务器将创建它。应用程序不需要显式管理其队列的生命周期,但鼓励删除不再使用的队列。

响应正文包含与请求中提交的每条消息对应的资源路径列表,顺序相同。如果在处理提交的消息过程中出现服务器端错误,则整个批次将被放弃(all-or-nothing),客户端将必须重试其请求。

请求文档的大小(以字符为单位,包括空格)默认为 256 KiB(可配置)。文档必须是格式良好的 JSON 或 MessagePack 文档(Zaqar 将进行验证)。

body 指定要发送的消息的主体。

ttl 是服务器在消息过期并从队列中删除之前应等待的时间。值必须在 60 到 1209600 秒(14 天,可配置)之间(含)。请注意,服务器可能直到消息的年龄达到(ttl + 60)秒才实际删除消息,以允许存储实现具有灵活性。如果请求中省略了 ttl 字段,则使用默认值,通常为 3600 秒,除非服务提供商自定义。

删除消息

模板

DELETE /v1.1/queues/{queue_name}/messages/{message_id}{?claim_id}


请求

DELETE /v1.1/queues/fizbit/messages/50b68a50d6f5b8c8a7c62b01 HTTP/1.1
Host: marconi.example.com

...


响应

HTTP/1.1 204 No Content


讨论

message_id 指定要删除的消息。

claim_id 指定只有当消息具有指定的 claim ID 并且该 claim 未过期时,才应删除该消息。这对于确保只有一个 agent 处理任何给定的消息非常有用;每当 worker client 的 claim 在其有机会删除其已处理的消息之前过期时,worker 必须回滚基于该消息所采取的任何操作,因为另一个 worker 现在将能够 claim 并处理相同的消息。

请注意,如果未指定 claim_id,但消息已被 claim,则操作将失败。换句话说,已 claim 的消息只能通过提供适当的 claim_id 来删除。

删除多条消息

模板

DELETE /v1.1/queues/{queue_name}/messages{?ids,pop}


请求

DELETE /v1.1/queues/fizbit/messages?ids=50b68a50d6f5b8c8a7c62b01,50b68a50d6f5b8c8a7c62b02 HTTP/1.1
Host: marconi.example.com

...

响应

HTTP/1.1 204 No Content


或者,原子地弹出多条消息(当只有 2 条消息可用时)

请求

DELETE /v1.1/queues/fizbit/messages?pop=5 HTTP/1.1
Host: marconi.example.com

...

响应

HTTP/1.1 200 OK
Content-Type: application/json

...

{
  "messages": [
    {
      "href": "/v1.1/queues/fizbit/messages/50b68a50d6f5b8c8a7c62b01?claim_id=a28ee94e-6cb4-11e2-b4d5-7703267a7926",
      "id": "50b68a50d6f5b8c8a7c62b01",
      "ttl": 800,
      "age": 100,
      "body": {
        "object_id": "8a50d6",
        "target": "h.264"
      }
    },
    {
      "href": "/v1.1/queues/fizbit/messages/50b68a50d6f5b8c8a7c62b02?claim_id=a28ee94e-6cb4-11e2-b4d5-7703267a7926",
      "id": "50b68a50d6f5b8c8a7c62b02",
      "ttl": 800,
      "age": 790,
      "body": {
        "object_id": "fb8c8a",
        "target": "h.264"
      }
    }
  ]
}

pop 和 ids 参数是互斥的。在请求中一起使用它们将导致 HTTP 400。

请求

DELETE /v1.1/queues/fizbit/messages?pop=5&ids=50b68a50d6f5b8c8a7c62b02 HTTP/1.1
Host: marconi.example.com

...

响应

HTTP/1.1 400 Bad Request
Content-Type: application/json

...

{
  "message": pop and id params cannot be present together in the delete request
}

讨论

批量删除消息。

ids 指定要删除的消息,最多 20 条(限制可配置,与列出消息的最大设置相同)

pop 指定从队列中弹出的特定数量的消息,这相当于原子地认领和删除这些消息(因此保证了仅一次交付)。此参数的最大值与单个操作中可以认领的最大消息数相同。请注意,如果工作进程在弹出消息后但在处理它们之前崩溃,其他工作进程将无法重新认领这些消息。因此,只有当偶尔丢失消息的风险可以被证明是合理时,才应使用“pop”操作。

请注意,idspop 参数是互斥的。换句话说,如果两个参数都存在,服务器将响应“400 Bad Request”状态。

如果任何消息 ID 格式不正确或不存在,则会被忽略。其余有效的消息 ID 将被删除。

警告:已认领的消息不会被跳过;它们将与未认领的消息一起删除。如果使用工作进程池模式(其中工作进程认领批次消息),我们鼓励一次删除一条消息。这确保了在工作进程恰好在错误时刻崩溃时,最多只有一条消息被处理但未被删除。

    for each message in batch:
        process message
        delete message

与此相反

    for each message in batch:
        process message
    bulk-delete batch

声明

Claim 消息

模板

POST /v1.1/queues/{queue_name}/claims{?limit}
Content-Type: application/json

...

{
    "ttl": {claim_ttl},
    "grace": {message_grace}
}


请求

POST /v1.1/queues/fizbit/claims?limit=5 HTTP/1.1
Host: marconi.example.com
Content-Type: application/json
Accept: application/json

...

{
    "ttl": 300,
    "grace": 300
}

或者,要使用 ttl 和 grace 的默认值,可以在请求中省略一个或两个字段

POST /v1.1/queues/fizbit/claims?limit=5 HTTP/1.1
Host: marconi.example.com
Content-Type: application/json
Accept: application/json

...

{
    "ttl": 300,
}

注意:如果省略所有字段,则可以省略整个请求正文(尽管提交一个空的 JSON 对象也是可接受的)

POST /v1.1/queues/fizbit/claims?limit=5 HTTP/1.1
Host: marconi.example.com
Content-Length: 0
Accept: application/json


响应

客户端接收一个 claim URI 和一个已 claim 消息的列表(如果有的话)

HTTP/1.1 201 Created
Content-Type: application/json
Location: https://marconi.example.com/v1.1/queues/fizbit/claims/a28ee94e-6cb4-11e2-b4d5-7703267a7926

...

{
  "messages": [
    {
      "href": "/v1.1/queues/fizbit/messages/50b68a50d6f5b8c8a7c62b01?claim_id=a28ee94e-6cb4-11e2-b4d5-7703267a7926",
      "id": "50b68a50d6f5b8c8a7c62b01",
      "ttl": 800,
      "age": 100,
      "body": {
        "object_id": "8a50d6",
        "target": "h.264"
      }
    },
    {
      "href": "/v1.1/queues/fizbit/messages/50b68a50d6f5b8c8a7c62b02?claim_id=a28ee94e-6cb4-11e2-b4d5-7703267a7926",
      "id": "50b68a50d6f5b8c8a7c62b02",
      "ttl": 800,
      "age": 790,
      "body": {
        "object_id": "fb8c8a",
        "target": "h.264"
      }
    }
  ]
}

或者,如果没有可认领的消息

HTTP/1.1 201 Created
Content-Type: application/json
Location: https://marconi.example.com/v1.1/queues/fizbit/claims/f9151272-673e-11e3-8e72-43d6a24410d2

...

{
  "messages": []
}


讨论

Claim 一组消息,最多达到限制,从最旧到最新,跳过任何已 claim 的消息。如果没有可用的未 claim 消息,则返回 204 No Content。

客户端应在完成消息处理后,在 claim 过期之前删除该消息,以确保消息仅处理一次。作为删除操作的一部分,所有 worker client 都应指定 claim ID(最好是通过简单地使用提供的 href 来完成)。这样,服务器就可以在 claim 刚刚过期时返回错误(通知客户端存在竞争条件),从而使 worker 有机会回滚其对给定消息的处理,因为另一个 worker 可能会 claim 并处理该消息。

与消息的年龄一样,claim 的年龄也是相对于服务器时钟而言的,对于确定消息的处理速度以及给定消息的 claim 是否即将过期非常有用。

当 claim 过期时,它将被移除,允许另一个客户端 worker 在原始 worker 无法处理它时 claim 该消息。

limit 指定要 claim 的最多 20 条消息(可配置)。如果未指定,则 limit 默认为 10。请注意,claim 创建是尽力而为的,这意味着服务器可能会 claim 并返回少于请求数量的消息。

ttl 是服务器在释放认领之前应等待的时间。值必须在 60 到 43200 秒(12 小时,可配置)之间(含)。如果省略,则使用默认值,通常为 300 秒,除非服务提供商自定义。

grace 是消息宽限期,以秒为单位。值必须在 60 到 43200 秒(12 小时,可配置)之间(含)。服务器将延长已认领消息的生命周期,使其至少与认领本身的生命周期加上指定的宽限期一样长,以应对崩溃的工作进程(最长可达 1209600 或 14 天,包括认领生命周期)。这使得其他工作进程有机会在先前的认领到期后但在消息本身到期之前重新认领消息。请注意,如果消息的生命周期已经超过认领的 TTL 加上宽限期,则消息的生命周期不会调整。如果省略,则使用默认值,通常为 60 秒,除非服务提供商自定义。

查询 Claim

模板

GET /v1.1/queues/{queue_name}/claims/{claim_id}


请求

GET /v1.1/queues/foo-bar/claims/a28ee94e-6cb4-11e2-b4d5-7703267a7926
Host: marconi.example.com

...


响应

HTTP/1.1 200 OK

...

{
  "age": 19,
  "ttl": 30,
  "messages": [
    ...
  ]
}


讨论

续订认领

模板

PATCH /v1.1/queues/{queue_name}/claims/{claim_id}
Content-Type: application/json


请求

PATCH /v1.1/queues/fizbit/claims/a28ee94e-6cb4-11e2-b4d5-7703267a7926 HTTP/1.1
Host: marconi.example.com
Content-Type: application/json

...

{ "ttl": 300 }


响应

HTTP/1.1 204 No Content


讨论

客户端应在长时间运行的工作批次期间定期续订认领,以避免在处理消息中间丢失认领。这通过向特定的认领资源发出 PATCH 请求并包含认领的新 TTL 来完成(可能与原始 TTL 不同)。服务器将重置认领的年龄并应用新的 TTL。

释放声明

模板

DELETE /v1.1/queues/{queue_name}/claims/{claim_id}


请求

DELETE /v1.1/queues/foo-bar/claims/a28ee94e-6cb4-11e2-b4d5-7703267a7926 HTTP/1.1
Host: marconi.example.com


响应

HTTP/1.1 204 No Content


讨论

使用此操作立即释放 claim,使与该 claim 关联的任何(剩余的、未删除的)消息可供其他 worker 使用。

当 worker 执行优雅关闭、无法处理一个或多个消息,或者处理消息花费的时间比预期更长,并且希望使剩余消息可供其他 worker 使用时,此操作很有用。

消息存储池

警告:在生产部署中,存储池 API 应通过基于角色的身份验证(例如,通过 policy middleware)限制为管理员。您还可以通过在 zaqar.conf 中设置 admin_mode = False 来完全禁用面向公众的 Web 头部上的池端点,从而减少攻击面。

Zaqar 通过以下方式支持异构池

  • stevedore 用于动态存储驱动程序加载
  • 节点权重


只要在已安装模块中定义了一个与池连接 URI 方案匹配的入口点,Zaqar 就能够使用该池。例如,池条目可能如下所示

{
  "weight": 100,
  "uri": "mongodb://:27017",
  "options": {
    "max_retry_sleep": 1
  }
}

注册池

模板

PUT /v1.1/pools/{pool}

请求

PUT /v1.1/pools/wat HTTP/1.1
Host: marconi.example.com

{
  "weight": 100,
  "uri": "mongodb://:27017",
  "options": {
    "max_retry_sleep": 1,
    "partitions": 8
  }
}


响应

HTTP/1.1 201 Created
Location: /v1.1/pools/wat


讨论

注册一个池。

pool 是要赋予存储池条目的名称。名称长度不得超过 64 字节,并且仅限于 US-ASCII 字母、数字、下划线和连字符。

weight 是选择此池进行下一次队列分配的可能性。它必须是一个大于 -1 的整数。

uri 是与尝试连接到该池的存储客户端(例如 pymongo)兼容的连接字符串。

options 一个可选的请求组件,提供存储驱动程序实现使用的特定于存储的选项。有效参数来自给定存储后端的注册选项,例如:mongodbsqlite

获取池信息

模板

GET /v1.1/pools/{pool}?detailed=True

请求

GET /v1.1/pools/wat HTTP/1.1
Host: marconi.example.com


响应

HTTP/1.1 200 OK
Content-Location: /v1.1/pools/wat

{
  "uri": "mongodb://marconi1.example.com:27017",
  "weight": 100
}


讨论

返回有关已注册池的信息。

删除池

模板

DELETE /v1.1/pools/{pool}

请求

DELETE /v1.1/pools/wat HTTP/1.1
Host: marconi.example.com


响应

HTTP/1.1 204 No Content

讨论

从注册表中删除存储池。

更新池

模板

PATCH /v1.1/pools/{pool}

请求

PATCH /v1.1/pools/wat HTTP/1.1
Host: marconi.example.com

{
  "uri": "mongodb://marconi3.example.com:27018",
  "weight": 120
}


响应

HTTP/1.1 204 No Content

讨论

允许更新 `weight`、`uri`、`options` 中的任何一个或所有。必须至少指定其中一个字段,否则返回 HTTP 400。

列出池

模板

GET /v1.1/pools?detailed=True&limit=10&marker=taco

请求

GET /v1.1/pools HTTP/1.1
Host: marconi.proxy.example.com


响应

HTTP/1.1 200 OK
Content-Location: /v1.1/pools

{
  "links": [
    {
      "rel": "next",
      "href": "/v1.1/pools?marker=wot&limit=10&detailed=True
    }
  ],
  "pools": [
    {"href": "/v1.1/pools/wat", "weight": 100, "uri": "mongodb://marconi1.example.com:27017"},
    {"href": "/v1.1/pools/wot", "weight": 50, "uri": "redis://marconi2.example.com:6379"}
  ]
}

或者,如果不存在池

HTTP/1.1 200 OK
Content-Location: /v1.1/pools

{
  "links": [
    {
      "rel": "next",
      "href": "/v1.1/pools?marker=bar&limit=10&detailed=True
    }
  ],
  "pools": []
}


讨论

列出已注册的池。

detailed 如果为 True,则在列表中返回 options 字段。marker 用于分页 - 从哪个池开始列出?limit 每个请求返回多少条目?

队列风味

警告:在生产部署中,队列风格 API 应通过基于角色的身份验证(例如,通过 policy middleware)限制为管理员。您还可以通过在 zaqar.conf 中设置 admin_mode = False 来完全禁用面向公众的 Web 头部上的风格端点,从而减少攻击面。

队列风格允许用户根据存储功能拥有不同类型的队列。通过使用风格,可以允许服务的消费者在持久存储、快速存储等之间进行选择。风格必须由服务管理员创建,并且依赖于池的存在。

一个风格条目可能如下所示

{
  "pool": "",
  "capabilities": {
      "durable": true
  }
}

创建风格

模板

PUT /v1.1/flavors/{flavor}

请求

PUT /v1.1/flavors/wat HTTP/1.1
Host: zaqar.example.com

{
  "pool": "my_pool",
  "capabilities": {
     "durable": true
  }
}


响应

HTTP/1.1 201 Created
Location: /v1.1/flavors/wat


讨论

创建一个风格。

flavor 是要赋予队列风格条目的名称。名称长度不得超过 64 字节,并且仅限于 US-ASCII 字母、数字、下划线和连字符。

pool 是此风格所依赖的池的名称。

capabilities 一个可选的请求组件,描述特定于风格的功能。这些功能应描述此风格根据存储功能所具备的能力。它们用于告知最终用户此类功能。

获取风格信息

模板

GET /v1.1/flavors/{flavor}?detailed=True

请求

GET /v1.1/flavors/wat HTTP/1.1
Host: zaqar.example.com


响应

HTTP/1.1 200 OK
Content-Location: /v1.1/flavors/wat

{
  "pool": "my_pool",
}


讨论

返回有关已注册池的信息。

删除风格

模板

DELETE /v1.1/flavors/{flavor}

请求

DELETE /v1.1/flavors/wat HTTP/1.1
Host: zaqar.example.com


响应

HTTP/1.1 204 No Content

讨论

从注册表中删除队列风格。

更新风格

模板

PATCH /v1.1/flavors/{flavor}

请求

PATCH /v1.1/flavors/wat HTTP/1.1
Host: zaqar.example.com

{
  "pool": "my_new_pool",
}


响应

HTTP/1.1 204 No Content

讨论

允许更新 `pool`、`capabilities` 中的任何一个或所有。必须至少指定其中一个字段,否则返回 HTTP 400。

列出风格

模板

GET /v1.1/flavors?detailed=True&limit=10&marker=taco

请求

GET /v1.1/flavors HTTP/1.1
Host: zaqar.proxy.example.com


响应

HTTP/1.1 200 OK
Content-Location: /v1.1/flavors

{
  "links": [
    {
      "rel": "next",
      "href": "/v1.1/flavors?marker=wot&detailed=True&limit=10"
  ],
  "flavors":[
    {"href": "/v1.1/flavors/wat", "pool": "pool1", "capabilities": {}},
    {"href": "/v1.1/flavors/wot", "pool": "pool1", "capabilities": {"durable": true}}
  ]
}

或者,如果不存在风格

HTTP/1.1 200 OK
Content-Location: /v1.1/flavors

{
  "links": [
    {
      "rel": "next",
      "href": "/v1.1/flavors?marker=bar&detailed=True&limit=10"
  ],
  "flavors":[]
}


讨论

列出已注册的风格。

detailed 如果为 True,则在列表中返回 capabilities 字段。marker 用于分页 - 从哪个风格开始列出?limit 每个请求返回多少条目?