Files
easyai-ai-gateway/apps/api/internal/httpapi/admin_execution_pools.go
T
wangbo 7786692d32 feat(routing): 引入多执行池智能调度
将 Worker 发现、路由画像、容量与执行传输抽象为平台无关接口,新增 Kubernetes 和静态容量适配器,并以 shadow 模式接入生产配置。

实现网络与容量评分、路由防抖、池队列、同步 Worker 租约、一次性执行令牌,以及提交状态不明时禁止重复分配的安全语义。

新增 0105 兼容迁移、管理接口、指标、OpenAPI 和回归测试。已执行全量 Go 测试、go vet、OpenAPI、迁移安全、Compose 与 Kustomize 验证。
2026-08-05 22:25:37 +08:00

144 lines
6.1 KiB
Go

package httpapi
import (
"net/http"
"time"
"github.com/easyai/easyai-ai-gateway/apps/api/internal/executionpool"
)
type AdminExecutionPoolWorker struct {
WorkerID string `json:"workerId"`
InstanceID string `json:"instanceId"`
Revision string `json:"revision,omitempty"`
ProtocolVersion string `json:"protocolVersion"`
Capabilities map[string]any `json:"capabilities"`
Allocated int `json:"allocated"`
SafeCapacity int `json:"safeCapacity"`
ActiveTasks int `json:"activeTasks"`
PressureState string `json:"pressureState"`
HeartbeatAt time.Time `json:"heartbeatAt"`
}
type AdminExecutionPool struct {
PoolID string `json:"poolId"`
Labels map[string]string `json:"labels"`
Capabilities map[string]any `json:"capabilities"`
State string `json:"state"`
Capacity *AdminExecutionPoolCapacity `json:"capacity,omitempty"`
Workers []AdminExecutionPoolWorker `json:"workers"`
}
type AdminExecutionPoolCapacity struct {
WorkerCount int `json:"workerCount"`
Allocated int `json:"allocated"`
SafeCapacity int `json:"safeCapacity"`
ActiveTasks int `json:"activeTasks"`
HeavyCapacity int `json:"heavyCapacity"`
HeavyTasks int `json:"heavyTasks"`
QueueDepth int `json:"queueDepth"`
EstimatedQueueWaitMilli int64 `json:"estimatedQueueWaitMs"`
ResourceHeadroom float64 `json:"resourceHeadroom"`
Stability float64 `json:"stability"`
Critical bool `json:"critical"`
SampledAt time.Time `json:"sampledAt"`
}
type AdminRouteHealth struct {
PoolID string `json:"poolId"`
RouteProfileKey string `json:"routeProfileKey"`
State string `json:"state"`
SuccessRate float64 `json:"successRate"`
ConnectTLSP95Milli int64 `json:"connectTlsP95Ms"`
FirstByteP95Milli int64 `json:"firstByteP95Ms"`
UploadBytesPerSecond float64 `json:"uploadBytesPerSecond"`
JitterP95Milli int64 `json:"jitterP95Ms"`
ConsecutiveFailures int `json:"consecutiveFailures"`
ConsecutiveSuccesses int `json:"consecutiveSuccesses"`
SampleCount int `json:"sampleCount"`
SampledAt time.Time `json:"sampledAt"`
ExpiresAt time.Time `json:"expiresAt"`
}
type AdminExecutionPoolResponse struct {
Pools []AdminExecutionPool `json:"pools"`
RouteHealth []AdminRouteHealth `json:"routeHealth"`
CapturedAt time.Time `json:"capturedAt"`
}
// listExecutionPools godoc
// @Summary 查询逻辑执行池、Worker 容量和上游路由健康状态
// @Tags Admin Runtime
// @Produce json
// @Success 200 {object} AdminExecutionPoolResponse
// @Failure 500 {object} ErrorEnvelope
// @Security BearerAuth
// @Router /api/admin/runtime/execution-pools [get]
func (s *Server) listExecutionPools(w http.ResponseWriter, r *http.Request) {
now := time.Now()
pools, err := s.coordinationStore.ListExecutionPools(r.Context())
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
workers, err := s.coordinationStore.ListWorkers(r.Context(), now)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
capacities, err := s.coordinationStore.ListCapacity(r.Context(), now)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
health, err := s.coordinationStore.ListAllRouteHealth(r.Context())
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
capacityByPool := make(map[string]executionpool.CapacitySnapshot, len(capacities))
for _, capacity := range capacities {
capacityByPool[capacity.PoolID] = capacity
}
workersByPool := make(map[string][]AdminExecutionPoolWorker)
for _, worker := range workers {
workersByPool[worker.PoolID] = append(workersByPool[worker.PoolID], AdminExecutionPoolWorker{
WorkerID: worker.WorkerID, InstanceID: worker.InstanceID, Revision: worker.Revision,
ProtocolVersion: worker.ProtocolVersion, Capabilities: worker.Capabilities,
Allocated: worker.Allocated, SafeCapacity: worker.SafeCapacity,
ActiveTasks: worker.ActiveTasks, PressureState: worker.PressureState,
HeartbeatAt: worker.HeartbeatAt,
})
}
response := AdminExecutionPoolResponse{
Pools: make([]AdminExecutionPool, 0, len(pools)), RouteHealth: make([]AdminRouteHealth, 0, len(health)), CapturedAt: now,
}
for _, item := range health {
response.RouteHealth = append(response.RouteHealth, AdminRouteHealth{
PoolID: item.PoolID, RouteProfileKey: item.RouteProfileKey, State: string(item.State),
SuccessRate: item.SuccessRate, ConnectTLSP95Milli: item.ConnectTLSP95.Milliseconds(),
FirstByteP95Milli: item.FirstByteP95.Milliseconds(), UploadBytesPerSecond: item.UploadBytesPerSecond,
JitterP95Milli: item.JitterP95.Milliseconds(), ConsecutiveFailures: item.ConsecutiveFailures,
ConsecutiveSuccesses: item.ConsecutiveSuccesses, SampleCount: item.SampleCount,
SampledAt: item.SampledAt, ExpiresAt: item.ExpiresAt,
})
}
for _, pool := range pools {
item := AdminExecutionPool{
PoolID: pool.ID, Labels: pool.Labels, Capabilities: pool.Capabilities,
State: string(pool.State), Workers: workersByPool[pool.ID],
}
if capacity, ok := capacityByPool[pool.ID]; ok {
item.Capacity = &AdminExecutionPoolCapacity{
WorkerCount: capacity.WorkerCount, Allocated: capacity.Allocated, SafeCapacity: capacity.SafeCapacity,
ActiveTasks: capacity.ActiveTasks, HeavyCapacity: capacity.HeavyCapacity, HeavyTasks: capacity.HeavyTasks,
QueueDepth: capacity.QueueDepth, EstimatedQueueWaitMilli: capacity.EstimatedQueueWait.Milliseconds(),
ResourceHeadroom: capacity.ResourceHeadroom, Stability: capacity.Stability,
Critical: capacity.Critical, SampledAt: capacity.SampledAt,
}
}
response.Pools = append(response.Pools, item)
}
writeJSON(w, http.StatusOK, response)
}