fix: 修复手动代理节点请求不计数及延迟/心跳不显示的问题 (#368)

* fix: 修复手动代理节点请求不计数及延迟/心跳不显示的问题

问题描述:
- 手动添加的代理节点请求数始终为0,不会递增
- 手动代理节点的延迟和最后心跳时间不显示

根因:
Rust 重写版中缺失了 Python 版的手动代理节点请求计数逻辑。
隧道节点通过心跳上报计数,但手动节点没有心跳机制,
需要在 usage recording 路径中递增计数并更新延迟信息。

修复方案:
1. 在 ExecutionPlan 中提取 proxy 信息,注入到 request_metadata
2. 在 request_metadata 白名单中添加 proxy 字段
3. 新增 INCREMENT_MANUAL_PROXY_NODE_REQUESTS_SQL,
   递增 total_requests/failed_requests,
   同时更新 avg_latency_ms 和 last_heartbeat_at
4. 在 ProxyNodeWriteRepository trait 新增 increment_manual_node_requests 方法
5. 在 write_event_record 中解析 request_metadata 中的 proxy 信息,
   对非隧道模式的手动节点调用递增方法
6. 为 GatewayDataState 实现 ManualProxyNodeCounter trait

影响范围:
- 仅影响手动代理节点的统计数据
- 隧道节点不受影响(继续通过心跳计数)
- 不影响请求转发逻辑

* style: cargo fmt

---------

Co-authored-by: root <root@ser406777952330.local>
This commit is contained in:
jiuwovo-ai
2026-05-03 14:50:36 +08:00
committed by GitHub
parent c4ea042eb4
commit 3e2eca4fd0
9 changed files with 195 additions and 9 deletions

View File

@@ -18,6 +18,17 @@ pub trait UsageEventRecorder: Send + Sync {
async fn record_usage_event(&self, event: &UsageEvent) -> Result<(), DataLayerError>;
}
#[async_trait]
pub trait ManualProxyNodeCounter: Send + Sync {
async fn increment_manual_proxy_node_requests(
&self,
node_id: &str,
total_delta: i64,
failed_delta: i64,
latency_ms: Option<i64>,
) -> Result<(), DataLayerError>;
}
#[async_trait]
pub trait UsageRecordWriter: Send + Sync {
async fn upsert_usage_record(
@@ -39,7 +50,7 @@ impl<T> UsageDataEventRecorder<T> {
#[async_trait]
impl<T> UsageEventRecorder for UsageDataEventRecorder<T>
where
T: UsageRecordWriter + UsageSettlementWriter + Send + Sync,
T: UsageRecordWriter + UsageSettlementWriter + ManualProxyNodeCounter + Send + Sync,
{
async fn record_usage_event(&self, event: &UsageEvent) -> Result<(), DataLayerError> {
write_event_record(self.data.as_ref(), event).await
@@ -198,22 +209,73 @@ pub fn build_usage_queue_worker<T>(
config: UsageRuntimeConfig,
) -> Result<UsageQueueWorker, DataLayerError>
where
T: UsageRecordWriter + UsageSettlementWriter + Send + Sync + 'static,
T: UsageRecordWriter + UsageSettlementWriter + ManualProxyNodeCounter + Send + Sync + 'static,
{
UsageQueueWorker::new(runner, Arc::new(UsageDataEventRecorder::new(data)), config)
}
pub async fn write_event_record<T>(data: &T, event: &UsageEvent) -> Result<(), DataLayerError>
where
T: UsageRecordWriter + UsageSettlementWriter + Send + Sync,
T: UsageRecordWriter + UsageSettlementWriter + ManualProxyNodeCounter + Send + Sync,
{
let record = build_upsert_usage_record_from_event(event)?;
if let Some(stored) = data.upsert_usage_record(record).await? {
settle_usage_if_needed(data, &stored).await?;
}
increment_manual_proxy_node_from_event(data, event).await;
Ok(())
}
async fn increment_manual_proxy_node_from_event<T>(data: &T, event: &UsageEvent)
where
T: ManualProxyNodeCounter + Send + Sync,
{
let is_terminal = matches!(
event.event_type,
crate::UsageEventType::Completed | crate::UsageEventType::Failed
);
if !is_terminal {
return;
}
let Some(node_id) = extract_manual_proxy_node_id(event) else {
return;
};
let failed = matches!(event.event_type, crate::UsageEventType::Failed);
let failed_delta = if failed { 1i64 } else { 0i64 };
let latency_ms = event.data.response_time_ms.map(|v| v as i64);
if let Err(err) = data
.increment_manual_proxy_node_requests(&node_id, 1, failed_delta, latency_ms)
.await
{
warn!(
event_name = "manual_proxy_node_increment_failed",
log_type = "ops",
node_id = %node_id,
error = ?err,
"failed to increment manual proxy node request count"
);
}
}
fn extract_manual_proxy_node_id(event: &UsageEvent) -> Option<String> {
let metadata = event.data.request_metadata.as_ref()?;
let proxy = metadata.get("proxy")?.as_object()?;
let mode = proxy
.get("mode")
.and_then(serde_json::Value::as_str)
.unwrap_or("")
.trim();
if mode == "tunnel" {
return None;
}
proxy
.get("node_id")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|v| !v.is_empty())
.map(String::from)
}
fn consumer_name() -> String {
let host = std::env::var("HOSTNAME")
.ok()
@@ -233,7 +295,7 @@ mod tests {
use aether_data_contracts::repository::usage::{StoredRequestUsageAudit, UpsertUsageRecord};
use async_trait::async_trait;
use super::{write_event_record, UsageRecordWriter};
use super::{write_event_record, ManualProxyNodeCounter, UsageRecordWriter};
use crate::{UsageEvent, UsageEventData, UsageEventType, UsageSettlementWriter};
#[derive(Default)]
@@ -317,6 +379,19 @@ mod tests {
}
}
#[async_trait]
impl ManualProxyNodeCounter for TestUsageStore {
async fn increment_manual_proxy_node_requests(
&self,
_node_id: &str,
_total_delta: i64,
_failed_delta: i64,
_latency_ms: Option<i64>,
) -> Result<(), aether_data_contracts::DataLayerError> {
Ok(())
}
}
fn sample_event() -> UsageEvent {
UsageEvent::new(
UsageEventType::Completed,