最近记录了一些java中常踩的坑、设计思路和小知识点,大家可以看看
详细记录一次接入xxl-job的踩坑路径
30s快速解决循环依赖
idea中一个小小的操作竟能解决如此多的问题
docker中的服务接入xxljob需要注意的一点
关于一次fullgc的告警分析
mysql中的int类型竟变成了它?
jpa中的字段总是自己莫名更新?
获取不到类上的注解?空指针?
学会这招,再也不怕依赖冲突!
redis的热点key还能这么处理?
领导让我设计一个任务系统
当服务重启时,大部分人没考虑这点
参数还能这么优雅校验?
文件上传报错,全局异常处理!
常见的点赞功能如何实现,如何防止刷赞
1. 接入企业公用邮箱的邮箱告警改造
因为xxl-job是自带邮箱告警的,但是我需要的场景是,使用企业内部邮箱,发件人是企业公用账户,由于一些文档的缺失,导致这块接起来也废了点劲,主要需要改造下EmailJobAlarm这个类。
原本发送邮件的部分是这样的,这种适用于我已经有了发件人的邮箱账号和密码,是可以直接使用无需改造的。
MimeMessage mimeMessage = XxlJobAdminConfig.getAdminConfig().getMailSender().createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setFrom(XxlJobAdminConfig.getAdminConfig().getEmailFrom(), personal);
helper.setTo(email);
helper.setSubject(title);
helper.setText(content, true);
XxlJobAdminConfig.getAdminConfig().getMailSender().send(mimeMessage);
但是对于公用账户而言,密码是动态的,所以需要去动态获取设置,改造为
JavaMailSenderImpl mailSender = XxlJobAdminConfig.getAdminConfig().getMailSender();
mailSender.setPassword(getMailPassword(mailSender.getUsername(), token));
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, StandardCharsets.UTF_8.name());
helper.setFrom(XxlJobAdminConfig.getAdminConfig().getEmailFrom());
helper.setTo(email);
helper.setSubject(title);
helper.setText(content, true);
mailSender.send(mimeMessage);
主要就是将XxlJobAdminConfig中的mailSender的实现类换为JavaMailSenderImpl,这样我们就可以给他动态设置password,然后再自己写个根据token获取密码的http接口即可。
最后,因为很多企业邮箱是缺少证书的,而xxl-job中默认是开启了邮箱安全验证的,这个时候就会报类似
Caused by: javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
网上很多办法是虚拟一个证书之类的,但是没必要,我是直接把xxl-job的邮箱安全验证关掉的。
把他们注释掉就好了
#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
2. 关于xxl-job接入eureka的坑
客户端接入eureka教程很简单,大家自己去搜,我这里列一下我踩的坑
- 关于pom依赖的导致的注册失败
我一开始引入的依赖是idea自动引入的
<!-- eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
要改成这样
<!-- eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
首先是引入的包发生了变化,只有下面这个才会自动注册(原因还在分析)
其次是版本号去掉,eureka要和springboot版本号匹配(有些是不兼容的)
但是直接在这里写版本号不利于统一管理,idea也会报错,所以groupId一致的最好交由父项目统一管理
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2021.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
这里统一管理了org.springframework.boot与org.springframework.cloud两种依赖的版本,其中org.springframework.cloud是我后来加的,因为eureka用的是它。
但是springCloud和springBoot也是有兼容关系的!
兼容关系如下(大家复制以json格式打开)
{"git":{"branch":"ab69b84e03e0cd44f38dab35bc3ae1cdcc35416e","commit":{"id":"ab69b84","time":"2022-08-23T15:02:55Z"}},"build":{"version":"0.0.1-SNAPSHOT","artifact":"start-site","versions":{"spring-boot":"2.7.3","initializr":"0.13.0-SNAPSHOT"},"name":"start.spring.io website","time":"2022-08-23T15:04:28.319Z","group":"io.spring.start"},"bom-ranges":{"codecentric-spring-boot-admin":{"2.4.3":"Spring Boot >=2.3.0.M1 and <2.5.0-M1","2.5.6":"Spring Boot >=2.5.0.M1 and <2.6.0-M1","2.6.8":"Spring Boot >=2.6.0.M1 and <2.7.0-M1","2.7.4":"Spring Boot >=2.7.0.M1 and <3.0.0-M1","3.0.0-M4":"Spring Boot >=3.0.0-M1 and <3.1.0-M1"},"solace-spring-boot":{"1.1.0":"Spring Boot >=2.3.0.M1 and <2.6.0-M1","1.2.1":"Spring Boot >=2.6.0.M1 and <2.7.0-M1"},"solace-spring-cloud":{"1.1.1":"Spring Boot >=2.3.0.M1 and <2.4.0-M1","2.1.0":"Spring Boot >=2.4.0.M1 and <2.6.0-M1","2.3.0":"Spring Boot >=2.6.0.M1 and <2.7.0-M1"},"spring-cloud":{"Hoxton.SR12":"Spring Boot >=2.2.0.RELEASE and <2.4.0.M1","2020.0.6":"Spring Boot >=2.4.0.M1 and <2.6.0-M1","2021.0.0-M1":"Spring Boot >=2.6.0-M1 and <2.6.0-M3","2021.0.0-M3":"Spring Boot >=2.6.0-M3 and <2.6.0-RC1","2021.0.0-RC1":"Spring Boot >=2.6.0-RC1 and <2.6.1","2021.0.3":"Spring Boot >=2.6.1 and <3.0.0-M1","2022.0.0-M1":"Spring Boot >=3.0.0-M1 and <3.0.0-M2","2022.0.0-M2":"Spring Boot >=3.0.0-M2 and <3.0.0-M3","2022.0.0-M3":"Spring Boot >=3.0.0-M3 and <3.0.0-M4","2022.0.0-M4":"Spring Boot >=3.0.0-M4 and <3.1.0-M1"},"spring-cloud-azure":{"4.3.0":"Spring Boot >=2.5.0.M1 and <3.0.0-M1"},"spring-cloud-gcp":{"2.0.11":"Spring Boot >=2.4.0-M1 and <2.6.0-M1","3.3.0":"Spring Boot >=2.6.0-M1 and <2.7.0-M1"},"spring-cloud-services":{"2.3.0.RELEASE":"Spring Boot >=2.3.0.RELEASE and <2.4.0-M1","2.4.1":"Spring Boot >=2.4.0-M1 and <2.5.0-M1","3.3.0":"Spring Boot >=2.5.0-M1 and <2.6.0-M1","3.4.0":"Spring Boot >=2.6.0-M1 and <2.7.0-M1","3.5.0":"Spring Boot >=2.7.0-M1 and <3.0.0-M1"},"spring-geode":{"1.3.12.RELEASE":"Spring Boot >=2.3.0.M1 and <2.4.0-M1","1.4.13":"Spring Boot >=2.4.0-M1 and <2.5.0-M1","1.5.14":"Spring Boot >=2.5.0-M1 and <2.6.0-M1","1.6.10":"Spring Boot >=2.6.0-M1 and <2.7.0-M1","1.7.2":"Spring Boot >=2.7.0-M1 and <3.0.0-M1","2.0.0-M4":"Spring Boot >=3.0.0-M1 and <3.1.0-M1"},"spring-shell":{"2.1.1":"Spring Boot >=2.7.0 and <3.0.0-M1"},"vaadin":{"14.8.16":"Spring Boot >=2.1.0.RELEASE and <2.6.0-M1","23.1.7":"Spring Boot >=2.6.0-M1 and <2.8.0-M1"},"wavefront":{"2.0.2":"Spring Boot >=2.1.0.RELEASE and <2.4.0-M1","2.1.1":"Spring Boot >=2.4.0-M1 and <2.5.0-M1","2.2.2":"Spring Boot >=2.5.0-M1 and <2.7.0-M1","2.3.0":"Spring Boot >=2.7.0-M1 and <3.0.0-M1"}},"dependency-ranges":{"native":{"0.9.0":"Spring Boot >=2.4.3 and <2.4.4","0.9.1":"Spring Boot >=2.4.4 and <2.4.5","0.9.2":"Spring Boot >=2.4.5 and <2.5.0-M1","0.10.0":"Spring Boot >=2.5.0-M1 and <2.5.2","0.10.1":"Spring Boot >=2.5.2 and <2.5.3","0.10.2":"Spring Boot >=2.5.3 and <2.5.4","0.10.3":"Spring Boot >=2.5.4 and <2.5.5","0.10.4":"Spring Boot >=2.5.5 and <2.5.6","0.10.5":"Spring Boot >=2.5.6 and <2.5.9","0.10.6":"Spring Boot >=2.5.9 and <2.6.0-M1","0.11.0-M1":"Spring Boot >=2.6.0-M1 and <2.6.0-RC1","0.11.0-M2":"Spring Boot >=2.6.0-RC1 and <2.6.0","0.11.0-RC1":"Spring Boot >=2.6.0 and <2.6.1","0.11.0":"Spring Boot >=2.6.1 and <2.6.2","0.11.1":"Spring Boot >=2.6.2 and <2.6.3","0.11.2":"Spring Boot >=2.6.3 and <2.6.4","0.11.3":"Spring Boot >=2.6.4 and <2.6.6","0.11.5":"Spring Boot >=2.6.6 and <2.7.0-M1","0.12.0":"Spring Boot >=2.7.0-M1 and <2.7.1","0.12.1":"Spring Boot >=2.7.1 and <3.0.0-M1"},"okta":{"1.4.0":"Spring Boot >=2.2.0.RELEASE and <2.4.0-M1","1.5.1":"Spring Boot >=2.4.0-M1 and <2.4.1","2.0.1":"Spring Boot >=2.4.1 and <2.5.0-M1","2.1.6":"Spring Boot >=2.5.0-M1 and <3.0.0-M1"},"mybatis":{"2.1.4":"Spring Boot >=2.1.0.RELEASE and <2.5.0-M1","2.2.2":"Spring Boot >=2.5.0-M1"},"camel":{"3.5.0":"Spring Boot >=2.3.0.M1 and <2.4.0-M1","3.10.0":"Spring Boot >=2.4.0.M1 and <2.5.0-M1","3.13.0":"Spring Boot >=2.5.0.M1 and <2.6.0-M1","3.17.0":"Spring Boot >=2.6.0.M1 and <2.7.0-M1","3.18.1":"Spring Boot >=2.7.0.M1 and <3.0.0-M1"},"picocli":{"4.6.3":"Spring Boot >=2.4.0.RELEASE and <3.0.0-M1"},"open-service-broker":{"3.2.0":"Spring Boot >=2.3.0.M1 and <2.4.0-M1","3.3.1":"Spring Boot >=2.4.0-M1 and <2.5.0-M1","3.4.1":"Spring Boot >=2.5.0-M1 and <2.6.0-M1","3.5.0":"Spring Boot >=2.6.0-M1 and <2.7.0-M1"}}}
然后这里是springCloud的版本
最后因为xxl-job的springBoot版本是2.6.7,所以选出了2021.0.3这个版本的springCloud。
启动,注册成功。
所以总结一下,就是首先eureka引入的依赖要正确,其次它的版本不能与springBoot冲突,不然就会报各种错,服务都起不来。
2021-12-22 18:06:42.658 ERROR 12172 — [nfoReplicator-0] c.n.d.s.t.d.RedirectingEurekaHttpClient : Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.2.5.RELEASE.jar:2.2.5.RELEASE]
Caused by: java.net.ConnectException: Connection refused: connect at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method) ~[na:1.8.0_41]
3. 关于xxl-job注册到容器中
这个其实不算坑了,算是一个需要注意的点吧,就是服务注册到实体机和容器中的一个小区别。
就是注册的host和port最好是获取容器对外暴漏的host与port,相应的也要根据部署环境的不同,写不同的配置文件,这个因为每个人的场景可能都不一样,所以不细说了。
4. 关于调度器无法访问容器中的执行器的坑
调度器和执行器都注册成功以后,发现调度器无法访问执行器了,奇怪,明明本地试的时候还可以。
一开始报错Full authentication is required to access this resource,一查,应该是spring安全策略的问题,可是如果这样的话本地按理说也会有这个报错,所以感觉不是这个问题。
但还是试了试把报错的url加入到了安全白名单,然后试了一下发现404了。
所以判断应该不是安全策略的问题,应该是访问路径出了问题。
最后发现试执行器注册的信息有误,正确的注册信息应该是
@Bean
public XxlJobSpringExecutor assetsXxlJobExecutor() {
log.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
// xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(innerPort);
xxlJobSpringExecutor.setAddress("http://{ip_port}/".replace("{ip_port}", IpUtil.getIpPort(ip, externalPort)));
return xxlJobSpringExecutor;
}
这里的innerPort是容器内部端口,也就是执行器启动的时候要使用的端口,一般会在容器部署的时候分配一个端口。
这里的externalPort是容器对外暴漏的端口,也就是容器真正起来以后,容器内部端口所分配到的对外暴露端口。
这里的内部端口就类似于 8081 8080之类的,每个容器中都有一套,但一个实体机上会有多个容器,所以这个端口其实不是真实的。
对外暴露的端口可能就是31239之类的,其实容器所在实体机上的真正端口,也就是对外暴露的端口,用做执行器注册与调度通信,它与容器内部端口是有映射关系的。
然后这里注册的setAddress是用于 “执行器注册” 和 “调度中心请求并触发任务”,这点xxl-job官方文档中也有写,setPort传进去是单独用做执行器启动的。
这样设置以后,执行器和调度器就可以正常通信了。