首先是效果图
当微服务很多时,部署和管理都是一件很麻烦的事情。会打开多个控制台,实施人员水平也是参差不齐,部署的时候,容易出错导致某个服务没有成功启动,排查困难。
使用这个窗体程序,可以简化发布。只需要把所有需要部署的微服务放在一个指定的文件夹下,就可以扫描到。
设置可以配置端口和启动顺序,同一优先级的同时启动,优先级高的先启动,高优先级的启动完了,才能启动低一级的服务。这里配置的端口是用于监听服务是否正常启动的(不能影响服务的启动端口)。
左边列表显示的服务默认是黑色,启动成功后是蓝色。启动异常是红色。
因为使用端口监听判断服务是否正常启动,所以如果有端口冲突,那么启动过程中,该服务可能是蓝色的名字,但是启动完后,如果端口冲突,启动会失败,那么颜色还是会恢复成黑色,所以最终是蓝色的一定是成功启动的。
右边的消息显示的是一些系统提示。
工具栏的内存显示,每秒刷新一次。
这个程序的一个难点是多线程多进程之间的交互,启动服务的方式多样,造成一些复杂性。
另一个难点是自定义一个控制台,把原有的控制台消息显示到自定义的地方(这个是属于难者不会,会者不难)。
关于重定向控制台的消息到自定义的文本域:
首先是创建一个进程,获取它的inputstream,然后把流读取到自定义的文本域中,这里需要创建一个线程处理,不然主进程会阻塞。
Process process= getAndCreateProcessIfNull();
started=true;
status=ProcessStatus.RUNNABLE;
InputStream iStream=process.getInputStream();
ServiceStartSupervisor.register(this);
new Thread(()->{
BufferedReader br = new BufferedReader(new InputStreamReader(iStream));
String c =null;
try{
while(started && (c = br.readLine()) != null){
getjTextArea().append(c+"\r\n");
if (getjTextArea().getLineCount()>100){
int start=getjTextArea().getLineStartOffset(0);
int end=getjTextArea().getLineEndOffset(getjTextArea().getLineCount()-70);
getjTextArea().replaceRange("",start,end);
}
getjTextArea().setCaretPosition(getjTextArea().getLineEndOffset(getjTextArea().getLineCount()-1));
}
}catch(Exception e){
this.close();
throw new RuntimeException(e);
} finally {
if (BatServiceProcessDefinition.this.status==ProcessStatus.RUNNING) {
BatServiceProcessDefinition.this.status=ProcessStatus.RUNNABLE;
}else {
if (started)
BatServiceProcessDefinition.this.status=ProcessStatus.DEFATED;
if (getCountDownLatch()!=null) {
getCountDownLatch().countDown();
}
}
ServiceStartSupervisor.unregister(this);
setCountDownLatch(null);
System.out.println("end");
}
}).start();
监听服务启动情况(在没有端口冲突的情况下有效):
先启动一个控制台进程,获取进程的outputstream,然后定时扫描,当获得返回消息时,说明端口已经监听了,服务正常启动。
端口有冲突的情况,需要使用其它方式额外校验。
一种简单的处理方式是启动前就扫描需要的所有端口,如果有冲突,直接就提示先解决冲突在启动服务。
executorService.execute(()->{
Process process=null;
try {
process=Runtime.getRuntime().exec("cmd");
map.put(service.getPort(), process);
OutputStream os= process.getOutputStream();
InputStream is=process.getInputStream();
final AtomicBoolean continuteSearch=new AtomicBoolean(true);
executorService.execute(()->{
byte[] bs=("netstat -ano|findstr "+service.getPort()+" \r\n").getBytes();
while (continuteSearch.get()) {
try {
os.write(bs);
os.flush();
Thread.sleep(2000);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
BufferedReader br = new BufferedReader(new InputStreamReader(is,Charset.forName("GBK")));
String c;
while ((c=br.readLine())!=null) {
if (c.contains("TCP")) {
continuteSearch.set(false);
//启动成功
service.setStatus(ProcessStatus.RUNNING);
mainWindow.changeListColor();
if (service.getCountDownLatch()!=null) {
service.getCountDownLatch().countDown();
}
break;
}
}
map.remove(service.getPort());
} catch (IOException e) {
e.printStackTrace();
}finally {
if (process!=null && process.isAlive()) {
process.destroy();
}
}
});
使用ServiceProcessDefinition这个类来描述一个服务进程以及相关的信息(启动命令,服务名,端口,级别)。
详细源码后续会放在git上。
使用jsmooth打包成exe后,点击即可启动。