环境准备
下载
下载地址:https://github.com/xuxueli/xxl-job/tree/2.5.0-release
工程结构
- xxl-job-admin:调度中心
- xxl-job-core:公共依赖
- xxl-job-executor-samples:执行器Sample示例
相关路径
初始化脚本:/xxl-job/doc/db/tables_xxl_job.sql,其中用户名密码的初始化脚本,建议修改
配置文件:/xxl-job/xxl-job-admin/src/main/resources/application.properties
如区别本地、开发、生产环境,需自行添加 properties
/xxl-job/xxl-job-admin/src/main/resources/application-dev.properties
/xxl-job/xxl-job-admin/src/main/resources/application-prod.properties
logback.xml:/xxl-job/xxl-job-admin/src/main/resources/logback.xml
修改配置文件
properties修改
### web
server.port=18092
server.servlet.context-path=/monitor-job-admin
### actuator
management.server.base-path=/monitor/manager/path
management.health.mail.enabled=false
management.security.enabled=true
management.ip.whitelist=0:0:0:0:0:0:0:1,127.0.0.1,128.10.10.1,128.10.10.2
management.endpoints.web.base-path=/monitor/manager/path
management.endpoints.web.exposure.include=health
management.endpoints.security.username=monitor-job
management.endpoints.security.password={bcrypt}$2a$10$KPpPOXz4QRcRJgYsxVe2veW0QbrzmhoePMeXfEMCWqUbZyPWdPBIm
management.endpoint.health.show-details=always
management.endpoint.shutdown.enabled=false
management.endpoint.env.enabled=false
management.endpoint.beans.enabled=false
### resources
spring.mvc.servlet.load-on-startup=0
spring.mvc.static-path-pattern=/static/**
spring.web.resources.static-locations=classpath:/static/
### freemarker
spring.freemarker.templateLoaderPath=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.charset=UTF-8
spring.freemarker.request-context-attribute=request
spring.freemarker.settings.number_format=0.##########
spring.freemarker.settings.new_builtin_class_resolver=safer
### mybatis
mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
### datasource-pool
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.max-lifetime=900000
spring.datasource.hikari.connection-timeout=10000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.validation-timeout=1000
### xxl-job, email
spring.mail.host=smtp.ali.mail.com
spring.mail.port=25
spring.mail.username=guanyi.tanggy@dtzhejiang.com
spring.mail.from=xxx@qq.com
spring.mail.password=xxx
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
### xxl-job, access token
xxl.job.timeout=3
### xxl-job, i18n (default is zh_CN, and you can choose "zh_CN", "zh_TC" and "en")
xxl.job.i18n=zh_CN
## xxl-job, triggerpool max size
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100
### xxl-job, log retention days
xxl.job.logretentiondays=30
### xxl-job, datasource
spring.datasource.url=jdbc:mysql://192.168.21.32:3306/sre-monitor-dev?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=admin
spring.datasource.password=ENC(nQYwxmcNBYnVmqO6R+p92ulrg+Uo1TLZxkiBta5J1u5+gQGTL1sPrPEbsyDLWgm1)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
### xxl-job, access token
xxl.job.accessToken=ENC(jl2wm/aWchdp+lyoYMtIwBxGiHiq8ja/NDRGHZggG7uyL8Aib5pJXfGh9P58d24K37c/hlkCGViw3+LdrOewk1kdqZsEo7ENbsi7m/cwim8=)
注:
server.port:admin访问端口,建议修改
server.servlet.context-path:访问路径,修改
management.endpoints.web.base-path:actutor暴露短点路径,修改
management.endpoints.web.exposure.include:暴露内容,修改
management.endpoints.security.username:Spring security 的访问用户名
management.endpoints.security.password:Spring security 的访问密码,加密修改
xxl.job.accessToken:xxlJob的访问token,加密修改
配置说明
### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### 调度中心通讯TOKEN [选填]:非空时启用;
xxl.job.admin.accessToken=default_token
### 调度中心通讯超时时间[选填],单位秒;默认3s;
xxl.job.admin.timeout=3
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-executor-sample
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯使用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
xxl.job.executor.ip=
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
xxl.job.executor.logretentiondays=30
logback修改
主要修改 log.path
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="1 seconds">
<contextName>logback</contextName>
<property name="log.path" value="./xxl-job-admin.log"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}.%d{yyyy-MM-dd}.zip</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
</configuration>
SpringSecurity配置
引入Security的目的是避免actuator导致敏感的运行数据泄漏等安全问题
- 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 修改配置
management.security.enabled=true
management.endpoints.security.username=monitor-job
management.endpoints.security.password={bcrypt}$2a$10$KPpPOXz4QRcRJgYsxVe2veW0QbrzmhoePMeXfEMCWqUbZyPWdPBIm
- 添加SecurityConfig类
@Configuration
public class ActuatorSecurityConfig {
@Value("${management.endpoints.security.username}")
private String username;
@Value("${management.endpoints.security.password}")
private String pwd;
// 配置HTTP安全策略
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.antMatchers("/monitor/manager/path/**").hasRole("ADMIN")
.anyRequest().permitAll()
)
.httpBasic() // 使用 httpBasic 认证
.authenticationEntryPoint(new CustomAuthenticationEntryPoint()) // 未认证处理
.and()
.exceptionHandling()
.accessDeniedHandler(new CustomAccessDeniedHandler()) // 无权限处理
.and()
.logout()
.logoutUrl("/security/logout")
.and()
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
);
return http.build();
}
// 配置内存用户(生产环境应使用数据库或LDAP)
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails admin = User.builder()
.username(username)
.password(pwd)
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(admin);
}
}
- 添加CustomAccessDeniedHandler,避免与xxl-job的error页面冲突
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 403
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":403,\"msg\":\"无权限访问\"}");
}
}
- 添加CustomAuthenticationEntryPoint,避免与xxl-job的login页面冲突
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setHeader("WWW-Authenticate", "Basic realm=\"RealmName\"");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"msg\":\"未认证,请登录\"}");
}
}
- 添加一个Filter类,结合management.ip.whitelist配置,过滤访问ip
@WebFilter(filterName = "AuthFilter", urlPatterns = "/monitor-job-admin/monitor/manager/path/*")
public class AuthFilter implements Filter {
@Value("#{'${management.ip.whitelist:}'.empty ? 'localhost' : '${management.ip.whitelist:}'.split(',')}")
private Set<String> whitelist;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
String userIp = getIP(request);
if(!whitelist.contains(userIp)){
throw new BadCredentialsException("Invalid IP Address");
}
filterChain.doFilter(servletRequest, servletResponse);
}
/**
* 获取IP地址
*
* @param request 请求
* @return request发起客户端的IP地址
*/
private String getIP(HttpServletRequest request) {
if (request == null) {
return "0.0.0.0";
}
String Xip = request.getHeader("X-Real-IP");
String XFor = request.getHeader("X-Forwarded-For");
String UNKNOWN_IP = "unknown";
if (StringUtils.isNotEmpty(XFor) && !UNKNOWN_IP.equalsIgnoreCase(XFor)) {
//多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = XFor.indexOf(",");
if (index != -1) {
return XFor.substring(0, index);
} else {
return XFor;
}
}
XFor = Xip;
if (StringUtils.isNotEmpty(XFor) && !UNKNOWN_IP.equalsIgnoreCase(XFor)) {
return XFor;
}
if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
XFor = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
XFor = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
XFor = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isBlank(XFor) || UNKNOWN_IP.equalsIgnoreCase(XFor)) {
XFor = request.getRemoteAddr();
}
return XFor;
}
}
部署
执行器所在项目配置
- 添加依赖
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.5.0</version>
</dependency>
- 添加 XxlJobSpringExecutor
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
log.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setAddress(address);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
- xxl-job配置文件配置
xxl:
job:
admin:
addresses: http://localhost:18092/monitor-job-admin
accessToken: ENC(jl2wm/aWchdp+lyoYMtIwBxGiHiq8ja/NDRGHZggG7uyL8Aib5pJXfGh9P58d24K37c/hlkCGViw3+LdrOewk1kdqZsEo7ENbsi7m/cwim8=)
executor:
appName: sre-monitor
address:
ip:
port: 18093
logPath: ./
logRetentionDays: 30
本地版
以上配置完成,执行SQL脚本,运行工程,访问 http://localhost:port/server.servlet.context-path(默认为http://localhost:8080/xxl-job-admin), 即可进入xxl-job控制台
输入初始化的账号密码进入
本地测试类或者将xxl-job中的SampleXxlJob类直接拷贝到项目中
@Component
@Slf4j
public class XxlJobHealthCheck {
@XxlJob("xxlJobHealthCheck")
public void testXxlJob() {
log.info("xxl-job-test excute is ok");
}
}
新建一个执行器
新建一个任务
选择执行一次
返回项目查看执行日志:
2024-06-19 09:54:01.090||INFO ||com.xxl.job.core.executor.XxlJobExecutor||>>>>>>>>>>> xxl-job regist JobThread success, jobId:4, handler:com.xxl.job.core.handler.impl.MethodJobHandler@7d2b2f3a[class com.sre.monitor.jobHandle.XxlJobHealthCheck#testXxlJob]
2024-06-19 09:54:01.114||INFO ||com.sre.monitor.jobHandle.XxlJobHealthCheck||xxl-job-test excute is ok
远程版
-
通过maven打包,获取admin的jar
-
将admin的jar包上传至服务器,通过命令启动
后台启动命令:
简单启动:nohup java -jar xxl-job-admin-2.5.0.jar > xxl-job-admin.log 2>&1 &
带参数启动:
nohup java -Xms4g -Xmx4g -jar -Dspring.profiles.active=dev -Djasypt.encryptor.password=xxx -XX:+PrintCommandLineFlags xxl-job-admin-2.5.0.jar > xxl-job-admin.log 2>&1 &
start.sh脚本启动
#! /bin/bash
echo 'start...'
cd /data/app/xxl-job
java -Xms4g -Xmx4g -jar -Dspring.profiles.active=dev -Djasypt.encryptor.password=xxx -XX:+PrintCommandLineFlags xxl-job-admin-2.5.0.jar > xxl-job-admin.log 2>&1 &
echo 'started'
exit 0
集群版
xxl-job的集群很简单,只需要将admin的jar上传到各个服务上,分别启动即可,注意一下几条:
DB配置保持一致;
集群机器时钟保持一致;
执行器回调地址(xxl.job.admin.addresses)需要保持一致;执行器根据该配置进行执行器自动注册等操作
同一个执行器集群内AppName(xxl.job.executor.appname)需要保持一致;调度中心根据该配置动态发现不同集群的在线执行器列表
集群任务的执行方式,建议配置随机
推荐通过nginx为调度中心集群做负载均衡,分配域名。调度中心访问、执行器回调配置、调用API服务等操作均通过该域名进行
Nginx配置
# xxl-job admin机器
upstream monitor.xxljob {
server 172.xxx.xxx.1:18092;
server 172.xxx.xxx.2:18092;
server 172.xxx.xxx.3:18092;
}
# 允许以下ip访问xxl-job
map $real_ip $is_allowed {
default 0;
172.xxx.xxx.10 1;
172.xxx.xxx.11 1;
172.xxx.xxx.12 1;
}
server {
listen 80;
server_name 172.xxx.xxx.4;
resolver 172.xxx.xxx.xxx;
location ^~ /monitor-job-admin/ {
access_log /var/log/nginx/monitor-admin.log;
set $real_ip $remote_addr;
if ($http_x_forwarded_for) {
set $real_ip $http_x_forwarded_for;
}
if ($is_allowed = 0) {
return 403;
}
proxy_pass http://monitor.xxljob;
}
}
其他基础使用可以参考社区文档:
https://www.xuxueli.com/xxl-job/
问题
- 定时任务无固定间隔时间执行的方式,还是得用@Scheduled(fixedDelay = xxx)
- 任务创建完,默认是未开启状态,如果项目中存量的定时任务较多,建议采用sql的方式(拷贝一条已经新建成功的记录转为insert语句),直接导入xxl_job_info