实战指南:手把手带你写一个用在分布式重试机制中的 DiscardPolicy 默默丢弃器

酷涛大大_3331

酷涛大大_3331

2026-05-31

207人浏览

原创

分布式重试中不能直接用jdk原生discardpolicy,因其无日志、无监控、不可追溯;应实现safediscardpolicy,满足可追溯(记录任务类型/业务id/重试次数)、可度量(上报prometheus指标)、可收敛(采样日志+动态开关)三大要求。

实战指南:手把手带你写一个用在分布式重试机制中的 discardpolicy 默默丢弃器

分布式重试机制中,当任务队列已满、线程池饱和或下游服务持续不可用时,新来的重试任务不能无限制堆积——这时需要一个可控的丢弃策略。DiscardPolicy 是 JDK 线程池内置的一种拒绝策略,它什么也不做,直接丢弃任务。但“默默丢弃”在分布式场景下风险极高:没日志、没告警、没补偿,等于让业务“静默失败”。所以,我们真正要写的,不是原样照搬 ThreadPoolExecutor.DiscardPolicy,而是一个有上下文感知、可审计、带轻量监控的“安全丢弃器”。

为什么不能直接用原生 DiscardPolicy?

原生 DiscardPolicy 的实现只有一行:return;。它:

  • 不记录被丢弃的任务内容(比如重试的接口名、参数 ID、重试次数)
  • 不触发任何可观测行为(无日志、无指标、无告警)
  • 无法区分是“合理背压”还是“异常雪崩”,运维完全无感
  • 在分布式重试中,可能丢掉的是用户支付回调、订单超时补单等关键任务

一个靠谱的分布式 DiscardPolicy 长什么样?

它得满足三个基本要求:可追溯、可度量、可收敛。不是“丢”,而是“有据可依地丢”。建议结构如下:

  • 记录关键元数据:任务类型(如 OrderTimeoutRetryTask)、业务 ID(如 order_123456)、当前重试次数、丢弃时间戳、所属线程池名
  • 异步打点 + 采样日志:高频丢弃时避免日志刷爆磁盘,按固定比例(如 1%)打印完整 DEBUG 日志,其余仅上报指标
  • 暴露丢弃计数器:接入 Micrometer / Prometheus,暴露 retry_task_discarded_total{policy="safe_discard", pool="retry-pool-1"}
  • 支持动态降级开关(可选):通过配置中心控制是否启用丢弃(比如故障期间临时切为阻塞或降级为本地延时队列)

手写 SafeDiscardPolicy 实战代码

以下是一个 Spring Boot 项目中可用的轻量实现(基于 RejectedExecutionHandler 接口):

public class SafeDiscardPolicy implements RejectedExecutionHandler {

    private static final Logger log = LoggerFactory.getLogger(SafeDiscardPolicy.class);
    private final MeterRegistry meterRegistry;
    private final String poolName;
    private final int sampleRate; // 每 100 次丢弃打 1 条完整日志

    public SafeDiscardPolicy(MeterRegistry meterRegistry, String poolName) {
        this(meterRegistry, poolName, 100);
    }

    public SafeDiscardPolicy(MeterRegistry meterRegistry, String poolName, int sampleRate) {
        this.meterRegistry = meterRegistry;
        this.poolName = poolName;
        this.sampleRate = sampleRate;
    }

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 1. 上报指标
        Counter.builder("retry.task.discarded")
               .tag("policy", "safe_discard")
               .tag("pool", poolName)
               .register(meterRegistry)
               .increment();

        // 2. 尝试提取业务上下文(假设 Runnable 是自定义重试任务)
        if (r instanceof RetryTask task) {
            String bizId = task.getBizId();
            String taskType = task.getClass().getSimpleName();
            int retryCount = task.getRetryCount();

            // 3. 采样日志
            if (ThreadLocalRandom.current().nextInt(sampleRate) == 0) {
                log.warn("[DISCARD] pool={} type={} bizId={} retryCount={} queueSize={} active={} max={}",
                        poolName, taskType, bizId, retryCount,
                        executor.getQueue().size(), executor.getActiveCount(), executor.getMaximumPoolSize());
            }
        } else {
            // 非标准任务,至少记个类名
            if (ThreadLocalRandom.current().nextInt(sampleRate) == 0) {
                log.warn("[DISCARD] pool={} unknown task: {}", poolName, r.getClass().getName());
            }
        }
    }
}

使用时,在构建重试线程池时传入即可:

new ThreadPoolExecutor(
    core, max, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue(queueCapacity),
    new CustomThreadFactory("retry-pool"),
    new SafeDiscardPolicy(meterRegistry, "retry-pool-1")
);

配套建议:别只靠丢弃,要形成闭环

丢弃只是最后一道防线。搭配以下措施才真正可靠:

  • 前置限流:在任务入队前,用 Sentinel 或 Redis 计数器对相同 bizId 的重试请求限频
  • 分级重试:核心任务走高优先级线程池 + 持久化队列(如 RocketMQ),非核心才走内存池 + SafeDiscardPolicy
  • 丢弃后补偿钩子(进阶):在 rejectedExecution 中触发异步补偿,例如把丢弃任务写入死信表,供人工核查或定时重投
  • 监控看板:将 retry_task_discarded_total 与下游成功率、RT、线程池活跃度放在同一面板,设置突增告警(如 5 分钟内 > 100 次)

丢弃本身不难,难的是让丢弃变得“可信任”。写一个 SafeDiscardPolicy,本质是在混沌中划出一条清晰的止损边界——它不解决问题,但它让你知道问题在哪,以及还能撑多久。

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

2023.06.15

10097

6

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

2023.07.05

7242

9

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

2023.07.31

6352

8

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.01

1104

3

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.08.02

908

3

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

1336

5

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.02

2669

5

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

2023.08.03

19971

3

配置java环境变量
配置java环境变量

配置Java环境变量是为了让操作系统能够识别和使用Java的相关命令和功能。本专题为大家提供配置java环境变量相关文章,帮助大家解决问题。

2023.08.03

1175

8

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.4万人学习