Skip to content

feat(channel-concurrency): 新增渠道级并发限制功能#4172

Open
Miraaaacle wants to merge 1 commit intoQuantumNous:mainfrom
Miraaaacle:feat/channel-concurrency
Open

feat(channel-concurrency): 新增渠道级并发限制功能#4172
Miraaaacle wants to merge 1 commit intoQuantumNous:mainfrom
Miraaaacle:feat/channel-concurrency

Conversation

@Miraaaacle
Copy link
Copy Markdown

@Miraaaacle Miraaaacle commented Apr 10, 2026

Closes #1730

为每个渠道维护原子计数器实现并发控制,超限时自动降级到其他渠道,
所有渠道满后返回 429。前端支持渠道列表内联编辑和编辑弹窗配置。

后端:

  • 新增 common/concurrency_limiter.go(sync.Map + atomic.Int64)
  • Channel 模型新增 max_concurrency 字段
  • 重试循环中集成 acquire/release,不消耗 retry 次数
  • 支持按 Tag 批量编辑 max_concurrency

前端:

  • 渠道列表新增最大并发列(内联编辑)
  • 编辑弹窗新增最大并发数输入框

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)
企业微信截图_b75d1a8f-39fd-4c38-b3ef-321b5e34824f

Summary by CodeRabbit

Release Notes

  • New Features

    • Added per-channel concurrency limiting to control maximum simultaneous requests
    • Users can configure maximum concurrency limits through channel settings
    • New "Max Concurrency" column in channels management table for visibility and control
    • Automatic error handling when channel concurrency capacity is exceeded
  • Localization

    • Added English and Chinese language support for concurrency management features

  为每个渠道维护原子计数器实现并发控制,超限时自动降级到其他渠道,
  所有渠道满后返回 429。前端支持渠道列表内联编辑和编辑弹窗配置。

  后端:
  - 新增 common/concurrency_limiter.go(sync.Map + atomic.Int64)
  - Channel 模型新增 max_concurrency 字段
  - 重试循环中集成 acquire/release,不消耗 retry 次数
  - 支持按 Tag 批量编辑 max_concurrency

  前端:
  - 渠道列表新增最大并发列(内联编辑)
  - 编辑弹窗新增最大并发数输入框
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 10, 2026

Walkthrough

Introduces per-channel concurrency limiting using an atomic counter infrastructure. Tracks concurrent requests per channel via a global sync.Map, enforces limits during request relay, and returns HTTP 429 when capacity is exceeded. Extends channel configuration with maxConcurrency settings and provides frontend UI controls for management.

Changes

Cohort / File(s) Summary
Backend Concurrency Infrastructure
common/concurrency_limiter.go, types/error.go
New module implementing per-channel atomic counters with acquire/release semaphore operations. Added ErrorCodeChannelConcurrencyFull error code for capacity-exhausted responses.
Channel Data Model
model/channel.go
Added MaxConcurrency field to Channel struct with getter method returning 0 (unlimited) when null. Updated EditChannelByTag signature to accept and apply maxConcurrency parameter.
Tag-based Channel Configuration
controller/channel.go
Extended ChannelTag struct with MaxConcurrency field and passed it to model layer during tag-based edits.
Request Relay Handler
controller/relay.go
Integrated concurrency acquisition/release around relay operations. Acquires permit before forwarding request, retries with skip counting up to 5 failures, and returns 429 error when threshold exceeded. Enhanced getChannel to retrieve cached channel data including maxConcurrency field.
Frontend UI Components
web/src/components/table/channels/ChannelsColumnDefs.jsx, web/src/components/table/channels/modals/EditChannelModal.jsx
Added max concurrency column renderer with branching logic for individual vs. tag-group rows and confirmation modal. Added form field in edit modal with min=0 and unlimited indicator.
Frontend State Management
web/src/hooks/channels/useChannelsData.jsx
Extended hook with MAX_CONCURRENCY column support and request handler for persisting max concurrency updates via API with integer parsing and non-negative clamping.
Internationalization
web/src/i18n/locales/en.json, web/src/i18n/locales/zh-CN.json
Added localization strings for max concurrency UI labels, descriptions, and bulk-edit confirmation prompts in English and Chinese.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Relay as Relay Handler
    participant Limiter as Concurrency Limiter
    participant Model as Channel Model
    participant Backend as API Backend

    Client->>Relay: Request to channel
    Relay->>Model: Get channel config
    Model-->>Relay: Channel with maxConcurrency
    
    loop Acquire Semaphore
        Relay->>Limiter: ConcurrencyAcquire(channelId, maxConcurrency)
        Limiter-->>Relay: permit granted? (bool)
        alt Permit Denied
            Relay->>Relay: Increment skip counter
            alt Skip threshold exceeded (5)
                Relay-->>Client: HTTP 429 Too Many Requests
                note over Relay: concurrency_full error
            else Retry
                Relay->>Relay: ResetRetryNextTry()
                Relay->>Model: Select different channel
            end
        else Permit Granted
            rect rgba(100, 200, 100, 0.5)
                Relay->>Backend: Forward request
                Backend-->>Relay: Response
            end
            Relay->>Limiter: ConcurrencyRelease(channelId)
            Limiter-->>Relay: Released
            Relay-->>Client: Response
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A limiter so grand, per channel it stands,
Counting requests atomic, across the backend lands,
When concurrency peaks, a 429 replies,
Fair sharing of resources—how efficiency flies! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately describes the main change: adding channel-level concurrency limiting functionality as the primary feature.
Linked Issues check ✅ Passed The PR successfully implements concurrency limiting control per channel as required by issue #1730, with direct rejection strategy when capacity is exceeded.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing channel-level concurrency limiting; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.42.1)
web/src/components/table/channels/modals/EditChannelModal.jsx

[]


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
web/src/components/table/channels/modals/EditChannelModal.jsx (1)

2463-2475: 建议给 max_concurrency 增加显式默认值,避免新建/重置时出现未定义状态。

当前字段已接线正确;再补一个默认值会让提交与重置行为更稳定。

♻️ 建议修改
@@
   const originInputs = {
@@
     priority: 0,
     weight: 0,
+    max_concurrency: 0,
     tag: '',
@@
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/table/channels/modals/EditChannelModal.jsx` around lines
2463 - 2475, The max_concurrency Form.InputNumber can be undefined on new/reset
forms; set an explicit default (e.g., 0) when initializing or resetting the form
state so the input always receives a value. Update the component that supplies
the form values in EditChannelModal.jsx to initialize max_concurrency to 0 (or
set a defaultValue on the Form.InputNumber) and ensure
handleInputChange('max_concurrency', ...) preserves numeric defaults on resets
so submissions never encounter undefined for max_concurrency.
web/src/components/table/channels/ChannelsColumnDefs.jsx (1)

685-744: Add validation for max_concurrency in submitTagEdit.

The column definition correctly follows the pattern used for weight, but the submitTagEdit function in useChannelsData.jsx (lines 627-658) lacks a validation case for 'max_concurrency'. While weight validates that the value is a non-negative integer before the API call, max_concurrency will fall through without validation.

Consider adding validation in submitTagEdit:

case 'max_concurrency':
  if (data.max_concurrency === undefined || data.max_concurrency < 0 || data.max_concurrency === '') {
    showInfo('最大并发数必须是非负整数!');
    return;
  }
  data.max_concurrency = parseInt(data.max_concurrency);
  break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/table/channels/ChannelsColumnDefs.jsx` around lines 685 -
744, submitTagEdit is missing validation for the 'max_concurrency' case so
non-numeric or negative values can be submitted; update submitTagEdit (in
useChannelsData.jsx) to mirror the 'weight' validation: add a case for
'max_concurrency' that checks for undefined/empty string or negative values,
shows the same user-facing error (e.g., showInfo('最大并发数必须是非负整数!')) and returns
early, then parseInt the value into data.max_concurrency before proceeding with
the API call.
common/concurrency_limiter.go (1)

8-9: Consider adding a cleanup mechanism for deleted channels.

The sync.Map entries persist indefinitely. While each entry is small (~8 bytes for atomic.Int64), if channels are frequently created/deleted, this could accumulate. Consider adding a cleanup function similar to CleanupChannelPollingLocks in model/channel.go that removes entries for non-existent channels periodically.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/concurrency_limiter.go` around lines 8 - 9, The channelConcurrency
sync.Map currently retains entries forever; add a cleanup routine (similar to
CleanupChannelPollingLocks in model/channel.go) that periodically iterates
channelConcurrency, checks whether each channel ID still exists (using the
existing channel lookup function such as ChannelExists or GetChannel in the
model package), and calls channelConcurrency.Delete(key) for entries whose
channels are gone; run this cleanup on a timer (e.g., background goroutine or
scheduled task) and ensure it safely handles the stored *atomic.Int64 values and
does not race with normal increment/decrement operations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@common/concurrency_limiter.go`:
- Around line 8-9: The channelConcurrency sync.Map currently retains entries
forever; add a cleanup routine (similar to CleanupChannelPollingLocks in
model/channel.go) that periodically iterates channelConcurrency, checks whether
each channel ID still exists (using the existing channel lookup function such as
ChannelExists or GetChannel in the model package), and calls
channelConcurrency.Delete(key) for entries whose channels are gone; run this
cleanup on a timer (e.g., background goroutine or scheduled task) and ensure it
safely handles the stored *atomic.Int64 values and does not race with normal
increment/decrement operations.

In `@web/src/components/table/channels/ChannelsColumnDefs.jsx`:
- Around line 685-744: submitTagEdit is missing validation for the
'max_concurrency' case so non-numeric or negative values can be submitted;
update submitTagEdit (in useChannelsData.jsx) to mirror the 'weight' validation:
add a case for 'max_concurrency' that checks for undefined/empty string or
negative values, shows the same user-facing error (e.g.,
showInfo('最大并发数必须是非负整数!')) and returns early, then parseInt the value into
data.max_concurrency before proceeding with the API call.

In `@web/src/components/table/channels/modals/EditChannelModal.jsx`:
- Around line 2463-2475: The max_concurrency Form.InputNumber can be undefined
on new/reset forms; set an explicit default (e.g., 0) when initializing or
resetting the form state so the input always receives a value. Update the
component that supplies the form values in EditChannelModal.jsx to initialize
max_concurrency to 0 (or set a defaultValue on the Form.InputNumber) and ensure
handleInputChange('max_concurrency', ...) preserves numeric defaults on resets
so submissions never encounter undefined for max_concurrency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ad2f7785-36f9-4a45-a9ec-25b20a044c6c

📥 Commits

Reviewing files that changed from the base of the PR and between 0664bb3 and 13b5e88.

📒 Files selected for processing (10)
  • common/concurrency_limiter.go
  • controller/channel.go
  • controller/relay.go
  • model/channel.go
  • types/error.go
  • web/src/components/table/channels/ChannelsColumnDefs.jsx
  • web/src/components/table/channels/modals/EditChannelModal.jsx
  • web/src/hooks/channels/useChannelsData.jsx
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/zh-CN.json

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

功能请求:渠道速率限制与并发控制功能

1 participant