处理失败和故障切换
Pingora-proxy 允许用户定义如何在代理请求的整个生命周期中处理失败。
当响应头尚未发送到下游时,用户有以下选项:
- 发送错误页面到下游,然后终止请求。
- 重试同一个上游请求。
- 如果适用,尝试另一个上游请求。
如果响应头已经发送到下游,代理无法做任何处理,只能记录错误并放弃请求。
重试 / 故障切换
为了实现重试或故障切换,fail_to_connect()
或 error_while_proxy()
需要将错误标记为“可重试”。对于故障切换,还需要在 CTX
中更新状态,指示 upstream_peer()
不要再使用同一个 Peer
。
安全性
通常情况下,幂等的 HTTP 请求(例如 GET
)是安全可重试的。而非幂等请求(例如 POST
)如果已发送,则不安全可重试。当调用 fail_to_connect()
时,Pingora-proxy 保证未向上游发送任何内容。在调用 error_while_proxy()
后,不建议重试非幂等请求,除非对上游服务器的行为非常了解并确定其安全。
示例
在以下示例中,我们通过 CTX
的 tries
变量跟踪连接尝试次数。在 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)
。
pub struct MyProxy();
pub struct MyCtx {
tries: usize,
}
#[async_trait]
impl ProxyHttp for MyProxy {
type CTX = MyCtx;
fn new_ctx(&self) -> Self::CTX {
MyCtx { tries: 0 }
}
fn fail_to_connect(
&self,
_session: &mut Session,
_peer: &HttpPeer,
ctx: &mut Self::CTX,
mut e: Box<Error>,
) -> Box<Error> {
if ctx.tries > 0 {
return e;
}
ctx.tries += 1;
e.set_retry(true); // 标记为可重试
e
}
async fn upstream_peer(
&self,
_session: &mut Session,
ctx: &mut Self::CTX,
) -> Result<Box<HttpPeer>> {
let addr = if ctx.tries < 1 {
("192.0.2.1", 443) // 第一次尝试的上游
} else {
("1.1.1.1", 443) // 故障切换后的上游
};
let mut peer = Box::new(HttpPeer::new(addr, true, "one.one.one.one".to_string()));
peer.options.connection_timeout = Some(Duration::from_millis(100)); // 设置超时时间
Ok(peer)
}
}
示例解释
- 上下文管理:
MyCtx
保存了连接尝试次数 (tries
)。 - 故障处理:
fail_to_connect
判断是否需要重试,并标记错误为可重试。 - 上游选择:
upstream_peer
根据tries
决定连接哪个上游。 - 超时设置:通过
HttpPeer
的connection_timeout
配置连接超时。
这种方式确保了简单而有效的重试和故障切换策略,同时避免了非安全的重试行为。