背景
CentOS服務器上,我們用Systemd部署了一個SpringBoot服務。關於如何部署,可以參考這篇文章。這個SpringBoot服務會用ProcessBuilder
去調用機器上一個C++
的可執行文件。
問題描述
SpringBoot程序跑得很正常,但是我們發現C++
程序卻沒有log輸出,也就是說它從沒被執行過。
查看了ProcessBuilder
的返回值,是127。127的意思是系統找不到對應的命令。於是我們把C++
程序對應的目錄(裏面包括第三方動態鏈接庫)添加到了LD_LIBRARY_PATH
。怎麼添加的呢?我們在~/.bash_profile
腳本中添加了命令:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/cppFoler/
經過測試,SpringBoot服務依然無法成功調用C++
程序,依然返回127。但奇怪的是,當我們直接使用java -jar
命令,並且用nohup
的方式令SpringBoot程序跑在後臺,可以正常調用C++
程序:
nohup java -jar mySpringBoot.jar
問題分析
這裏就不得不說Linux中nohup
與service
的區別了。
nohup
: 在linux中,我們通過終端(shell
)登陸機器,會有一個進程與shell
關聯。當我們退出的時候(如Ctrl+C
),會向該進程發送一個SIGHUP
信號,進程收到信號便會自動被銷燬。nohup
命令的作用,是將shell中產生的輸出(stdout
)和錯誤(stderr
)重定向到文件nohup.out
中。當我們退出shell的時候,nohup
會忽略SIGHUP
信號,讓相關聯的程序繼續運行。由此看出,nohup
進程是一次性的,其本質就是讓程序在原先的shell
進程中繼續運行。service
: 服務是一種後臺守護進程,不與任何終端(shell
)關聯。它是系統的一部分,可以被service
或systemctl
命令方便的管理。service
的具體的啓動過程可以參考這篇文章。
回到我們的問題,nohup
的方式可以工作,而service
不行,說明前面設置的LD_LIBRARY_PATH
參數只對nohup
生效,對service
無效。這讓我去重新學習了CentOS中~/.bash_profile
腳本,以及相關的/etc/profile
,~/.bashrc
和/etc/bashrc
。
/etc/profile
: 腳本設置了一些系統級別的環境變量,它會在有用戶通過終端登陸的時候被執行,做一些初始化操作。初始化時,它會去/etc/profile.d/
目錄下,去逐個執行.sh
文件。所以,與其直接修改/etc/profile
文件,不如將自定義腳本放在/etc/profile.d/
下面。
該腳本只會在**交互式登陸終端(interactive - login shell)**啓動的時候運行,也就是它只在用戶登陸的時候運行一次。以下摘自文件的註釋
System wide environment and startup programs, for login setup. Functions and aliases go in /etc/bashrc. It’s NOT a good idea to change this file unless you know what you are doing. It’s much better to create a custom.sh shell script in /etc/profile.d/ to make custom changes to your environment, as this will prevent the need for merging in future updates.
/etc/bashrc
: 腳本和/etc/profile
類似。區別是,/etc/bashrc
更側重的是系統級別的別名(alias)。而且,該腳本只會在**交互式非登陸終端(interactive - non-login shell)**啓動的時候運行,即在每次新打開一個shell
的時候,都會執行一遍。以下摘自文件的註釋
System wide functions and aliases, Environment stuff goes in /etc/profile.
~/.bash_profile
: 作用和/etc/profile
一樣,只不過作用範圍只是當前用戶~/.bashrc
: 作用和/etc/bashrc
一樣,只不過作用範圍只是當前用戶
到這裏,問題已經明朗了,我們在~/.bash_profile
中設置的環境變量,只對shell
所對應的進程生效,而對後臺的service
無效。當用nohup
方式執行程序的時候,因爲還是在shell
對應的線程中,所以可以正常讀到設置的環境變量。
問題解決
解決辦法是,給service
獨立設置環境變量。這裏有兩種方式。
1. Systemd Environment
在Systemd Service的配置文件中,顯示指定Environment參數,來設置service
運行時的環境變量,如:
[Unit]
Description=My SpringBoot Service
After=syslog.target
[Service]
User=nightfield
ExecStart=/path/to/mySpringBoot.jar
SuccessExitStatus=143
Restart=always
RestartSec=5
Environment="LD_LIBRARY_PATH=/path/to/cppFoler/"
[Install]
WantedBy=multi-user.target
這樣,service
運行的時候,就可以正確讀到對應的變量。
2. ProcessBuild中指定environment()
ProcessBuild
可以通過environment()
方法,設置process運行的環境參數:
ProcessBuilder pb = new ProcessBuilder("java -version");
Map<String, String> env = pb.environment();
env.put("LD_LIBRARY_PATH", "/path/to/cppFoler/");
Process p = pb.start();
總結
通過這個問題,更深刻地學習了nohup
和service
的區別,profile
與bashrc
的適用場景。