M5: 渠道体系(负载均衡+并发控制+健康检查+故障转移)
- channel.Candidates 按模型绑定取候选 + Pick 加权随机负载均衡 - TryAcquire 每渠道并发信号量, 满载溢出到其他候选 - HealthMonitor 后台定时探测, 连续失败进 cooldown, 恢复放回(可配 interval/threshold) - doProxy 遍历候选故障转移: 网络错误/429/5xx/超时且未写出响应头时安全重试; 400 等业务错误透传, 流式写出首字节后放弃重试 - 单测覆盖候选过滤/加权/并发/健康状态机 - E2E: 杀上游自动切换、cooldown、恢复、并发溢出 10/10 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -24,20 +24,15 @@ func (g *Gateway) chatCompletions(c *gin.Context) {
|
||||
c.Set("protocol", convert.ProtoChat)
|
||||
c.Set("model_name", br.Model)
|
||||
|
||||
ch, err := g.selectChannel(c, br.Model)
|
||||
if err != nil {
|
||||
cands := g.candidateChannels(br.Model)
|
||||
if len(cands) == 0 {
|
||||
apiError(c, http.StatusServiceUnavailable, "no_channel", "No available upstream channel")
|
||||
g.recordError(c, nil, nil, now(), "no_channel")
|
||||
return
|
||||
}
|
||||
plan, err := prepareUpstream(ch.Provider, convert.ProtoChat, body)
|
||||
if err != nil {
|
||||
apiError(c, http.StatusInternalServerError, "conversion_error", "Failed to convert request: "+err.Error())
|
||||
return
|
||||
}
|
||||
sink := &usageSink{}
|
||||
c.Set("usage_raw", &sinkHolder{sink: sink})
|
||||
g.doProxy(c, ch, plan, br.Stream, sink)
|
||||
g.doProxy(c, cands, convert.ProtoChat, body, br.Stream, sink)
|
||||
}
|
||||
|
||||
// responses POST /v1/responses(OpenAI Responses API)
|
||||
@@ -57,20 +52,15 @@ func (g *Gateway) responses(c *gin.Context) {
|
||||
c.Set("protocol", convert.ProtoResponses)
|
||||
c.Set("model_name", br.Model)
|
||||
|
||||
ch, err := g.selectChannel(c, br.Model)
|
||||
if err != nil {
|
||||
cands := g.candidateChannels(br.Model)
|
||||
if len(cands) == 0 {
|
||||
apiError(c, http.StatusServiceUnavailable, "no_channel", "No available upstream channel")
|
||||
g.recordError(c, nil, nil, now(), "no_channel")
|
||||
return
|
||||
}
|
||||
plan, err := prepareUpstream(ch.Provider, convert.ProtoResponses, body)
|
||||
if err != nil {
|
||||
apiError(c, http.StatusInternalServerError, "conversion_error", "Failed to convert request: "+err.Error())
|
||||
return
|
||||
}
|
||||
sink := &usageSink{}
|
||||
c.Set("usage_raw", &sinkHolder{sink: sink})
|
||||
g.doProxy(c, ch, plan, br.Stream, sink)
|
||||
g.doProxy(c, cands, convert.ProtoResponses, body, br.Stream, sink)
|
||||
}
|
||||
|
||||
// messages POST /v1/messages(Anthropic Messages API)
|
||||
@@ -90,20 +80,15 @@ func (g *Gateway) messages(c *gin.Context) {
|
||||
c.Set("protocol", convert.ProtoMessages)
|
||||
c.Set("model_name", br.Model)
|
||||
|
||||
ch, err := g.selectChannel(c, br.Model)
|
||||
if err != nil {
|
||||
cands := g.candidateChannels(br.Model)
|
||||
if len(cands) == 0 {
|
||||
apiError(c, http.StatusServiceUnavailable, "no_channel", "No available upstream channel")
|
||||
g.recordError(c, nil, nil, now(), "no_channel")
|
||||
return
|
||||
}
|
||||
plan, err := prepareUpstream(ch.Provider, convert.ProtoMessages, body)
|
||||
if err != nil {
|
||||
apiError(c, http.StatusInternalServerError, "conversion_error", "Failed to convert request: "+err.Error())
|
||||
return
|
||||
}
|
||||
sink := &usageSink{}
|
||||
c.Set("usage_raw", &sinkHolder{sink: sink})
|
||||
g.doProxy(c, ch, plan, br.Stream, sink)
|
||||
g.doProxy(c, cands, convert.ProtoMessages, body, br.Stream, sink)
|
||||
}
|
||||
|
||||
// usageSinkHolder 桥接:gin context 里保存 sink 引用,供 finishUsage 读取最终 usage。
|
||||
|
||||
Reference in New Issue
Block a user