55

安全友好的客户端行为

 5 years ago
source link: https://mozillazg.com/2019/03/safe-client-behaviour-notes-retry-jitter-back-off-more.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文是 Safe Client Behaviour | USENIX 这个视频的简单总结,可以当做是 调用远程服务的一些备忘录 - Huang Huang 的博客 的延伸/补充内容。强烈建议直接查看 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% 的重试。
    • 阻止重连请求影响其他正常请求。
  • 适当的限流 * 基于失败率直接在客户端取消掉新的请求,通过这种方式来减轻服务端的负载。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK