处理失败和故障切换

Pingora-proxy 允许用户定义如何在代理请求的整个生命周期中处理失败。

当响应头尚未发送到下游时,用户有以下选项:

  1. 发送错误页面到下游,然后终止请求。
  2. 重试同一个上游请求。
  3. 如果适用,尝试另一个上游请求。

如果响应头已经发送到下游,代理无法做任何处理,只能记录错误并放弃请求。


重试 / 故障切换

为了实现重试或故障切换,fail_to_connect()error_while_proxy() 需要将错误标记为“可重试”。对于故障切换,还需要在 CTX 中更新状态,指示 upstream_peer() 不要再使用同一个 Peer

安全性

通常情况下,幂等的 HTTP 请求(例如 GET)是安全可重试的。而非幂等请求(例如 POST)如果已发送,则不安全可重试。当调用 fail_to_connect() 时,Pingora-proxy 保证未向上游发送任何内容。在调用 error_while_proxy() 后,不建议重试非幂等请求,除非对上游服务器的行为非常了解并确定其安全。


示例

在以下示例中,我们通过 CTXtries 变量跟踪连接尝试次数。在 upstream_peer 方法中,当 tries < 1 时,我们连接到 192.0.2.1。如果连接失败,在 fail_to_connect 中递增 tries 并调用 e.set_retry(true),标记该错误为可重试。当再次重试时,进入 upstream_peer 方法,这次连接到 1.1.1.1。如果仍然无法连接,我们返回 502,因为仅在 tries = 0 时设置了 e.set_retry(true)

  1. pub struct MyProxy();
  2. pub struct MyCtx {
  3. tries: usize,
  4. }
  5. #[async_trait]
  6. impl ProxyHttp for MyProxy {
  7. type CTX = MyCtx;
  8. fn new_ctx(&self) -> Self::CTX {
  9. MyCtx { tries: 0 }
  10. }
  11. fn fail_to_connect(
  12. &self,
  13. _session: &mut Session,
  14. _peer: &HttpPeer,
  15. ctx: &mut Self::CTX,
  16. mut e: Box<Error>,
  17. ) -> Box<Error> {
  18. if ctx.tries > 0 {
  19. return e;
  20. }
  21. ctx.tries += 1;
  22. e.set_retry(true); // 标记为可重试
  23. e
  24. }
  25. async fn upstream_peer(
  26. &self,
  27. _session: &mut Session,
  28. ctx: &mut Self::CTX,
  29. ) -> Result<Box<HttpPeer>> {
  30. let addr = if ctx.tries < 1 {
  31. ("192.0.2.1", 443) // 第一次尝试的上游
  32. } else {
  33. ("1.1.1.1", 443) // 故障切换后的上游
  34. };
  35. let mut peer = Box::new(HttpPeer::new(addr, true, "one.one.one.one".to_string()));
  36. peer.options.connection_timeout = Some(Duration::from_millis(100)); // 设置超时时间
  37. Ok(peer)
  38. }
  39. }

示例解释

  1. 上下文管理MyCtx 保存了连接尝试次数 (tries)。
  2. 故障处理fail_to_connect 判断是否需要重试,并标记错误为可重试。
  3. 上游选择upstream_peer 根据 tries 决定连接哪个上游。
  4. 超时设置:通过 HttpPeerconnection_timeout 配置连接超时。

这种方式确保了简单而有效的重试和故障切换策略,同时避免了非安全的重试行为。