API分页最佳实践

354

我需要一些帮助来处理我正在构建的分页API中的一个奇怪的边缘情况。

像许多API一样,这个API对大型结果进行分页。 如果您查询/foos,则会获得100个结果(即foo#1-100)和链接/foos?page = 2,它应返回foo#101-200。

不幸的是,如果在API使用者进行下一次查询之前从数据集中删除了foo#10,/foos?page = 2将偏移100并返回foos#102-201。

对于试图拉取所有foos的API使用者来说,这是一个问题-他们将不会收到foo#101。

如何最好地处理这种情况?我们希望尽可能轻量级(即避免处理API请求的会话)。 非常感谢其他API的示例!


2
我一直在面对这个问题并寻找解决方案。据我所知,如果每个页面都执行新的查询,那么真正没有可靠的机制来完成这个任务。我能想到的唯一解决方案是保持一个活动会话,并将结果集保存在服务器端,而不是为每个页面执行新的查询,只需获取下一个缓存的记录集即可。 - Jerry Dodge
哦,我刚看到你问题的那一部分,你想避免那种情况。 - Jerry Dodge
36
看看 Twitter 是如何实现这一点的:https://dev.twitter.com/rest/public/timelines - java_geek
1
@java_geek,since_id参数是如何更新的?在Twitter网页中,它似乎使用相同的值进行两个请求。我想知道它何时会更新,以便如果添加了新的推文,它们可以被计算在内? - Petar
1
@Petar,since_id参数需要由API的使用者进行更新。如果您看到,那个例子是指客户端处理推文。 - java_geek
显示剩余2条评论
13个回答

3
我经过深思熟虑,最终找到了下面所描述的解决方案。虽然这是一个相当复杂的步骤,但如果你采取了这一步,你将得到你真正想要的结果,即未来请求的确定性结果。
你提到删除项目仅仅是冰山一角。如果在请求之间有人更改了物品颜色,那么按照“颜色=蓝色”的过滤器可靠地分页获取所有物品是不可能实现的...除非我们实施“修订历史”。
我已经实施了这个功能,而且它其实比我预期的要简单。我做的如下:
  • 我创建了一个名为“changelogs”的表,并带有自动增量ID列
  • 我的实体有一个“id”字段,但这不是主键
  • 实体有一个“changeId”字段,既是主键,也是外键引用“changelogs”。
  • 每当用户创建、更新或删除记录时,系统都会在“changelogs”中插入新的记录,抓取id并将其分配给实体的“新”版本,然后将其插入到数据库中。
  • 我的查询选择最大的changeId(按id分组),并进行自连接以获取所有记录的最新版本。
  • 筛选器应用于最新的记录
  • 状态字段跟踪项目是否被删除
  • 最大的changeId返回给客户端,并在后续请求中添加一个查询参数
  • 因为只有新的更改才会创建,每个changeId都代表了更改创建时基础数据的唯一快照。
  • 这意味着你可以永久缓存具有changeId参数的请求结果。这些结果永远不会过期,因为它们永远不会改变。
  • 这也开启了令人兴奋的功能,如回滚/还原,同步客户端缓存等。任何从更改历史中受益的功能。

我有点困惑。这怎么解决你提到的使用案例呢?(缓存中的一个随机字段发生变化,您希望使缓存失效) - U Avalos
对于您自己所做的任何更改,您只需查看响应即可。服务器将提供一个新的changeId,您可以在下一次请求中使用它。对于其他人所做的更改,您可以定期轮询最新的changeId,如果它比您自己的高,那么您就知道有未完成的更改。或者您可以设置一些通知系统(长轮询、服务器推送、WebSockets),当有未完成的更改时,它会向客户端发出警报。 - Stijn de Witt

2
参考API分页设计,我们可以通过游标来设计分页API。

他们有一个叫做游标的概念——它是一行的指针。因此,你可以对数据库说:“在那个之后给我返回100行”。由于有一个索引字段,你很可能能够确定行,所以这对于数据库来说更容易实现。突然间,你不需要获取和跳过那些行,而是直接跳过它们。 以下是一个例子:

  GET /api/products
  {"items": [...100 products],
   "cursor": "qWe"}

API返回一个(不透明的)字符串,您可以使用它来检索下一页:
GET /api/products?cursor=qWe
{"items": [...100 products],
 "cursor": "qWr"}

实现上有很多选择。一般来说,你会有一些排序标准,比如产品ID。在这种情况下,您将使用一些可逆算法(例如hashids)对产品ID进行编码。并且在接收到带有光标的请求后,您会对其进行解码并生成类似于WHERE id>:cursor LIMIT 100的查询。
优点:
  • 可以通过cursor提高数据库的查询性能
  • 在查询时处理新内容插入到数据库中的情况
缺点:
  • 无法为无状态API生成previous page链接

0
我最终来到这里,是因为我想知道MS Exchange Online PowerShell命令(如Get-QuarantineMessage)返回的数据有多少是没有返回的。根据所有的阅读,我得出的结论是,消费者永远不能相信分页服务提供商会返回所有的结果。开发人员主观性、技能差距、误解等问题太多了。分页只有一个好处,那就是性能保护,但代价是无法保证数据的完整性。
但这不是什么大问题,只是好知道而已。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接