feat: 大规模性能优化 - Redis Pipeline 批量操作、索引系统、连接池优化

This commit is contained in:
SunSeekerX
2025-12-31 02:08:47 +08:00
parent a345812cd7
commit 584fa8c9c1
68 changed files with 6541 additions and 4536 deletions

View File

@@ -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()