前言
要做这个,大家要先知道systemctl和chkconfig是什么东西来的,然后再写脚本。
具体参考:
linux下java程序在centos的部署上篇—jar程序服务化、nohup用法及管理、nohup输出日志定时切割(草稿篇)
实践
在下列目录添加service脚本,
vim /usr/lib/systemd/system/
当然,以一个实际模块作为例子,我们将服务名称定义为:
dev@MicroBase.service
ps:假如将脚本放到system的同级—user下面,那就会触发unit not found的错误的。压根识别不了。
然后–假如有看之前文章的话肯定会知道jar文件是存在的,譬如:
下面开始。
systemctl自定义服务
引用参考:
CentOS 7的服务systemctl脚本存放在:/usr/lib/systemd/,有系统(system)和用户(user)之分,像须要开机不登陆就能执行的程序,还是存在系统服务里吧,即:/usr/lib/systemd/system文件夹下
每个服务以.service结尾,通常会分为3部分:[Unit]、[Service]和[Install],我写的这个服务用于开机执行Node.js项目,详细内容例如以下:
[Unit]
Description=xiyoulibapi
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/node.js/pid
ExecStart=/usr/local/bin/forever start /node.js/xiyoulib/bin/www
ExecReload=/usr/local/bin/forever restart /node.js/xiyoulib/bin/www
ExecStop=/usr/local/bin/forever stop /node.js/xiyoulib/bin/www
PrivateTmp=true
[Install]
WantedBy=multi-user.target
好了,可以先自定义一个simple的unit服务玩一玩。
首先打开:
vim /usr/lib/systemd/system/dev@MicroBase.service
然后复制下面内容
[Unit]
Description=base micro service
[Service]
Type=simple
PIDFile=/usr/local/pid-files/MicroBaseApp-dev.pid
#ExecStart=/usr/micro-service/apps/Service4MicroBase.sh -e dev -o start
ExecStart=/usr/bin/nohup /usr/bin/java -jar /usr/micro-service/apps/dev/MicroBase/MicroBaseApp/MicroBaseApp.jar >/usr/local/logs/dev-MicroBase-2.log 2>&1 &
ExecStop=/usr/bin/kill -9 $MAINPID
Restart=always
#取消启动频率限制吧。
StartLimitInterval=0
PrivateTmp=true
[Install]
WantedBy=multi-user.target
接下来,
chmod 754 /usr/lib/systemd/system/dev@MicroBase.service
ps:
不要多手在添加一个x上去,譬如:
chmod +x /usr/lib/systemd/user/dev@MicroBase.service
会报错的,到时直接提示不要设置execute的权限的。
然后:
#注意,确保之前没有后台程序
echo ""> /usr/local/logs/dev-MicroBase-2.log
systemctl daemon-reload
systemctl stop dev@MicroBase.service
systemctl start dev@MicroBase.service
systemctl status dev@MicroBase.service
journalctl -xe
vim /usr/local/logs/dev-MicroBase-2.log
执行状态时候的结果:
systemctl status dev@MicroBase.service
终于终于终于没有203错误了!
好了,看看服务的日志:
journalctl -xe
这次也没有报错—-还有:
服务已经启动这句话明明是程序system.out出来的,怎么会在服务日志里面?不应该在nohup的输出文件吗?
看看nohup日志:
vim /usr/local/logs/dev-MicroBase-2.log
。。。。还是空文件。。。
好了测试通过
坑1、启动太频繁 repeated too quickly
systemctl status dev@MicroBase.service
下面解决一下。
按照字面意思是太频繁了,那么我们先重启再尝试一下。
完全不行,方向错误了。。。
参考:
[CentOS 7之Systemd详解之服务单元设置system.service(https://blog.csdn.net/yuesichiu/article/details/51485147)
StartLimitInterval=, StartLimitBurst=
限制该服务的启动频率。默认值是每10秒内不得超过5次(StartLimitInterval=10s StartLimitBurst=5)。
StartLimitInterval= 的默认值等于systemd配置文件中 DefaultStartLimitInterval= 的值,”0”表示取消启动频率限制。
StartLimitBurst= 的默认值等于systemd配置文件中 DefaultStartLimitBurst= 的值。
虽然这两个选项经常与 Restart= 一起使用,但是它们不只限制 Restart= 罗辑所导致的重启,而是限制所有类型的启动(包括手动启动)。 注意,当 Restart=逻辑所导致的重启超出了启动频率限制之后,Restart= 逻辑将会被禁用(也就是不会在下一个时间段内再次尝试重启), 然而,如果该单元随后又被手动重启,那么 Restart= 罗辑将被再次激活。 注意,”systemctl reset-failed …”命令会清除该服务的重启次数计数器,这通常用于在手动启动之前清除启动限制。
StartLimitInterval=0 加上这一句。。
加上以后变成:
然后重试:
坑2、system.in.read引发的血案
假设我们的java文件代码如下:
package net.w2p.MicroBase;
import com.alibaba.fastjson.JSONObject;
import net.w2p.MicroBase.searcher.account.MemberCondition;
import net.w2p.MicroBase.service.account.MemberRoleService;
import net.w2p.MicroBase.service.account.MemberService;
import net.w2p.MicroBase.vo.account.Member;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
import java.util.ArrayList;
public class Provider {
public static void main(String[] args) throws IOException {
System.out.println("服务已经启动...");
System.in.read();
}
}
日志及bad file descriptor问题
就一个服务已经启动的代码,我们看看日志如何获得。
正常情况是这样的。那么用nohup,直接使用命令:
看看日志文件:
ps:这段代码可是参考了网上主流文章而而写出来的,这样也错就只能说大家都错了或者大家都忽略了一些东西了。
所有,bad file descriptor是什么鬼?
直接执行正常,加了nohup就不行了?
查阅资料:
execute nohup command with playframework get Bad file descriptor error
这里抄录一下:
问:
I use playframework2.2 and sbt 0.13.1, I can run the sbt and start the server on command line
sbt start
it works ok. but when I run:
nohup sbt start
It run a while and then stop with log error:
(Starting server. Type Ctrl+D to exit logs, the server will remain in background) java.io.IOException: Bad file descriptor
at java.io.FileInputStream.read0(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:210)
at jline.internal.NonBlockingInputStream.read(NonBlockingInputStream.java:248)
at jline.internal.InputStreamReader.read(InputStreamReader.java:261)
at jline.internal.InputStreamReader.read(InputStreamReader.java:198)
at jline.console.ConsoleReader.readCharacter(ConsoleReader.java:2038)
at play.PlayConsoleInteractionMode$$anonfun$waitForKey$1.play$PlayConsoleInteractionMode$$anonfun$$waitEOF$1(PlayInteractionMode.scala:36)
at play.PlayConsoleInteractionMode$$anonfun$waitForKey$1$$anonfun$apply$1.apply$mcV$sp(PlayInteractionMode.scala:45)
at play.PlayConsoleInteractionMode$$anonfun$doWithoutEcho$1.apply(PlayInteractionMode.scala:52)
at play.PlayConsoleInteractionMode$$anonfun$doWithoutEcho$1.apply(PlayInteractionMode.scala:49)
at play.PlayConsoleInteractionMode$.withConsoleReader(PlayInteractionMode.scala:31)
at play.PlayConsoleInteractionMode$.doWithoutEcho(PlayInteractionMode.scala:49)
at play.PlayConsoleInteractionMode$$anonfun$waitForKey$1.apply(PlayInteractionMode.scala:45)
at play.PlayConsoleInteractionMode$$anonfun$waitForKey$1.apply(PlayInteractionMode.scala:34)
at play.PlayConsoleInteractionMode$.withConsoleReader(PlayInteractionMode.scala:31)
at play.PlayConsoleInteractionMode$.waitForKey(PlayInteractionMode.scala:34)
at play.PlayConsoleInteractionMode$.waitForCancel(PlayInteractionMode.scala:55)
at play.PlayRun$$anonfun$24$$anonfun$apply$9.apply(PlayRun.scala:373)
at play.PlayRun$$anonfun$24$$anonfun$apply$9.apply(PlayRun.scala:352)
at scala.util.Either$RightProjection.map(Either.scala:536)
at play.PlayRun$$anonfun$24.apply(PlayRun.scala:352)
at play.PlayRun$$anonfun$24.apply(PlayRun.scala:334)
at sbt.Command$$anonfun$sbt$Command$$apply1$1$$anonfun$apply$6.apply(Command.scala:72)
at sbt.Command$.process(Command.scala:95)
at sbt.MainLoop$$anonfun$1$$anonfun$apply$1.apply(MainLoop.scala:100)
at sbt.MainLoop$$anonfun$1$$anonfun$apply$1.apply(MainLoop.scala:100)
at sbt.State$$anon$1.process(State.scala:179)
at sbt.MainLoop$$anonfun$1.apply(MainLoop.scala:100)
at sbt.MainLoop$$anonfun$1.apply(MainLoop.scala:100)
at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:18)
at sbt.MainLoop$.next(MainLoop.scala:100)
at sbt.MainLoop$.run(MainLoop.scala:93)
at sbt.MainLoop$$anonfun$runWithNewLog$1.apply(MainLoop.scala:71)
at sbt.MainLoop$$anonfun$runWithNewLog$1.apply(MainLoop.scala:66)
at sbt.Using.apply(Using.scala:25)
at sbt.MainLoop$.runWithNewLog(MainLoop.scala:66)
at sbt.MainLoop$.runAndClearLast(MainLoop.scala:49)
at sbt.MainLoop$.runLoggedLoop(MainLoop.scala:33)
at sbt.MainLoop$.runLogged(MainLoop.scala:25)
at sbt.StandardMain$.runManaged(Main.scala:57)
at sbt.xMain.run(Main.scala:29)
at xsbt.boot.Launch$$anonfun$run$1.apply(Launch.scala:57)
at xsbt.boot.Launch$.withContextLoader(Launch.scala:77)
at xsbt.boot.Launch$.run(Launch.scala:57)
at xsbt.boot.Launch$$anonfun$explicit$1.apply(Launch.scala:45)
at xsbt.boot.Launch$.launch(Launch.scala:65)
at xsbt.boot.Launch$.apply(Launch.scala:16)
at xsbt.boot.Boot$.runImpl(Boot.scala:32)
at xsbt.boot.Boot$.main(Boot.scala:21)
at xsbt.boot.Boot.main(Boot.scala)
error[0m] [0mjava.io.IOException: Bad file descriptor[0m
error[0m] [0mUse 'last' for the full log.[0m
回答:
The error happens because standard input get redirected from /dev/null by nohup - you get the same error if you do play start < /dev/null. The sbt process starts the actual server in a separate process, the sets itself up to display logs and wait for you to type Ctrl-D or Ctrl-C. It uses JLine to wait for user input, which attempts to attach to the standard input as a terminal. /dev/null can't be used in this way, so it dies complaining of a bad file descriptor. However, the background server process continues running.
If you want to start Play non-interactively, you need to use the stage task. See Using the stage task in the Play documentation.
。。。。这里的意思似乎是,交互类型的程序才出这个问题—本身nohup就是放后台运行的。。。。看到这里我灵机一动。。。改改代码编译然后再试试。
ps:为什么这里要执着于这个问题,system.in.read的问题?因为这个方式是阻塞了主线程的方法,在此期间,微服务会一直开启一直保持处理状态,没有这个的话估计微服务运行不起来。也就是说,所有用nohup的后台程序都要考虑如何保持java程序一直运行而不是一闪而过
解决方案:
这里写链接内容
大家可以参考一下:
代码修改,本地测试通过:
好了,上传到jenkins,然后编译,然后部署到目标目录上面去:
第一,原始方法执行:
java -jar /usr/micro-service/apps/dev/MicroBase/MicroBaseApp/MicroBaseApp.jar
只能用ctrl c断掉,程序测试通过。
好了,清空一下日志,然后用nohup来执行。
echo ""> /usr/local/logs/dev-MicroBase.log
/usr/bin/nohup /usr/bin/java -jar /usr/micro-service/apps/dev/MicroBase/MicroBaseApp/MicroBaseApp.jar >/usr/local/logs/dev-MicroBase.log 2>&1 /usr/local/pids/dev-MicroBase.pid &
vim /usr/local/logs/dev-MicroBase.log
好了,没有刚才的问题了,那么,退出来看看nohup是不是真的有这个后台程序在执行。
jobs -l
有了这个程序了,我们可以切换到前台然后ctrl c直接杀死进程。
fg 1
好了,用了信号量以后,既可以阻塞主程序,性能也低,也可以避开人机交互。