项目场景:
部署环境:centos7系统、docker部署
描述:公司产品后台服务版本升级之更新功能开发:资源打包后上传到云服务上,项目定时下载云服务器上最新版本对应的资源包解压至本地服务器,然后客户登录web点击更新按钮,进行服务的更新,更新功能对应的操作有:
①当前版本和最新版本标识调整;
②处理跨版本更新中间的增量sql和脚本的执行;
③重定向jar包自启动处理(保证当前项目运行的是最新版本);
④项目启动初始化时检测最新版本并启动最新的jar包;
开发过程中的想法和挫折:更新后重定向jar包自启动的操作本来想通过修改操作系统的自启动文件(/etc/rc.d/rc.local)来实现,但是发现使用java修改该文件后再重启容器,容器启动不了了,如果是手动修改就可以,这个问题弄了一天无果后,再加上leader催任务催的紧,换个思路,首先目的是为了本地服务器断电,保证项目启动的是最新的版本。LZ想到可以项目初始化的时候就去检测最新版本然后通过脚本停止old版本的jar包,最后启动最新的jar包(以上说的第四步)
话不多说,上代码:
Service层:
更新功能如下:
/**
* @Author: yz
* @Date 14:40 2023/1/6
* @Param []
* @return 版本更新功能
*/
@Override
public ResponseResult updateLatestVersion() {
log.info("开始获取最新的版本信息");
VersionProjectVO vpInfo = versionMapper.getLatestProjectVersion();
if (vpInfo == null) {
throw new BusinessException(CommonExEnum.VERSION_PROJECT_ERR);
}
log.info("最新版本为:"+vpInfo.getCurrentEdition());
if (vpInfo.getCurrentEdition()==1) {
log.info("当前已是最新版本,无需更新。");
throw new BusinessException("当前已是最新版本,无需更新。");
}
//不是最新版本,需要更新
VersionProject cpVersion = versionMapper.getCurrentProjectDetailVersion();
if (Objects.isNull(cpVersion)) {
log.info("当前版本信息不存在");
throw new BusinessException("当前版本信息不存在");
}else{
log.info("开始更新版本");
//更新jar
if (!cpVersion.getJar().equals(vpInfo.getJar())) {
//修改st_version_jar表的execute字段前版本改为0,新版本改为1、st_version_project表的current_edition字段前版本改为0,新版本改为1
/** 业务逻辑省略 **/
//如果是跨版本,找到中间的所有版本
List<VersionSqlShellExec> middleVersion = versionMapper.getMiddleVersion(cpVersion.getOrder(),vpInfo.getOrder());
try {
String mysqlPwd = Constants.MYSQL_PWD;
String sqlShellUrl = Constants.SQL_SHELL_URL;
//执行脚本和sql
log.info("开始执行脚本和sql,中间版本数量为:"+middleVersion.size()+"个!");
for (int i = 0; i < middleVersion.size(); i++) {
String newJarVersion = middleVersion.get(i).getJar();
//String newJarOrder = middleVersion.get(i).getOrder();
String sqlCommond = "sh /root/runSql.sh "+mysqlPwd+" "+newJarVersion+"";
String shellCommond = "sh "+sqlShellUrl+"shell/"+newJarVersion+".sh";
//执行sql文件
log.info("sql脚本命令:"+sqlCommond);
log.info("shell脚本命令:"+shellCommond);
Process process = Runtime.getRuntime().exec(sqlCommond);
//写出脚本执行中的过程信息
String rightLine="";
StringBuilder result=new StringBuilder();
String errorLine="";
BufferedReader infoInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader errorInput = new BufferedReader(new InputStreamReader(process.getErrorStream()));
log.debug("*****sql文件正确信息开始输出*****");
while ((rightLine = infoInput.readLine()) != null) {
result.append(rightLine);
log.info(rightLine);
}
log.debug("*****sql文件正确信息输出结束*****");
log.debug("*****sql文件错误信息开始输出*****");
while ((errorLine = errorInput.readLine()) != null) {
log.info(errorLine);
}
log.info("*****sql文件错误输出结束*****");
infoInput.close();
errorInput.close();
//阻塞执行线程直至脚本执行完成后返回
process.waitFor();
//Runtime.getRuntime().exec(sqlCommond2);
//执行脚本文件
Process exec = Runtime.getRuntime().exec(shellCommond);
String rightLine1="";
StringBuilder result1=new StringBuilder();
String errorLine1="";
BufferedReader infoInput1 = new BufferedReader(new InputStreamReader(exec.getInputStream()));
BufferedReader errorInput1 = new BufferedReader(new InputStreamReader(exec.getErrorStream()));
log.debug("*****shell脚本正确信息开始输出*****");
while ((rightLine1 = infoInput1.readLine()) != null) {
result1.append(rightLine1);
log.info(rightLine1);
}
log.debug("*****shell脚本正确信息输出结束*****");
log.debug("*****shell脚本错误信息开始输出*****");
while ((errorLine1 = errorInput1.readLine()) != null) {
log.info(errorLine1);
}
log.info("*****shell脚本错误输出结束*****");
infoInput1.close();
errorInput1.close();
//阻塞执行线程直至脚本执行完成后返回
exec.waitFor();
}
log.info("脚本和sql执行完成");
//获取jar版本对应的路径和截取到jar名
String jar = vpInfo.getJar();
String jarPath = versionMapper.getJarPath(jar);
if(StringUtils.isEmpty(jarPath)){
throw new BusinessException(CommonExEnum.NEW_VERSION_JAR_DELETE);
}
int i = jarPath.lastIndexOf("/");
String name = jarPath.substring(i + 1);
//jar包替换:备份自启动文件、尝试修改自启动文件重启容器
//复制shell文件
log.info("复制备份自启动文件...");
Runtime.getRuntime().exec("touch /etc/rc.d/rc.local.bak");
Runtime.getRuntime().exec("chmod 777 /etc/rc.d/rc.local.bak");
Runtime.getRuntime().exec("cp /etc/rc.d/rc.local/etc/rc.d/rc.local.bak");
String str = "ic_jar_path=\""+Constants.VERSION_JAR_PATH+name+"\"";
BufferedReader reader = new BufferedReader(new FileReader(new File("/etc/rc.d/rc.local.bak")));
//创建/etc/rc.d/rc.local文件和授权docker
Runtime.getRuntime().exec("touch /etc/rc.d/rc.local.test");
Runtime.getRuntime().exec("chmod 777 /etc/rc.d/rc.local.test");
BufferedWriter in = new BufferedWriter(new FileWriter(new File("/etc/rc.d/rc.local.test")));
String line=null;
//修改jar启动命令指向
while ((line=reader.readLine())!=null) {
//进入文件写入操作
log.info("进入文件写入操作...");
log.info("读取文件当前行数据:"+line);
if(line.startsWith("ic_jar_path=")){
log.info("第一行数据写入成功");
in.write(str+"\r\n");
}else{
in.write(line+"\r\n");
}
}
//关闭流,不可以少,否则数据在缓存,没有实际写入
reader.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
log.info("jar包指向替换完成");
log.info("版本信息已经更新完成,可以重启项目了!");
}
}
return ResponseResult.success("项目已更新到版本V"+vpInfo.getEdition()+",请重新启动项目");
}
项目新版本检测和重启功能如下:
@Override
public ResponseResult restartServer() {
//判断当前系统环境是否为docker
String osName = System.getProperty("os.name");
if (!osName.startsWith("Windows")) {
log.info("开始获取最新的版本信息");
VersionProjectVO vpInfo = versionMapper.getLatestProjectVersion();
if (vpInfo == null) {
throw new BusinessException(CommonExEnum.VERSION_PROJECT_ERR);
}
//判断当前版本是否为最新版本
boolean nowIsLast = versionMapper.getNowIsLast(vpInfo.getId());
if (nowIsLast) {
log.info("最新版本为:"+vpInfo.getCurrentEdition());
//获取jar版本对应的路径和截取到jar名
String jar = vpInfo.getJar();
String jarPath = versionMapper.getJarPath(jar);
if(StringUtils.isEmpty(jarPath)){
throw new BusinessException(CommonExEnum.NEW_VERSION_JAR_DELETE);
}
int i = jarPath.lastIndexOf("/");
String name = jarPath.substring(i + 1);
String commond = "sh /data/stop_ic.sh";
String commond1 = "nohup java -jar "+Constants.VERSION_JAR_PATH+name+" --spring.profiles.active=pro >"+Constants.VERSION_JAR_PATH+"nohup.out 2>&1&";
//判断新版本的jar是否存在
File file = new File(Constants.VERSION_JAR_PATH + name);
if (file.exists()) {
try {
//判断是否运行的是新版本的jar,是的话不需要继续启动从而造成无限循环了
String countCommond = "ps -ef|grep -c " + Constants.VERSION_JAR_PATH + name;
String[] command = { "/bin/sh", "-c", countCommond};
Process exec = Runtime.getRuntime().exec(command);
log.info(countCommond);
//写出脚本执行中的过程信息
String rightLine="";
String errorLine="";
BufferedReader infoInput = new BufferedReader(new InputStreamReader(exec.getInputStream()));
BufferedReader errorInput = new BufferedReader(new InputStreamReader(exec.getErrorStream()));
log.debug("*****sql文件正确信息开始输出*****");
while ((rightLine = infoInput.readLine()) != null) {
log.info("jar包运行行数:"+rightLine);
break;
}
log.debug("*****sql文件正确信息输出结束*****");
log.debug("*****sql文件错误信息开始输出*****");
while ((errorLine = errorInput.readLine()) != null) {
log.info(errorLine);
}
log.info("*****sql文件错误输出结束*****");
if(StringUtils.isNotEmpty(rightLine)&&Integer.parseInt(rightLine)<3){
//log.info("开始重启IC容器,执行命令:"+commond);
log.info("停止IC项目,执行命令:"+commond);
log.info("开始重启项目,执行命令:"+commond1);
Runtime.getRuntime().exec(commond);
Runtime.getRuntime().exec(commond1);
//processShell(commond);
log.info("重启IC容器成功");
}else{
log.info("当前版本jar包运行的是最新版本的jar,无需重启");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}else{
log.info("当前版本jar包已删除或不存在");
}
}else{
log.info("当前版本不是最新版本,请先更新");
}
}
return ResponseResult.success(1);
}
项目初始化添加检测如下:
@PostConstruct // 构造函数之后执行
public void init(){
log.info("检测最新版本,重新启动新版本");
restartServer();
}
结果:
总结:
虽然实现了功能,但是方案:
更新后重定向jar包自启动的操作本来想通过修改操作系统的自启动文件(/etc/rc.d/rc.local)来实现(容器重启命令通过ssh连接宿主机:sshpass -p 'yourPwd' ssh -o StrictHostKeyChecking=no -p 22 root@yourIP docker restart containerName),容器启动不了的情况仍未解决,有同样困惑的伙伴解决后欢迎交流告知
结束语:
每天做好每天该做的事情,用做而不是用想,做该做的事情,并且接受它的事与愿违,不确定的事情越来越多,但是最后还得要选择接受,也许人生唯一确定的就是不确定的人生,人生唯一恐惧的就是恐惧本身。