在几个月前上线的一个采集项目,构架是基于java + selenium + chromedriver + chrome实现的采集。至于为哈不直接用jsoup或httpclient实现采集功能,是由于不少被采集页面都是经过js来渲染内容的,因此必须用webdriver+chrome来模拟真正的浏览器访问来采集。
每隔一段时间就会出采集失败问题,出现的时间没有规律,可能两天出现一次,可能一星期出现一次,可能一个月出现一次....java
用linux top命令来查看服务器,会发现不少的chromedriver和chrome的进程linux
用ps命令查看服务器git
ps -aux | grep chrome
存在状态为Sl和Z的休眠进程和僵尸进程,启动时间都不是当天,根据系统自己业务逻辑,进程不会存在运行那么长时间的状况。而java进程则所有都能正常关闭,但java进程启动的chromedriver和chrome进程不必定能同时关闭,目前出现这种问题的缘由未找到。
最初想用命令把卡死的进程查出来批量杀掉github
ps -A -o stat,ppid,pid,cmd | grep -e '^[Zz]' | awk '{print $2}' | xargs kill -9 //杀死僵尸进程
结果发现只能查杀Z状态的僵尸进程,Sl状态的进程,一部分是正常的,一部分是须要杀死的(启动时间为Nov07,Nov06的进程须要杀掉),至于哪些须要杀死,须要经过人工判断启动时间来肯定是否须要杀掉进程。web
以前一直忙时,都是先经过ps命令把chromedriver和chrome相关进程查询出来,而后经过人工判断进程是否属于休眠状态,再手工kill杀掉进程。正则表达式
最近有空了,本着能程序解决,就毫不要人工维护,把以前的手工杀休眠进程操做程序化。一开始想直接经过java的Runtime.getRuntime().exec()代码调用linux命令操做的,不过在java经常使用类库中(https://www.21doc.net/java/awesomejava#processes),找到zt-exec库,能够简化命令行调用操做。
程序化的代替人工维护实现定时清理休眠进程代码以下:chrome
import org.apache.log4j.Logger; import org.apache.log4j.RollingFileAppender; import org.zeroturnaround.exec.ProcessExecutor; import org.zeroturnaround.exec.stream.LogOutputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ProcessKill { private static Logger log = Logger.getLogger(ProcessKill.class); public ProcessKill(){ } public void run(){ try{ List<List<String>> list = new ArrayList<List<String>>(); // 先经过ps aux | grep chrome命令,获取全部包含chrome文本内容的进程 new ProcessExecutor().command("/bin/sh","-c","ps aux | grep chrome") .redirectOutput(new LogOutputStream() { protected void processLine(String line) { log.info("line========" + line); List<String> lines = split(line.trim()); // 判断进程启动时间,肯定是否为执行超时休眠的进程 String time = lines.get(8); String format = "[0-5][0-9]:[0-5][0-9]"; Pattern p = Pattern.compile(format); Matcher m = p.matcher(time); boolean result = m.find(); if(result == true) { log.info("time ========" + time + " " + result); } else{ log.info("time xxxxxxxx" + time + " " + result); list.add(lines); } } }) .execute(); log.info("list========size:" + list.size()); list.forEach(l->{ log.info("list line========" + l); }); for(int i = 0; i < list.size(); i++){ List<String> strs = list.get(i); try{ String pid = strs.get(1); String stat = strs.get(7); String time = strs.get(8); String cmd = strs.get(10); log.info("pid========" + pid + ", " + stat + ", " + time + ", " + cmd); // 经过命令“kill -9 pid”杀掉进程 String output = new ProcessExecutor().command("/bin/sh","-c","kill -9 " + pid) .readOutput(true).execute() .outputUTF8(); log.info("kill========" + pid + ", " + output); } catch(Exception ex){ ex.printStackTrace(); } } } catch(Exception e){ e.printStackTrace(); } } public List<String> split(String s){ // ps aux | grep chrome 命令返回字段: USER,PID ,%CPU,%MEM,VSZ, RSS,TTY,STAT,START,TIME,COMMAND List<String> list = new ArrayList<String>(); int blankCount = 0; StringBuffer sff = new StringBuffer(); for(int i = 0; i < s.length(); i++){ char c = s.charAt(i); if(list.size() < 10){ if(c != ' '){ sff.append(c); blankCount = 0; } else if(c == ' '){ blankCount++; } if(blankCount == 1){ list.add(sff.toString()); sff = new StringBuffer(); } } else{ sff.append(c); } } if(sff.length() > 0){ list.add(sff.toString()); } return list; } public static void setLogFile(String name){ Logger rootLogger = Logger.getRootLogger(); Enumeration en = rootLogger.getAllAppenders(); while (en.hasMoreElements()){ Object obj = en.nextElement(); if(obj instanceof RollingFileAppender){ RollingFileAppender file = (RollingFileAppender)obj; file.setFile(name + ".log"); file.activateOptions(); } } } public static void main(String[] args){ setLogFile("log/" + ProcessKill.class.getSimpleName()); new ProcessKill().run(); } }
文末记录下ps和grep命令用法。apache
ps 显示瞬间进程的状态 参数: -A :全部的进程均显示出来,与 -e 具备一样的效用; -a : 显示现行终端机下的全部进程,包括其余用户的进程; -u :以用户为主的进程状态 ; x :一般与 a 这个参数一块儿使用,可列出较完整信息。 ps aux USER:该进程属于那个使用者帐号。 PID :该进程的进程ID号。 %CPU:该进程使用掉的 CPU 资源百分比; %MEM:该进程所占用的物理内存百分比; VSZ :该进程使用掉的虚拟内存量 (Kbytes) RSS :该进程占用的固定的内存量 (Kbytes) TTY :该进程是在那个终端机上面运做,若与终端机无关,则显示。另外, tty1-tty6 是本机上面的登入者程序,若为 pts/0 等等的,则表示为由网络链接进主机的程序。 STAT:该程序目前的状态,主要的状态有: R :该程序目前正在运做,或者是可被运做; S :该程序目前正在睡眠当中,但可被某些讯号(signal) 唤醒。 T :该程序目前正在侦测或者是中止了; Z :该程序应该已经终止,可是其父程序却没法正常的终止他,形成 zombie (疆尸) 程序的状态 START:该进程被触发启动的时间; TIME :该进程实际使用 CPU 运做的时间。 COMMAND:该程序的实际指令。 ps -ef |grep java UID :程序被该 UID 所拥有 PID :就是这个程序的 ID PPID :则是其上级父程序的ID C :CPU使用的资源百分比 STIME :系统启动时间 TTY :登入者的终端机位置 TIME :使用掉的CPU时间。 CMD :所下达的是什么指令
grep命令的经常使用格式为:grep [选项] ”模式“ [文件] 经常使用选项: -E :开启扩展(Extend)的正则表达式。 -i :忽略大小写(ignore case)。 -v :反过来(invert),只打印没有匹配的,而匹配的反而不打印。 -n :显示行号 -w :被匹配的文本只能是单词,而不能是单词中的某一部分,如文本中有liker,而我搜寻的只是like,就可使用-w选项来避免匹配liker -c :显示总共有多少行被匹配到了,而不是显示被匹配到的内容,注意若是同时使用-cv选项是显示有多少行没有被匹配到。 -o :只显示被模式匹配到的字符串。 --color :将匹配到的内容以颜色高亮显示。 -A n:显示匹配到的字符串所在的行及其后n行,after -B n:显示匹配到的字符串所在的行及其前n行,before -C n:显示匹配到的字符串所在的行及其先后各n行,context
查看系统状态下的僵尸进程: 浏览器
ps -ef | grep defunct 后面尖括号里是defunct的都是僵尸进程。 ps aux | grep -w 'Z' 其中状态为Z的表明僵尸进程。