腾讯云 API 最佳实践: 善用幂等性

2021-08-11 22:49:29 浏览数 (1)

有些开发者问我云服务器“创建实例”接口有一个参数“ClientToken”不知道有什么作用。本文作一个简单的解答。

  1. ClientToken 是防止重复创建资源的;
  2. 对于创建资源的接口,你总是应该用到 ClientToken;
  3. 不同的请求用不同的 ClientToken,同一个请求用相同的 ClientToken;
  4. 欢迎对不支持 ClientToken 的发货类接口提出支持的诉求。

注意这里的同一个请求不是说你参数相同就是同一个请求,而是指你的目的性。举例来说,zqfan这个用户现在创建了一台服务器,过会儿又想创建同样的一台服务器,这叫两个不同的请求。虽然参数一样,但目的就是创建规格一样的两台机器,分两次请求发送,这两个请求的 ClientToken 应该指定不同的值。

ClientToken是什么?

如文档所言:

“用于保证请求幂等性的字符串。该字符串由客户生成,需保证不同请求之间唯一,最大值不超过64个ASCII字符。若不指定该参数,则无法保证请求的幂等性。”

ClientToken是需要调用者指定的,通常你直接给一个 UUID就够了 ,例如 a9a90aa6-751a-41b6-aad6-fae360632808 。更好一点的,你可以指定一个前缀以示用途,例如CT-a9a90aa6-751a-41b6-aad6-fae360632808 。

什么是幂等性?

wikipedia上的解释说:

“Idempotence is the property of certain operations in mathematics and computer science that they can be applied multiple times without changing the result beyond the initial application.”

简单地说就是无论操作多少次,结果都应该是一样的。用数学语言来表述: x * x = x

为什么要幂等性?

在查询类的接口里,你基本上是不会想要上一次的结果,而只关心当前的结果,在你不做改变系统状态的操作时,你反复调用查询接口,其返回应该是一样的。同样的,修改操作你也不会关心上一次结果,当你重复调用修改操作时,只要这一次成功了,目的就算达到了,如果失败了,再重试(如果可重试的话)直到成功即可。删除操作同理。

但是对于一些资源创建类的,单纯的重试就有问题了。云服务器的“创建实例”接口可以一次创建一百台实例,包年包月预付费。如果这一次你调用接口失败了,例如在返回结果前网络中断了,死机了,你设置的超时时间太短提前关闭了连接等等,服务器当前的状态你是不知道的。你只能去查询实例列表,还得按时间排序,确定下到底是否创建成功了,哪一批是上一次请求创建的,最终的结果难以保证正确性。如果是在代码里呢,你该怎么办?到底是重试,还是放弃直接抛出异常?重试,你会立刻创建新的一百台实例。抛出异常,可能服务器明明成功了,只是因为你所在环境网络不稳定,你的程序就运行不下去了,你的代码健壮性就太差了。

为了防止创建资源时发生重复下单的问题,引入幂等性的概念。服务器根据唯一标识符, 在腾讯云 API 中是 ClientToken ,判断操作是否曾经发生过。如果找到了同样的标识符,则表示这个操作发生过,直接返回上一次的结果;如果没有发生过,继续执行。

例如,现在你调用“创建实例”接口,同时指定了 ClientToken=CT-a9a90aa6-751a-41b6-aad6-fae360632808 。假设接口发生异常,你立刻再次发送同样的请求,带上同样的 ClientToken ,服务器会返回上一次的结果,而不是再去创建一批新的实例。

幂等性的局限性?

严格的幂等是理想的结果,实际上,现实的系统是难以达到的。古希腊哲学家赫拉克利特说过:"人不能两次踏进同一条河流"。

例如云服务器有一个接口“查看实例列表”,它是个只读的接口,返回的实例里有个实例状态的属性,按理它应该是要幂等的,但是当你对其中某个云服务器关机后,又或者单纯的是服务器正在例行维护自动关机了,那下次再去调用返回的结果,显然和上一次会不一样的。何况每一个请求都有 RequestId 标识符,这个标识符每次都不一样以区别不同的请求。

再比如云服务器的另一个接口“退还实例”,如果退还成功,它会正常返回。当你再次退还,会返回资源未找到的错误信息。这种情况下,虽然返回的不同,但是你的目的达到了。正常删除资源和试图删除不存在的资源,它们表义不同,但实际结果是等价的,都达到了令目标资源不再存在的目的。

又如前文所述,幂等性需要记录每一个带有幂等操作标识符的请求的结果。服务器无意承担一些不必要的存储负担,因此不涉及到资源变动的只读接口,或者涉及到资源变动,但是本身就自带幂等属性的修改、删除接口都是不会有幂等操作标识符的。

我们还需要考虑高并发的场景。假设两个请求同时发起,都有同样的 ClientToken ,那么总有一个返回先后,后返回的理论上是要和先返回的一致的。但是在分布式高并发系统里,先发出的请求未必先到达服务器,先到达服务器的未必先入库。如果先入库的请求还未处理完毕,另一个请求无法入库(唯一性)又查询不到结果,只能返回一个内部异常(理想情况下应该返回操作正在执行中)。在调用者看来,可能会发生第一个返回是内部异常,第二个返回是正常的预期结果的情况。严格来说,这并不是幂等的,但是只要调用者合理安排,这种情况是可以避免的,例如失败请求后你可以休眠几十毫秒到几秒再去请求。只要某一次请求有确定的结果,后续重复的请求都将返回这个结果。

调用者使用幂等性时,需要注意,不同的请求,它的 ClientToken 必须是不一样的,同一个请求,它的 ClientToken 必须是一样的。也就是说,服务器端完全依靠 ClientToken 来判断是否是同一个请求,而不管其他具体的参数。这就要求调用者得自己确保 ClientToken 不冲突,否则你以为接口返回成功了,其实是之前某一个请求的结果 。此外,为了将来能重复查询,你还必须将其缓存或者持久化。

腾讯云对幂等性支持的情况?

原则上,我们要求所有的资源创建类接口都要支持幂等性。但是每个产品的情况是各有差异的,我们暂时无法一刀切,只能根据实际情况进行判断。有些产品可能刚开始觉得没必要支持幂等性,但是后来随着用户的诉求,或者产品自身的发展,才决定要支持幂等性。这是一个发展的过程,还请用户谅解,并对我们产品的 API 监督和督促,提出意见和建议,共同成长,创造价值。

以下是目前(2021-08-11)我掌握的,支持幂等性的 API 3.0 接口:

  • 云服务器 CVM
    • API 版本 2017-03-12
      • AllocateHosts ,创建专用宿主机实例
      • InquiryPriceRunInstances,创建实例询价(实际并无必要)
      • RunInstances,创建实例
      • CreateDisasterRecoverGroup
  • 云硬盘 CBS
    • API 版本 2017-03-12
      • CreateDisks,创建云硬盘
  • 批量计算 Batch
    • API 版本 2017-03-12
      • CreateComputeEnv,创建计算环境
      • CreateCpmComputeEnv, 创建黑石计算环境
      • SubmitJob,提交作业
  • 云数据库 CDB
    • API 版本 2017-03-12
      • CreateDBInstanceHour
      • CreateDBInstance
  • 负载均衡 CLB
    • API版本 2018-03-17
      • CreateLoadBalancer
  • EMR
    • API 版本 2019-01-03
      • CreateInstance
      • RunJobFlow
      • ScaleOutInstance
  • CFS
    • API 版本 2019-07-19
      • CreateCfsFileSystem
  • GAAP
    • API 版本 2018-05-29
      • CloseProxies
      • CreateProxy
      • ModifyProxiesAttribute
      • ModifyProxiesProject
      • ModifyProxyConfiguration
      • OpenProxies
  • ECM
    • API 版本 2019-07-19
      • RunInstances

总结

看完了这篇短文,你明白了 ClientToken 的作用了吗?这里再回顾一下你在使用腾讯云 API 时应当采取的策略:

  1. ClientToken 是防止重复创建资源的;
  2. 对于创建资源的接口,你总是应该用到 ClientToken;
  3. 不同的请求用不同的 ClientToken,同一个请求用相同的 ClientToken;
  4. 欢迎对不支持 ClientToken 的创建类接口提出诉求。

0 人点赞