修改后的源码仓库地址:GitHub. :

改造原因

  1. 原有的xxl-job使用自己实现的http协议进行注册以及调度等,与目前框架中本身的注册中心格格不入,会影响健康检查、日志处理、问题排查。
  2. 技术栈统一。避免执行器内包含两套注册逻辑。
  3. 提高分布式健壮性,原有的服务注册以及发现等功能较弱,且与实际应用可用与否完全无关,经常存在xxl-job线程出问题,但主服务正常,或主服务出问题,但xxl-job线程正常。
  4. 灰度扩展,目前系统灰度使用eureka定制实现,为执行器支持灰度,必须进行改造。

主要改造思路

调度中心

调度中心侧获取服务时,将原有的基于数据库的地址list,修改为动态从eureka中心获取服务的地址列表,两者通过xxl-job-admin配置的执行器app-name,与执行器的spring.application.name(即注册到eureka的服务标识)关联。

//com.xxl.job.admin.core.trigger.XxlJobTrigger
XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup());
//@edit 如果是自动获取地址的话,则使用
if (group.getAddressType() == 0) {
group.setAddressList(SpringAdminContext.getEurekaAddressList(group.getAppname()));
}
//这种静态和spring容器不分的写法,还是有点别扭的
@Component("springAdminContext")
public class SpringAdminContext {
@Autowired
DiscoveryClient discoveryClient; private static SpringAdminContext springAdminContext;
@PostConstruct
public void initialize() {
springAdminContext = this;
springAdminContext.discoveryClient=this.discoveryClient;
}
public static String getEurekaAddressList(String appName){
//may be springContext not init
if(springAdminContext !=null){
DiscoveryClient discoveryClient = SpringAdminContext.springAdminContext.discoveryClient;
List<ServiceInstance> instances = discoveryClient.getInstances(appName);
StringBuilder addressBuilder = new StringBuilder();
for (int i = 0; i < instances.size(); i++) {
addressBuilder.append(instances.get(i).getUri().toString());
if(i!=instances.size()) {
addressBuilder.append(",");
}
}
return addressBuilder.toString();
}else {
return "";
} }
}

调度中心侧调度服务时,增加灰度策略,在获取到eureka的instanceList后,从instance的meta原数据中取出灰度标识,进行灰度调度。代码结合1中的列表获取,具体灰度实现与百度到的eureka灰度相同,略。

调度中心 执行器侧

通过修改core包,将原有的注册线程删除,并删除embedServer的实现,修改为springMVC。

//修改后的代替embedServer的处理类
@Controller
public class XxlJobHandlerController {
private static final Logger logger = LoggerFactory.getLogger(XxlJobHandlerController.class); private ExecutorBiz executorBiz;
@Value("${xxl.job.accessToken:}")
private String accessToken;
@Autowired
ThreadPoolExecutor bizThreadPool;
@PostConstruct
public void start() {
executorBiz = new ExecutorBizImpl();
} @PostMapping("/job/{method}")
@ResponseBody
public ReturnT jobHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, @PathVariable("method") String methodName) {
return doHandlerReq(httpServletRequest,httpServletResponse,"/"+methodName);
}
// ---------------------- registry ---------------------- protected ReturnT doHandlerReq(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,String method) {
try {
//read request
//@edit 这里请求都模拟的原有处理方式,包括header这些
int contentLength = httpServletRequest.getContentLength();
byte[] reqBody=new byte[contentLength];
httpServletRequest.getInputStream().read(reqBody,0,contentLength);
String requestData=new String(reqBody, StandardCharsets.UTF_8);
String uri = method;
String accessTokenReq = httpServletRequest.getHeader(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN);
//@edit 这里原有是netty纯异步,但是到这边http就不太合适了,这么写虽然看起来吞吐会下降,但是一般web容器现在底层也支持nio了,应该关系不大。
FutureTask<ReturnT> stringFutureTask=new FutureTask<ReturnT>(() -> process(uri, requestData, accessTokenReq));
// invoke
bizThreadPool.execute(stringFutureTask);
ReturnT returnT = stringFutureTask.get();
httpServletResponse.setHeader(HttpHeaders.CONTENT_TYPE, "text/html;charset=UTF-8"); return returnT;
} catch (Exception e) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty.");
}
} private ReturnT process(String uri, String requestData, String accessTokenReq) { if (uri == null || uri.trim().length() == 0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty.");
}
if (accessToken != null
&& accessToken.trim().length() > 0
&& !accessToken.equals(accessTokenReq)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong.");
} // services mapping
try {
if ("/beat".equals(uri)) {
return executorBiz.beat();
} else if ("/idleBeat".equals(uri)) {
IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class);
return executorBiz.idleBeat(idleBeatParam);
} else if ("/run".equals(uri)) {
TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
return executorBiz.run(triggerParam);
} else if ("/kill".equals(uri)) {
KillParam killParam = GsonTool.fromJson(requestData, KillParam.class);
return executorBiz.kill(killParam);
} else if ("/log".equals(uri)) {
LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
return executorBiz.log(logParam);
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping(" + uri + ") not found.");
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<String>(ReturnT.FAIL_CODE, "request error:" + ThrowableUtil.toString(e));
}
}
}

修改处理callback的逻辑,使得任务执行结果可以回调到admin

//com.xxl.job.core.executor
//@edit 这个方法主要用于执行器在获取调度中心列表时调用,目的是执行器获取调度中心列表进行callback回调通知执行结果。
//如果callback失败或者这里出问题,那么会导致在管理台看到的执行结果永远没有。
//管理台的调度结果和执行结果是分开的,调度结果依赖单次http请求,执行结果依赖callback
// @see TriggerCallbackThread
public static List<AdminBiz> getAdminBizList(){
List<AdminBiz> adminBizs=new ArrayList<>();
String adminAppName="xxl-job-admin-cloud";
List<String> addressList = Arrays.asList(SpringContext.getEurekaAddressList(adminAppName).split(","));
addressList.forEach(e ->{
adminBizs.add(new AdminBizClient(e.concat("/").concat("xxl-job-admin"),accessToken));
});
return adminBizs;
}

总结

其实还有一些细节的修改包括eureka的配置,原有注册代码等的调整,就不好一一列出来了,具体可以拉下来项目搜索@edit,主要修改的地方我都加了这个。

分布式系统:xxl-job改造spring-cloud的更多相关文章

  1. Spring Cloud与分布式系统

    本文不是讲解如何使用spring Cloud的教程,而是探讨Spring Cloud是什么,以及它诞生的背景和意义. 背景 2008年以后,国内互联网行业飞速发展,我们对软件系统的需求已经不再是过去” ...

  2. 一:Spring Boot、Spring Cloud

    上次写了一篇文章叫Spring Cloud在国内中小型公司能用起来吗?介绍了Spring Cloud是否能在中小公司使用起来,这篇文章是它的姊妹篇.其实我们在这条路上已经走了一年多,从16年初到现在. ...

  3. 微服务架构-选择Spring Cloud,放弃Dubbo

    Spring Cloud 在国内中小型公司能用起来吗?从 2016 年初一直到现在,我们在这条路上已经走了一年多. 在使用 Spring Cloud 之前,我们对微服务实践是没有太多的体会和经验的.从 ...

  4. 放弃Dubbo,选择最流行的Spring Cloud微服务架构实践与经验总结

    http://developer.51cto.com/art/201710/554633.htm Spring Cloud 在国内中小型公司能用起来吗?从 2016 年初一直到现在,我们在这条路上已经 ...

  5. Spring Cloud Consul 实现服务注册和发现

    Spring Cloud 是一个基于 Spring Boot 实现的云应用开发工具,它为基于 JVM 的云应用开发中涉及的配置管理.服务发现.断路器.智能路由.微代理.控制总线.全局锁.决策竞选.分布 ...

  6. 一张图了解Spring Cloud微服务架构

    Spring Cloud作为当下主流的微服务框架,可以让我们更简单快捷地实现微服务架构.Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟.经得起实际考验的服务框架组合起来 ...

  7. (转)springcloud(一):大话Spring Cloud

    http://www.ityouknow.com/springcloud/2017/05/01/simple-springcloud.html 研究了一段时间Spring Boot了准备向Spring ...

  8. 微服务架构集大成者—Spring Cloud (转载)

    软件是有生命的,你做出来的架构决定了这个软件它这一生是坎坷还是幸福. 本文不是讲解如何使用Spring Cloud的教程,而是探讨Spring Cloud是什么,以及它诞生的背景和意义. 1 背景 2 ...

  9. Spring Cloud学习(一)

    Spring Cloud是什么? Spring Cloud是一系列框架的有序集合.它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册.配置中心.消息总线.负载 ...

  10. 玩转SpringCloud Spring Cloud 微服务

    Spring Cloud 简介 Spring Cloud是一系列框架的有序集合.它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册.配置中心.消息总线.负载均 ...

随机推荐

  1. vue 分支结构

    分支循环结构 分支循环结构指令 v-if v-else v-else-if v-show v-if 指令 可以直接在元素中添加指令,添加判断的值 最后运行可以得到结果是:  v-show v-show ...

  2. 浅谈强连通分量(Tarjan)

    强连通分量\(\rm (Tarjan)\)             --作者:BiuBiu_Miku \(1.\)一些术语   · 无向图:指的是一张图里面所有的边都是双向的,好比两个人打电话 \(U ...

  3. Flink 反压 浅入浅出

    前言 微信搜[Java3y]关注这个朴实无华的男人,点赞关注是对我最大的支持! 文本已收录至我的GitHub:https://github.com/ZhongFuCheng3y/3y,有300多篇原创 ...

  4. CentOS 8 部署 ASP.NET Core 3.1 应用程序 kestrel+Nginx IIS+kestrel

    vs2019发布到IIS  下载文档https://files.cnblogs.com/files/netlock/%E4%BD%BF%E7%94%A8VS2019%E5%8F%91%E5%B8%83 ...

  5. Barcodex帮助文档

    前言 官方文档及ocx控件下载,下载很慢,直接上传到博客园文件管理中了. http://files.cnblogs.com/files/masonblog/barcodex.zip 帮助文档 Prop ...

  6. [.NET] - OleDb读取CSV文件:使用指定的分隔符号

    今天在用OleDb方式读取一个CSV文件的时候,发现得到的文本不是通常用逗号隔开的.而是用Tab制表符来隔开的. OrderID OrderName 1 1 2 2 3 3 然后去MSND查询了了下发 ...

  7. python序列(五)切片操作

    功能:截取列表中的任何部分. 切片适用于列表.元组.字符串.range对象等类型.. 格式:[::]切片使用两个冒号分隔的3个数字来完成. 第一个数字表示切片开始位置(默认为0). 第二个数字表示切片 ...

  8. 通配符的匹配很全面, 但无法找到元素 'dubbo:application' 的声明 解决办法

    直接升级dubbo的版本到2.6.4 下面的是我的项目的pom.xml配置的依赖 <dependency>                <groupId>com.alibab ...

  9. [leetcode]205. Isomorphic Strings同构字符串

    哈希表可以用ASCII码数组来实现,可以更快 public boolean isIsomorphic(String s, String t) { /* 思路是记录下每个字符出现的位置,当有重复时,检查 ...

  10. Python pillow库

    由于pillow库功能很强大本文章主要介绍pillow的Image模块 关于Pillow与PIL PIL(Python Imaging Library)是Python一个强大方便的图像处理库,名气也比 ...