xxl-job扩展基于dubbo调用的JobHandler

xxl-job是非常流行的分布式调度中心,通常我们直接拿来用或做二开与自己的服务集成,作为微服务架构里的统一调度组件,对定时调度类作业集中管理,将调度与业务剥离。

xxl-job的架构里,xxl-job-admin是负责调度的组件,根据时间轮算法触发任务,通过RPC方式调用xxl-job-executor执行具体的任务,可以制定不同的JobHandler实现不同的执行方式。通常为了业务解耦,xxl-job-executor会独立部署,与业务通过RPC调用方式来实现任务的业务逻辑执行。

以dubbo为RPC通信组件的微服务集群中,实现以xxl-job-executor作为consumer的JobHandler,调用provider的dubbo接口,业务人员只需要关注业务逻辑实现,像其它微服务接口一样提供定时作业的接口,即可方便的实现定时任务。dubbo提供了泛化调用的方式,可以在不引入provider方的接口定义,只需要知道接口签名、方法形参、实参就可以实现dubbo调用。以下是示例,基于dubbo2.7版本。

  1. 引入dubbo依赖

        
            org.apache.dubbo
            dubbo-spring-boot-starter
        
        
            org.apache.dubbo
            dubbo-dependencies-zookeeper
            pom
            
                
                    org.slf4j
                    slf4j-log4j12
                
            
        
  1. 定义DubboJobHandler

核心是genericService.$invoke

@Component
public class DubboJobHandler {

    private static final Logger logger = LoggerFactory.getLogger(DubboJobHandler.class);
    // ReferenceConfig比较重,建议缓存
    private final Map> referenceConfigMap = new ConcurrentHashMap<>();

    @Autowired
    private ObjectProvider applicationConfigObjectProvider;

    @Autowired
    private ObjectProvider registryConfigObjectProvider;

    /**
     * 兼容2.7.3版本与2.7.13版本,二者版本的入参类型不一致,会导致NoSuchMethod异常
     */
    private static final SupplierHolder> REFERENCE_CONFIG_CACHE_GET_METHOD =
            new SupplierHolder<>(() -> Arrays.stream(ReferenceConfigCache.class.getDeclaredMethods())
                    .filter(m -> "get".equals(m.getName()))
                    .filter(m -> m.getParameterTypes().length == 1)
                    .filter(m -> !m.getParameterTypes()[0].equals(Class.class))
                    .filter(m -> !m.getParameterTypes()[0].equals(String.class))
                    .peek(m -> {
                        if (m.isAccessible()) {
                            m.setAccessible(true);
                        }
                    })
                    .collect(Collectors.toList()));

    /**
     * 兼容2.7.3版本与2.7.13版本,二者版本的入参类型不一致,会导致NoSuchMethod异常
     */
    private static final SupplierHolder> REFERENCE_CONFIG_CACHE_DESTROY_METHOD =
            new SupplierHolder<>(() -> Arrays.stream(ReferenceConfigCache.class.getDeclaredMethods())
                    .filter(m -> "destroy".equals(m.getName()))
                    .filter(m -> m.getParameterTypes().length == 1)
                    .filter(m -> !m.getParameterTypes()[0].equals(Class.class))
                    .filter(m -> !m.getParameterTypes()[0].equals(String.class))
                    .peek(m -> {
                        if (m.isAccessible()) {
                            m.setAccessible(true);
                        }
                    })
                    .collect(Collectors.toList()));

    /**
     * 3、dubbo调用任务,定义handler名称为dubboJobHandler
     */
    @XxlJob("dubboJobHandler")
    public void dubboJobHandler() throws Exception {
        logger.info("E|任务执行|dubboJobHandler|{}|{},{}", XxlJobHelper.getJobId(),
                XxlJobHelper.getJobTag(), XxlJobHelper.getJobDesc());
        try {
            final DubboParams dubboParams = GsonTool.fromJson(XxlJobHelper.getJobParam(), DubboParams.class);
            final ReferenceConfig referenceConfig = buildReferenceConfig(dubboParams);
            final ReferenceConfigCache referenceConfigCache = ReferenceConfigCache.getCache();
            // 获取泛化方法
            GenericService genericService = null;
            try {
                genericService = referenceConfigCache.get(referenceConfig); // 直接调用get方法
            } catch (NoSuchMethodError e) {
                final List methods = REFERENCE_CONFIG_CACHE_GET_METHOD.get();
                if (methods.isEmpty()) {
                    XxlJobHelper.handleFail("获取调用接口失败");
                    return;
                }
                final Optional methodAny = methods.stream().filter(m -> {
                    final Class type = m.getParameterTypes()[0];
                    return type.equals(referenceConfig.getClass())
                            || type.isAssignableFrom(referenceConfig.getClass());
                }).findAny();
                if (!methodAny.isPresent()) {
                    XxlJobHelper.handleFail("获取调用接口失败");
                    return;
                }
                // 通过反射拿到get方法再调用,拿到泛化服务
                try {
                    genericService = (GenericService) methodAny.get().invoke(referenceConfigCache, referenceConfig);
                } catch (Exception e1) {
                    logger.error("invoke ReferenceConfigCache#get(ReferenceConfig) error", e1);
                    XxlJobHelper.handleFail("获取调用接口失败");
                    return;
                }
            } catch (Exception e) {
                logger.error("invoke ReferenceConfigCache#get(ReferenceConfig) error", e);
                try {
                    referenceConfigCache.destroy(referenceConfig); // 直接调用destroy方法
                } catch (NoSuchMethodError e1) {
                    logger.warn("invoke ReferenceConfigCache#destroy(ReferenceConfig) cause NoSuchMethodError");
                    final List methods = REFERENCE_CONFIG_CACHE_DESTROY_METHOD.get();
                    methods.stream().filter(m -> {
                        final Class type = m.getParameterTypes()[0];
                        return type.equals(referenceConfig.getClass())
                                || type.isAssignableFrom(referenceConfig.getClass());
                    }).findAny().ifPresent(m -> {
                        try {
                            m.invoke(referenceConfigCache, referenceConfig);
                        } catch (Exception e2) {
                            logger.warn("invoke ReferenceConfigCache#destroy(ReferenceConfig) fail, msg={}",
                                    e2.getMessage());
                        }
                    });
                }
                // 服务不正常时清理缓存
                removeReferenceConfig(dubboParams);
                XxlJobHelper.handleFail("获取调用接口失败");
                return;
            }
            // 传递attachment
            Optional.ofNullable(dubboParams.getAttachments())
                    .ifPresent(m -> m.forEach(RpcContext.getContext()::setAttachment));
            // 使用泛化方式调用dubbo接口
            final Object result = genericService.$invoke(dubboParams.getInvoke().split("#")[1],
                    dubboParams.getTypes(), dubboParams.getArgs());
            XxlJobHelper.log(result == null ? null : result.toString());
        } catch (Exception e) {
            logger.error("execute fail for jobId={}, param={}", XxlJobHelper.getJobId(), XxlJobHelper.getJobParam(), e);
            XxlJobHelper.log(e);
            XxlJobHelper.handleFail("Exception(" + e.getClass().getName() + "," + e.getMessage() + ")");
        }
    }

    private ReferenceConfig buildReferenceConfig(DubboParams dubboParams) {
        final String key = Stream.of(dubboParams.getInvoke(), dubboParams.getGroup(),
                dubboParams.getVersion(), dubboParams.getTimeout(), dubboParams.getRetry())
                .map(String::valueOf).collect(Collectors.joining(":"));
        return referenceConfigMap.computeIfAbsent(key, k -> {
            ReferenceConfig reference = new ReferenceConfig<>();
            reference.setGeneric("true");
            reference.setRetries(dubboParams.getRetry());
            reference.setTimeout(dubboParams.getTimeout());
            reference.setApplication(applicationConfigObjectProvider.getIfAvailable());
            reference.setRegistry(registryConfigObjectProvider.getIfAvailable());
            reference.setInterface(dubboParams.getInvoke().split("#")[0]);
            reference.setVersion(dubboParams.getVersion());
            reference.setGroup(dubboParams.getGroup());
            return reference;
        });
    }

    private void removeReferenceConfig(DubboParams dubboParams) {
        final String key = Stream.of(dubboParams.getInvoke(), dubboParams.getGroup(),
                dubboParams.getVersion(), dubboParams.getTimeout(), dubboParams.getRetry())
                .map(String::valueOf).collect(Collectors.joining(":"));
        referenceConfigMap.remove(key);
    }

}

实现很简单,如果不考虑跨版本的兼容性问题,通过反射获取到泛化方法那些处理都可以省掉。至于为什么会有版本兼容性问题,一来是版本管理做得不好,版本没有统一;二来无非是想支持多个版本。

DubboParams是调用参数,配置在xxl-job的任务参数中,如

{
  "args": [
    "statTag#181"
  ],
  "types": [
    "java.lang.String"
  ],
  "invoke": "com.xxx.facade.admin.CompensateTaskService#doCompensate",
  "version": "1.0",
  "timeout": 30000
}

DubboParams对象定义如下:

@Data
@Accessors(chain = true)
public class DubboParams implements Serializable {

    // 请求接口:接口名#方法名
    private String invoke;
    // 版本号
    private String version = "1.0";
    // 分组
    private String group;
    // 参数类型,如[java.lang.Integer]
    private String[] types;
    // 参数,如[1]
    private Object[] args;
    // 超时时间
    private Integer timeout;
    // 重试次数
    private int retry;
    // 附加参数
    private Map attachments;
}

xxl-job-admin中配置如下:

xxl-job扩展基于dubbo调用的JobHandler_第1张图片

你可能感兴趣的:(Java,中间件,架构)