mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
feat: 大规模性能优化 - Redis Pipeline 批量操作、索引系统、连接池优化
This commit is contained in:
@@ -7,6 +7,56 @@ class AccountGroupService {
|
||||
this.GROUPS_KEY = 'account_groups'
|
||||
this.GROUP_PREFIX = 'account_group:'
|
||||
this.GROUP_MEMBERS_PREFIX = 'account_group_members:'
|
||||
this.REVERSE_INDEX_PREFIX = 'account_groups_reverse:'
|
||||
this.REVERSE_INDEX_MIGRATED_KEY = 'account_groups_reverse:migrated'
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保反向索引存在(启动时自动调用)
|
||||
* 检查是否已迁移,如果没有则自动回填
|
||||
*/
|
||||
async ensureReverseIndexes() {
|
||||
try {
|
||||
const client = redis.getClientSafe()
|
||||
if (!client) return
|
||||
|
||||
// 检查是否已迁移
|
||||
const migrated = await client.get(this.REVERSE_INDEX_MIGRATED_KEY)
|
||||
if (migrated === 'true') {
|
||||
logger.debug('📁 账户分组反向索引已存在,跳过回填')
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('📁 开始回填账户分组反向索引...')
|
||||
|
||||
const allGroupIds = await client.smembers(this.GROUPS_KEY)
|
||||
if (allGroupIds.length === 0) {
|
||||
await client.set(this.REVERSE_INDEX_MIGRATED_KEY, 'true')
|
||||
return
|
||||
}
|
||||
|
||||
let totalOperations = 0
|
||||
|
||||
for (const groupId of allGroupIds) {
|
||||
const group = await client.hgetall(`${this.GROUP_PREFIX}${groupId}`)
|
||||
if (!group || !group.platform) continue
|
||||
|
||||
const members = await client.smembers(`${this.GROUP_MEMBERS_PREFIX}${groupId}`)
|
||||
if (members.length === 0) continue
|
||||
|
||||
const pipeline = client.pipeline()
|
||||
for (const accountId of members) {
|
||||
pipeline.sadd(`${this.REVERSE_INDEX_PREFIX}${group.platform}:${accountId}`, groupId)
|
||||
}
|
||||
await pipeline.exec()
|
||||
totalOperations += members.length
|
||||
}
|
||||
|
||||
await client.set(this.REVERSE_INDEX_MIGRATED_KEY, 'true')
|
||||
logger.success(`📁 账户分组反向索引回填完成,共 ${totalOperations} 条`)
|
||||
} catch (error) {
|
||||
logger.error('❌ 账户分组反向索引回填失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +100,7 @@ class AccountGroupService {
|
||||
// 添加到分组集合
|
||||
await client.sadd(this.GROUPS_KEY, groupId)
|
||||
|
||||
logger.success(`✅ 创建账户分组成功: ${name} (${platform})`)
|
||||
logger.success(`创建账户分组成功: ${name} (${platform})`)
|
||||
|
||||
return group
|
||||
} catch (error) {
|
||||
@@ -101,7 +151,7 @@ class AccountGroupService {
|
||||
// 返回更新后的完整数据
|
||||
const updatedGroup = await client.hgetall(groupKey)
|
||||
|
||||
logger.success(`✅ 更新账户分组成功: ${updatedGroup.name}`)
|
||||
logger.success(`更新账户分组成功: ${updatedGroup.name}`)
|
||||
|
||||
return updatedGroup
|
||||
} catch (error) {
|
||||
@@ -143,7 +193,7 @@ class AccountGroupService {
|
||||
// 从分组集合中移除
|
||||
await client.srem(this.GROUPS_KEY, groupId)
|
||||
|
||||
logger.success(`✅ 删除账户分组成功: ${group.name}`)
|
||||
logger.success(`删除账户分组成功: ${group.name}`)
|
||||
} catch (error) {
|
||||
logger.error('❌ 删除账户分组失败:', error)
|
||||
throw error
|
||||
@@ -234,7 +284,10 @@ class AccountGroupService {
|
||||
// 添加到分组成员集合
|
||||
await client.sadd(`${this.GROUP_MEMBERS_PREFIX}${groupId}`, accountId)
|
||||
|
||||
logger.success(`✅ 添加账户到分组成功: ${accountId} -> ${group.name}`)
|
||||
// 维护反向索引
|
||||
await client.sadd(`account_groups_reverse:${group.platform}:${accountId}`, groupId)
|
||||
|
||||
logger.success(`添加账户到分组成功: ${accountId} -> ${group.name}`)
|
||||
} catch (error) {
|
||||
logger.error('❌ 添加账户到分组失败:', error)
|
||||
throw error
|
||||
@@ -245,15 +298,26 @@ class AccountGroupService {
|
||||
* 从分组移除账户
|
||||
* @param {string} accountId - 账户ID
|
||||
* @param {string} groupId - 分组ID
|
||||
* @param {string} platform - 平台(可选,如果不传则从分组获取)
|
||||
*/
|
||||
async removeAccountFromGroup(accountId, groupId) {
|
||||
async removeAccountFromGroup(accountId, groupId, platform = null) {
|
||||
try {
|
||||
const client = redis.getClientSafe()
|
||||
|
||||
// 从分组成员集合中移除
|
||||
await client.srem(`${this.GROUP_MEMBERS_PREFIX}${groupId}`, accountId)
|
||||
|
||||
logger.success(`✅ 从分组移除账户成功: ${accountId}`)
|
||||
// 维护反向索引
|
||||
let groupPlatform = platform
|
||||
if (!groupPlatform) {
|
||||
const group = await this.getGroup(groupId)
|
||||
groupPlatform = group?.platform
|
||||
}
|
||||
if (groupPlatform) {
|
||||
await client.srem(`account_groups_reverse:${groupPlatform}:${accountId}`, groupId)
|
||||
}
|
||||
|
||||
logger.success(`从分组移除账户成功: ${accountId}`)
|
||||
} catch (error) {
|
||||
logger.error('❌ 从分组移除账户失败:', error)
|
||||
throw error
|
||||
@@ -399,7 +463,7 @@ class AccountGroupService {
|
||||
await this.addAccountToGroup(accountId, groupId, accountPlatform)
|
||||
}
|
||||
|
||||
logger.success(`✅ 批量设置账户分组成功: ${accountId} -> [${groupIds.join(', ')}]`)
|
||||
logger.success(`批量设置账户分组成功: ${accountId} -> [${groupIds.join(', ')}]`)
|
||||
} catch (error) {
|
||||
logger.error('❌ 批量设置账户分组失败:', error)
|
||||
throw error
|
||||
@@ -409,8 +473,9 @@ class AccountGroupService {
|
||||
/**
|
||||
* 从所有分组中移除账户
|
||||
* @param {string} accountId - 账户ID
|
||||
* @param {string} platform - 平台(可选,用于清理反向索引)
|
||||
*/
|
||||
async removeAccountFromAllGroups(accountId) {
|
||||
async removeAccountFromAllGroups(accountId, platform = null) {
|
||||
try {
|
||||
const client = redis.getClientSafe()
|
||||
const allGroupIds = await client.smembers(this.GROUPS_KEY)
|
||||
@@ -419,12 +484,127 @@ class AccountGroupService {
|
||||
await client.srem(`${this.GROUP_MEMBERS_PREFIX}${groupId}`, accountId)
|
||||
}
|
||||
|
||||
logger.success(`✅ 从所有分组移除账户成功: ${accountId}`)
|
||||
// 清理反向索引
|
||||
if (platform) {
|
||||
await client.del(`account_groups_reverse:${platform}:${accountId}`)
|
||||
} else {
|
||||
// 如果没有指定平台,清理所有可能的平台
|
||||
const platforms = ['claude', 'gemini', 'openai', 'droid']
|
||||
const pipeline = client.pipeline()
|
||||
for (const p of platforms) {
|
||||
pipeline.del(`account_groups_reverse:${p}:${accountId}`)
|
||||
}
|
||||
await pipeline.exec()
|
||||
}
|
||||
|
||||
logger.success(`从所有分组移除账户成功: ${accountId}`)
|
||||
} catch (error) {
|
||||
logger.error('❌ 从所有分组移除账户失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取多个账户的分组信息(性能优化版本,使用反向索引)
|
||||
* @param {Array<string>} accountIds - 账户ID数组
|
||||
* @param {string} platform - 平台类型
|
||||
* @param {Object} options - 选项
|
||||
* @param {boolean} options.skipMemberCount - 是否跳过 memberCount(默认 true)
|
||||
* @returns {Map<string, Array>} accountId -> 分组信息数组的映射
|
||||
*/
|
||||
async batchGetAccountGroupsByIndex(accountIds, platform, options = {}) {
|
||||
const { skipMemberCount = true } = options
|
||||
|
||||
if (!accountIds || accountIds.length === 0) {
|
||||
return new Map()
|
||||
}
|
||||
|
||||
try {
|
||||
const client = redis.getClientSafe()
|
||||
|
||||
// Pipeline 批量获取所有账户的分组ID
|
||||
const pipeline = client.pipeline()
|
||||
for (const accountId of accountIds) {
|
||||
pipeline.smembers(`${this.REVERSE_INDEX_PREFIX}${platform}:${accountId}`)
|
||||
}
|
||||
const groupIdResults = await pipeline.exec()
|
||||
|
||||
// 收集所有需要的分组ID
|
||||
const uniqueGroupIds = new Set()
|
||||
const accountGroupIdsMap = new Map()
|
||||
let hasAnyGroups = false
|
||||
accountIds.forEach((accountId, i) => {
|
||||
const [err, groupIds] = groupIdResults[i]
|
||||
const ids = err ? [] : groupIds || []
|
||||
accountGroupIdsMap.set(accountId, ids)
|
||||
ids.forEach((id) => {
|
||||
uniqueGroupIds.add(id)
|
||||
hasAnyGroups = true
|
||||
})
|
||||
})
|
||||
|
||||
// 如果反向索引全空,回退到原方法(兼容未迁移的数据)
|
||||
if (!hasAnyGroups) {
|
||||
const migrated = await client.get(this.REVERSE_INDEX_MIGRATED_KEY)
|
||||
if (migrated !== 'true') {
|
||||
logger.debug('📁 Reverse index not migrated, falling back to getAccountGroups')
|
||||
const result = new Map()
|
||||
for (const accountId of accountIds) {
|
||||
try {
|
||||
const groups = await this.getAccountGroups(accountId)
|
||||
result.set(accountId, groups)
|
||||
} catch {
|
||||
result.set(accountId, [])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
// 批量获取分组详情
|
||||
const groupDetailsMap = new Map()
|
||||
if (uniqueGroupIds.size > 0) {
|
||||
const detailPipeline = client.pipeline()
|
||||
const groupIdArray = Array.from(uniqueGroupIds)
|
||||
for (const groupId of groupIdArray) {
|
||||
detailPipeline.hgetall(`${this.GROUP_PREFIX}${groupId}`)
|
||||
if (!skipMemberCount) {
|
||||
detailPipeline.scard(`${this.GROUP_MEMBERS_PREFIX}${groupId}`)
|
||||
}
|
||||
}
|
||||
const detailResults = await detailPipeline.exec()
|
||||
|
||||
const step = skipMemberCount ? 1 : 2
|
||||
for (let i = 0; i < groupIdArray.length; i++) {
|
||||
const groupId = groupIdArray[i]
|
||||
const [err1, groupData] = detailResults[i * step]
|
||||
if (!err1 && groupData && Object.keys(groupData).length > 0) {
|
||||
const group = { ...groupData }
|
||||
if (!skipMemberCount) {
|
||||
const [err2, memberCount] = detailResults[i * step + 1]
|
||||
group.memberCount = err2 ? 0 : memberCount || 0
|
||||
}
|
||||
groupDetailsMap.set(groupId, group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构建最终结果
|
||||
const result = new Map()
|
||||
for (const [accountId, groupIds] of accountGroupIdsMap) {
|
||||
const groups = groupIds
|
||||
.map((gid) => groupDetailsMap.get(gid))
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
|
||||
result.set(accountId, groups)
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
logger.error('❌ 批量获取账户分组失败:', error)
|
||||
return new Map(accountIds.map((id) => [id, []]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AccountGroupService()
|
||||
|
||||
Reference in New Issue
Block a user