[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-1000-wanduanxin-1-xiaoshifawanzenmeshejixianchengchi":3},{"id":4,"title":5,"slug":6,"summary":7,"content":8,"contentHtml":9,"wordCount":10,"readingTime":11,"categoryId":12,"tags":13,"coverImage":13,"thumbnail":13,"status":14,"isTop":15,"isRecommended":15,"allowComments":15,"password":13,"viewCount":16,"likeCount":15,"commentCount":15,"seoTitle":5,"seoKeywords":17,"seoDescription":18,"source":13,"sourceUrl":13,"publishTime":19},"2051303568504320001","1000 万短信 1 小时发完，怎么设计线程池？","1000-wanduanxin-1-xiaoshifawanzenmeshejixianchengchi","设计一个能在一小时内稳定发送一千万条短信的线程池，绝不仅仅是设置几个参数那么简单。这是一个典型的**高并发、IO密集型**任务，需要从架构层面进行系统性设计，以确保高性能、高可靠和系统稳定。 以下是完整的设计方案： ### 🎯 核心目标与约束 首先，明确我们的目标： * **总量**: 10,000,000 条短信 * **时限**: 1 小时 (3600 秒) * **平均速率**: `10,000,000 \u002F 3600 ≈ 2778` 条\u002F秒 这意味着我们的系统需要稳定地维持近 2800 QPS 的发送能力。 ### 🛠️ 线程池核心配置 在生产环境中，严禁使用 `Executors.newFixedThreadPool()` 等方式创建线程池，因为它们使用无界队列，在海量任务下极易导致内存溢出（OOM）。我们必须手动创建 `ThreadPoolExecutor` 并进行精细化配置...","设计一个能在一小时内稳定发送一千万条短信的线程池，绝不仅仅是设置几个参数那么简单。这是一个典型的**高并发、IO密集型**任务，需要从架构层面进行系统性设计，以确保高性能、高可靠和系统稳定。\n\n以下是完整的设计方案：\n\n### 🎯 核心目标与约束\n\n首先，明确我们的目标：\n*   **总量**: 10,000,000 条短信\n*   **时限**: 1 小时 (3600 秒)\n*   **平均速率**: `10,000,000 \u002F 3600 ≈ 2778` 条\u002F秒\n\n这意味着我们的系统需要稳定地维持近 2800 QPS 的发送能力。\n\n### 🛠️ 线程池核心配置\n\n在生产环境中，严禁使用 `Executors.newFixedThreadPool()` 等方式创建线程池，因为它们使用无界队列，在海量任务下极易导致内存溢出（OOM）。我们必须手动创建 `ThreadPoolExecutor` 并进行精细化配置。\n\n#### 1. 确定线程数量\n短信发送是典型的 **IO 密集型** 任务（主要耗时在网络调用），而非 CPU 密集型。对于 IO 密集型任务，可以使用以下经验公式来估算初始线程数：\n\n`Nthreads = Ncpu * Ucpu * (1 + W\u002FC)`\n\n*   `Ncpu`: CPU 核心数\n*   `Ucpu`: 目标 CPU 利用率 (通常设为 1)\n*   `W\u002FC`: 等待时间与计算时间的比值。对于网络请求，W远大于C，因此这个值很大。\n\n在实际工程中，一个常用的简化策略是将线程数设置为 **CPU 核心数的 2 倍**作为起点。例如，一台 8 核的机器，可以从 16 个核心线程开始。但这只是一个初始值，最终需要通过压力测试来确定最优配置。\n\n#### 2. 选择有界队列\n必须使用有界队列（如 `LinkedBlockingQueue` 并指定容量）来限制等待任务的数量，这是防止 OOM 的关键防线。队列的大小需要根据可用内存和单个任务占用的内存来估算。\n\n#### 3. 设置合理的拒绝策略\n当线程池和队列都满了之后，新提交的任务如何处理？默认的 `AbortPolicy` 会直接抛出异常，导致任务丢失，这在我们的场景中是不可接受的。\n\n强烈推荐使用 **`CallerRunsPolicy`**。\n*   **工作原理**: 当任务被拒绝时，由提交任务的线程（例如主线程）自己去执行该任务。\n*   **核心优势**: 这在离线批量处理场景中形成了一种 **“天然背压 (Backpressure)”** 机制。当生产者（从数据库拉取任务的线程）被迫自己处理任务时，它就没法继续从数据库拉取新任务，从而自动减缓了任务的注入速度，给线程池喘息的机会，有效避免了系统因过载而崩溃。\n\n### 🛡️ 生产级可靠性保障\n\n仅有线程池是不够的，必须构建一套完整的可靠性保障体系。\n\n#### 动态调优与监控\n线上流量是变化的，硬编码的参数无法应对所有情况。\n*   **参数动态化**: 将线程池的核心参数（`corePoolSize`, `maxPoolSize`, `queueCapacity`）配置在 Apollo、Nacos 等配置中心，支持运行时动态调整，无需重启服务。\n*   **全链路监控**: 实时监控线程池的活跃度、队列剩余容量等关键指标。当队列使用率超过阈值（如 80%）时，自动触发告警，甚至可以联动配置中心进行动态扩容。\n*   **开源工具**: 可以引入业内成熟的动态线程池框架，如 **Hippo4J** 或 **DynamicTp**，它们开箱即用地提供了上述功能。\n\n#### 任务持久化与补偿\n为了防止应用宕机导致内存队列中的任务丢失，必须有兜底方案。\n1.  **本地持久化**: 在任务提交到线程池之前，先在数据库或 Redis 中将该任务的状态标记为“发送中”。\n2.  **Ack 机制**: 线程成功发送短信后，回调更新任务状态为“已完成”。\n3.  **离线补偿**: 部署一个定时任务，定期扫描数据库中状态为“发送中”且超过一定时间（如 10 分钟）的记录，将它们重新投递到消息队列或线程池中，确保任务不遗漏。\n\n### ⚙️ 外部限流与网关协同\n\n你的线程池再强大，也必须考虑下游短信网关的承受能力。如果网关有 QPS 限制（例如每秒最多接收 3000 条），那么你的发送速率就不能超过这个限制。\n\n此时，可以在任务执行逻辑中引入限流器，如 Guava 的 **`RateLimiter`**。\n```java\n\u002F\u002F 创建一个每秒放行 2800 个令牌的限流器\nfinal RateLimiter rateLimiter = RateLimiter.create(2800.0);\n\n\u002F\u002F 在线程池的任务中\npublic void sendSmsTask() {\n    \u002F\u002F 获取令牌，如果速率超限则会阻塞等待\n    rateLimiter.acquire(); \n    \u002F\u002F 执行真正的短信发送逻辑\n    smsGateway.send(...);\n}\n```\n通过这种方式，可以确保发送给网关的流量是平滑且受控的，避免因瞬时流量过大而被网关拒绝。\n\n### 📝 总结\n\n综上所述，一个健壮的千万级短信推送方案应该是多层次的：\n\n| 层级 | 策略 | 目的 |\n| :--- | :--- | :--- |\n| **基础层** | 手动创建 `ThreadPoolExecutor`，使用有界队列和 `CallerRunsPolicy` | 避免 OOM，实现背压，保证单机稳定性 |\n| **业务层** | 基于 IO 密集型公式设定初始线程数，并通过压测调优 | 最大化资源利用率和吞吐能力 |\n| **保障层** | 任务状态持久化 + 离线补偿任务 | 确保任务在任何情况下都不丢失 |\n| **治理层** | 动态线程池 + 全链路监控 | 实时感知系统状态，灵活应对流量变化 |\n| **协同层** | 使用 `RateLimiter` 等工具进行限流 | 保护下游依赖，遵守外部系统约束 |","\u003Cp data-line=\"0\">设计一个能在一小时内稳定发送一千万条短信的线程池，绝不仅仅是设置几个参数那么简单。这是一个典型的\u003Cstrong>高并发、IO密集型\u003C\u002Fstrong>任务，需要从架构层面进行系统性设计，以确保高性能、高可靠和系统稳定。\u003C\u002Fp>\n\u003Cp data-line=\"2\">以下是完整的设计方案：\u003C\u002Fp>\n\u003Ch3 data-line=\"4\" id=\"🎯 核心目标与约束\">🎯 核心目标与约束\u003C\u002Fh3>\n\u003Cp data-line=\"6\">首先，明确我们的目标：\u003C\u002Fp>\n\u003Cul data-line=\"7\">\n\u003Cli data-line=\"7\">\u003Cstrong>总量\u003C\u002Fstrong>: 10,000,000 条短信\u003C\u002Fli>\n\u003Cli data-line=\"8\">\u003Cstrong>时限\u003C\u002Fstrong>: 1 小时 (3600 秒)\u003C\u002Fli>\n\u003Cli data-line=\"9\">\u003Cstrong>平均速率\u003C\u002Fstrong>: \u003Ccode>10,000,000 \u002F 3600 ≈ 2778\u003C\u002Fcode> 条\u002F秒\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp data-line=\"11\">这意味着我们的系统需要稳定地维持近 2800 QPS 的发送能力。\u003C\u002Fp>\n\u003Ch3 data-line=\"13\" id=\"🛠️ 线程池核心配置\">🛠️ 线程池核心配置\u003C\u002Fh3>\n\u003Cp data-line=\"15\">在生产环境中，严禁使用 \u003Ccode>Executors.newFixedThreadPool()\u003C\u002Fcode> 等方式创建线程池，因为它们使用无界队列，在海量任务下极易导致内存溢出（OOM）。我们必须手动创建 \u003Ccode>ThreadPoolExecutor\u003C\u002Fcode> 并进行精细化配置。\u003C\u002Fp>\n\u003Ch4 data-line=\"17\" id=\"1. 确定线程数量\">1. 确定线程数量\u003C\u002Fh4>\n\u003Cp data-line=\"18\">短信发送是典型的 \u003Cstrong>IO 密集型\u003C\u002Fstrong> 任务（主要耗时在网络调用），而非 CPU 密集型。对于 IO 密集型任务，可以使用以下经验公式来估算初始线程数：\u003C\u002Fp>\n\u003Cp data-line=\"20\">\u003Ccode>Nthreads = Ncpu * Ucpu * (1 + W\u002FC)\u003C\u002Fcode>\u003C\u002Fp>\n\u003Cul data-line=\"22\">\n\u003Cli data-line=\"22\">\u003Ccode>Ncpu\u003C\u002Fcode>: CPU 核心数\u003C\u002Fli>\n\u003Cli data-line=\"23\">\u003Ccode>Ucpu\u003C\u002Fcode>: 目标 CPU 利用率 (通常设为 1)\u003C\u002Fli>\n\u003Cli data-line=\"24\">\u003Ccode>W\u002FC\u003C\u002Fcode>: 等待时间与计算时间的比值。对于网络请求，W远大于C，因此这个值很大。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp data-line=\"26\">在实际工程中，一个常用的简化策略是将线程数设置为 \u003Cstrong>CPU 核心数的 2 倍\u003C\u002Fstrong>作为起点。例如，一台 8 核的机器，可以从 16 个核心线程开始。但这只是一个初始值，最终需要通过压力测试来确定最优配置。\u003C\u002Fp>\n\u003Ch4 data-line=\"28\" id=\"2. 选择有界队列\">2. 选择有界队列\u003C\u002Fh4>\n\u003Cp data-line=\"29\">必须使用有界队列（如 \u003Ccode>LinkedBlockingQueue\u003C\u002Fcode> 并指定容量）来限制等待任务的数量，这是防止 OOM 的关键防线。队列的大小需要根据可用内存和单个任务占用的内存来估算。\u003C\u002Fp>\n\u003Ch4 data-line=\"31\" id=\"3. 设置合理的拒绝策略\">3. 设置合理的拒绝策略\u003C\u002Fh4>\n\u003Cp data-line=\"32\">当线程池和队列都满了之后，新提交的任务如何处理？默认的 \u003Ccode>AbortPolicy\u003C\u002Fcode> 会直接抛出异常，导致任务丢失，这在我们的场景中是不可接受的。\u003C\u002Fp>\n\u003Cp data-line=\"34\">强烈推荐使用 \u003Cstrong>\u003Ccode>CallerRunsPolicy\u003C\u002Fcode>\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cul data-line=\"35\">\n\u003Cli data-line=\"35\">\u003Cstrong>工作原理\u003C\u002Fstrong>: 当任务被拒绝时，由提交任务的线程（例如主线程）自己去执行该任务。\u003C\u002Fli>\n\u003Cli data-line=\"36\">\u003Cstrong>核心优势\u003C\u002Fstrong>: 这在离线批量处理场景中形成了一种 \u003Cstrong>“天然背压 (Backpressure)”\u003C\u002Fstrong> 机制。当生产者（从数据库拉取任务的线程）被迫自己处理任务时，它就没法继续从数据库拉取新任务，从而自动减缓了任务的注入速度，给线程池喘息的机会，有效避免了系统因过载而崩溃。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 data-line=\"38\" id=\"🛡️ 生产级可靠性保障\">🛡️ 生产级可靠性保障\u003C\u002Fh3>\n\u003Cp data-line=\"40\">仅有线程池是不够的，必须构建一套完整的可靠性保障体系。\u003C\u002Fp>\n\u003Ch4 data-line=\"42\" id=\"动态调优与监控\">动态调优与监控\u003C\u002Fh4>\n\u003Cp data-line=\"43\">线上流量是变化的，硬编码的参数无法应对所有情况。\u003C\u002Fp>\n\u003Cul data-line=\"44\">\n\u003Cli data-line=\"44\">\u003Cstrong>参数动态化\u003C\u002Fstrong>: 将线程池的核心参数（\u003Ccode>corePoolSize\u003C\u002Fcode>, \u003Ccode>maxPoolSize\u003C\u002Fcode>, \u003Ccode>queueCapacity\u003C\u002Fcode>）配置在 Apollo、Nacos 等配置中心，支持运行时动态调整，无需重启服务。\u003C\u002Fli>\n\u003Cli data-line=\"45\">\u003Cstrong>全链路监控\u003C\u002Fstrong>: 实时监控线程池的活跃度、队列剩余容量等关键指标。当队列使用率超过阈值（如 80%）时，自动触发告警，甚至可以联动配置中心进行动态扩容。\u003C\u002Fli>\n\u003Cli data-line=\"46\">\u003Cstrong>开源工具\u003C\u002Fstrong>: 可以引入业内成熟的动态线程池框架，如 \u003Cstrong>Hippo4J\u003C\u002Fstrong> 或 \u003Cstrong>DynamicTp\u003C\u002Fstrong>，它们开箱即用地提供了上述功能。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch4 data-line=\"48\" id=\"任务持久化与补偿\">任务持久化与补偿\u003C\u002Fh4>\n\u003Cp data-line=\"49\">为了防止应用宕机导致内存队列中的任务丢失，必须有兜底方案。\u003C\u002Fp>\n\u003Col data-line=\"50\">\n\u003Cli data-line=\"50\">\u003Cstrong>本地持久化\u003C\u002Fstrong>: 在任务提交到线程池之前，先在数据库或 Redis 中将该任务的状态标记为“发送中”。\u003C\u002Fli>\n\u003Cli data-line=\"51\">\u003Cstrong>Ack 机制\u003C\u002Fstrong>: 线程成功发送短信后，回调更新任务状态为“已完成”。\u003C\u002Fli>\n\u003Cli data-line=\"52\">\u003Cstrong>离线补偿\u003C\u002Fstrong>: 部署一个定时任务，定期扫描数据库中状态为“发送中”且超过一定时间（如 10 分钟）的记录，将它们重新投递到消息队列或线程池中，确保任务不遗漏。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 data-line=\"54\" id=\"⚙️ 外部限流与网关协同\">⚙️ 外部限流与网关协同\u003C\u002Fh3>\n\u003Cp data-line=\"56\">你的线程池再强大，也必须考虑下游短信网关的承受能力。如果网关有 QPS 限制（例如每秒最多接收 3000 条），那么你的发送速率就不能超过这个限制。\u003C\u002Fp>\n\u003Cp data-line=\"58\">此时，可以在任务执行逻辑中引入限流器，如 Guava 的 \u003Cstrong>\u003Ccode>RateLimiter\u003C\u002Fcode>\u003C\u002Fstrong>。\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"59\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F 创建一个每秒放行 2800 个令牌的限流器\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">final\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">RateLimiter\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">rateLimiter\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> RateLimiter.create(\u003Cspan class=\"hljs-number\">2800.0\u003C\u002Fspan>);\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F 在线程池的任务中\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">sendSmsTask\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">()\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 获取令牌，如果速率超限则会阻塞等待\u003C\u002Fspan>\n    rateLimiter.acquire(); \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 执行真正的短信发送逻辑\u003C\u002Fspan>\n    smsGateway.send(...);\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"71\">通过这种方式，可以确保发送给网关的流量是平滑且受控的，避免因瞬时流量过大而被网关拒绝。\u003C\u002Fp>\n\u003Ch3 data-line=\"73\" id=\"📝 总结\">📝 总结\u003C\u002Fh3>\n\u003Cp data-line=\"75\">综上所述，一个健壮的千万级短信推送方案应该是多层次的：\u003C\u002Fp>\n\u003Ctable data-line=\"77\">\n\u003Cthead data-line=\"77\">\n\u003Ctr data-line=\"77\">\n\u003Cth style=\"text-align:left\">层级\u003C\u002Fth>\n\u003Cth style=\"text-align:left\">策略\u003C\u002Fth>\n\u003Cth style=\"text-align:left\">目的\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody data-line=\"79\">\n\u003Ctr data-line=\"79\">\n\u003Ctd style=\"text-align:left\">\u003Cstrong>基础层\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd style=\"text-align:left\">手动创建 \u003Ccode>ThreadPoolExecutor\u003C\u002Fcode>，使用有界队列和 \u003Ccode>CallerRunsPolicy\u003C\u002Fcode>\u003C\u002Ftd>\n\u003Ctd style=\"text-align:left\">避免 OOM，实现背压，保证单机稳定性\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"80\">\n\u003Ctd style=\"text-align:left\">\u003Cstrong>业务层\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd style=\"text-align:left\">基于 IO 密集型公式设定初始线程数，并通过压测调优\u003C\u002Ftd>\n\u003Ctd style=\"text-align:left\">最大化资源利用率和吞吐能力\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"81\">\n\u003Ctd style=\"text-align:left\">\u003Cstrong>保障层\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd style=\"text-align:left\">任务状态持久化 + 离线补偿任务\u003C\u002Ftd>\n\u003Ctd style=\"text-align:left\">确保任务在任何情况下都不丢失\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"82\">\n\u003Ctd style=\"text-align:left\">\u003Cstrong>治理层\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd style=\"text-align:left\">动态线程池 + 全链路监控\u003C\u002Ftd>\n\u003Ctd style=\"text-align:left\">实时感知系统状态，灵活应对流量变化\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"83\">\n\u003Ctd style=\"text-align:left\">\u003Cstrong>协同层\u003C\u002Fstrong>\u003C\u002Ftd>\n\u003Ctd style=\"text-align:left\">使用 \u003Ccode>RateLimiter\u003C\u002Fcode> 等工具进行限流\u003C\u002Ftd>\n\u003Ctd style=\"text-align:left\">保护下游依赖，遵守外部系统约束\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n",2565,8,"0","",2,0,397,"任务,线,程池,中,队列","设计一个能在一小时内稳定发送一千万条短信的线程池，绝不仅仅是设置几个参数那么简单。这是一个典型的高并发、IO密集型任务，需要从架构层面进行系统性设计，以确保高性能、高可靠和系统稳定。","2026-05-04 00:00:00"]