各位坐稳扶好,我们要开车了。不过在开车之前,我们还是例行回顾一下上期分享的要点。

经过前两期的铺垫及烧脑的分享,我们大概对「如何实现 Java 应用进程的状态监控,如果被监控的进程 down 掉,是否有机制能启动起来?」问题本身有了一个新的认识,那这期我们不妨拿出攻城狮的绝招 Ctrl + C、Ctrl + V,从 Resin 源码中摘取一二,稍微简单实践一下。

按照图示,咱们先演示一下实践效果吧,首先找到并运行程序入口 MonitorApp,日志输出如下。

此时我们不妨在控制台输入 jps 命令,看一看效果。

18830 MonitorApp

18831 Resin

  

发现成功启动了 MonitorApp、Resin 两个进程,和 Resin 应用服务器是一模一样的,如果我们把进程号为 18831 的 kill 掉,会是什么效果?发现控制台日志输出又多了一些,貌似丫鬟 Resin 又被重新给启动了。

在控制台输入 jps 命令再确认一下是否真的变了。

18830 MonitorApp

18935 Resin

  

那我们到底该如何实现?那不妨照葫芦画瓢,模仿一下 Resin 的实现一下(这就是绝招:仿一仿)。

首先定义我们的监控应用入口 MonitorApp,很简单就是把创建子进程的任务给启动起来。

package com.caucho.server.resin;
public class MonitorApp {
public static void main(String[] args) {
new WatchdogChildTask().start();
}
}

接下来再编写 WatchdogChildTask 子进程任务的代码,大部分来源于 Resin 的源码,只是剔除了很多很多很多,简化了很多很多很多。仔细看发现也很简单,就有一个循环一直调用 WatchdogChildProcess 的 run 方法,目的也就是一直让丫鬟进程跑起来。

package com.caucho.server.resin;	

import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger; class WatchdogChildTask implements Runnable { private static final Logger log = Logger.getLogger(WatchdogChildTask.class.getName()); private WatchdogChildProcess _process; /**
* Starts management of the watchdog process
*/
public void start() {
//TODO 手动创建线程池会更好 【阿里开发规约】
Executors.newFixedThreadPool(1).execute(this);
} /**
* Main thread watching over the health of the Resin instances.
*/
public void run() {
try {
int i = 0;
long retry = Long.MAX_VALUE;
while (i++ < retry) {
WatchdogChildProcess process = new WatchdogChildProcess();
_process = process;
try {
log.log(Level.INFO, "我是大总管,准备让乳名为Resin的丫鬟跑起来");
_process.run();
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
} finally {
_process = null;
if (process != null) {
log.log(Level.INFO, "我是大总管,发现乳名为Resin的丫鬟出状况了,需要让她释放资源,重新跑起来");
process.kill();
}
}
}
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
} finally {
if (_process != null) {
_process.kill();
_process = null;
}
}
}
}

  

具体是怎么把丫鬟进程跑起来的,这个事情专门交给 WatchdogChildProcess 去做了,先启动了一个 socket 通讯端口;然后采用 ProcessBuilder 启动 Resin 进程;然后等待丫鬟进程建立 socket 连接通讯。大部分也是来源于 Resin 的源码,只不过做了大量删减。另外重点提一嘴:拿下去只需修改 com.caucho.server.resin.Resin 为你要监控应用的主函数即可。

package com.caucho.server.resin;	

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger; class WatchdogChildProcess { private static final Logger log = Logger.getLogger(WatchdogChildProcess.class.getName()); private Socket _childSocket;
private OutputStream _stdOs;
private int _status = -1;
private AtomicReference<Process> _processRef = new AtomicReference<Process>(); public void run() {
ServerSocket ss = null;
Socket s = null;
try {
ss = new ServerSocket(0, 5, InetAddress.getByName("127.0.0.1"));
int port = ss.getLocalPort();
log.log(Level.INFO, "我是大总管,我启动一个端口为{0}的socket,让丫鬟们实时与我通讯",port); Process process = createProcess(port);
if (process != null) {
_processRef.compareAndSet(null, process); InputStream stdIs = process.getInputStream();
_stdOs = process.getOutputStream(); //TODO 不要显式创建线程,请使用线程池【阿里开发规约】
new Thread(new WatchdogProcessLogThread(stdIs)).start(); s = connectToChild(ss);
_status = process.waitFor();
logStatus(_status);
}
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
try {
Thread.sleep(5000);
} catch (Exception e1) {
}
} catch (Throwable e) {
log.log(Level.WARNING, e.toString(), e);
} finally {
if (ss != null) {
try {
ss.close();
} catch (Throwable e) {
}
}
try {
if (s != null) {
s.close();
}
} catch (Throwable e) {
log.log(Level.FINER, e.toString(), e);
}
kill();
synchronized (this) {
notifyAll();
}
}
} private void logStatus(int status) {
String code = " (exit code=" + status + ")";
log.warning("大总管突然发现丫鬟进程罢工了!!");
} void kill() {
Process process = _processRef.getAndSet(null);
if (process != null) {
try {
process.destroy();
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
}
} OutputStream stdOs = _stdOs;
_stdOs = null;
if (stdOs != null) {
try {
stdOs.close();
} catch (Throwable e) {
log.log(Level.FINE, e.toString(), e);
}
} Socket childSocket = _childSocket;
_childSocket = null; if (childSocket != null) {
try {
childSocket.close();
} catch (Throwable e) {
log.log(Level.FINE, e.toString(), e);
}
} if (process != null) {
try {
process.waitFor();
} catch (Exception e) {
log.log(Level.INFO, e.toString(), e);
}
}
} /**
* Waits for a socket connection from the child, returning the socket
*
* @param ss TCP ServerSocket from the watchdog for the child to connect to
*/
private Socket connectToChild(ServerSocket ss)
throws IOException {
Socket s = null;
try {
ss.setSoTimeout(60000);
for (int i = 0; i < 120 && s == null; i++) {
try {
s = ss.accept();
} catch (SocketTimeoutException e) {
}
} if (s != null) {
_childSocket = s;
}
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
} finally {
ss.close();
}
return s;
} /**
* Creates a new Process for the Resin JVM, initializing the environment
* and passing value to the new process.
*
* @param socketPort the watchdog socket port
* @param out the debug log jvm-default.log
*/
private Process createProcess(int socketPort)
throws IOException {
HashMap<String, String> env = buildEnv();
ArrayList<String> jvmArgs = buildJvmArgs(); jvmArgs.add("com.caucho.server.resin.Resin");
jvmArgs.add("-socketwait");
jvmArgs.add(String.valueOf(socketPort)); ProcessBuilder builder = new ProcessBuilder();
builder.environment().putAll(env);
builder = builder.command(jvmArgs);
builder.redirectErrorStream(true);
return builder.start();
} private HashMap<String, String> buildEnv()
throws IOException {
HashMap<String, String> env = new HashMap<String, String>();
env.putAll(System.getenv()); StringBuilder classPath = new StringBuilder();
// TODO 系统不一样分割符也不同 windows为分号;
classPath.append(".:"); String appPath = System.getProperty("user.dir");
classPath.append(appPath).append("/resin/target/classes");
env.put("CLASSPATH", classPath.toString()); // 。。。删除了可多可多的代码 。。。
return env;
} private ArrayList<String> buildJvmArgs() {
ArrayList<String> jvmArgs = new ArrayList<String>();
jvmArgs.add("java");
// ... 又删除了可多代码 ...
return jvmArgs;
} /**
* Watchdog thread responsible for writing jvm-default.log by reading the
* JVM's stdout and copying it to the log.
*/
class WatchdogProcessLogThread implements Runnable {
private InputStream _is; /**
* @param is the stdout stream from the Resin
*/
WatchdogProcessLogThread(InputStream is) {
_is = is;
} @Override
public void run() {
try {
int len;
byte[] data = new byte[4096];
while ((len = _is.read(data, 0, data.length)) > 0) {
System.out.print(new String(data, 0, len));
}
} catch (Throwable e) {
log.log(Level.WARNING, e.toString(), e);
} finally {
kill();
}
}
}
}

  

下面这个要重点说下,因为这套模型你拿过去,只需修改下面 Resin 这个类的代码,这个其实也就是我们要监控的应用。其实很简单,就有一个 connect 方法主要用于与大总管进行通讯,一旦通讯失败本身就退出。

package com.caucho.server.resin;	

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger; public class Resin { private static ExecutorService executorService = Executors.newFixedThreadPool(1); private static final Logger log = Logger.getLogger(Resin.class.getName()); public static void main(String[] args) {
log.log(Level.INFO, "我是乳名为Resin的丫鬟,大总管给的通讯端口为{0} {1}", args);
//获取传入的参数 port
int port = Integer.parseInt(args[1]); connect(port);
} public static void connect(final int port) {
log.log(Level.INFO, "我是乳名为Resin的丫鬟,我要开始与端口为{0}的大总管进行通讯",port);
executorService.execute(new Runnable() {
@Override
public void run() {
Socket socket = null;
try {
socket = new Socket("127.0.0.1", port);
InputStream s = socket.getInputStream();
byte[] buf = new byte[1024];
int len;
while ((len = s.read(buf)) != -1) {
log.log(Level.INFO, "通讯信息 {0}", new String(buf, 0, len));
}
} catch (IOException e) {
log.log(Level.WARNING, "我是乳名为Resin的丫鬟,与端口为{0}的大总管进行通讯发生异常",port);
} finally {
try {
socket.close();
} catch (IOException e) {
log.log(Level.WARNING, e.getMessage(), e);
}
log.log(Level.INFO, "我是乳名为Resin的丫鬟,与端口为{0}的大总管进行通讯结束,我要退下啦",port);
System.exit(0);
}
}
});
log.log(Level.INFO, "丫鬟与大总管通讯完成");
}
}

  

到这,代码也就码完了,不妨把代码拔下去,运行一下,稍微体验体验,看看是不是那回事儿!其中为了演示需要删除了 N 多代码,有些地方很不优雅,还需按照阿里开发规约适当调整调整,不过这些不是咱们这期分享的重点,咱们重点是思想 + 轻实践。

好了,思想也落地了,接下来就看你怎么让它老树开新花啦。分享就到这儿吧,希望能够解你所惑;希望能在你前进的道路上,帮你披荆斩棘。如果感觉有点帮助,欢迎秒赞,疯狂分享转发,因为你的每一次分享,我都认真当成了鼓励与喜欢。

如何让Java应用成为杀不死的小强?(下篇)的更多相关文章

  1. 如何让Java应用成为杀不死的小强?(上篇)

    各位坐稳扶好,我们要开车了.不过在开车之前,我们还是例行回顾一下上期分享的要点. 项庄舞剑意在沛公,而咱们上期主要借助应用服务器 Resin 的源码,体验了一次 JMX 的真实应用.鉴于 9012 年 ...

  2. 如何让Java应用成为杀不死的小强?(中篇)

    各位坐稳扶好,我们要开车了.不过在开车之前,我们还是例行回顾一下上期分享的要点. 上期我们抛了一个砖:“如何实现 Java 应用进程的状态监控,如果被监控的进程 down 掉,是否有机制能启动起来?” ...

  3. 一个杀不死的小强,kill进程无效的原因 记录故障排查过程中kill进程无效的分析过程

    今天在处理一个机器异常负载(1000+)的问题,碰到了一个从未碰到过的情况,遇到了一个异常顽固的分子.我使用了所能想到的所有杀进程的方法,却始终无法干掉这个顽固分子,最后终于在谷歌大神的指引下,干掉了 ...

  4. Linux中杀不死的进程

    前段时间,一哥们,去杀Linux服务器的进程,发现kill命令失灵了,怎么杀都杀不死. 然后上网查了下资料,原来是要被杀的进程,成为了僵尸进程. 僵尸进程的查看方法: 利用命令ps,可以看到有标记为Z ...

  5. paip.杀不死进程的原因--僵尸进程的解决.txt

    paip.杀不死进程的原因--僵尸进程的解决.txt 作者Attilax  艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csdn ...

  6. 手把手教你写LKM rookit! 之 杀不死的pid&root后门

    ......上一节,我们编写了一个基本的lkm模块,从功能上来说它还没有rootkit的特征,这次我们给它添加一点有意思的功能.我们让一个指定的进程杀不死, 曾经,想写一个谁也杀不死的进程,进程能捕捉 ...

  7. Linux下tomcat的shutdown命令可以关闭服务但是杀不死进程

    Linux下tomcat的shutdown命令可以关闭服务但是杀不死进程 原因: 一般造成这种原因是因为项目中有非守护线程的存在: 解决方案: 一.从Tomcat上解决 方案1:(推荐的方案:因为一台 ...

  8. 不死的小强 .net core 微服务 快速开发框架 Viper 限流

    1.Viper是什么? Viper 是.NET平台下的Anno微服务框架的一个示例项目.入门简单.安全.稳定.高可用.全平台可监控.底层通讯可以随意切换thrift grpc. 自带服务发现.调用链追 ...

  9. 《手把手教你》系列基础篇(七十六)-java+ selenium自动化测试-框架设计基础-TestNG实现DDT - 下篇(详解教程)

    1.简介 今天这一篇宏哥主要是结合实际工作中将遇到的测试场景和前边两篇学习的知识结合起来给大家讲解和分享一下,希望以后大家在以后遇到其他的测试场景也可以将自己的所学的知识应用到测试场景中. 2.测试场 ...

随机推荐

  1. seo搜索优化技巧02-seo问答推广怎么做?

    问答推广就是以用户的​‌‌角度去提问,这些问题内容就是用户搜索想要知道的.简单来说通过第三方平台站在用户的角度去帮助客户解答问题,同时附带自身的品牌和产品信息就是问答推广. 常见的问答平台有百度知道. ...

  2. MySQL 【教程一】

    前言 什么是数据库? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库. 每个数据库都有一个或多个不同的 API 用于创建,访问,管理,搜索和复制所保存的数据. 我们也可以将数据存 ...

  3. django学习笔记 多文件上传

    习惯了flask 再用django 还是不太习惯  好麻烦 配置文件也忒多了 不过还是要学的 之前只能一个一个文件长传,这次试试多个文件 不适用django的forms创建表单 直接在html中使用 ...

  4. js 实现图片的放大和缩小

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  5. 龙生九子-浅谈Java的继承

    龙生九子-浅谈Java的继承 书接上回,我们之前谈过了类和对象的概念,今天我来讲一讲面向对象程序设计的另外一个基本概念-继承 目录 为什么需要继承 自动转型与强制转型 继承能干啥 复写和隐藏 supe ...

  6. (11)nc命令(每周一个linux命令)

    nc(netcat)实用程序几乎可用于所有涉及TCP或UDP的事情.它可以打开TCP连接,发送UDP数据包,监听任意TCP和UDP端口,进行端口扫描,处理IPv4和IPv6.与telnet不同,nc可 ...

  7. Natas10 Writeup(正则表达式、grep命令)

    Natas10: 页面与上一关类似,只是增加了提示“出于安全原因,我们现在过滤某些字符”. 查看源码,发现关键代码如下: $key = ""; if(array_key_exist ...

  8. mysql数据库设计文档-导出字段设计

    navicat 是我一直在使用的一个数据库操作工具,非常方便快捷.如果没有可用navicat可以留言邮箱我直接发您. 今天来介绍一下使用navicat导出数据库字段设计.废话不多说,先看导出效果. 查 ...

  9. python函数版ATM

    最近系统的学习python函数知识点,感觉在面向对象之前,函数的功能确实强大. 最近使用函数写了ATM项目,虽然需求简单但也有很多知识点需要注意,这个项目把python基础的很多知识点都用上了. 前前 ...

  10. 使用new Image()可以针对单单请求,不要返回数据的情况

    使用new Image()可以针对单单请求,不要返回数据的情况,比如我这里写了一个Demo,请求百度的Logo一个示例: <html> <head> </head> ...