mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
Merge pull request #698 from mikewong23571/feature/proxy-optimizations
perf(proxy): cache agents with opt-in pooling
This commit is contained in:
@@ -53,6 +53,10 @@ DEFAULT_PROXY_TIMEOUT=600000
|
|||||||
MAX_PROXY_RETRIES=3
|
MAX_PROXY_RETRIES=3
|
||||||
# IP协议族配置:true=IPv4, false=IPv6, 默认IPv4(兼容性更好)
|
# IP协议族配置:true=IPv4, false=IPv6, 默认IPv4(兼容性更好)
|
||||||
PROXY_USE_IPV4=true
|
PROXY_USE_IPV4=true
|
||||||
|
# 代理连接池 / Keep-Alive 配置(默认关闭,如需启用请取消注释)
|
||||||
|
# PROXY_KEEP_ALIVE=true
|
||||||
|
# PROXY_MAX_SOCKETS=50
|
||||||
|
# PROXY_MAX_FREE_SOCKETS=10
|
||||||
|
|
||||||
# ⏱️ 请求超时配置
|
# ⏱️ 请求超时配置
|
||||||
REQUEST_TIMEOUT=600000 # 请求超时设置(毫秒),默认10分钟
|
REQUEST_TIMEOUT=600000 # 请求超时设置(毫秒),默认10分钟
|
||||||
|
|||||||
@@ -73,6 +73,30 @@ const config = {
|
|||||||
proxy: {
|
proxy: {
|
||||||
timeout: parseInt(process.env.DEFAULT_PROXY_TIMEOUT) || 600000, // 10分钟
|
timeout: parseInt(process.env.DEFAULT_PROXY_TIMEOUT) || 600000, // 10分钟
|
||||||
maxRetries: parseInt(process.env.MAX_PROXY_RETRIES) || 3,
|
maxRetries: parseInt(process.env.MAX_PROXY_RETRIES) || 3,
|
||||||
|
// 连接池与 Keep-Alive 配置(默认关闭,需要显式开启)
|
||||||
|
keepAlive: (() => {
|
||||||
|
if (process.env.PROXY_KEEP_ALIVE === undefined || process.env.PROXY_KEEP_ALIVE === '') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return process.env.PROXY_KEEP_ALIVE === 'true'
|
||||||
|
})(),
|
||||||
|
maxSockets: (() => {
|
||||||
|
if (process.env.PROXY_MAX_SOCKETS === undefined || process.env.PROXY_MAX_SOCKETS === '') {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const parsed = parseInt(process.env.PROXY_MAX_SOCKETS)
|
||||||
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined
|
||||||
|
})(),
|
||||||
|
maxFreeSockets: (() => {
|
||||||
|
if (
|
||||||
|
process.env.PROXY_MAX_FREE_SOCKETS === undefined ||
|
||||||
|
process.env.PROXY_MAX_FREE_SOCKETS === ''
|
||||||
|
) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const parsed = parseInt(process.env.PROXY_MAX_FREE_SOCKETS)
|
||||||
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined
|
||||||
|
})(),
|
||||||
// IP协议族配置:true=IPv4, false=IPv6, 默认IPv4(兼容性更好)
|
// IP协议族配置:true=IPv4, false=IPv6, 默认IPv4(兼容性更好)
|
||||||
useIPv4: process.env.PROXY_USE_IPV4 !== 'false' // 默认 true,只有明确设置为 'false' 才使用 IPv6
|
useIPv4: process.env.PROXY_USE_IPV4 !== 'false' // 默认 true,只有明确设置为 'false' 才使用 IPv6
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ services:
|
|||||||
# 🌐 代理配置
|
# 🌐 代理配置
|
||||||
- DEFAULT_PROXY_TIMEOUT=${DEFAULT_PROXY_TIMEOUT:-60000}
|
- DEFAULT_PROXY_TIMEOUT=${DEFAULT_PROXY_TIMEOUT:-60000}
|
||||||
- MAX_PROXY_RETRIES=${MAX_PROXY_RETRIES:-3}
|
- MAX_PROXY_RETRIES=${MAX_PROXY_RETRIES:-3}
|
||||||
|
- PROXY_USE_IPV4=${PROXY_USE_IPV4:-true}
|
||||||
|
- PROXY_KEEP_ALIVE=${PROXY_KEEP_ALIVE:-}
|
||||||
|
- PROXY_MAX_SOCKETS=${PROXY_MAX_SOCKETS:-}
|
||||||
|
- PROXY_MAX_FREE_SOCKETS=${PROXY_MAX_FREE_SOCKETS:-}
|
||||||
|
|
||||||
# 📈 使用限制
|
# 📈 使用限制
|
||||||
- DEFAULT_TOKEN_LIMIT=${DEFAULT_TOKEN_LIMIT:-1000000}
|
- DEFAULT_TOKEN_LIMIT=${DEFAULT_TOKEN_LIMIT:-1000000}
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ const config = require('../../config/config')
|
|||||||
* 支持 SOCKS5 和 HTTP/HTTPS 代理,可配置 IPv4/IPv6
|
* 支持 SOCKS5 和 HTTP/HTTPS 代理,可配置 IPv4/IPv6
|
||||||
*/
|
*/
|
||||||
class ProxyHelper {
|
class ProxyHelper {
|
||||||
|
// 缓存代理 Agent,避免重复创建浪费连接
|
||||||
|
static _agentCache = new Map()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建代理 Agent
|
* 创建代理 Agent
|
||||||
* @param {object|string|null} proxyConfig - 代理配置对象或 JSON 字符串
|
* @param {object|string|null} proxyConfig - 代理配置对象或 JSON 字符串
|
||||||
@@ -33,34 +36,91 @@ class ProxyHelper {
|
|||||||
// 获取 IPv4/IPv6 配置
|
// 获取 IPv4/IPv6 配置
|
||||||
const useIPv4 = ProxyHelper._getIPFamilyPreference(options.useIPv4)
|
const useIPv4 = ProxyHelper._getIPFamilyPreference(options.useIPv4)
|
||||||
|
|
||||||
|
// 配置连接池与 Keep-Alive
|
||||||
|
const proxySettings = config.proxy || {}
|
||||||
|
const agentCommonOptions = {}
|
||||||
|
|
||||||
|
if (typeof proxySettings.keepAlive === 'boolean') {
|
||||||
|
agentCommonOptions.keepAlive = proxySettings.keepAlive
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof proxySettings.maxSockets === 'number' &&
|
||||||
|
Number.isFinite(proxySettings.maxSockets) &&
|
||||||
|
proxySettings.maxSockets > 0
|
||||||
|
) {
|
||||||
|
agentCommonOptions.maxSockets = proxySettings.maxSockets
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof proxySettings.maxFreeSockets === 'number' &&
|
||||||
|
Number.isFinite(proxySettings.maxFreeSockets) &&
|
||||||
|
proxySettings.maxFreeSockets >= 0
|
||||||
|
) {
|
||||||
|
agentCommonOptions.maxFreeSockets = proxySettings.maxFreeSockets
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof proxySettings.timeout === 'number' &&
|
||||||
|
Number.isFinite(proxySettings.timeout) &&
|
||||||
|
proxySettings.timeout > 0
|
||||||
|
) {
|
||||||
|
agentCommonOptions.timeout = proxySettings.timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存键:保证相同配置的代理可复用
|
||||||
|
const cacheKey = JSON.stringify({
|
||||||
|
type: proxy.type,
|
||||||
|
host: proxy.host,
|
||||||
|
port: proxy.port,
|
||||||
|
username: proxy.username,
|
||||||
|
password: proxy.password,
|
||||||
|
family: useIPv4,
|
||||||
|
keepAlive: agentCommonOptions.keepAlive,
|
||||||
|
maxSockets: agentCommonOptions.maxSockets,
|
||||||
|
maxFreeSockets: agentCommonOptions.maxFreeSockets,
|
||||||
|
timeout: agentCommonOptions.timeout
|
||||||
|
})
|
||||||
|
|
||||||
|
if (ProxyHelper._agentCache.has(cacheKey)) {
|
||||||
|
return ProxyHelper._agentCache.get(cacheKey)
|
||||||
|
}
|
||||||
|
|
||||||
// 构建认证信息
|
// 构建认证信息
|
||||||
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
|
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
|
||||||
|
let agent = null
|
||||||
|
|
||||||
// 根据代理类型创建 Agent
|
// 根据代理类型创建 Agent
|
||||||
if (proxy.type === 'socks5') {
|
if (proxy.type === 'socks5') {
|
||||||
const socksUrl = `socks5h://${auth}${proxy.host}:${proxy.port}`
|
const socksUrl = `socks5h://${auth}${proxy.host}:${proxy.port}`
|
||||||
const socksOptions = {}
|
const socksOptions = { ...agentCommonOptions }
|
||||||
|
|
||||||
// 设置 IP 协议族(如果指定)
|
// 设置 IP 协议族(如果指定)
|
||||||
if (useIPv4 !== null) {
|
if (useIPv4 !== null) {
|
||||||
socksOptions.family = useIPv4 ? 4 : 6
|
socksOptions.family = useIPv4 ? 4 : 6
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SocksProxyAgent(socksUrl, socksOptions)
|
agent = new SocksProxyAgent(socksUrl, socksOptions)
|
||||||
} else if (proxy.type === 'http' || proxy.type === 'https') {
|
} else if (proxy.type === 'http' || proxy.type === 'https') {
|
||||||
const proxyUrl = `${proxy.type}://${auth}${proxy.host}:${proxy.port}`
|
const proxyUrl = `${proxy.type}://${auth}${proxy.host}:${proxy.port}`
|
||||||
const httpOptions = {}
|
const httpOptions = { ...agentCommonOptions }
|
||||||
|
|
||||||
// HttpsProxyAgent 支持 family 参数(通过底层的 agent-base)
|
// HttpsProxyAgent 支持 family 参数(通过底层的 agent-base)
|
||||||
if (useIPv4 !== null) {
|
if (useIPv4 !== null) {
|
||||||
httpOptions.family = useIPv4 ? 4 : 6
|
httpOptions.family = useIPv4 ? 4 : 6
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HttpsProxyAgent(proxyUrl, httpOptions)
|
agent = new HttpsProxyAgent(proxyUrl, httpOptions)
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`⚠️ Unsupported proxy type: ${proxy.type}`)
|
logger.warn(`⚠️ Unsupported proxy type: ${proxy.type}`)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (agent) {
|
||||||
|
ProxyHelper._agentCache.set(cacheKey, agent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return agent
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn('⚠️ Failed to create proxy agent:', error.message)
|
logger.warn('⚠️ Failed to create proxy agent:', error.message)
|
||||||
return null
|
return null
|
||||||
|
|||||||
Reference in New Issue
Block a user