Tomcat篇01-概念簡介和守護進程配置

image

本文主要包括tomcat服務器的主要概念介紹、在systemd上的tomcat守護進程的配置、jsvc的原理介紹和systemd的并發(fā)實現(xiàn)原理介紹。

1、Tomcat簡介

在了解tomcat之前我們需要了解一些基本的概念。

1.1 web應用

所謂Web應用,就是指需要通過編程來創(chuàng)建的Web站點。Web應用中不僅包括普通的靜態(tài)HTML文檔,還包含大量可被Web服務器動態(tài)執(zhí)行的程序。用戶在Internet上看到的能開展業(yè)務的各種Web站點都可看作Web應用,例如,網上商店和網上銀行都是Web應用。此外,公司內部基于Web的Intranet工作平臺也是Web應用。

Web應用與傳統(tǒng)的桌面應用程序相比,具有以下特點:

  • 以瀏覽器作為展示客戶端界面的窗口。
  • 客戶端界面一律表現(xiàn)為網頁形式,網頁由HTML語言寫成。
  • 客戶端與服務器端能進行和業(yè)務相關的動態(tài)交互
  • 能完成與桌面應用程序類似的功能。
  • 使用瀏覽器—服務器架構(B/S),瀏覽器與服務器之間采用HTTP協(xié)議通信。
  • Web應用通過Web服務器來發(fā)布。

web應用的一大好處就是可以輕易地跨平臺運行,不論是windows、mac、ios、android還是linux,只要安裝了瀏覽器,一般都可以使用web應用,而瀏覽器在各個平臺都是標配的軟件,因此給web應用的普及提供了非常良好的條件。同樣的,web應用使用的是B/S架構,即Browser/Server架構,主要的計算任務都交給Server端進行,因此都客戶端的性能要求較低,同時也推動了服務端的負載均衡、高可用等技術的發(fā)展。

Context:在tomcat中一般指web應用

1.2 Servlet

Servlet(Server Applet),全稱Java Servlet。是用Java編寫的服務器端程序。其主要功能在于交互式地瀏覽和修改數據,生成動態(tài)Web內容。狹義的Servlet是指Java語言實現(xiàn)的一個接口,廣義的Servlet是指任何實現(xiàn)了這個Servlet接口的類別,一般情況下,我們說的Servlet為后者。

Servlet運行于支持Java的應用服務器中。從實現(xiàn)上講,Servlet可以響應任何類型的請求,但絕大多數情況下Servlet只用來擴展基于HTTP協(xié)議的Web服務器。也就是說Web服務器可以訪問任意一個Web應用中所有實現(xiàn)Servlet接口的類。而Web應用中用于被Web服務器動態(tài)調用的程序代碼位于Servlet接口的實現(xiàn)類中。既然servlet和java關系密切,那么servlet接口的標準制定毫無疑問也是由甲骨文公司來主導。

Servlet規(guī)范把能夠發(fā)布和運行Java Web應用的Web服務器稱為Servlet容器。Servlet容器最主要的特征是動態(tài)執(zhí)行Java Web應用中Servlet實現(xiàn)類的程序代碼。由Apache開源軟件組織創(chuàng)建的Tomcat是一個符合Servlet規(guī)范的優(yōu)秀Servlet容器。

image

1.3 jsp

JSP(全稱JavaServer Pages)是由Sun Microsystems公司主導建立的一種動態(tài)網頁技術標準。JSP是HttpServlet的擴展。JSP將Java代碼和特定變動內容嵌入到靜態(tài)的頁面中,實現(xiàn)以靜態(tài)頁面為模板,動態(tài)生成其中的部分內容。JSP在首次被訪問的時候被應用服務器轉換為servlet,在以后的運行中,容器直接調用這個servlet,而不再訪問JSP頁面。JSP的實質仍然是servlet。

1.4 Tomcat

Tomcat
是在Oracle公司的JSWDK(JavaServer Web DevelopmentKit,是Oracle公司推出的小型Servlet/JSP調試工具)的基礎上發(fā)展起來的一個優(yōu)秀的Servlet容器,Tomcat本身完全用Java語言編寫。作為一個開源軟件,Tomcat除了運行穩(wěn)定、可靠,并且效率高之外,還可以和目前大部分的主流Web服務器(如IIS、Apache、Nginx等)一起工作。

tomcat的版本實際上比較復雜,目前有7、8、9、10四個版本并行發(fā)布,具體的各個版本的兼容信息我們可以通過官網查詢。

2、Tomcat安裝配置

tomcat的配置安裝需要先在系統(tǒng)上配置好jdk環(huán)境,這里我們使用centos7.7版本的Linux系統(tǒng)和jdk8版本。

2.1 配置jdk8

我們首先到官網下載JDK8的安裝包,這里我們選擇tar.gz格式的壓縮包下載,需要注意建議先使用瀏覽器下載再使用工具傳輸到Linux上,因為下載需要登錄注冊賬號。

接著我們解壓將安裝包解壓到自己想要配置的jdk安裝目錄下,這里我們使用/home/目錄

tar -zxvf jdk-8u241-linux-x64.tar.gz -C /home/

/etc/profile中添加以下三個參數并導入

JAVA_HOME=/home/jdk_1.8.0_241
CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib
PATH=$PATH:$JAVA_HOME/bin:$JAVA_HOME/jre/bin
export JAVA_HOME CLASSPATH PATH

重新載入配置文件

source /etc/profile

檢查配置是否生效,如不生效可以重啟終端試試:

[root@tiny-yun ~]# java -version
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)

2.2 配置tomcat

tomcat的安裝配置和上面幾乎一樣,由于我們已經在/etc/profile中設定了全局的java環(huán)境變量,因此在tomcat中就不用再特殊配置,直接就會使用默認的全局變量。

這里我們還是使用官網
提供的tar.gz壓縮包來安裝。

# tomcat可以直接使用wget下載
wget https://downloads.apache.org/tomcat/tomcat-8/v8.5.53/bin/apache-tomcat-8.5.53.tar.gz
# 解壓到安裝目錄并重命名
tar -zxvf apache-tomcat-8.5.53.tar.gz /home/
cd /home
mv apache-tomcat-8.5.53 tomcat-8.5.53

tomcat目錄

首先我們來看一下tomcat中的主要目錄:

  • /bin 存放用于啟動及關閉的文件,以及其他一些腳本。其中,UNIX 系統(tǒng)專用的 *.sh 文件在功能上等同于 Windows 系統(tǒng)專用的 *.bat 文件。因為 Win32 的命令行缺乏某些功能,所以又額外地加入了一些文件。
  • /conf 配置文件及相關的 DTD。其中最重要的文件是 server.xml,這是容器的主配置文件。
  • /log 日志文件的默認目錄。
  • /webapps 存放 Web 應用的相關文件。

接著我們進入tomcat目錄下的bin目錄就可以看到各種各樣的腳本文件,主要分為batsh兩類,其中bat主要是在windows系統(tǒng)上使用的,我們可以把它們刪掉,接著我們執(zhí)行一些version.sh這個腳本就可以看到版本信息。

image

接下來我們來看一下和tomcat相關的幾個變量:

JRE_HOME

這里我們可以看到JRE_HOME這個變量是之前設置了的JAVA_HOME環(huán)境變量。

  • 如果同時定義了JRE_HOMEJAVA_HOME這兩個變量,那么使用的是JRE_HOME
  • 如果只定義了JAVA_HOME,那么JRE_HOME變量值就是JAVA_HOME的變量值
  • 如果兩個變量都沒定義,那么tomcat無法運行

前面我們提到過tomcat是使用Java編寫的,這也就意味著它在運行的時候需要創(chuàng)建一個JVM虛擬機,所以如果沒定義JAVA環(huán)境變量,tomcat是無法運行的

CATALINA_HOME

tomcat安裝目錄的根目錄

CATALINA_BASE

tomcat實例運行的目錄,默認情況下等于CATALINA_HOME,如果我們需要在一臺機器上運行多個tomcat實例,可以設置多個CATALINA_BASE

setenv.sh

這個腳本默認是不存在的,需要我們自己手動創(chuàng)建在bin目錄下,在windows系統(tǒng)則應該是setenv.bat,我們在里面指定了JRE_HOME環(huán)境變量以及PID文件的位置,這樣在運行的時候就能比較方便的定位到運行進程

注意前面提到的CATALINA_HOMECATALINA_BASE兩個變量不能在這里設定,因為tomcat就是根據這兩個變量來找到 setenv.sh的。

[admin@tiny-yun bin]$ cat setenv.sh 
JRE_HOME=/home/jdk1.8.0_241/jre
CATALINA_PID="$CATALINA_BASE/tomcat.pid"

這時候運行./catalina.sh start或者是./startup.sh文件就可以啟動tomcat,注意要在防火墻中放行默認的8080端口。如果沒有指定PID文件的位置,在關閉tomcat的時候可能會出現(xiàn)錯誤。此外,一般不建議使用root用戶來運行tomcat。

image

個人感覺使用catalina.sh加參數的方式來控制tomcat進程要更加靈活強大一些。

[admin@tiny-yun bin]$ ./catalina.sh -h
Using CATALINA_BASE:   /home/tomcat-8.5.53
Using CATALINA_HOME:   /home/tomcat-8.5.53
Using CATALINA_TMPDIR: /home/tomcat-8.5.53/temp
Using JRE_HOME:        /home/jdk1.8.0_241/jre
Using CLASSPATH:       /home/tomcat-8.5.53/bin/bootstrap.jar:/home/tomcat-8.5.53/bin/tomcat-juli.jar
Using CATALINA_PID:    /home/tomcat-8.5.53/tomcat.pid
Usage: catalina.sh ( commands ... )
commands:
  debug             Start Catalina in a debugger
  debug -security   Debug Catalina with a security manager
  jpda start        Start Catalina under JPDA debugger
  run               Start Catalina in the current window
  run -security     Start in the current window with security manager
  start             Start Catalina in a separate window
  start -security   Start in a separate window with security manager
  stop              Stop Catalina, waiting up to 5 seconds for the process to end
  stop n            Stop Catalina, waiting up to n seconds for the process to end
  stop -force       Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running
  stop n -force     Stop Catalina, wait up to n seconds and then use kill -KILL if still running
  configtest        Run a basic syntax check on server.xml - check exit code for result
  version           What version of tomcat are you running?
Note: Waiting for the process to end and use of the -force option require that $CATALINA_PID is defined
[admin@tiny-yun bin]$ 

3、 jsvc配置daemon(守護進程)

在Windows上,tomcat會默認注冊成系統(tǒng)服務,這樣設置啟動和運行都方便很多,而在Linux上,我們需要借助jsvc來實現(xiàn)這一效果。

3.1 什么是jsvc

Commons Daemon(共享守護進程),原名JSVC,是一個屬于Apache的Commons項目的Java庫。守護程序提供了一種啟動和停止正在運行服務器端應用程序的Java虛擬機(JVM)的便攜式方法。守護程序包括兩部分:用C編寫的操作系統(tǒng)接口的原生庫 ,以及提供用Java編寫的Daemon API的庫。

有兩種使用Commons守護程序的方法:直接調用實現(xiàn)守護程序接口(interface)或調用為守護程序提供所需方法(method)的類(class)。例如,Tomcat-4.1.x使用守護程序接口,而Tomcat-5.0.x提供了一個類,該類的方法直接由JSVC調用。

3.2 jsvc工作原理

jsvc使用了三個進程來工作:一個啟動進程、一個控制進程、一個被控制進程。其中被控制進程一般來說就是java主線程(我們這里就是tomcat),如果JVM虛擬機崩潰了,那么控制進程會在下一分鐘重啟。因為jsvc是守護進程,所以它應該使用root用戶來啟動,同時我們可以使用-user參數來進行用戶的降級(downgrade),即先使用root用戶來創(chuàng)建進程,然后再降級到指定的非root用戶而不丟失root用戶的特殊權限,如監(jiān)聽1024以下的端口。

3.3 jsvc配置tomcat守護進程(daemon)

tomcat的二進制安裝包中的bin目錄下就有jsvc的安裝包,我們需要使用GCC編譯器對其進行編譯安裝。同時在編譯的時候我們需要指定jdk的路徑,由于我們前面已經手動指定了,這里不需要再指定。如果沒有,可以使用./configure --with-java=$JAVA_HOME來進行操作。

# 首先我們進入tomcat的bin目錄進行編譯
cd $CATALINA_HOME/bin
tar xvfz commons-daemon-native.tar.gz
cd commons-daemon-1.2.2-native-src/unix
./configure
make
# 編譯完成后,會在當前文件夾生成一個jsvc的文件,將它拷貝到tomcat的/bin/目錄下
cp jsvc ../..
cd ../..
# 接著我們可以這樣查看jsvc的幫助文檔
./jsvc -help

使用jsvc來啟動tomcat,我們使用下面的參數來進行啟動

./jsvc \
    -user tomcat \
    -classpath $CATALINA_HOME/bin/bootstrap.jar:$CATALINA_HOME/bin/tomcat-juli.jar \
    -outfile $CATALINA_BASE/logs/catalina.out \
    -errfile $CATALINA_BASE/logs/catalina.err \
    -Dcatalina.home=$CATALINA_HOME \
    -Dcatalina.base=$CATALINA_BASE \
    -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \
    -Djava.util.logging.config.file=$CATALINA_BASE/conf/logging.properties \
    org.apache.catalina.startup.Bootstrap
image

注意看這時的用戶和PID,上面的12839的用戶為root,也就是我們前面說的控制進程,后面被12839進程控制的12840進程才是我們主要運行的tomcat進程,而這里的用戶也符合我們使用-user參數指定的tomcat用戶。如果我們不指定進程的PID文件位置,那么默認就會在/var/run目錄下生成PID文件,我們可以看到這個jsvc.pid對應的正好是jsvc運行的三個進程中的被控制進程。

如果需要關閉,我們可以使用下面的命令:

./jsvc -stop org.apache.catalina.startup.Bootstrap stop

# 還可以指定pid文件位置,如果前面沒有使用默認的pid文件目錄的話
./jsvc -stop -pidfile /var/run/jsvc.pid org.apache.catalina.startup.Bootstrap stop

這個時候可能就會有同學發(fā)現(xiàn),前面不是說jsvc主要有三個進程來工作的嗎,怎么這里只有兩個進程呢?

我們在上面的啟動命令的選項里面加入一個-wait 10的參數,然后啟動之后迅速查看一下進程。

一般情況下,啟動進程在啟動了控制進程之后就會結束,而當我們使用了-wait參數之后,啟動進程會等待被控制進程啟動好了之后向其發(fā)送一個"I am ready"信號,啟動進程在收到信號之后就會結束。-wait 10表示等待時間為10秒,需要注意等待時間要是10的倍數。

image

這時候可以看到存在三個jsvc相關的進程,等tomcat啟動完之后再查看的時候我們就會發(fā)現(xiàn)最上面的19347號進程,也就是jsvc啟動進程消失了。并且控制進程19350的父進程變成了1號進程。

image

我們再進一步查看以下進程的關系:

image

接著我們再來查看一下1號進程??梢园l(fā)現(xiàn),在centos7中的1號進程是systemd

image

接著我們可以總結以上的整個過程為下列步驟:

  1. 系統(tǒng)啟動,0號進程啟動,0號通過fork()生成1號進程systemd;
  2. 1號進程systemd通過fork()創(chuàng)建進程sshd,這就是我們使用的ssh服務的進程;
  3. 用戶使用ssh遠程登錄系統(tǒng),sshd進程創(chuàng)建了對應的終端進程pts;
  4. 用戶在終端輸入指令,pts根據系統(tǒng)中指定的該用戶使用的shell(此處為bash shell)來執(zhí)行對應的操作,這里具體表現(xiàn)為根據我們輸入的指令來創(chuàng)建jsvc的啟動進程;
  5. jsvc啟動進程創(chuàng)建jsvc控制進程,并根據啟動參數決定是否在等待jsvc控制進程的"I am ready"信號再結束,同時jsvc啟動進程在結束之前會把jsvc控制進程交給1號進程systemd來管理控制;
  6. jsvc控制進程創(chuàng)建jsvc被控制進程,也就是我們的主要進程tomcat,同時jsvc控制進程會監(jiān)視jsvc被控制進程,如果它崩潰了,jsvc控制進程則會重啟,確保其正常運行;

這里使用jsvc來啟動tomcat的好處就是啟動完成了之后即使我們的shell終端關閉了也不會影響它的運行,當然如果我們直接使用tomcat的bin目錄下的啟動腳本來進行啟動然后再送入后臺運行也是可以達到這樣的效果。實際上我們還可以通過編寫systemd的unit單元配置文件,將tomcat注冊成系統(tǒng)服務。

3.4 daemon.sh

同樣的,在tomcat的bin目錄下,集成了一個daemon.sh的腳本,用來調用jsvc從而實現(xiàn)tomcat的守護進程。daemon.sh的實現(xiàn)原理還是jsvc,只不過在腳本中加入了大量的變量判斷和環(huán)境配置文件讀取等操作

在官網上會建議我們直接把daemon.sh腳本復制到 /etc/init.d 目錄下,就可以實現(xiàn)開機自動啟動了。不過在CentOS7等使用了systemd的系統(tǒng)上,我個人更推薦使用systemd來管理。

4、systemd配置

這里先放上archwiki和fedoraproject官網上面的鏈接作為參考資料:

https://wiki.archlinux.org/index.php/Systemd

https://docs.fedoraproject.org/en-US/quick-docs/understanding-and-administering-systemd/index.html

4.1 systemd簡介

systemd 是 Linux 下一個與 SysV 和 LSB 初始化腳本兼容的系統(tǒng)和服務管理器,是 Linux 系統(tǒng)中最新的初始化系統(tǒng)(init),它主要的設計目標是克服 sysvinit 固有的缺點,提高系統(tǒng)的啟動速度。systemd 和 ubuntu 的 upstart 是競爭對手,不過現(xiàn)在ubuntu也使用了systemd。

systemd 使用 socket 和 D-Bus 來開啟服務,提供基于守護進程(daemon)的按需啟動策略,保留了 Linux cgroups 的進程追蹤功能,支持快照和系統(tǒng)狀態(tài)恢復,維護掛載和自掛載點,實現(xiàn)了各服務間基于從屬關系的一個更為精細的邏輯控制,擁有前衛(wèi)的并行性能。systemd 無需經過任何修改便可以替代 sysvinit 。

systemd 開啟和監(jiān)督整個系統(tǒng)是基于 unit 的概念。unit 是由一個與配置文件對應的名字和類型組成的(例如:avahi.service unit 有一個具有相同名字的配置文件,是守護進程 Avahi 的一個封裝單元)。一個unit單元配置文件可以描述的內容有:系統(tǒng)服務(.service)、掛載點(.mount)、sockets(.sockets) 、系統(tǒng)設備(.device)、交換分區(qū)(.swap)、文件路徑(.path)、啟動目標(.target)、由 systemd 管理的計時器(.timer)。

  • service :守護進程的啟動、停止、重啟和重載是此類 unit 中最為明顯的幾個類型。
  • socket :此類 unit 封裝系統(tǒng)和互聯(lián)網中的一個 socket 。當下,systemd 支持流式、數據報和連續(xù)包的 AF_INET、AF_INET6、AF_UNIX socket 。也支持傳統(tǒng)的 FIFO(先進先出) 傳輸模式。每一個 socket unit 都有一個相應的服務 unit 。相應的服務在第一個連接(connection)進入 socket 或 FIFO 時就會啟動(例如:nscd.socket 在有新連接后便啟動 nscd.service)。
  • device :此類 unit 封裝一個存在于 Linux 設備樹中的設備。每一個使用 udev 規(guī)則標記的設備都將會在 systemd 中作為一個設備 unit 出現(xiàn)。udev 的屬性設置可以作為配置設備 unit 依賴關系的配置源。
  • mount :此類 unit 封裝系統(tǒng)結構層次中的一個掛載點。
  • automount :此類 unit 封裝系統(tǒng)結構層次中的一個自掛載點。每一個自掛載 unit 對應一個已掛載的掛載 unit (需要在自掛載目錄可以存取的情況下盡早掛載)。
  • target :此類 unit 為其他 unit 進行邏輯分組。它們本身實際上并不做什么,只是引用其他 unit 而已。這樣便可以對 unit 做一個統(tǒng)一的控制。(例如:multi-user.target 相當于在傳統(tǒng)使用 SysV 的系統(tǒng)中運行級別5,即GUI圖形化界面);bluetooth.target 只有在藍牙適配器可用的情況下才調用與藍牙相關的服務,如:bluetooth 守護進程、obex 守護進程等)
  • snapshot :與 target unit 相似,快照本身不做什么,唯一的目的就是引用其他 unit 。

systemd的unit文件可以從多個地方加載,使用systemctl show --property=UnitPath 可以按優(yōu)先級從低到高顯示加載目錄。

image

主要的unit文件在下面的兩個目錄中:

  • /usr/lib/systemd/system/ :軟件包安裝的單元
  • /etc/systemd/system/ :系統(tǒng)管理員安裝的單元

4.2 systemd原理

這里我們重點分析一下systemd的并行操作性能以及service服務的配置單元。

和前任的sysvinit的完全串行相比,systemd為了加速整個系統(tǒng)啟動,實現(xiàn)了幾乎所有的進程都并行啟動(包括需要上下進程依賴的進程也并行啟動)。想要實現(xiàn)這一點,主要需要解決三個方面的依賴問題:socket、D-Bus和文件系統(tǒng)。

socket 依賴(inetd)

絕大多數的服務依賴是套接字依賴。比如服務 A 通過一個套接字端口 S1 提供自己的服務,其他的服務如果需要服務 A,則需要連接 S1。因此如果服務 A 尚未啟動,S1 就不存在,其他的服務就會得到啟動錯誤。

所以傳統(tǒng)地,人們需要先啟動服務 A,等待它進入就緒狀態(tài),再啟動其他需要它的服務。

systemd 認為,只要我們預先把套接字端口S1建立好,那么其他所有的服務就可以同時啟動而無需等待服務 A來創(chuàng)建套接字端口S1了。如果服務 A 尚未啟動,那么其他進程向套接字端口S1發(fā)送的服務請求實際上會被 Linux 操作系統(tǒng)緩存,其他進程會在這個請求的地方等待(這里使用FIFO方式)。一旦服務A啟動就緒,就可以立即處理緩存的請求,一切都開始正常運行。

那么服務如何使用由 init 進程創(chuàng)建的套接字呢?

Linux 操作系統(tǒng)有一個特性,當進程調用fork或者exec創(chuàng)建子進程之后,所有在父進程中被打開的文件句柄 (file descriptor) 都被子進程所繼承。套接字也是一種文件句柄,進程A可以創(chuàng)建一個套接字,此后當進程 A調用 exec 啟動一個新的子進程時,只要確保該套接字的close_on_exec標志位被清空,那么新的子進程就可以繼承這個套接字。子進程看到的套接字和父進程創(chuàng)建的套接字是同一個系統(tǒng)套接字,就仿佛這個套接字是子進程自己創(chuàng)建的一樣,沒有任何區(qū)別。

這個特性以前被一個叫做inetd的系統(tǒng)服務所利用。Inetd進程會負責監(jiān)控一些常用套接字端口,比如 ssh,當該端口有連接請求時,inetd才啟動telnetd進程,并把有連接的套接字傳遞給新的telnetd進程進行處理。這樣,當系統(tǒng)沒有 ssh 客戶端連接時,就不需要啟動 sshd 進程。Inetd 可以代理很多的網絡服務,這樣就可以節(jié)約很多的系統(tǒng)負載和內存資源,只有當有真正的連接請求時才啟動相應服務,并把套接字傳遞給相應的服務進程。

和 inetd 類似,systemd(1號進程)是所有其他進程的父進程,它可以先建立所有需要的套接字,然后在調用 exec 的時候將該套接字傳遞給新的服務進程,而新進程直接使用該套接字進行服務即可。

D-Bus 依賴(bus activation)

D-Bus 是 desktop-bus 的簡稱,是一個低延遲、低開銷、高可用性的進程間通信機制。它越來越多地用于應用程序之間通信,也用于應用程序和操作系統(tǒng)內核之間的通信。很多現(xiàn)代的服務進程都使用D-Bus 取代套接字作為進程間通信機制,對外提供服務。

Linux的 NetworkManager 服務就使用 D-Bus 和其他的應用程序或者服務進行交互:Linux上常見的郵件客戶端軟件 evolution 可以通過 D-Bus 從 NetworkManager 服務獲取網絡狀態(tài)的改變,以便做出相應的處理。

D-Bus 支持所謂"bus activation"功能。如果服務 A 需要使用服務 B 的 D-Bus 服務,而服務 B 并沒有運行,則 D-Bus 可以在服務 A 請求服務 B 的 D-Bus 時自動啟動服務 B。而服務 A 發(fā)出的請求會被 D-Bus 緩存,服務 A 會等待服務 B 啟動就緒。利用這個特性,依賴 D-Bus 的服務就可以實現(xiàn)并行啟動。

文件系統(tǒng)依賴(automounter)

系統(tǒng)啟動過程中,文件系統(tǒng)相關的活動是最耗時的,比如掛載文件系統(tǒng),對文件系統(tǒng)進行磁盤檢查(fsck),磁盤配額檢查等都是非常耗時的操作。在等待這些工作完成的同時,系統(tǒng)處于空閑狀態(tài)。那些想使用文件系統(tǒng)的服務似乎必須等待文件系統(tǒng)初始化完成才可以啟動。但是 systemd 發(fā)現(xiàn)這種依賴也是可以避免的。

systemd 參考了 autofs 的設計思路,使得依賴文件系統(tǒng)的服務和文件系統(tǒng)本身初始化兩者可以并行工作。autofs 可以監(jiān)測到某個文件系統(tǒng)掛載點真正被訪問到的時候才觸發(fā)掛載操作,這是通過內核 automounter 模塊的支持而實現(xiàn)的。systemd 集成了autofs的實現(xiàn),對于系統(tǒng)中的掛載點,比如/home,當系統(tǒng)啟動的時候,systemd 為其創(chuàng)建一個臨時的自動掛載點。在這個時刻/home 真正的掛載設備尚未啟動好,真正的掛載操作還沒有執(zhí)行,文件系統(tǒng)檢測也還沒有完成??墒悄切┮蕾囋撃夸浀倪M程已經可以并發(fā)啟動,他們的 open()操作被內建在 systemd 中的 autofs 捕獲,將該 open()調用掛起(可中斷睡眠狀態(tài))。然后等待真正的掛載操作完成,文件系統(tǒng)檢測也完成后,systemd 將該自動掛載點替換為真正的掛載點,并讓 open()調用返回。由此,實現(xiàn)了那些依賴于文件系統(tǒng)的服務和文件系統(tǒng)本身同時并發(fā)啟動。

對于/根目錄的依賴實際上一定還是要串行執(zhí)行,因為 systemd 自己也存放在/根目錄之下,必須等待系統(tǒng)根目錄掛載檢查好。

不過對于類似/home等掛載點,這種并發(fā)可以提高系統(tǒng)的啟動速度,尤其是當/home是遠程的 NFS 節(jié)點,或者是加密盤等,需要耗費較長的時間才可以準備就緒的情況下,因為并發(fā)啟動,這段時間內,系統(tǒng)并不是完全無事可做,而是可以利用這段空余時間做更多的啟動進程的事情,總的來說就縮短了系統(tǒng)啟動時間。

總結

從上面的三個辦法我們可以看出,systemd讓多個程序并行啟動的解決思路就是先創(chuàng)建一個虛擬點,讓各類需要依賴的服務先運行起來,最后再把虛擬點換成實際的服務使得能夠正常運行。

4.3 systemd實現(xiàn)tomcat的daemon進程

我們在/usr/lib/systemd/system/目錄下新建一個tomcat9.service文件,接下來我們可以使用systemctl命令來進行控制:

  • 使用 systemctl 控制單元時,通常需要使用unit文件的全名,包括擴展名(例如 sshd.service )。但是有些unit可以在 systemctl 中使用簡寫方式。

  • 如果無擴展名,systemctl 默認把擴展名當作 .service 。例如 tomcat 和 tomcat.service 是等價的。

  • 掛載點會自動轉化為相應的 .mount 單元。例如 /home 等價于 home.mount

  • 設備會自動轉化為相應的 .device 單元,所以 /dev/sda1 等價于 dev-sda1.device

使用daemon.sh

首先我們嘗試在systemd中使用自帶的腳本進行啟動和關閉tomcat,這里我們先把startup.shshutdown.sh兩個腳本給排除掉,雖然它們無法啟動守護進程的缺陷可以使用systemd來進行彌補,但是還是無法使用jsvc,無法在特權端口和運行用戶之間取得兩全,我們直接使用daemon.sh來運行。

需要注意的是,systemd并不會去讀取我們先前在/etc/profile中設定的變量,因此我們直接把變量寫進unit配置文件中。

[Unit]
Description=Apache Tomcat 9

[Service]
User=tomcat
Group=tomcat
PIDFile=/var/run/tomcat.pid
Environment=JAVA_HOME=/home/jdk8/
Environment=JRE_HOME=/home/jdk8/jre
Environment=CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib
Environment=CATALINA_HOME=/home/tomcat9
Environment=CATALINA_BASE=/home/tomcat9
Environment=CATALINA_TMPDIR=/home/tomcat9/temp
ExecStart=/home/tomcat9/bin/daemon.sh start
ExecStop=/home/tomcat9/bin/daemon.sh stop

[Install]
WantedBy=multi-user.target

添加了新的unit單元之后我們先systemctl daemon-reload重啟一下daemon進程,再使用systemctl start tomcat9.service來啟動服務,接著查看狀態(tài),發(fā)現(xiàn)無法正常運行,一啟動進程就failed掉了,查看daemon腳本默認的日志文件(位于tomcat目錄下的logs/catalina-daemon.out)我們發(fā)現(xiàn)返回了143錯誤。

Service exit with a return value of 143

網上搜索了一下,有個解決方案是把daemon.sh腳本中的wait參數時間從10調成240,在125行左右的位置:

# Set the default service-start wait time if necessary
test ".$SERVICE_START_WAIT_TIME" = . && SERVICE_START_WAIT_TIME=10

wait參數調大之后,等待啟動成功之后(這里用的主機配置很低,啟動比較耗時)就可以正常訪問了

image

但是在四分鐘(240s)之后我們再查看tomcat9.service就會發(fā)現(xiàn),進程已經結束了,再次訪問默認的8080端口也無法訪問,查找進程也沒有找到相關的進程。

image

試圖分析一波

我們來根據上面的情況結合原理來試圖分析一下:

首先我們可以看到-wait參數時長調到240之后,bash shell進程的生命周期延長了,根據之前的jsvc工作原理部分我們可以知道-wait參數會影響jsvc的啟動進程的生命周期,而從systemd輸出的信息來看,有包括jsvc三個進程和bash shell進程在內共計四個進程,這和之前我們直接運行daemon.sh之后最終只有jsvc的兩個進程(控制進程和被控制進程不同),且Main PID參數指向的是bash shell進程。

于是乎我們大膽猜測一下:使用daemon.sh start命令啟動tomcat,systemd會把啟動daemon.sh的bash的PID作為整個service的PID來監(jiān)控,而這個bash進程在啟動了jsvc之后是會自行退出的,這也就導致了systemd認為service已經運行失敗,從而清理掉了關聯(lián)的進程,進而使得jsvc相關的tomcat進程也被清理掉了。而-wait參數時長調到240之后,bash shell進程的存活時間變長,我們就能在tomcat啟動完成之后且bash shell進程結束之前訪問到tomcat服務器。

考慮到這種情況,我們可以試一下使用daemon.sh run來啟動tomcat,因為在終端中使用run參數的時候會一直把log信息輸出到終端,我猜測這個運行方式是和start不太一樣的。

把systemd的unit文件的啟動參數改為run,同時將-wait參數時長調回默認的10,再次啟動服務。

image

這次我們可以看到systemd的Main PID對應為jsvc的主進程,tomcat服務也能一直正常的在后臺運行。應該算是成功的使用systemd來管理jsvc啟動的tomcat進程了。

那么這兩者的區(qū)別在哪里呢?接著我們打開daemon.sh這個腳本來查看一下兩者的不同:

image

從圖中我們可以看到兩者最大的不同就是使用run命令的時候是exec調用jsvc來啟動tomcat并且使用了-nodetach參數。

shell中的exec命令和直接調用不同,命令exec將并不啟動新的shell,而是用要被執(zhí)行命令替換當前的shell進程,并且將老進程的環(huán)境清理掉,而且exec命令后的其它命令將不再執(zhí)行。

也就是說,run命令使用exec調用了jsvc,是直接替代原來啟動daemon.sh的bash shell進程,并且在這個exec命令執(zhí)行完之后才會執(zhí)行后面的exit命令。這樣就可以讓systemd的Main PID從bash shell進程順理成章地變?yōu)閖svc的啟動進程。

那么我們知道,jsvc的啟動進程在啟動完jsvc控制進程之后還是會退出的,這個時候systemd還是會監(jiān)聽失敗。而-nodetach參數的作用就是不脫離父進程而成為守護進程( don't detach from parent process and become a daemon),這樣就能順利地使得jsvc控制進程從它的父進程jsvc啟動進程那里“得到”systemd的Main PID的位置,成為該service的主要進程。

我們直接在終端中運行jsvc并加上-nodetach參數,可以看到即使是運行成功了之后也不會退出(控制進程繼承了啟動進程成為守護進程一直運行),而沒加的情況下則是jsvc啟動進程退出后就會退出。

image

這里再放上systemd使用daemon.sh啟動tomcat的整個unit文件的配置及注釋:

[Unit]
Description=Apache Tomcat 9
# 對整個serive的描述,相當于備注,會出現(xiàn)在systemd的log中
After=network.target
# 在network服務啟動之后再啟動

[Service]
User=tomcat
Group=tomcat
# 運行該service的用戶及用戶組

PIDFile=/var/run/tomcat.pid
# 該service的PID文件

Environment=JAVA_HOME=/home/jdk8/
Environment=JRE_HOME=/home/jdk8/jre
Environment=CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib
Environment=CATALINA_HOME=/home/tomcat9
Environment=CATALINA_BASE=/home/tomcat9
Environment=CATALINA_TMPDIR=/home/tomcat9/temp
# 定義了運行時需要的變量

ExecStart=/home/tomcat9/bin/daemon.sh start
ExecStop=/home/tomcat9/bin/daemon.sh stop
# 對應systemd控制的start和stop命令

[Install]
WantedBy=multi-user.target
# 運行級別為第三級(帶有網絡的多用戶模式)

直接使用jsvc

既然搞清楚了運行原理,我們也就可以跳過腳本直接在unit文件中定義各種參數:

[Unit]
Description=Apache Tomcat 9
After=network.target

[Service]
User=root
Group=root
# 這里使用root用戶啟動方便jsvc監(jiān)聽特權端口
# 后面可以在jsvc參數中使用-user降權到tomcat用戶

PIDFile=/var/run/tomcat.pid

Environment=JAVA_HOME=/home/jdk8/
Environment=JRE_HOME=/home/jdk8/jre
Environment=CLASSPATH=%JAVA_HOME%/lib:%JAVA_HOME%/jre/lib
Environment=CATALINA_HOME=/home/tomcat9
Environment=CATALINA_BASE=/home/tomcat9
Environment=CATALINA_TMPDIR=/home/tomcat9/temp

ExecStart=/home/tomcat9/bin/jsvc \
        -user tomcat \
        -nodetach \
        -java-home ${JAVA_HOME} \
        -pidfile ${CATALINA_BASE}/tomcat.pid \
        -classpath ${CATALINA_HOME}/bin/bootstrap.jar:${CATALINA_HOME}/bin/tomcat-juli.jar \
        -outfile ${CATALINA_BASE}/logs/catalina.out \
        -errfile ${CATALINA_BASE}/logs/catalina.err \
        -Dcatalina.home=${CATALINA_HOME} \
        -Dcatalina.base=${CATALINA_BASE} \
        -Djava.io.tmpdir=${CATALINA_TMPDIR} \
        -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \
        -Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \
        org.apache.catalina.startup.Bootstrap

ExecStop=/home/tomcat9/bin/jsvc \
        -stop \
        -classpath ${CLASSPATH} \
        -Dcatalina.base=${CATALINA_BASE} \
        -Dcatalina.home=${CATALINA_HOME} \
        -pidfile ${CATALINA_BASE}/tomcat.pid \
        -Djava.io.tmpdir=${CATALINA_TMPDIR} \
        -Djava.util.logging.config.file=${CATALINA_BASE}/conf/logging.properties \
        -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager \
        org.apache.catalina.startup.Bootstrap
 
[Install]
WantedBy=multi-user.target

注意:ExecStart和ExecStop兩個命令中的執(zhí)行文件路徑需要使用絕對路徑

image
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容