分布式定时任务系列10:XXL-job源码分析之路由策略
传送门分布式定时任务系列1XXL-job安装分布式定时任务系列2XXL-job使用分布式定时任务系列3任务执行引擎设计分布式定时任务系列4任务执行引擎设计续分布式定时任务系列5XXL-job中blockingQueue的应用分布式定时任务系列6XXL-job触发日志过大引发的CPU告警分布式定时任务系列7XXL-job源码分析之任务触发分布式定时任务系列8XXL-job源码分析之远程调用分布式定时任务系列9XXL-job路由策略分布式定时任务系列12XXL-job的任务触发为什么是死循环分布式定时任务系列13死循环是任务触发的银弹分布式定时任务系列14XXL-job的注册模型Java并发编程实战1java中的阻塞队列不忘初心好几个月前就打算分析一下XXL-job路由策略的源码所以有了XXL-job路由策略。不过当时偷懒只从官网上把介绍贴出来了路由策略当执行器集群部署时提供丰富的路由策略包括FIRST第一个固定选择第一个机器LAST最后一个固定选择最后一个机器ROUND轮询RANDOM随机随机选择在线的机器CONSISTENT_HASH一致性HASH每个任务按照Hash算法固定选择某一台机器且所有任务均匀散列在不同机器上。LEAST_FREQUENTLY_USED最不经常使用使用频率最低的机器优先被选举LEAST_RECENTLY_USED最近最久未使用最久未使用的机器优先被选举FAILOVER故障转移按照顺序依次进行心跳检测第一个心跳检测成功的机器选定为目标执行器并发起调度BUSYOVER忙碌转移按照顺序依次进行空闲检测第一个空闲检测成功的机器选定为目标执行器并发起调度SHARDING_BROADCAST(分片广播)广播触发对应集群中所有机器执行一次任务同时系统自动传递分片参数可根据分片参数开发分片任务再谈前置条件一般提到路由可能更多的是理解为对请求做转发时的路由匹配比如nginx的Location路由或者SpringCloud组件的Gateway网关上请求Predicate的URL路由匹配。不过这里说的路由其实指的在集群条件下对执行器进行的路由选择是一种负载均衡策略。所以这里假设了一个场景就是在分布式环境下有多个执行器组成的集群。这里回顾一下xxl-rpc部署示意图这里指的路由策略是执行器集群部署关于调度器集群部署不在此范围暂不讨论后面会单开一节具体讨论如何集群部署达到高性能、高可用目的路由策略这里继续引用XXL-job源码分析之任务触发里面关于代码执行的流程可以看到路由策略的执行代码类路径在com.xxl.job.admin.core.trigger.XxlJobTrigger 方法路径在com.xxl.job.admin.core.trigger.XxlJobTrigger#processTriggerexecutorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());策略定义XXL-job定义了策略枚举public enum ExecutorRouteStrategyEnum { /** FIRST第一个固定选择第一个机器 */ FIRST(I18nUtil.getString(jobconf_route_first), new ExecutorRouteFirst()), /** 最后一个固定选择最后一个机器 */ LAST(I18nUtil.getString(jobconf_route_last), new ExecutorRouteLast()), /** 轮询 */ ROUND(I18nUtil.getString(jobconf_route_round), new ExecutorRouteRound()), /** 随机随机选择在线的机器 */ RANDOM(I18nUtil.getString(jobconf_route_random), new ExecutorRouteRandom()), /** 一致性HASH每个任务按照Hash算法固定选择某一台机器且所有任务均匀散列在不同机器上。 */ CONSISTENT_HASH(I18nUtil.getString(jobconf_route_consistenthash), new ExecutorRouteConsistentHash()), /** 最不经常使用使用频率最低的机器优先被选举 */ LEAST_FREQUENTLY_USED(I18nUtil.getString(jobconf_route_lfu), new ExecutorRouteLFU()), /** 最近最久未使用最久未使用的机器优先被选举 */ LEAST_RECENTLY_USED(I18nUtil.getString(jobconf_route_lru), new ExecutorRouteLRU()), /** 故障转移按照顺序依次进行心跳检测第一个心跳检测成功的机器选定为目标执行器并发起调度 */ FAILOVER(I18nUtil.getString(jobconf_route_failover), new ExecutorRouteFailover()), /** 忙碌转移按照顺序依次进行空闲检测第一个空闲检测成功的机器选定为目标执行器并发起调度 */ BUSYOVER(I18nUtil.getString(jobconf_route_busyover), new ExecutorRouteBusyover()), /** (分片广播)广播触发对应集群中所有机器执行一次任务同时系统自动传递分片参数可根据分片参数开发分片任务 */ SHARDING_BROADCAST(I18nUtil.getString(jobconf_route_shard), null); ExecutorRouteStrategyEnum(String title, ExecutorRouter router) { this.title title; this.router router; } private String title; private ExecutorRouter router; public String getTitle() { return title; } public ExecutorRouter getRouter() { return router; } public static ExecutorRouteStrategyEnum match(String name, ExecutorRouteStrategyEnum defaultItem){ if (name ! null) { for (ExecutorRouteStrategyEnum item: ExecutorRouteStrategyEnum.values()) { if (item.name().equals(name)) { return item; } } } return defaultItem; } }其中枚举里面有一个属性router真正的路由策略实现都在这个接口com.xxl.job.admin.core.route.ExecutorRouter。路由接口看一看路由接口定义代码public abstract class ExecutorRouter { protected static Logger logger LoggerFactory.getLogger(ExecutorRouter.class); /** * route address * * param addressList * return ReturnT.contentaddress */ public abstract ReturnTString route(TriggerParam triggerParam, ListString addressList); }而上面的各种策略都实现了这个接口这种是典型的策略模式应用这里也可以看出好的代码通过设计模式可以很方便的做到扩展路由策略详解对于这些路由策略实现从简单到复杂一个个的来解析。FIRST第一个此策略的定义是固定选择第一个机器意思就是不论执行器有多少个始终选择执行器列表的第一个进行任务执行。这个策略的实现也相当简单public class ExecutorRouteFirst extends ExecutorRouter { Override public ReturnTString route(TriggerParam triggerParam, ListString addressList){ return new ReturnTString(addressList.get(0)); } }这个代码里面就是固定从addressList里面get第一个执行器LAST最后一个此策略的定义是固定选择最后一个机器意思就是不论执行器有多少个始终选择执行器列表的最后一个进行任务执行。这个策略的实现也相当简单public class ExecutorRouteLast extends ExecutorRouter { Override public ReturnTString route(TriggerParam triggerParam, ListString addressList) { return new ReturnTString(addressList.get(addressList.size()-1)); } }这个代码里面就是固定从addressList里面get最后一个个执行器ROUND轮询此策略的定义是意思就是不论执行器有多少个从执行器列表逐个选择进行任务执行。这个策略的实现如下public class ExecutorRouteRound extends ExecutorRouter { private static ConcurrentMapInteger, AtomicInteger routeCountEachJob new ConcurrentHashMap(); private static long CACHE_VALID_TIME 0; private static int count(int jobId) { // cache clear if (System.currentTimeMillis() CACHE_VALID_TIME) { routeCountEachJob.clear(); CACHE_VALID_TIME System.currentTimeMillis() 1000*60*60*24; } AtomicInteger count routeCountEachJob.get(jobId); if (count null || count.get() 1000000) { // 初始化时主动Random一次缓解首次压力 count new AtomicInteger(new Random().nextInt(100)); } else { // count count.addAndGet(1); } routeCountEachJob.put(jobId, count); return count.get(); } Override public ReturnTString route(TriggerParam triggerParam, ListString addressList) { String address addressList.get(count(triggerParam.getJobId())%addressList.size()); return new ReturnTString(address); } }这个代码稍微复杂一点为了实现轮询的效果在内存中声明了一个Map来来进行计数其中的key为任务jobIdvalue为每个任务的调用次数声明一个Map类型字段routeCountEachJob来进行每个任务的调用次数计数其中为ConcurrentMap、AtomicInteger类型的原因是防止并发count(int jobId)方法的作用对当前触发的任务进行计数这里AtomicInteger原子类对每次执行后就1每24小时后重新开始计数然后根据当前任务jobId的调用次数从addressList里面选择执行器即count % 执行器个数这样文字可能理解起来还是不太直接其实就是类似如下的示例RANDOM随机此策略的定义是意思就是不论执行器有多少个从执行器列表随机选择在线的机器。这个策略的实现也比较直观public class ExecutorRouteRandom extends ExecutorRouter { private static Random localRandom new Random(); Override public ReturnTString route(TriggerParam triggerParam, ListString addressList) { String address addressList.get(localRandom.nextInt(addressList.size())); return new ReturnTString(address); } }这个代码里面就是随机从addressList里面get一个执行器localRandom.nextInt(addressList.size())LEAST_FREQUENTLY_USED最不经常使用此策略的定义是意思就是不论执行器有多少个使用频率最低的机器优先被选择出来进行任务执行。这个策略的实现如下public class ExecutorRouteLFU extends ExecutorRouter { // 任务调用计算器,其中key为jobId-任务IDvalue为HashMap记录每个实例的调用次数 private static ConcurrentMapInteger, HashMapString, Integer jobLfuMap new ConcurrentHashMapInteger, HashMapString, Integer(); private static long CACHE_VALID_TIME 0; public String route(int jobId, ListString addressList) { // 缓存1天24小时然后重新计数 // cache clear if (System.currentTimeMillis() CACHE_VALID_TIME) { jobLfuMap.clear(); CACHE_VALID_TIME System.currentTimeMillis() 1000*60*60*24; } // 初始化 // lfu item init HashMapString, Integer lfuItemMap jobLfuMap.get(jobId); // Key排序可以用TreeMap构造入参CompareValue排序暂时只能通过ArrayList if (lfuItemMap null) { lfuItemMap new HashMapString, Integer(); jobLfuMap.putIfAbsent(jobId, lfuItemMap); // 避免重复覆盖 } // put new for (String address: addressList) { if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) 1000000 ) { lfuItemMap.put(address, new Random().nextInt(addressList.size())); // 初始化时主动Random一次缓解首次压力 } } // 这里有一个删除动作其实是因为实例可能动态上下线对于下线的节点需要排除 // remove old ListString delKeys new ArrayList(); for (String existKey: lfuItemMap.keySet()) { if (!addressList.contains(existKey)) { delKeys.add(existKey); } } // 移除下线节点尽量防止调度到下线的节点上导致失败 if (delKeys.size() 0) { for (String delKey: delKeys) { lfuItemMap.remove(delKey); } } // 进行调用次数排序 // load least userd count address ListMap.EntryString, Integer lfuItemList new ArrayListMap.EntryString, Integer(lfuItemMap.entrySet()); Collections.sort(lfuItemList, new ComparatorMap.EntryString, Integer() { Override public int compare(Map.EntryString, Integer o1, Map.EntryString, Integer o2) { return o1.getValue().compareTo(o2.getValue()); } }); // 调用次数1 Map.EntryString, Integer addressItem lfuItemList.get(0); String minAddress addressItem.getKey(); addressItem.setValue(addressItem.getValue() 1); return addressItem.getKey(); } Override public ReturnTString route(TriggerParam triggerParam, ListString addressList) { String address route(triggerParam.getJobId(), addressList); return new ReturnTString(address); } }这个代码比较复杂一点 不过如果对比缓存的淘汰策略的话这个其实就是所谓的LFULFUThe Least Frequently Used最近不多使用算法与LRU的区别在于LRU是以时间衡量LFU是以时间段内的次数算法若是一个数据在必定时间内被访问的次数很低那么被认为在将来被访问的几率也是最低的当规定空间用尽且需要放入新数据的时候会优先淘汰时间段内访问次数最低的数据。优势LFU也能够有效的保护缓存相对场景来说比LRU有更好的缓存命中率。由于是以次数为基准因此更加准确天然能有效的保证和提升命中率。缺点由于LFU须要记录数据的访问频率所以需要额外的空间当访问模式改变的时候算法命中率会急剧降低这也是他最大弊端所以这个策略里面为了实现LFU的效果在内存中声明了一个Map来来进行计数其中的key为任务jobIdvalue为每个任务的调用次数声明一个Map类型字段jobLfuMap来进行每个任务的调用次数计数其中为ConcurrentMap原因是防止并发jobLfuMap的value是一个HashMapString, String其中key为任务的实例地址addressvalue为调用次数。这样设计的目的是为了通过这样的数据结构来达到记录每一个任务jobId在每一个实例上的调用次数与ROUND的区别是ROUND记录的每个任务jobId的所有调用数次LFU多了一个维度通过上面的这种数据结构最终可以对每个实例的调用次数进行排序所以要依赖一个ArrayList来排序最终选取调用次数最少的实例来作为任务执行目标机器LEAST_RECENTLY_USED最近最久未使用此策略的定义是意思就是不论执行器有多少个最久未使用的机器优先被选举。这个策略的实现如下public class ExecutorRouteLRU extends ExecutorRouter { private static ConcurrentMapInteger, LinkedHashMapString, String jobLRUMap new ConcurrentHashMapInteger, LinkedHashMapString, String(); private static long CACHE_VALID_TIME 0; public String route(int jobId, ListString addressList) { // 缓存1天24小时然后重新计数 // cache clear if (System.currentTimeMillis() CACHE_VALID_TIME) { jobLRUMap.clear(); CACHE_VALID_TIME System.currentTimeMillis() 1000*60*60*24; } // init lru LinkedHashMapString, String lruItem jobLRUMap.get(jobId); if (lruItem null) { /** * LinkedHashMap * a、accessOrdertrue访问顺序排序get/put时排序false插入顺序排期 * b、removeEldestEntry新增元素时将会调用返回true时会删除最老元素可封装LinkedHashMap并重写该方法比如定义最大容量超出是返回true即可实现固定长度的LRU算法 */ lruItem new LinkedHashMapString, String(16, 0.75f, true); jobLRUMap.putIfAbsent(jobId, lruItem); } // 新加入的节点处理添加到lru中进行统计 // put new for (String address: addressList) { if (!lruItem.containsKey(address)) { lruItem.put(address, address); } } // 这里有一个删除动作其实是因为实例可能动态上下线对于下线的节点需要排除 // remove old ListString delKeys new ArrayList(); for (String existKey: lruItem.keySet()) { if (!addressList.contains(existKey)) { delKeys.add(existKey); } } if (delKeys.size() 0) { for (String delKey: delKeys) { lruItem.remove(delKey); } } // 排序最后一个节点 // load String eldestKey lruItem.entrySet().iterator().next().getKey(); String eldestValue lruItem.get(eldestKey); return eldestValue; } Override public ReturnTString route(TriggerParam triggerParam, ListString addressList) { String address route(triggerParam.getJobId(), addressList); return new ReturnTString(address); } }这个代码比较复杂一点 不过如果对比缓存的淘汰策略的话这个其实就是所谓的LRULRUThe Least Recently Used最近最久未使用算法。相比于FIFO算法智能些。算法若是一个数据最近不多被访问到那么被认为在将来被访问的几率也是最低的当规定空间用尽且需要放入新数据的时候会优先淘汰最久未被访问的数据。优势LRU能够有效的对访问比较频繁的数据进行保护也就是针对热点数据的命中率提升有明显的效果。缺点对于周期性、偶发性的访问数据有大几率可能形成缓存污染也就是置换出去了热点数据把这些偶发性数据留下了从而致使LRU的数据命中率急剧降低。所以这个策略里面为了实现LFU的效果在内存中声明了一个Map来来进行计数其中的key为任务jobIdvalue为每个任务的调用次数声明一个Map类型字段jobLRUMap来进行每个任务的调用次数计数其中为ConcurrentMap原因是防止并发jobLRUMap的value是一个LinkedHashMapString, String其中key为任务的实例地址addressvalue为address。这里比较巧妙的是直接利用了LinkedHashMap的排序能力/** * Constructs an empty ttLinkedHashMap/tt instance with the * specified initial capacity, load factor and ordering mode. * * param initialCapacity the initial capacity * param loadFactor the load factor * param accessOrder the ordering mode - tttrue/tt for * access-order, ttfalse/tt for insertion-order * throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder accessOrder; }LinkedHashMap的排序模式a、accessOrdertrue访问顺序排序get/put时排序false插入顺序排期LinkedHashMap中accessOrder‘字段的用途是什么LinkedHashMap是Java中的一个类它是HashMap的一个子类具有HashMap的所有特性并且还保持了插入顺序或访问顺序的特性。accessOrder字段是LinkedHashMap类中的一个布尔类型的属性用于指定迭代顺序是否基于访问顺序。当accessOrder为true时表示迭代顺序将基于最近访问顺序即最近访问的元素将排在迭代顺序的末尾当accessOrder为false时表示迭代顺序将基于插入顺序即元素将按照插入的顺序进行迭代。使用accessOrder字段可以方便地实现LRULeast Recently Used最近最少使用缓存淘汰算法。通过将accessOrder设置为true当访问某个元素时该元素会被移到链表的末尾这样在需要淘汰元素时只需要移除链表头部的元素即可。LinkedHashMap的应用场景包括但不限于缓存系统通过设置accessOrder为true可以实现基于访问顺序的缓存淘汰策略。LRU缓存通过继承LinkedHashMap并重写removeEldestEntry方法可以实现固定大小的LRU缓存。记录访问顺序当需要按照访问顺序记录某些数据时可以使用LinkedHashMap。FAILOVER故障转移此策略的定义是按照顺序依次进行心跳检测第一个心跳检测成功的机器选定为目标执行器并发起调度。这个策略的实现如下public class ExecutorRouteFailover extends ExecutorRouter { Override public ReturnTString route(TriggerParam triggerParam, ListString addressList) { StringBuffer beatResultSB new StringBuffer(); for (String address : addressList) { // beat ReturnTString beatResult null; try { ExecutorBiz executorBiz XxlJobScheduler.getExecutorBiz(address); beatResult executorBiz.beat(); } catch (Exception e) { logger.error(e.getMessage(), e); beatResult new ReturnTString(ReturnT.FAIL_CODE, e ); } beatResultSB.append( (beatResultSB.length()0)?brbr:) .append(I18nUtil.getString(jobconf_beat) ) .append(braddress).append(address) .append(brcode).append(beatResult.getCode()) .append(brmsg).append(beatResult.getMsg()); // beat success if (beatResult.getCode() ReturnT.SUCCESS_CODE) { beatResult.setMsg(beatResultSB.toString()); beatResult.setContent(address); return beatResult; } } return new ReturnTString(ReturnT.FAIL_CODE, beatResultSB.toString()); } }这个策略的实现也比较直观根据注册的实例列表依次发起心跳检测如果成功则选取为执行节点代码并不复杂不过可能对FAILOVER故障转移这个术语所震撼觉得很高大上。对于FAILOVER这种故障处理策略来说不同的框架或者场景实现不同难易程度也不同而且也不是所有的系统/接口适合故障转移。比如对一个有超时机制的微服务架构来说如果链路比较多一个业务请求需要经过A-B-C3个服务每个服务有2个节点。假设在A服务调用B的时候存在网络问题A的实例A1失败这里转移到A2成功A-B的时候B1失败B2成功同理B-CC1失败C2成功虽然最终请求成功但是整体耗时会增大一倍早已经进过了网关整体响应时长导致timeout了所以有些场景的FAILOVER并不一定是有益甚至有害的重试也增加了服务调用次数服务的压力。关于这一块会后面单独开一节服务故障模式的讨论BUSYOVER忙碌转移此策略的定义是按照顺序依次进行空闲检测第一个空闲检测成功的机器选定为目标执行器并发起调度。这个策略的实现如下public class ExecutorRouteBusyover extends ExecutorRouter { Override public ReturnTString route(TriggerParam triggerParam, ListString addressList) { StringBuffer idleBeatResultSB new StringBuffer(); for (String address : addressList) { // beat ReturnTString idleBeatResult null; try { ExecutorBiz executorBiz XxlJobScheduler.getExecutorBiz(address); idleBeatResult executorBiz.idleBeat(new IdleBeatParam(triggerParam.getJobId())); } catch (Exception e) { logger.error(e.getMessage(), e); idleBeatResult new ReturnTString(ReturnT.FAIL_CODE, e ); } idleBeatResultSB.append( (idleBeatResultSB.length()0)?brbr:) .append(I18nUtil.getString(jobconf_idleBeat) ) .append(braddress).append(address) .append(brcode).append(idleBeatResult.getCode()) .append(brmsg).append(idleBeatResult.getMsg()); // beat success if (idleBeatResult.getCode() ReturnT.SUCCESS_CODE) { idleBeatResult.setMsg(idleBeatResultSB.toString()); idleBeatResult.setContent(address); return idleBeatResult; } } return new ReturnTString(ReturnT.FAIL_CODE, idleBeatResultSB.toString()); } }这个策略的实现也比较直观根据注册的实例列表依次发起空闲检测如果成功则选取为执行节点代码并不复杂不过可能需要联合起前面XXL-rpc的章节来配合理解CONSISTENT_HASH一致性HASH此策略的定义是每个任务按照Hash算法固定选择某一台机器且所有任务均匀散列在不同机器上。这个策略的实现如下/** * 分组下机器地址相同不同JOB均匀散列在不同机器上保证分组下机器分配JOB平均且每个JOB固定调度其中一台机器 * a、virtual node解决不均衡问题 * b、hash method replace hashCodeString的hashCode可能重复需要进一步扩大hashCode的取值范围 * Created by xuxueli on 17/3/10. */ public class ExecutorRouteConsistentHash extends ExecutorRouter { private static int VIRTUAL_NODE_NUM 100; /** * get hash code on 2^32 ring (md5散列的方式计算hash值) * param key * return */ private static long hash(String key) { // md5 byte MessageDigest md5; try { md5 MessageDigest.getInstance(MD5); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(MD5 not supported, e); } md5.reset(); byte[] keyBytes null; try { keyBytes key.getBytes(UTF-8); } catch (UnsupportedEncodingException e) { throw new RuntimeException(Unknown string : key, e); } md5.update(keyBytes); byte[] digest md5.digest(); // hash code, Truncate to 32-bits long hashCode ((long) (digest[3] 0xFF) 24) | ((long) (digest[2] 0xFF) 16) | ((long) (digest[1] 0xFF) 8) | (digest[0] 0xFF); long truncateHashCode hashCode 0xffffffffL; return truncateHashCode; } public String hashJob(int jobId, ListString addressList) { // ------A1------A2-------A3------ // -----------J1------------------ TreeMapLong, String addressRing new TreeMapLong, String(); for (String address: addressList) { for (int i 0; i VIRTUAL_NODE_NUM; i) { long addressHash hash(SHARD- address -NODE- i); addressRing.put(addressHash, address); } } long jobHash hash(String.valueOf(jobId)); SortedMapLong, String lastRing addressRing.tailMap(jobHash); if (!lastRing.isEmpty()) { return lastRing.get(lastRing.firstKey()); } return addressRing.firstEntry().getValue(); } Override public ReturnTString route(TriggerParam triggerParam, ListString addressList) { String address hashJob(triggerParam.getJobId(), addressList); return new ReturnTString(address); } }这个策略的实现也比较直观这里就不展开讨论了有兴趣的可以在评论区晒出理解分享给大家路由策略的选择上面介绍了各种路由策略的实现关于这里面的路由策略的选择就不过多讨论了建议默认情况采用下轮询各种路由策略比较XXL-job路由策略对比序号名称适用场景1FIRST第一个执行器单机部署因为只有一台也就无所谓什么路由策略调试任务不频繁量比较小的时候这个策略一般都不是首选除非的确有一台机器的性能明显高于其它节点可以考虑。而且在实际操作过程中还要保证执行器的注册顺序鸡肋2LAST最后一个这个策略本质上跟FIRST没有区别...3ROUND轮询在执行器分布式集群部署场景下的首选策略因为它兼顾了平均及节点利用率适合节点多、调度频繁一般的生产物理机或云主机其实性能都差不多这个策略能把任务相对平均的分配到各个节点提升整体利用率4RANDOM随机在执行器分布式集群部署场景下的次选策略适合节点非多、调度非常频繁一般的生产物理机或云主机其实性能都差不多这个策略在概率上能把任务相对平均的分配到各个节点节点足够多提升整体利用率5LEAST_FREQUENTLY_USED最不经常使用LFU其实是为了解决多节点利用率的问题跟下面的LRU类似不过由于其实现是依赖本地JVM里面的内存操作可能对CPU占用会高一些无特殊需求也非首选6LEAST_RECENTLY_USED最近最久未使用同LFU7FAILOVER故障转移执行器集群部署能实现任务调度的高可用尽量大努力重试所以它有一定的限制就是不能在调度里面设置超时时间或者不能太长而且它即做不到调度任务的平均分发也就无法提升整体利用率因为是按照注册列表addressList顺序遍历其实就FIRST的加强PLUS版本所以只有在某些特殊场景下比如调度频率很低日终\日结类业务一天可能就跑一次的但是又需要尽可能成功的时候可以考虑此策略8BUSYOVER忙碌转移这个策略本质上跟FAILOVER没有区别只不过它可以根据执行器自身的执行情况反馈的结果进行适当的调整idleBeat后面会从注册发现来探讨这个问题9CONSISTENT_HASH一致性HASH分组下机器地址相同不同JOB均匀散列在不同机器上保证分组下机器分配JOB平均且每个JOB固定调度其中一台机器可以自行看下一致性HASH

相关新闻

Snowy 3.x 前端文件在线预览组件 XnFilePreview 使用指南:图片、PDF 与 Office 文档预览实战

Snowy 3.x 前端文件在线预览组件 XnFilePreview 使用指南:图片、PDF 与 Office 文档预览实战

后端前端认证鉴权应用安全 【免费下载链接】Snowy 💖国内首个国密前后分离快速开发平台💖《免费商用》,基于开源技术栈精心打造,融合Vue3AntDesignVue4Vite5SpringBoot3MpHuToolSa-Token。平台内置国密加解密功能,保障…

2026/10/16 1:34:40 阅读更多 →
free-account-tool兑换失败处理清单:3次失败、日上限与CDK释放机制你该知道的事

free-account-tool兑换失败处理清单:3次失败、日上限与CDK释放机制你该知道的事

【免费下载链接】free-account-tool UPI redeem only extension 项目地址: https://gitcode.com/gh_mirrors/cd/free-account-tool 点击查看 免费下载 如果你用过 free-account-tool 这款 UPI 兑换专用浏览器扩展,一定遇到过"兑换失败"的场景…

2026/10/17 2:16:40 阅读更多 →
OpCore Simplify 六步菜单流:从硬件报告到可启动的 OpenCore EFI

OpCore Simplify 六步菜单流:从硬件报告到可启动的 OpenCore EFI

OpCore Simplify 六步菜单流:从硬件报告到可启动的 OpenCore EFI 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify OpCore Simplify 是 Open…

2026/10/16 1:33:40 阅读更多 →

最新新闻

Unity Shader实现航天器再入灼烧特效:温度映射、边缘发光与烧蚀残留全解析

Unity Shader实现航天器再入灼烧特效:温度映射、边缘发光与烧蚀残留全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 5:04:17 阅读更多 →
VBFS视频块文件系统:突破通用文件系统存储瓶颈的设计与实践

VBFS视频块文件系统:突破通用文件系统存储瓶颈的设计与实践

1. 从一个真实需求说起:为什么我们需要重新审视视频存储做视频相关项目的朋友大概率都经历过这样的场景:监控系统跑了大半年,硬盘里堆了几十万个视频文件,每个文件几分钟到几小时不等,想按时间段检索一段录像&#xff…

2026/10/17 5:04:17 阅读更多 →
基于Hadoop+Spark+Hive的美食推荐系统毕业设计实战解析

基于Hadoop+Spark+Hive的美食推荐系统毕业设计实战解析

做大数据方向的毕业设计,最怕的就是选题又大又空,最后交付一个没有实际业务闭环的Demo。而“基于HadoopSparkHive的美食推荐系统”这个题目,恰好把经典的大数据技术栈和贴近日常生活的场景结合在了一起。它不仅要你用分布式存储和计算&#x…

2026/10/17 5:04:17 阅读更多 →
OpenWAM世界-动作模型:开源预训练权重与微调实战指南

OpenWAM世界-动作模型:开源预训练权重与微调实战指南

1. 具身模型这条赛道,为什么突然被一个叫 OpenWAM 的东西搅动了第一次看到“OpenWAM”这个名字,是在一个做机器人学习的朋友群里。有人甩了张截图,说“世界-动作模型这块终于有人把预训练权重放出来了”,底下立刻炸出一堆人问“真…

2026/10/17 5:04:17 阅读更多 →
XSS-labs靶场实战:从原理到绕过,系统掌握XSS漏洞利用

XSS-labs靶场实战:从原理到绕过,系统掌握XSS漏洞利用

1. 靶场到底在练什么:先看懂XSS-labs的关卡地图XSS-labs(也有人叫XSS挑战靶场)是我接触过的Web安全练习环境里,最适合用来系统刷一遍XSS技能树的平台。界面确实朴素——一排带序号的关卡,每条输入框构造出不同的过滤场…

2026/10/17 5:04:17 阅读更多 →
TS3000-IFE探针台:从自动化到量产测试的完整解析

TS3000-IFE探针台:从自动化到量产测试的完整解析

TS3000-IFE这个型号,最近在半导体测试圈子里的讨论度确实上来了。很多刚接触晶圆级测试的朋友第一眼看到这种带尾缀的探针台型号,往往会愣一下:TS3000大概知道是个平台,后面的IFE是什么?选型时该不该为了这个尾缀多掏预…

2026/10/17 5:03:17 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →