feat(storage): 统一二进制对象存储与公开错误
新增 Aliyun OSS 与 S3 协议、通道内重试和按优先级跨通道切换,保留 server-main 兼容与环境 OSS 内存通道。 将请求及结果中的 Base64、Data URI、Buffer、multipart 和内联二进制统一对象化,生产路径不再写入本机静态目录,历史本地资源仅保留只读兼容。 引入 PublicErrorV1 并统一 API、异步查询、兼容协议和失败回调的安全错误输出,同时补充迁移、管理端、指标、OpenAPI 与本地模拟验收。 验证:go test ./... -count=1;go vet ./...;pnpm lint;pnpm test;pnpm build;pnpm openapi;tests/ci/migrations-test.sh。
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/publicerror"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
||||
)
|
||||
|
||||
@@ -86,6 +87,19 @@ type Metrics struct {
|
||||
candidateDisabledSkipped atomic.Uint64
|
||||
candidateAllFullQueued atomic.Uint64
|
||||
candidateRoutingOther atomic.Uint64
|
||||
storageAliyunWrites atomic.Uint64
|
||||
storageAliyunFailures atomic.Uint64
|
||||
storageAliyunWriteNanos atomic.Uint64
|
||||
storageS3Writes atomic.Uint64
|
||||
storageS3Failures atomic.Uint64
|
||||
storageS3WriteNanos atomic.Uint64
|
||||
storageServerMainWrites atomic.Uint64
|
||||
storageServerMainFailures atomic.Uint64
|
||||
storageServerMainWriteNanos atomic.Uint64
|
||||
storageChannelRetries atomic.Uint64
|
||||
storageChannelFailovers atomic.Uint64
|
||||
storageAllChannelsFailed atomic.Uint64
|
||||
storageObjectifiedBytes atomic.Uint64
|
||||
taskAdmissionWaitBuckets [11]atomic.Uint64
|
||||
taskAdmissionWaitMicros atomic.Uint64
|
||||
}
|
||||
@@ -265,6 +279,40 @@ func (m *Metrics) ObserveCandidateRouting(reason string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metrics) ObserveObjectStorage(event string, provider string, bytes int64, duration time.Duration) {
|
||||
provider = strings.ToLower(strings.TrimSpace(provider))
|
||||
if duration < 0 {
|
||||
duration = 0
|
||||
}
|
||||
switch event {
|
||||
case "write_success", "write_failure":
|
||||
var writes, failures, nanos *atomic.Uint64
|
||||
switch provider {
|
||||
case "aliyun_oss":
|
||||
writes, failures, nanos = &m.storageAliyunWrites, &m.storageAliyunFailures, &m.storageAliyunWriteNanos
|
||||
case "s3":
|
||||
writes, failures, nanos = &m.storageS3Writes, &m.storageS3Failures, &m.storageS3WriteNanos
|
||||
case "server_main_openapi":
|
||||
writes, failures, nanos = &m.storageServerMainWrites, &m.storageServerMainFailures, &m.storageServerMainWriteNanos
|
||||
default:
|
||||
return
|
||||
}
|
||||
writes.Add(1)
|
||||
nanos.Add(uint64(duration))
|
||||
if event == "write_failure" {
|
||||
failures.Add(1)
|
||||
} else if bytes > 0 {
|
||||
m.storageObjectifiedBytes.Add(uint64(bytes))
|
||||
}
|
||||
case "retry":
|
||||
m.storageChannelRetries.Add(1)
|
||||
case "failover":
|
||||
m.storageChannelFailovers.Add(1)
|
||||
case "all_failed":
|
||||
m.storageAllChannelsFailed.Add(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metrics) ObserveTaskAdmissionWait(wait time.Duration) {
|
||||
if wait < 0 {
|
||||
wait = 0
|
||||
@@ -471,6 +519,17 @@ func (m *Metrics) Handler(provider MetricsSnapshotProvider, issuer, audience str
|
||||
{"all_full_queued", m.candidateAllFullQueued.Load()},
|
||||
{"other", m.candidateRoutingOther.Load()},
|
||||
})
|
||||
storageProtocolCounters(w, "easyai_gateway_storage_write_attempts_total", "Object storage write attempts by bounded protocol.",
|
||||
m.storageAliyunWrites.Load(), m.storageS3Writes.Load(), m.storageServerMainWrites.Load())
|
||||
storageProtocolCounters(w, "easyai_gateway_storage_write_failures_total", "Object storage write failures by bounded protocol.",
|
||||
m.storageAliyunFailures.Load(), m.storageS3Failures.Load(), m.storageServerMainFailures.Load())
|
||||
storageProtocolDurationCounters(w,
|
||||
m.storageAliyunWriteNanos.Load(), m.storageS3WriteNanos.Load(), m.storageServerMainWriteNanos.Load())
|
||||
plainCounter(w, "easyai_gateway_storage_channel_retries_total", "Retries performed within the current storage channel.", m.storageChannelRetries.Load())
|
||||
plainCounter(w, "easyai_gateway_storage_channel_failovers_total", "Switches to the next configured storage channel.", m.storageChannelFailovers.Load())
|
||||
plainCounter(w, "easyai_gateway_storage_all_channels_failed_total", "Storage writes where every eligible channel failed.", m.storageAllChannelsFailed.Load())
|
||||
plainCounter(w, "easyai_gateway_storage_objectified_bytes_total", "Binary bytes successfully written through storage channels.", m.storageObjectifiedBytes.Load())
|
||||
publicErrorCounters(w, publicerror.MetricSnapshot())
|
||||
platformModelRateLimitUtilizationGauges(w, modelRateLimits)
|
||||
plainGauge(w, "easyai_gateway_postgres_pool_max_connections", "Maximum PostgreSQL connections in this process pool.", int64(postgresPool.MaxConnections))
|
||||
plainGauge(w, "easyai_gateway_postgres_pool_total_connections", "Current PostgreSQL connections in this process pool.", int64(postgresPool.TotalConnections))
|
||||
@@ -567,6 +626,29 @@ func plainFloatGauge(w http.ResponseWriter, name, help string, value float64) {
|
||||
fmt.Fprintf(w, "# HELP %s %s\n# TYPE %s gauge\n%s %.6f\n", name, help, name, name, value)
|
||||
}
|
||||
|
||||
func storageProtocolCounters(w http.ResponseWriter, name, help string, aliyunOSS, s3, serverMain uint64) {
|
||||
fmt.Fprintf(w, "# HELP %s %s\n# TYPE %s counter\n", name, help, name)
|
||||
fmt.Fprintf(w, "%s{protocol=\"aliyun_oss\"} %d\n", name, aliyunOSS)
|
||||
fmt.Fprintf(w, "%s{protocol=\"s3\"} %d\n", name, s3)
|
||||
fmt.Fprintf(w, "%s{protocol=\"server_main_openapi\"} %d\n", name, serverMain)
|
||||
}
|
||||
|
||||
func storageProtocolDurationCounters(w http.ResponseWriter, aliyunOSS, s3, serverMain uint64) {
|
||||
const name = "easyai_gateway_storage_write_duration_seconds_total"
|
||||
fmt.Fprintf(w, "# HELP %s Total time spent in object storage write attempts by bounded protocol.\n# TYPE %s counter\n", name, name)
|
||||
fmt.Fprintf(w, "%s{protocol=\"aliyun_oss\"} %.6f\n", name, float64(aliyunOSS)/float64(time.Second))
|
||||
fmt.Fprintf(w, "%s{protocol=\"s3\"} %.6f\n", name, float64(s3)/float64(time.Second))
|
||||
fmt.Fprintf(w, "%s{protocol=\"server_main_openapi\"} %.6f\n", name, float64(serverMain)/float64(time.Second))
|
||||
}
|
||||
|
||||
func publicErrorCounters(w http.ResponseWriter, values []publicerror.MetricCount) {
|
||||
const name = "easyai_gateway_public_errors_total"
|
||||
fmt.Fprintf(w, "# HELP %s Public error responses by bounded stable code.\n# TYPE %s counter\n", name, name)
|
||||
for _, value := range values {
|
||||
fmt.Fprintf(w, "%s{code=\"%s\"} %d\n", name, value.Code, value.Count)
|
||||
}
|
||||
}
|
||||
|
||||
func platformModelRateLimitUtilizationGauges(w http.ResponseWriter, statuses []store.ModelRateLimitStatus) {
|
||||
const name = "easyai_gateway_platform_model_rate_limit_utilization"
|
||||
fmt.Fprintf(w, "# HELP %s Cluster-wide platform model quota utilization.\n# TYPE %s gauge\n", name, name)
|
||||
|
||||
Reference in New Issue
Block a user