自适应类架构详解:自适应轮询、自适应重试、自适应采样

文章目录

写在前面

自适应架构核心本质:不使用固定参数,依靠外部反馈指标动态调整行为 。

分为四大类:轮询模式、重试模式、流量控制模式、采样调度模式。每一项包含:定义、适用场景、问题、伪代码。

一、轮询类模式(拉取状态,和你的库存业务最相关)

1. 自适应轮询 Adaptive Polling

(1)定义

根据业务状态指标,动态调整拉取间隔;风险越高轮询越频繁,风险越低间隔越大,平衡及时性与资源消耗。

(2)业务场景

1.第三方平台商品库存监控(对方不支持MQ推送),库存大于10十分钟轮询,小于1010秒钟轮询;

2.设备在线状态监控:设备正常5分钟轮询,设备告警10s轮询;

3.订单状态监控:大额订单高频轮询,普通订单低频轮询。

(3)存在问题

1.阈值附近震荡:库存9↔11,不停切换10s/2min;需要滞回(回差)

2.分布式多实例会重复轮询,需要分布式锁;

3.依然存在最大间隔的延迟,不是真正实时。

(4)伪代码(增加滞回防抖动)

java 复制代码
/**
 * 滞回阈值:
 * 当库存 <8 切换10s高频
 * 当库存 >15 切换回2min
 * >100 切换10min
 */
// 每个商品保存上一轮使用的轮询间隔,状态不能只靠本次库存计算
class ProductMonitor {
    Long productId;
    Long lastPollInterval; // ms,上一次生效间隔
    Long stock;
}

// 获取下一次轮询间隔
long getNextInterval(ProductMonitor monitor){
    long stock = monitor.stock;
    long oldInterval = monitor.lastPollInterval;

    // 当前已经是高频模式:需要升到15以上才退出高频
    if(oldInterval == 10*1000){
        if(stock > 15){
            if(stock >100){
                return 10*60*1000; //10min
            }else{
                return 2*60*1000; //2min
            }
        }else{
            return 10*1000; //保持10s
        }
    }
    // 当前中等间隔模式
    else if(oldInterval == 2*60*1000){
        if(stock < 8){
            return 10*1000;
        }else if(stock>100){
            return 10*60*1000;
        }else{
            return 2*60*1000;
        }
    }
    // 当前长间隔模式
    else{
        if(stock <8){
            return 10*1000;
        }else if(stock <=100){
            return 2*60*1000;
        }else{
            return 10*60*1000;
        }
    }
}

// 调度主循环
void pollLoop(ProductMonitor product){
    while(true){
        //分布式锁,防止多实例同时执行
        if(!tryLock(product.productId)){
            sleep(500);
            continue;
        }
        try{
            //拉取最新库存
            product.stock = remoteApi.getStock(product.productId);
            if(product.stock <=0){
                sendWarnAlert(product); //库存0告警
            }
            //计算下一次间隔
            long nextInterval = getNextInterval(product);
            product.lastPollInterval = nextInterval;
        }finally {
            unlock();
        }
        //增加随机抖动 ±10%,避免大量任务同一时刻执行(惊群)
        long jitter = (long)(nextInterval * (0.9 + Math.random()*0.2));
        sleep(jitter);
    }
}

2. 指数退避轮询 Exponential Backoff Polling

(1)定义

轮询等待结果,长时间得不到结果,间隔不断放大,上限封顶。等待越久查询越慢。

和自适应轮询相反:自适应轮询是风险越高越快;指数退避轮询是等待越久越慢。

(2)业务场景

1.异步导出文件任务轮询;

2.第三方支付异步结果查询;

3.大文件异步转码任务状态查询。

(3)存在问题

初始间隔不能太大,设置最大间隔防止无限增大;需要加jitter抖动。

(4)伪代码

java 复制代码
/**
 * 规则:初始1s,失败后 2s,4s,8s,最大32s
 */
long getBackoffPollInterval(int pollCount){
    long base = 1000;
    long interval = base * (1L << pollCount); // 2^pollCount
    long maxInterval = 32*1000;
    interval = Math.min(interval, maxInterval);
    // 随机抖动 ±30%
    double jitterFactor = 0.7 + Math.random()*0.6;
    return (long)(interval * jitterFactor);
}

void pollAsyncTask(Long taskId){
    int pollCount =0;
    while(true){
        TaskStatus status = remoteApi.queryTask(taskId);
        if(status == SUCCESS){
            handleSuccess();
            break;
        }
        if(status == FAIL){
            handleFail();
            break;
        }
        //还在处理中,继续轮询
        long sleepMs = getBackoffPollInterval(pollCount);
        pollCount++;
        sleep(sleepMs);
    }
}

3. 长轮询 Long Polling

(1)定义

客户端发送http请求,服务端hold连接挂起,有变更立刻返回;若无变更超时后客户端重新发起请求。介于轮询和推送中间。

(2)业务场景

1.Web端消息通知;

2.浏览器端实时库存展示;

3.小程序状态同步,不想维护websocket。

(3)缺点

占用连接,高并发场景服务端压力大。

java 复制代码
//服务端伪代码
HttpResponse longPoll(HttpRequest req){
    long timeout = 30000; //30s超时
    long start = now();
    while(now()-start < timeout){
        StockChange event = getNewStockEvent(req.productId);
        if(event != null){
            return Response.ok(event);
        }
        sleep(100);
    }
    //超时返回空,客户端立刻重请求
    return Response.noContent();
}

二、重试类模式

1. 指数退避重试 Exponential Backoff Retry

(1)定义

调用下游失败,每次重试等待时间指数放大,避免瞬间大量请求打崩故障服务。微服务最经典模式。

(2)业务场景

1.RPC调用第三方接口失败重试;

2.MQ消息消费失败本地重试;

3.调用数据库临时抖动重试。

注意:幂等!重试必须保证接口幂等,否则产生脏数据。

(3)伪代码

java 复制代码
/**
 * 最大重试5次,初始1000ms,最大等待16000ms
 */
<T> T callWithBack(Supplier<T> func,int maxRetry) throws Exception{
    int retryTimes =0;
    while(true){
        try{
            return func.get();
        }catch(Exception e){
            retryTimes++;
            if(retryTimes >= maxRetry){
                throw e;
            }
            long wait = 1000 * (1L << retryTimes);
            long maxWait =16000;
            wait = Math.min(wait,maxWait);
            //加抖动,防止大量请求同一时刻重试(惊群效应)
            wait = (long)(wait * (0.6 + Math.random()*0.8));
            sleep(wait);
        }
    }
}
//调用示例
callWithBack(()-> remoteApi.updateStock(),5);

2. 自适应重试 Adaptive Retry

(1)定义

不是简单指数退避,根据错误类型动态选择策略。

(2)业务场景

对外网关,调用多个异构第三方接口。

  • 参数错误(400):业务错误,直接不重试;
  • 限流(429):拉长等待时间;
  • 网络超时:快速重试一次。

(3)伪代码

java 复制代码
long getAdaptiveWaitTime(Exception ex,int retryCount){
    if(ex instanceof ParamInvalidException){
        return -1; //不重试
    }else if(ex instanceof RateLimitException){
        return 5000 + retryCount*2000; //限流等待更久
    }else if(ex instanceof TimeoutException){
        return 500; //超时快速重试
    }else{
        //默认指数退避
        return Math.min(1000*(1L<<retryCount),15000);
    }
}

三、流量控制模式

1. 自适应限流 Adaptive Rate‑Limiting

(1)定义

区别固定QPS限流,根据下游RT、错误率动态调整允许通过流量。Sentinel的系统自适应限流就是这个模式。

(2)业务场景

1.API网关,保护下游服务;

2.调用第三方接口,对方能力浮动,无法设置固定QPS。

逻辑:下游平均RT上涨、错误率上升,自动降低放行QPS;恢复正常,逐步提升QPS。

(3)伪代码

java 复制代码
class AdaptiveLimiter{
    double maxAllowedQps =100;
    double currentQps =100;
    double errorThreshold =0.05; //错误率超过5%开始降速
    double rtThreshold =200; //平均RT>200ms降速

    //每一个统计窗口调用一次,统计指标
    void adjust(double avgRt, double errorRate){
        if(errorRate> errorThreshold || avgRt> rtThreshold){
            //压力大,降低30%流量
            currentQps = currentQps *0.7;
            currentQps = Math.max(currentQps,5); //最低保护
        }else{
            //状态恢复,缓慢回升
            currentQps = currentQps *1.1;
            currentQps = Math.min(currentQps,maxAllowedQps);
        }
    }

    boolean tryAcquire(){
        return getCurrentPassQps() < currentQps;
    }
}

2. 分级熔断(Hystrix/Sentinel)

(1)定义

根据错误率熔断,半开状态试探恢复,动态开关流量。

(2)场景

微服务故障隔离。

1.错误率超过阈值→熔断拒绝请求;

2.熔断冷却时间后进入半开,放行少量请求试探;

3.如果调用成功,关闭熔断;失败重新打开熔断。

(3)伪代码

java 复制代码
class CircuitBreaker{
    enum State{CLOSED,OPEN,HALF_OPEN}
    State state = State.CLOSED;
    int errorCount;
    int windowTotal;
    long openTime;
    double errorRateTrigger =0.5; //错误率50%熔断
    long coolDownMs =5000;

    boolean allowRequest(){
        if(state == CLOSED) return true;
        if(state == OPEN){
            if(now()-openTime > coolDownMs){
                state = State.HALF_OPEN;
                return true; //半开放行少量请求试探
            }
            return false;
        }
        //HALF_OPEN
        return true;
    }

    void recordResult(boolean success){
        windowTotal++;
        if(!success) errorCount++;
        double rate = 1.0*errorCount / windowTotal;
        if(state == CLOSED && rate >= errorRateTrigger){
            state = State.OPEN;
            openTime = now();
        }
        if(state == HALF_OPEN){
            if(success){
                //恢复正常
                state = State.CLOSED;
                errorCount=0;windowTotal=0;
            }else{
                //试探失败重新熔断
                state = State.OPEN;
                openTime = now();
            }
        }
    }
}

四、自适应采样模式(链路追踪领域)

1.自适应采样 Adaptive Sampling

(1)定义

系统正常时低采样率减少存储压力;出现异常(错误、慢调用)提高采样率,尽可能采集故障链路。

(2)业务场景

SkyWalking/Jaeger链路追踪;日志采集。

百度地图定位上报,静止(10秒上报一次),慢速(5秒上报一次),高速(2秒上报一次)

(3)伪代码

java 复制代码
class AdaptiveSampler{
    double normalRate =0.1; //正常只采样10%
    double errorRate =1.0; //异常100%采样
    double slowRt =500; //超过500ms视为慢调用

    boolean isSample(Span span){
        if(span.isError() || span.costMs>slowRt){
            return Math.random() < errorRate;
        }else{
            return Math.random() < normalRate;
        }
    }
}

五、事件驱动架构 EDA(替代轮询)

(1)定义

数据变更时业务主动发出事件,通过MQ推送消费者,不需要轮询拉取。

(2)业务场景

内部系统库存变更,能够修改业务代码;库存变更、订单完成后触发告警、通知、下游业务。

生产最佳实践:事件驱动为主,自适应轮询做兜底,防止MQ丢消息漏告警。

(3)伪代码

java 复制代码
//库存写业务,更新DB后发送事件
void updateStock(Long productId, Long newStock){
    db.updateStock(productId,newStock);
    //发送库存变更事件到MQ
    mq.send(new StockChangeEvent(productId,newStock));
}

//消费者监听事件
void consume(StockChangeEvent event){
    if(event.stock <=0){
        sendAlert(event.productId);
    }
}
//兜底:自适应轮询定时扫描,补偿MQ丢失消息

六、全部模式横向对比总结

模式 驱动条件 频率/流量变化方向 典型业务
自适应轮询 业务指标(库存、设备状态) 风险越高,轮询越快 第三方库存监控、设备监控
指数退避轮询 轮询次数/等待时长 等待越久,轮询越慢 异步任务查询、导出任务
指数退避重试 失败次数 失败越多,重试间隔越大 RPC接口调用失败重试
自适应重试 错误类型 不同错误不同策略 异构第三方网关调用
自适应限流 下游RT、错误率 压力越大,放行流量越小 API网关保护下游服务
自适应采样 异常、慢请求 异常越多,采样率越高 链路追踪、日志采集
事件驱动EDA 数据变更事件 变更立刻触发,无轮询 内部业务解耦,库存变更通知

七生产落地关键点

  1. 抖动Jitter:轮询、重试务必增加随机抖动,避免惊群效应,大量任务同一时刻请求;
  2. 滞回回差:阈值判断类逻辑,一定要避免临界值来回震荡;
  3. 保护边界:所有动态间隔、QPS,必须设置最大值、最小值,防止极端条件参数失控;
  4. 分布式场景:轮询任务需要分布式锁,防止多实例重复执行;
  5. 可靠性:轮询优先作为兜底,能事件推送优先事件驱动。
相关推荐
ESDWAN1 小时前
SD-WAN 企业选型与落地指南:从链路测试到网络架构设计
运维·网络·架构
DianSan_ERP2 小时前
多平台订单自动下载与回传的技术实现:从消息推送到状态闭环引言
java·linux·服务器·前端·网络·架构·自动化
梦帮科技3 小时前
量子张量网络破局大模型:从矩阵乘积态 (MPS) 到张量列 (TT-SVD) 低秩收缩全推导
网络·数据结构·数据库·线性代数·矩阵·架构·模拟退火算法
霸道流氓气质3 小时前
LLM 应用限流与熔断机制完全指南:从多层防护架构到Java生产级弹性实战
java·开发语言·架构
Jmyd01234 小时前
元宇宙虚拟校史馆技术方案解析:一个引擎+两大平台架构拆解
架构·三维数字化
ESDWAN5 小时前
外贸企业网络专线选型与落地实战指南
网络·架构
小朱爱编程1235 小时前
我用 Jev 做了三个实用工具:整理标签页、分诊飞书反馈、找回 GitHub 收藏
java·开发语言·人工智能·后端·python·架构·ai编程
换元不配限5 小时前
Android 架构演进实战:MVC → MVP → MVVM → MVI
android·架构·mvc·mvvm·mvp·mvi
ClinicTech5 小时前
高速冷冻离心机的温控系统与安全保护逻辑解析
设计模式·架构·健康医疗·设计语言