安全友好的客户端行为

前言

本文是 Safe Client Behaviour | USENIX 这个视频的简单总结,可以当做是 调用远程服务的一些备忘录 的延伸/补充内容。强烈建议直接查看 Safe Client Behaviour | USENIX 这个原始视频,本文只是个简单的笔记没啥可看的,原视频才是精华。

不安全的客户端行为最直观的表现就是会在出现异常情况时 DDoS 服务端,所以安全的客户端行为就是要避免发生 DDoS 服务端的情况。视频里已一个 app 需要每 5 分钟从服务端获取一次信息为例,讲了几个原则,下面简单记录一下。

period = 300 // Once every 5 minutes
while true:
  send_rpc()
  wait(period)

Jitter Everything!

所谓的 Jitter 指的是给周期性的操作增加随机因子,不要固定操作周期平滑一下客户端的请求,让服务端的负载也平滑一下。 这里举了几个怎么加 jitter 的例子。

首先容易想到的是在 wait 的时候加 jitter:

period = 300 // Once every 5 minutes
while true:
  send_rpc()
  wait(period * random(.5, 1.5))

这种方法虽然确实平滑了后面周期性的请求,但是还有一个瑕疵,那就是第一次请求的时候多个客户端会产生一个尖峰,因为有可能发生大量客户端都在那个时候启动触发第一次请求,所以就有了第二方法:在第一次的时候就加 jitter,当然后面 wait 的时候也还是要继续加 jitter 的。

period = 300 // Once every 5 minutes
wait(period * random(.5, 1.5))
while true:
  send_rpc()
  wait(period * random(.5, 1.5))

通过在第一次请求前和后面 wait 的时候加 jitter 就可以尽可能的平滑请求,做一个不 DDoS 为服务端着想的好宝宝。

作者还说了一个不对周期时间做 jitter 而是对执行时间做 jitter 的方法,视频中说这种方法可以完全平滑请求曲线 ,达到一种完美的状态(但是我并没有搞懂这个方法中 truncate 函数是怎么实现的究竟包含了什么魔法,视频中也没说细节,大家如果知道的话可以留言告知我一下,谢谢了):

while true:
  period = 300 // Once every 5 minutes
  next_execution = now()
  next_execution = truncate(next_execution, period)
  next_execution += random(1.0, 2.0) * period
  wait_until(next_execution)
  send_rpc()

Don’t Retry! && If you retry, back off!

这个主要说的是如果要重试的话记得给重试增加 back-off(重试间隔指数递增) 和 jitter,例子:

while true:
  period = 300; delay = 10
  success = send_rpc()
  while not success:
    wait(delay * random(.5, 1.5))
    success = send_rpc()
    delay = delay * 2
  wait(period * random(.5, 1.5)

还有一个重试时比上面效率更高更平滑的方法是限制重试次数:

while true:
  period = 300; delay = 10
  success = send_rpc()
  while not success && delay <= period:
    wait(delay * random(.5, 1.5))
    success = send_rpc()
    delay = delay * 2
  wait(period * random(.5, 1.5)

还提到了关于重试的其他 tips:

  • 默认不要重试。
  • 重试时增加 back-off 指数递增的重试间隔。
  • 同时也别忘了增加 jitter 随机因子。
  • 不同场景下的重试策略:
    • 不要重试客户端错误(比如 HTTP 404 错误)。
    • 在服务端错误时重试(比如 HTTP 500 错误)。
    • 在发生网络错误时重试。
    • 在发生超时的时候小心重试。
    • 在配额超限的时候不要重试!

Safer clients: Move control to the server!

可以实现一些功能让服务端拥有控制客户端的能力:

  • 在客户端和服务端都实现 Retry-After 这个 HTTP Header,双方通过这个 Header 来约定下次重试的时机。
  • 服务端在实现这个重试周期的时候别忘了增加 jitter 随机因子。
  • 远程控制客户端的能力(比如远程把客户端的请求给临时停了,或者临时拉大重试基础间隔)。
  • 远程配置调用周期。
  • 维护一个可远程控制的客户端特性黑名单。

Safer clients: Expose information to server

客户端暴露越多的信息给服务端,就可以得到更精细的响应:

  • 给请求打标签
    • 客户端名称和版本号
    • 什么特性触发了当前请求
    • 失败请求的严重程度(比如短期内失败了多少次)
    • 当前请求是第一次请求还是重试发起的请求
  • 可能得到的服务端响应
    • 给请求赋予不同的优先级
    • drop 一些后台请求
    • 防止 drop 了一些可能会触发重连风暴的请求
    • 为客户端的 bug 做一些 workaround (比如旧版本的客户端 bug)

Safer Microservices

微服务环境下可以做的更多。

  • 重试预算
    • 限制只有多少百分比的重试可以正常发出,其他的重试直接取消。比如只允许发出 10% 的重试。
    • 阻止重连请求影响其他正常请求。
  • 适当的限流 * 基于失败率直接在客户端取消掉新的请求,通过这种方式来减轻服务端的负载。

总结

强烈建议直接查看 Safe Client Behaviour | USENIX 这个原始视频,本文只是个简单的笔记没啥可看的,原视频才是精华。


Comments