spring boot/cloud 多服务部署单机启动顺序有依赖的解决办法
来源:互联网 发布:中级软件设计师 编辑:程序博客网 时间:2024/06/05 07:26
spring boot/cloud 多服务部署单机启动顺序有依赖的解决办法
spring cloud 做多服务是很方便的,但为了方便伸缩和计算资源的限制,我们需要在一台主机上部署多个业务实例,也需要这些业务实例开机自启动,我们知道,spring cloud服务或者根据业务需要,各业务服务启动顺序是有依赖关系的。那么我们如何得知一个被依赖的服务已经启动成功了呢,我们就需要代码的简单注入和配合脚本(如shell)来进行。
我们知道一般linxu的标准服务会放在/etc/init.d目录下,然后chkconfig add xxxService 来完成开机自启动,linxu操作系统能保证按照顺序启动,但应用服务启动成功后是否真正准备好服务了,得由应用决定了,一般服务启动后会生成/var/run/xxxx.pid文件(这是linux应用启动成功外服务的一般做法),脚本判断前个服务的pid文件生成后再启动后续依赖的服务。那么java业务服务业务也可以采用这种类似的方式。特别是以spring boot 的java服务,外部脚本通过类似于
base_dir=$(dirname $0)tmprun=$base_dir/../tmprm -rf $tmprunmkdir -p $tmprunchmod 777 $tmprunexport LOGS_DIR="$base_dir/../logs"nohup java -Djava.io.tmpdir=$tmprun -jar ${base_dir}/../libs/xxxx.jar \ --config.profile=production \ --spring.profiles.active=production \ --spring.config.location=file:${base_dir}/../config/ \ --logging.config=${base_dir}/../config/logback.xml \ >/dev/null 2>&1 &方式来启动,由于脚本是推到后台运行,如何判断服务的所有bean已经准备好了呢?有同仁可能会想到
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> </configuration></plugin>通过maven生成可执行jar包,这样的jar包是linux操作系统服务命令直接在操作系统上运行,且在运行后会自动生成 pid文件,好像很方便的生成pid文件了,但它生成的pid文件的时机在jvm虚拟机启动响应业务服务开始就产生了,可能spring 还在初始化业务bean,造成另外服务启动访问依赖服务接口报错。所以这种生成pid文件的方式pass掉了,因此要自己添添加少量代码。
首先我们得知道什么时候大多数启动解决初始化的bean已经初始化完成,这就要监听org.springframework.context.ApplicationListener的ApplicationReadyEvent事件来社工弄成pid文件,不废话直接贴代码
package com.pekall.ass.common;import lombok.extern.log4j.Log4j;import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;import org.springframework.boot.context.event.ApplicationPreparedEvent;import org.springframework.boot.context.event.ApplicationReadyEvent;import org.springframework.context.ApplicationEvent;import org.springframework.context.ApplicationListener;import org.springframework.context.event.ContextClosedEvent;import org.springframework.context.event.ContextRefreshedEvent;import org.springframework.context.event.ContextStartedEvent;import org.springframework.context.event.ContextStoppedEvent;import java.io.File;import java.io.IOException;import java.lang.management.ManagementFactory;import java.net.URL;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;/** * Created by maxl on 17-9-13. */@Log4jpublic class ApplicationEventListener implements ApplicationListener { private String appName; private String mainPath = ""; public String getMainPath() { return mainPath;} public ApplicationEventListener(Class mainClass,String appName) { this.appName = appName; String url = mainClass.getProtectionDomain().getCodeSource().getLocation().toString(); Path dir = Paths.get(url); Path parentDir = dir.getParent(); if(!parentDir.endsWith("target")) { //测试,生产运行环境 //url jar:file:/apps/pekall/ass/service/depmon/libs/depmon.jar!/BOOT-INF/classes!/ mainPath = parentDir.getParent().getParent().getParent().toString(); //jar包中的路径字符串包含jar:file:前缀的9个字符,所以要去掉 mainPath = mainPath.substring(9); } else { //本地调试开发环境 //url file:/home/maxl/pekall_work/mdm_ass/server/eureka/target/classes/ mainPath = parentDir.getParent().toString(); } log.info("class路径:"+url); log.info("mainPath:"+mainPath); } @Override public void onApplicationEvent(ApplicationEvent event) { // 在这里可以监听到Spring Boot的生命周期 if (event instanceof ApplicationEnvironmentPreparedEvent) { log.info("初始化环境变量完成"); } else if (event instanceof ApplicationPreparedEvent) { log.info("初始化完成"); } else if (event instanceof ContextRefreshedEvent) { log.info("应用刷新完成"); } else if (event instanceof ApplicationReadyEvent) { //应用名从构造参数传递,不自动获取,因为spring.application.name可能与最终打包的appName.jar包名不一样 //ConfigurableApplicationContext configContext = ((ApplicationReadyEvent) event).getApplicationContext(); //ConfigurableEnvironment env =configContext.getEnvironment(); //String appName = env.getProperty("spring.application.name"); String pidFile = getPidFileFullPath(); String pid = CreatePidFile(pidFile); if(!pidFile.equals("")) { log.info("应用启动完成,写进程id文件:"+pidFile+" 进程id:"+pid); } else { log.info("应用启动完成,写进程id文件失败"); } } else if (event instanceof ContextStartedEvent) { log.info("应用启动完成,需要在代码动态添加监听器才可捕获"); } else if (event instanceof ContextStoppedEvent) { log.info("应用停止完成"); } else if (event instanceof ContextClosedEvent) { //删除进程ID文件,由于是异步动作,外部脚本不好估算时间, // 加上kafka-client开了另外的线程,不好估计时间,所以不删除pid文件 //deletePidFile(); //单独的关闭应用端口去掉 log.info("应用关闭完成"); } } private String getPidFileFullPath() {// URL url = this.getClass().getResource("/application.properties");// Path dir = Paths.get(url.getPath());// Path parentDir = dir.getParent().getParent();//// if(!parentDir.endsWith("target")) { //测试,生产运行环境// //url:/file:/apps/pekall/ass/service/eureka/libs/eureka.jar!/BOOT-INF/classes!/application.properties// mainPath = parentDir.getParent().getParent().getParent().toString();// //jar包中的路径字符串包含file:前缀的5个字符,所以要去掉// mainPath = mainPath.substring(5);// }// else { //本地调试开发环境// //url:file:/home/maxl/pekall_work/mdm_ass/server/eureka/target/classes/application.properties// mainPath = parentDir.getParent().toString();// } return mainPath+File.separator+ appName+".pid"; } private String CreatePidFile(String pidFileFullPath) { // get name representing the running Java virtual machine. //25107@hostname String name = ManagementFactory.getRuntimeMXBean().getName(); // get pid String pid = name.split("@")[0]; Path path = Paths.get(pidFileFullPath); try { //Files.createDirectories(path.getParent()); Files.write(path, pid.getBytes()); return pid; } catch(Exception e) { log.info(e.toString()); return ""; } } private void deletePidFile() { String pidFile = getPidFileFullPath(); Path filePath = Paths.get(pidFile); try { Files.deleteIfExists(filePath); } catch(IOException e) { log.info(e.toString()); } } public boolean isRunning() { String pidFile = getPidFileFullPath(); Path filePath = Paths.get(pidFile); return Files.exists(filePath); }}
main入口函数进行集成
/** * eureka server * @author 52395090@qq.com * http://git.oschina.net/zhou666/spring-cloud-7simple */package com.pekall.ass.eureka;import com.pekall.ass.common.ApplicationEventListener;import lombok.extern.log4j.Log4j;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;import org.springframework.boot.SpringApplication;@Log4j@SpringBootApplication@EnableEurekaServer//@EnableAutoConfiguration(// exclude={DataSourceAutoConfiguration.class,// HibernateJpaAutoConfiguration.class,// SpringBootWebSecurityConfiguration.class})public class EurekaServer { public static void main(String[] args) { ApplicationEventListener listener = new ApplicationEventListener(EurekaServer.class,"eureka"); if(listener.isRunning()) { log.info("eureka 进程文件已经存在,终止运行!!!"); return; } SpringApplication springApplication =new SpringApplication(EurekaServer.class); springApplication.addListeners(listener); springApplication.run(args); //SpringApplication.run(EurekaServer.class, args); }}
然后启动过程中循环定时判断pid文件是否生成用脚本来启动下一个服务
开机自动启动sampleService的内容基本内容:
#!/bin/bash### BEGIN INIT INFO# Provides: sampleService# Required-Start: $remote_fs $syslog $network# Required-Stop: $remote_fs $syslog $network# Default-Start: 2 3 4 5# Default-Stop: 0 1 6# Short-Description: sampleService# Description: sampleService# chkconfig: 2345 99 01### END INIT INFOexport JAVA_HOME=/usr/java/jdkexport LANG=zh_CN.UTF-8export TZ='Asia/Shanghai'source /etc/profileexport ASS_HOME=/apps/pekall/ass/service#每个服务启动最多等待1200秒产生pid文件wait_time_out=1200eureka_app_name=eurekaeureka_home=$ASS_HOME/$eureka_app_nameeureka_pid_file=$eureka_home/${eureka_app_name}.pideureka_java_ops="-Xms256M -Xmx256M"config_app_name=configconfig_home=$ASS_HOME/$config_app_nameconfig_pid_file=$config_home/${config_app_name}.pidconfig_java_ops="-Xms256M -Xmx256M"# ANSI ColorsechoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }echoRed2() { echo -ne $'\e[0;31m'"$1"$'\e[0m'; }echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }echoGreen2() { echo -ne $'\e[0;32m'"$1"$'\e[0m'; }echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }echoYellow2() { echo -ne $'\e[0;33m'"$1"$'\e[0m'; }is_running() { ps -p "$1" &> /dev/null}# $1:pid dile $2:tiemout time $3:app nameawait_file() { end=$(date +%s) let "end+=$2" while [[ ! -s "$1" ]] do now=$(date +%s) remain=`expr $end - $now` echoYellow2 "Starting [$3] ... overtime countdown $remain second \r" if [[ $remain -le 0 ]]; then break fi sleep 1 done echo ""}start() { do_start_wap $eureka_home $eureka_app_name $eureka_pid_file $wait_time_out "$eureka_java_ops" do_start_wap $config_home $config_app_name $config_pid_file $wait_time_out "$config_java_ops" do_start_wap $base_home $base_app_name $base_pid_file $wait_time_out "$base_java_ops" do_start_wap $dev_home $dev_app_name $dev_pid_file $wait_time_out "$dev_java_ops" do_start_wap $appmanage_home $appmanage_app_name $appmanage_pid_file $wait_time_out "$appmanage_java_ops" do_start_wap $appmarket_home $appmarket_app_name $appmarket_pid_file $wait_time_out "$appmarket_java_ops" do_start_wap $devres_home $devres_app_name $devres_pid_file $wait_time_out "$devres_java_ops" do_start_wap $depmon_home $depmon_app_name $depmon_pid_file $wait_time_out "$depmon_java_ops"}stop() { do_stop_wap $depmon_home $depmon_app_name $depmon_pid_file do_stop_wap $devres_home $devres_app_name $devres_pid_file do_stop_wap $appmarket_home $appmarket_app_name $appmarket_pid_file do_stop_wap $appmanage_home $appmanage_app_name $appmanage_pid_file do_stop_wap $dev_home $dev_app_name $dev_pid_file do_stop_wap $base_home $base_app_name $base_pid_file do_stop_wap $config_home $config_app_name $config_pid_file do_stop_wap $eureka_home $eureka_app_name $eureka_pid_file}status() { do_status_wap $eureka_home $eureka_app_name $eureka_pid_file do_status_wap $config_home $config_app_name $config_pid_file do_status_wap $base_home $base_app_name $base_pid_file do_status_wap $dev_home $dev_app_name $dev_pid_file do_status_wap $appmanage_home $appmanage_app_name $appmanage_pid_file do_status_wap $appmarket_home $appmarket_app_name $appmarket_pid_file do_status_wap $devres__home $devres__app_name $devres__pid_file do_status_wap $depmon_home $depmon_app_name $depmon_pid_file}# $1:app_home $2:app_name $3:pid_filedo_status_wap() { if [[ -d "$1" ]]; then if [[ -f "$3" ]]; then pid=$(cat "$3") is_running "$pid" if [ X"$?" == X"0" ] ; then echoGreen "Already running [$2]"; else echoYellow "Already stop [$2]" fi else echoYellow "Already stop [$2]" fi fi}# $1:app_home $2:app_name $3:pid_filedo_stop_wap() { if [[ -d "$1" ]]; then if [[ -f "$3" ]]; then pid=$(cat "$3") is_running "$pid" if [ X"$?" == X"0" ] ; then do_stop $3 $2 else echoYellow "Already stop [$2]" fi else echoYellow "Already stop [$2]" fi fi}do_stop() { echoYellow "Stopping [$2] ... " pid=$(cat "$1") kill "$pid" &> /dev/null || { echoRed ""; echoRed "Unable to kill [$2]"; return 1; } count=30 for i in $(seq 1 $count); do is_running "$pid" || { rm $1; echoGreen ""; echoGreen "Stoped [$2]"; return 0; } ([[ $i -eq 5 ]] || [[ $i -eq 10 ]] || [[ $i -eq 15 ]] || [[ $i -eq 20 ]] || [[ $i -eq 25 ]] || [[ $i -eq 30 ]]) && kill "$pid" &> /dev/null sleep 1 remain=`expr $count - $i` echoYellow2 "wait overtime $remain second... \r" done echoRed ""; "Unable to kill [$2]"; return 1;}restart() { stop start}# $1:app_home $2:app_name $3:pid_file $4:time_tout $5:JAVA_OPTIONSdo_start_wap() { if [[ -d "$1" ]]; then if [[ -f "$3" ]]; then pid=$(cat "$3") is_running "$pid" if [ X"$?" == X"0" ] ; then echoYellow "Already running [$2]" else do_start $2 $3 $4 "$5" fi else do_start $2 $3 $4 "$5" fi fi}do_start() { rm $2 &> /dev/null base_dir=$ASS_HOME/$1 tmp_run=$base_dir/tmp rm -rf $tmp_run mkdir -p $tmp_run chmod 777 $tmp_run log_var=$(echo $1 | tr '[a-z]' '[A-Z]')_LOGS_DIR="$base_dir/logs" export $log_var nohup java $4 -Djava.io.tmpdir=$tmp_run -jar ${base_dir}/libs/$1.jar \ --config.profile=production \ --spring.profiles.active=production \ --spring.config.location=file:${base_dir}/config/ \ --logging.config=${base_dir}/config/logback.xml \ \ >/dev/null 2>&1 & await_file "$2" $3 "$1" pid=$(cat "$2") [[ -z $pid ]] && { echoRed "Failed to start $1"; return 1; } echoGreen "Started [$1]"}# Call the appropriate functioncase "$1" in start) start "$@"; exit $?;; stop) stop "$@"; exit $?;; restart) restart "$@"; exit $?;; status) status "$@"; exit $?;; *) echo "Usage: $0 {start|stop|restart|status}"; exit 1;esacexit 0
操作系统中运行
cp ./etc/init.d/assService /etc/init.d/chmod 700 /etc/init.d/sampleServicechkconfig --add sampleService
总结:
多服务之间的顺序启动java服务由shell脚本来串联,脚本检测到依赖前一个服务产生pid文件之后在启动后一个服务,服务停止删除pid文件,当非正常关机,pid文件还在的时候,脚本用pid进程中的进程id查询系统中是否存在这样的进程,没有则删除之,启动应用服务,有则跳过提示服务已经启动,由此服务的顺序启动依赖问题解决了。
- spring boot/cloud 多服务部署单机启动顺序有依赖的解决办法
- spring boot 部署、启动
- Spring Cloud/Boot WebSocket 无法注入其他类的解决办法
- Spring Boot启动依赖分析
- mvn spring-boot:run 启动的应用有中文乱码的解决办法
- Springboot-启动 Spring Boot服务的方式
- window中设置服务启动依赖的先后顺序
- spring boot打包 部署 依赖 hc/info
- Spring boot热部署导致CacheManager重名的解决办法
- spring cloud + spring boot + ...分布式微服务云架构
- Spring cloud eureka+Client+Spring boot admin 服务注册监控
- spring boot / spring cloud
- spring boot cloud热部署插件简单配置
- spring boot正常启动之后无法访问报404的解决办法
- spring boot常用的依赖
- Spring Boot 部署与服务配置
- Spring Boot 部署与服务配置
- Spring Boot 部署与服务配置
- 完全理解Python关键字"with"与上下文管理器
- 爬虫入门系列(四):HTML文本解析库BeautifulSoup
- optparse参数输入
- Lambda函数与群众演员的共同之处是?
- 一步一步教你认识Python闭包
- spring boot/cloud 多服务部署单机启动顺序有依赖的解决办法
- 爬虫入门系列(五):正则表达式完全指南(上)
- 爬虫入门系列(六):正则表达式完全指南(下)
- 我看 PyCon 2017大会(内含视频合集下载)
- 用Google挖掘赚钱思路
- 如何快速入门Python
- Python装饰器为什么难理解?
- 为什么Python这么火
- log4j基础配置详解