windows服务器下java程序健康检测及假死崩溃后自动重启应用、开机自动启动

前两天由于项目需要,一个windows上的批处理任务(kitchen.bat),需要接到mq的消息通知后执行,为了快速实现这里我们通过springboot写了一个jar程序,用于接收mq的消息,并调用bat文件。

本程序需要实现的功能

  • 调用windows的批处理脚本bat,并支持传参
  • 可根据配置设置并发,同时消费多个mq消息调用多个批处理脚本
  • 确保java程序能一直正常运行(如果有假死或者宕机了可以自动重启)
  • 批处理脚本执行失败了,则再将信息重新放回到mq的队列尾部,等待下次执行

需要用的技术

  • Java的java.lang.Runtime类 用于调用windows服务器命令
  • 通过环境变量配置程序运行的参数,如mq信息、和执行的批处理脚本命令路径、并发等
  • 通过rabbitmq的手工ack来确定消息是否处理成功,及并发实现
  • 通过actuator来判断java程序是否健康
  • 通过windows定时任务来定时检查java程序是否正常提供服务,如果不正常则触发重启jar应用
  • 通过maven+ant打包程序,将可执行程序jar及相关脚本打包成一个zip文件,方便发给使用方使用

主要实现逻辑

开发环境:jdk1.8 + maven3.x + rabbitmq
运行环境:windows + jre1.8

Java调用bat批处理文件

package cn.iccboy.kitchen.common;import lombok.extern.slf4j.Slf4j;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;/*** @author iccboy*/@Slf4j
public class CmdUtil {/*** 处理执行进程的流** @param inputStream* 			  InputStream 执行进程的流* @param tag* 			  int 标志:1--InputStream;2--ErrorStream*/private static void processStreamHandler(final InputStream inputStream, int tag) {// 处理流的线程new Thread(() -> {String line;try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream);BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {while ((line = bufferedReader.readLine()) != null) {if(tag == 1) {log.info(line);} else {log.error(line);}}} catch (Exception e) {log.error("【异常】命令执行异常:{}", e.getMessage());}}).start();}public static int exec(String command, String... args) throws IOException {String cmd = StrUtil.splicingWithSpace(command, args);log.info("执行命令:{}", cmd);int ret = 99;Process process = Runtime.getRuntime().exec(cmd);processStreamHandler(process.getInputStream(), 1);processStreamHandler(process.getErrorStream(), 2);try {ret = process.waitFor();} catch (InterruptedException e) {log.error("【异常】process.waitFor:{}" , e.getMessage());}log.info("执行命令:{}, 返回状态码={}", cmd, ret);return ret;}
}

上面的程序中,一定要注意的是process.getErrorStream()process.getInputStream() 一定要将命令行执行输出的信息(输出流)和错误信息(错误流)都从缓冲区读取出来,不然会导致程序执行阻塞。

process的阻塞: 在runtime执行大点的命令中,输入流和错误流会不断有流进入存储在JVM的缓冲区中,如果缓冲区的流不被读取被填满时,就会造成runtime的阻塞。所以在进行比如:大文件复制等的操作时,需要不断的去读取JVM中的缓冲区的流,防止Runtime的死锁阻塞。

程序健康检查

这里通过actuator来实现,首先程序集成actuator,由于是springboot项目,所以很方便。然后通过一个简单的java程序(CheckActuator)来访问actuator的http地址,通过返回值来判断jar程序是否运行正常,然后通过windows的脚本(checkHealth.bat)来调用CheckActuator,根据返回值在进行java程序的重启等操作。

1. pom.xml增加actuator及prometheus的配置

	<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>io.micrometer</groupId><artifactId>micrometer-registry-prometheus</artifactId></dependency>

上的版本会根据springboot对应版本自动集成
2. 配置actuator
在application.yml中增加如下配置

management:health:rabbit:enabled: trueendpoints:web:exposure:include: ["prometheus","health"]endpoint:health:show-details: alwaysmetrics:export:prometheus:enabled: truejmx:enabled: true

3. 编写CheckActuator.java程序
当然也可以通过windows的批处理命令直接访问actuator的地址,来判断服务是否正常。

/*** 注意:该类不能删除!!!! 不能改名!!!!不能移动位置!!!!*/
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;/*** ===================================================* 注意:该类不能删除!!!! 不能改名!!!!不能移动位置!!!!** 该类用于检查程序是否健康(通过actuator进行判断是否健康)** 主要供脚本checkHealth.bat进行调用* ===================================================*/
public class CheckActuator {private static final String HEALTH_FLAG = "\"status\":\"UP\"";public static void main(String[] args) {String url = "http://127.0.0.1:8000/actuator/health";if(args != null && args.length != 0) {url = args[0];}testUrlWithTimeOut(url);}public static void testUrlWithTimeOut(String urlString){int timeOutMillSeconds = 2000;URL url;try {url = new URL(urlString);URLConnection conn =  url.openConnection();conn.setConnectTimeout(timeOutMillSeconds);conn.connect();InputStream in = conn.getInputStream();BufferedReader reader = new BufferedReader(  new InputStreamReader(in));String line;StringBuilder sb = new StringBuilder();while ((line = reader.readLine()) != null) {sb.append(line);}boolean healthFlag = sb.toString().contains(HEALTH_FLAG);if(healthFlag) {System.exit(0);} else {System.out.println("健康检查异常:" + sb);System.exit(1);}} catch (Exception e) {System.out.println("网络连接异常: e=" + e.getMessage());System.exit(1);}}
}

我将上面的CheckActuator.java文件放到maven项目的test/java/跟目录下,后面会通过ant命令将.class移动到指定位置

  1. 健康检测脚本checkHealth.bat

上面的springboot项目会通过http服务,其运行的端口是8000,下面脚本会通过8000端口来获取对应的进程pid

::存活监控!
@echo off
set strPath=%~dp0
echo %strPath%
mkdir %strPath%log
set "yMd=%date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,8%"
set strFile=%strPath%log/checkHealth-%date:~0,4%%date:~5,2%%date:~8,2%.log
java -classpath %strPath% CheckActuator
if ERRORLEVEL 1 (goto err) else (goto ok):err
echo %yMd% 程序连接失败,进行重启! >> %strFile%
set port=8000
for /f "tokens=1-5" %%i in ('netstat -ano^|findstr ":%port%"') do (echo kill the process %%m who use the port taskkill /pid %%m -t -f   
)
goto start
exit :ok
echo %yMd% 程序运行正常 >> %strFile%
exit 
:start
chcp 65001
setlocal enabledelayedexpansion
set filename=""
for /f %%a in ('dir strPath *.jar /o-d /tc /b ') do (set filename=%%~na%%~xaecho 文件名: !filename!, 最新创建时间: %%~ta >> %strFile%if not !filename! == ""  (goto startjar)
)
:startjar
rem 注释:查找最新文件结束,最新文件名为:%filename%
java -jar %strPath%%filename%

windows定时任务配置

  1. 新增-健康检查定时任务.bat
@echo off
set strPath=%~dp0
set checkBat=%strPath%checkHealth.bat
schtasks /create /tn xxx-health-check /tr %checkBat% /sc minute /mo 2
pause

上面的xxx-health-check是定时任务的名字; /sc minute /mo 2 表示每2分钟执行一次命令。上面是通过命令配置的定时任务,也可以通过windows的图形管理界面【计划任务】配置。

  1. 移除-健康检查定时任务.bat
@echo off
pause
schtasks /delete /tn xxx-health-check /f
pause
  1. 查看-健康检查定时任务.bat
@echo off
schtasks /query /V /FO LIST   /tn xxx-health-check
pause

通过windows环境变量设置java程序的配置

application.yml 部分配置如下:

server:port: ${K_PORT:8000}servlet:context-path: /
spring:application:name: xxxrabbitmq:host: ${K_MQ_HOST:172.18.1.100}password: ${K_MQ_PASSWORD:123456}port: ${K_MQ_PORT:5672}username: ${K_MQ_USERNAME:mq}connection-timeout: 15000listener:simple:acknowledge-mode: manual #开启手动ACKconcurrency: ${K_WORKS:1} # 并发max-concurrency: ${K_WORKS:1} # 最大并发prefetch: 1 # 每个消费每次预去取几个消息jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8shell:paths: ${K_BAT_PATHS:C:\invoke.bat}

可通过设置系统的环境变量来改变配置,可设置的变量包含:

变量说明默认值
K_PORT程序运行的http服务端口8000
K_MQ_HOSTrabbitmq 服务ip172.18.1.100
K_MQ_PORTrabbitmq 服务端口5672
K_MQ_USERNAMErabbitmq 用户名mq
K_MQ_PASSWORDrabbitmq 密码123456
K_BAT_PATHSbat脚本路径,可以配置多个,通过英文逗号分隔,配置多个就会启动多个消费者,如:C:\invoke_1.bat,C:\invoke_2.batC:\invoke.bat
K_WORKS每个消费者的并发数。如:K_BAT_PATHS配置了3个命令,K_WORKS 配置了 2 ,这表示有3*2=6个消费者1

消费mq消息并执行bat文件

package cn.iccboy.kitchen.mq;import cn.iccboy.kitchen.common.CmdUtil;
import cn.iccboy.kitchen.common.ThreadUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Headers;import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;import static cn.iccboy.kitchen.mq.TopicRabbitMqConfig.EXCHANGE_DATA;
import static cn.iccboy.kitchen.mq.TopicRabbitMqConfig.KEY_INDEX_PROCESS;/*** @author iccboy* @date 2023-08-05 15:35*/
@Slf4j
public class CmdMqReceive {@Setterprivate String batPath;@Setterprivate Integer seq;@RabbitListener(queues = TopicRabbitMqConfig.QUEUE_INDEX_PROCESS)public void receive(Message<String> message, @Headers Map<String,Object> headers, Channel channel) throws IOException {long deliveryTag = (long) headers.get(AmqpHeaders.DELIVERY_TAG);try {log.info("[start]第{}执行器,消息内容:{}", seq, message.getPayload());int status = CmdUtil.exec(batPath, message.getPayload());if(status != 0) {log.info("[err_1]第{}执行器,消息内容:{}加工脚本执行异常,状态码={}",seq, message.getPayload(), status);throw new RuntimeException("脚本执行异常");}log.info("[end]第{}执行器执行完成:{}", seq, message.getPayload());} catch (Exception e) {ThreadUtil.sleep(1000);log.error("[err]第{}执行器,执行异常重新进入队列:{}", seq, message.getPayload(), e);//channel.basicNack(deliveryTag, false, true);// 将处理错误的消息放到重新队列尾部channel.basicPublish(EXCHANGE_DATA,KEY_INDEX_PROCESS, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getPayload().getBytes(StandardCharsets.UTF_8));} finally {// 确认已处理channel.basicAck(deliveryTag,false);}}}

通过批处理命令配置个数,动态生成对应个数消费者

package cn.iccboy.kitchen.mq;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;import java.util.List;@Slf4j
@Configuration
@Import(DynamicBuildMqReceiveBean.ImportConfig.class)
public class DynamicBuildMqReceiveBean {public static class ImportConfig implements ImportBeanDefinitionRegistrar, EnvironmentAware {private List<String> batPaths;@Overridepublic void setEnvironment(Environment environment) {try {batPaths = environment.getProperty("shell.paths", List.class);} catch (Exception ex) {log.error("参数绑定", ex);}}@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {int seq = 0;for (String batPath : batPaths) {seq++;// 注册beanRootBeanDefinition beanDefinition = new RootBeanDefinition();beanDefinition.setBeanClass(CmdMqReceive.class);MutablePropertyValues values = new MutablePropertyValues();values.addPropertyValue("batPath", batPath);values.addPropertyValue("seq", seq);beanDefinition.setPropertyValues(values);registry.registerBeanDefinition(CmdMqReceive.class.getName() + "#" + seq, beanDefinition);}}}
}

上面通过ImportBeanDefinitionRegistrar的方式 实现了动态bean的生成

通过maven的ant插件实现打包

在项目的 pom.xml文件中增加如下配置

	<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-antrun-plugin</artifactId><version>1.8</version><executions><execution><id>clean</id><phase>clean</phase><configuration><target><delete file="${basedir}/shell/CheckActuator.class"/></target></configuration><goals><goal>run</goal></goals></execution><execution><id>test-compile</id><phase>test-compile</phase><configuration><target><copy overwrite="true" file="${project.build.directory}/test-classes/CheckActuator.class"todir="${basedir}/shell" /></target></configuration><goals><goal>run</goal></goals></execution><execution><id>package</id><phase>package</phase><configuration><target><delete dir="${project.build.directory}/kitchen-mq-bin"/><mkdir dir="${project.build.directory}/kitchen-mq-bin"/><copy todir="${project.build.directory}/kitchen-mq-bin" overwrite="true"><fileset dir="${basedir}/shell" erroronmissingdir="false"><include name="*"/></fileset></copy><copy overwrite="true" file="${project.build.directory}/${project.name}-${project.version}.jar" todir="${project.build.directory}/kitchen-mq-bin" /><zip destfile="${basedir}/kitchen-mq-bin.zip"><fileset dir="${project.build.directory}/kitchen-mq-bin"><include name="*"/></fileset></zip></target></configuration><goals><goal>run</goal></goals></execution></executions></plugin></plugins></build>

项目结构如下图:
在这里插入图片描述
获取执行包

  1. 执行打包命令mvn clean package
  2. 上面命令执行完成后,在项目的跟目录会产生一个压缩包kitchen-mq-bin.zip,将压缩包直接拷贝到目标服务器,解压即可。
  3. 解压后,直接执行新增-健康检查定时任务.bat即可。2分钟后就会启动程序。

下图是执行命令后,多出的 zip文件包,以及包里面的文件
在这里插入图片描述在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/44056.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

缺少或找不到vcruntime140_1.dll的解决方法

某天&#xff0c;当我准备打开电脑上的一个应用程序时&#xff0c;突然收到一个错误提示&#xff0c;显示缺少了vcruntime140_1.dll文件。这个文件是一个重要的系统组件&#xff0c;它的丢失导致了我无法正常运行该应用程序。于是&#xff0c;我开始了一场寻找和修复旅程。然而…

这是我的纪念日

我的1460天 写这篇文章&#xff0c;是为了纪念自己这一千多个日日夜夜&#xff0c;我的热爱总算有了回报。 每次看到有小伙伴点赞&#xff0c;评论的时候&#xff0c;我都很开心&#xff0c;我知道自己的选择是正确的&#xff0c;我喜欢分享自己的所见所学&#xff0c;我也很…

python3.7 安装pywin32报错,完美解决方法

本机环境 python&#xff1a;3.7 遇到2种报错 第一种 ImportError: DLL load failed: The specified module could not be found.第二种&#xff1a; import win32gui ModuleNotFoundError: No module named ‘win32gui‘解决方法 我安装pywin32时候&#xff0c;是直接pi…

stm32红绿灯源代码示例(附带Proteus电路图)

本代码不能直接用于红路灯&#xff0c;只是提供一个思路 #include "main.h" #include "gpio.h" void SystemClock_Config(void); void MX_GPIO_Init(void) {GPIO_InitTypeDef GPIO_InitStruct {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOB_CLK_ENAB…

Jenkins-CICD-python/Java包升级与回退

Jenkins- CICD流水线 python/Java代码升级与回退 1、执行思路 1.1、代码升级 jenkins上点击 upgrade和 代码版本号 --${tag} jenkins 推送 代码 和 执行脚本 到目标服务器/opt目录下 执行命令 sh run.sh 代码名称 版本号 upgrade 版本号 来自jenkins的 构建参数中的 标签…

Gitlab-第四天-CD到k8s集群的坑

一、.gitlab-ci.yml #CD到k8s集群的 stages: - deploy-test build-image-deploy-test: stage: deploy-test image: bitnami/kubectl:latest # 使用一个包含 kubectl 工具的镜像 tags: - k8s script: - ls -al - kubectl apply -f deployment.yaml # 根据实际情况替换…

LlamaGPT -基于Llama 2的自托管类chatgpt聊天机器人

LlamaGPT一个自托管、离线、类似 ChatGPT 的聊天机器人&#xff0c;由 Llama 2 提供支持。100% 私密&#xff0c;不会有任何数据离开你的设备。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 1、如何安装LlamaGPT LlamaGPT可以安装在任何x86或arm64系统上。 首先确保…

PHP8的字符串操作3-PHP8知识详解

今天继续分享字符串的操作&#xff0c;前面说到了字符串的去除空格和特殊字符&#xff0c;获取字符串的长度&#xff0c;截取字符串、检索字符串。 今天继续分享字符串的其他操作。如&#xff1a;替换字符串、分割和合成字符串。 5、替换字符串 替换字符串就是对指定字符串中…

vue浏览器插件安装-各种问题

方法1&#xff1a;vue.js devtolls插件下载 https://blog.csdn.net/qq_55640378/article/details/131553642 下载地址&#xff1a; Tags vuejs/devtools GitHub npm install 或是 cnpm install 遇到的报错 设置淘宝镜像源&#xff08;推荐使用nrm&#xff0c;这一步是为…

使用IText导出复杂pdf

1、问题描述 需要将发票导出成pdf&#xff0c;要求每页都必须包含发票信息和表头行。 2、解决方法 使用IText工具实现PDF导出 IText8文档&#xff1a;Examples (itextpdf.com) 3、我的代码 引入Itext依赖&#xff0c;我这里用的是8.0.1版本 <dependency><groupId>…

uniapp 上传比较大的视频文件就超时

uni.uploadFile&#xff0c;上传超过10兆左右的文件就报错err&#xff1a;uploadFile:fail timeout&#xff0c;超时 解决&#xff1a; 在manifest.json文件中做超时配置 uni.uploadFile({url: this.action,method: "POST",header: {Authorization: uni.getStorage…

Python编程——列表解析与常用操作

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 本文专栏&#xff1a;Python专栏 专栏介绍&#xff1a;本专栏为免费专栏&#xff0c;并且会持续更新python基础知识&#xff0c;欢迎各位订阅关注。 目录 一、列表是什么&#xff1f; 二、列表的特点 1、元素…

pyltp 0.2.1安装

1. LTP及pyltp pyltp是 LTP的 Python封装&#xff0c;它里面提供了包括分词&#xff0c;词性标注&#xff0c;命名实体识别&#xff0c;句法分析等等能力。 比较坑的是我们可能无法直接通过pip install pyltp0.2.1方式来安装&#xff0c;所以本文就简单记录下如何通过源码安装…

商城-学习整理-高级-性能压测缓存问题(十一)

目录 一、基本介绍1、性能指标2、JMeter1、JMeter 安装2、JMeter 压测示例1、添加线程组2、添加 HTTP 请求3、添加监听器4、启动压测&查看分析结果 3、JMeter Address Already in use 错误解决 二、性能监控1、jvm 内存模型2、堆3、jconsole 与 jvisualvm1、jvisualvm 能干…

音视频 FFmpeg音视频处理流程

ffmpeg -i test_1920x1080.mp4 -acodec copy -vcodec libx264 -s 1280x720 test_1280x720.flv推荐一个零声学院项目课&#xff0c;个人觉得老师讲得不错&#xff0c;分享给大家&#xff1a; 零声白金学习卡&#xff08;含基础架构/高性能存储/golang云原生/音视频/Linux内核&am…

【计算机视觉】相机基本知识(还在更新)

1.面阵工业相机与线阵工业相机 1.1 基本概念区别 面阵相机则主要采用的连续的、面状扫描光线来实现产品的检测&#xff1b; 线阵相机即利用单束扫描光来进行物体扫描的工作的。 1.2 优缺点 &#xff08;1&#xff09;面阵CCD工业相机&#xff1a; 优点&#xff1a;应用面…

python使用dir()函数获取对象中可用的属性和方法(看不到python源码又想知道怎么调用,DLL调用分析,SDK二次开发技巧)

有时候调用一些SDK&#xff0c;但是人家又是封装成dll文件形式调用的&#xff0c;这时没法看源码&#xff0c;也不想看其对应的开发文档&#xff08;尤其有些开发文档写得还很难懂&#xff0c;或者你从某个开源社区拿过来&#xff0c;就根本没找到开发文档&#xff09;&#xf…

学习笔记:Opencv实现图像特征提取算法SIFT

2023.8.19 为了在暑假内实现深度学习的进阶学习&#xff0c;特意学习一下传统算法&#xff0c;分享学习心得&#xff0c;记录学习日常 SIFT的百科&#xff1a; SIFT Scale Invariant Feature Transform, 尺度不变特征转换 全网最详细SIFT算法原理实现_ssift算法_Tc.小浩的博客…

2235.两整数相加:19种语言解法(力扣全解法)

【LetMeFly】2235.两整数相加&#xff1a;19种语言解法&#xff08;力扣全解法&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/add-two-integers/ 给你两个整数 num1 和 num2&#xff0c;返回这两个整数的和。 示例 1&#xff1a; 输入&#xff1a;num…

Seaborn数据可视化(一)

目录 1.seaborn简介 2.Seaborn绘图风格设置 21.参数说明&#xff1a; 2.2 示例&#xff1a; 1.seaborn简介 Seaborn是一个用于数据可视化的Python库&#xff0c;它是建立在Matplotlib之上的高级绘图库。Seaborn的目标是使绘图任务变得简单&#xff0c;同时产生美观且具有信…