无人售货柜4G弱网断网自动重试机制:指数退避重连、离线缓存队列

无人售货柜4G弱网断网自动重试机制:指数退避重连、离线缓存队列
无人售货柜4G弱网断网自动重试机制指数退避重连、离线缓存队列大家好我是黒漂技术佬。上一篇讲了怎么监听网络状态今天聊聊监听到断网之后怎么办。网络断了就重连嘛有啥难的还真有讲究——重连太频繁流量跑飞了、基站把你拉黑了重连太慢设备半天回不来用户投诉。弱网环境下更是麻烦发个消息半天没响应重试吧怕重复发不重试吧怕丢了。今天给大家一套工业级的重试缓存方案稳定又省流量。一、为什么不能断了立刻重连先讲个真实案例某客户的柜子放在地下停车场4G信号很差频繁断连。一开始代码写的是断开立刻重连结果一个月光流量费就超了预算三倍设备还经常被运营商限流。为什么会这样盲目重连的三大危害浪费流量每次重连都有握手开销一秒钟重连十次流量哗哗的基站限流频繁连接断开运营商基站可能判定为异常行为直接限流耗电发热4G模块频繁搜网功耗飙升设备发烫核心思想断网不是故障是常态。重试要有策略不能蛮干。二、指数退避重连算法这是网络重连的经典算法简单但超级好用。什么是指数退避每次重连失败等待时间翻倍第1次失败等 1 秒第2次失败等 2 秒第3次失败等 4 秒第4次失败等 8 秒…到最大值后不再增加比如最多等5分钟publicclassExponentialBackoff{privatefinallongbaseDelay;// 基础延迟比如1秒privatefinallongmaxDelay;// 最大延迟比如5分钟privatefinaldoublemultiplier;// 倍数一般是2privateintattemptCount0;publicExponentialBackoff(longbaseDelay,longmaxDelay){this.baseDelaybaseDelay;this.maxDelaymaxDelay;this.multiplier2.0;}/** * 获取下一次重试的等待时间 */publiclongnextDelay(){longdelay(long)(baseDelay*Math.pow(multiplier,attemptCount));delayMath.min(delay,maxDelay);attemptCount;returndelay;}/** * 重连成功后重置 */publicvoidreset(){attemptCount0;}}加点随机抖动Jitter纯指数退避有个问题如果一堆设备同时断网会同时重连把服务器打挂。加个随机抖动错开重连时间publiclongnextDelayWithJitter(){longbaseDelaynextDelay();// 加 ±20% 的随机抖动doublejitter0.8Math.random()*0.4;return(long)(baseDelay*jitter);}完整的重连管理器publicclassReconnectManager{privatestaticfinallongBASE_DELAY1000;// 1秒起步privatestaticfinallongMAX_DELAY300000;// 最多5分钟privateExponentialBackoffbackoffnewExponentialBackoff(BASE_DELAY,MAX_DELAY);privateHandlerhandlernewHandler(Looper.getMainLooper());privateRunnablereconnectTask;privatebooleanisReconnectScheduledfalse;/** * 触发重连调度 */publicvoidscheduleReconnect(){if(isReconnectScheduled){return;// 已经排过重连了不要重复排}longdelaybackoff.nextDelayWithJitter();Log.i(Reconnect,下一次重连在 delayms 后);reconnectTask()-{isReconnectScheduledfalse;doReconnect();};handler.postDelayed(reconnectTask,delay);isReconnectScheduledtrue;}/** * 连接成功重置重试计数 */publicvoidonConnectSuccess(){backoff.reset();cancelScheduledReconnect();}privatevoidcancelScheduledReconnect(){if(reconnectTask!null){handler.removeCallbacks(reconnectTask);reconnectTasknull;}isReconnectScheduledfalse;}}三、弱网分级处理策略不是所有网络差都是一个待遇分等级处理更智能网络等级划分等级状态判定标准处理策略0完全断开接口都没连上指数退避重连1假联网接口连上但探针失败降低频率探测不发业务数据2弱网探针通但延迟1000ms限流只发高优先级消息3正常延迟1000ms稳定正常发送所有消息弱网限流策略弱网的时候不要什么消息都发优先发重要的publicclassWeakNetworkLimiter{/** * 判断消息是否可以在弱网下发送 */publicbooleancanSendInWeakNetwork(MqttMessagemsg){switch(msg.getPriority()){casePRIORITY_HIGH:// 高优先级开门结果、异常告警、心跳必须发returntrue;casePRIORITY_MEDIUM:// 中优先级状态上报可以发但要限流returncheckRateLimit();casePRIORITY_LOW:// 低优先级日志、统计数据弱网下缓存起来returnfalse;default:returnfalse;}}// 简单的令牌桶限流privatelonglastSendTime0;privatestaticfinallongMIN_INTERVAL10000;// 弱网下最少10秒发一条privatebooleancheckRateLimit(){longnowSystem.currentTimeMillis();if(now-lastSendTimeMIN_INTERVAL){lastSendTimenow;returntrue;}returnfalse;}}四、离线消息缓存队列断网期间产生的消息不能丢存起来等联网了再发。队列设计要点持久化存储APP重启了缓存还在优先级排序联网后先发重要的去重机制同一条消息不要重复存容量限制别把存储空间占满了过期清理太久的消息就别发了数据库表设计CREATETABLEoffline_message(idINTEGERPRIMARYKEYAUTOINCREMENT,msg_idVARCHAR(64)UNIQUE,-- 消息唯一ID去重用topicVARCHAR(128),-- MQTT主题payloadTEXT,-- 消息内容priorityINTEGER,-- 优先级create_timeBIGINT,-- 创建时间retry_countINTEGERDEFAULT0,-- 重试次数statusINTEGERDEFAULT0-- 0待发送 1发送中 2失败);-- 索引按优先级时间排序发送CREATEINDEXidx_priority_timeONoffline_message(priorityDESC,create_timeASC);缓存管理器publicclassOfflineMessageQueue{privateOfflineMessageDbdb;privatestaticfinalintMAX_QUEUE_SIZE1000;// 最多缓存1000条privatestaticfinallongEXPIRE_TIME7*24*3600*1000;// 7天过期/** * 消息发不出去时入队缓存 */publicbooleanenqueue(MqttMessagemsg){// 1. 检查队列是否满了if(db.getPendingCount()MAX_QUEUE_SIZE){// 满了就删掉最老的低优先级消息db.deleteOldestLowPriority();}// 2. 去重检查if(db.exists(msg.getMsgId())){returnfalse;}// 3. 存入数据库db.insert(msg);returntrue;}/** * 网络恢复后批量取出待发送消息 */publicListMqttMessagedequeueBatch(intbatchSize){// 按优先级排序高优先级先发returndb.queryPending(batchSize);}/** * 清理过期消息 */publicvoidcleanExpired(){longexpireBeforeSystem.currentTimeMillis()-EXPIRE_TIME;db.deleteExpired(expireBefore);}}五、联网后批量补发策略网络恢复了缓存的消息怎么发补发流程网络恢复 ↓ 先清理过期消息 ↓ 按优先级排序取出一批比如20条 ↓ 逐条发送每条确认成功后再发下一条 ↓ 发送成功的删除失败的重试计数1 ↓ 重试超过3次的标记为失败延后再试 ↓ 一批发完了再取下一批 ↓ 全部发完或者网络又断了停止补发注意事项1. 不要一拥而上网络刚恢复别一下把几百条消息全发出去控制并发一条一条或者少量并发给服务器缓冲的时间2. 注意消息时效性状态上报类消息只发最新的就行中间的可以丢交易结果、开门记录这种必须发的不能丢按消息类型区别对待3. 失败重试有上限一条消息重试超过N次就别死磕了标记起来等下次网络状态更好的时候再试或者直接上报发送失败告警六、网络抖动防抖基站切换、信号波动会导致频繁断一下又好这时候不要触发完整的重连逻辑。防抖窗口设计publicclassNetworkJitterDebouncer{privatestaticfinallongJITTER_WINDOW5000;// 5秒抖动窗口privatelongdisconnectTime0;/** * 网络断开时调用 */publicvoidonDisconnect(){if(disconnectTime0){disconnectTimeSystem.currentTimeMillis();}// 断开时间还在抖动窗口内先不触发重连逻辑if(System.currentTimeMillis()-disconnectTimeJITTER_WINDOW){Log.i(Jitter,网络抖动等待恢复中...);return;}// 超过抖动窗口确认是真断了notifyRealDisconnected();}/** * 网络恢复时调用 */publicvoidonReconnect(){if(disconnectTime0){longdurationSystem.currentTimeMillis()-disconnectTime;Log.i(Jitter,网络恢复中断时长 durationms);disconnectTime0;if(durationJITTER_WINDOW){// 抖动范围内的恢复不触发完整的恢复流程notifyJitterRecovered();}else{// 真正的断网恢复notifyFullRecovered();}}}}七、踩坑经验坑1MQTT自动重连和自己的重连冲突现象MQTT客户端自带重连自己又写了一套两边同时重连越搞越乱解决要么用MQTT自带的重连配置好就行要么关掉自带的完全自己控制二选一坑2缓存消息越来越多数据库越来越大现象设备长期离线缓存了几万条消息恢复后发半天发不完解决设置队列上限过期清理同类消息合并比如状态上报只留最新的坑3指数退避到最大值后恢复太慢现象断网两小时退避到5分钟了网络恢复了还要等几分钟才重连解决监听系统网络变化广播收到网络已连接广播时立刻触发一次重连不等退避时间八、总结弱网断网重试的核心有策略地等有选择地发有计划地补。指数退避随机抖动重连不盲目省流量不撞车网络分级优先级限流弱网下保证核心业务离线队列批量补发断网不丢数据恢复有序补发抖动防抖基站切换不折腾减少误判这套方案下来设备在各种网络环境下都能稳稳运行流量也可控。我是黒漂技术佬专注无人零售物联网实战。下一篇讲《4G流量管控与异常自愈》聊聊怎么防止设备跑流量、4G模块卡死了怎么自动复位。有网络优化的经验欢迎私信交流大家一起踩坑一起进步~

最新新闻

日新闻

周新闻

月新闻