微服務架構 - 解決Docker-Compose服務編排啟動順序問題
基於Docker Compose進行服務編排時,一定碰到服務啟動順序的問題,例如:B服務啟動之前,A服務要已經啟動並且可以正常對外服務。
這個啟動順序的問題,Docker Compose本身它是無法解決的,即使定義了depends_on或者links,它只能保證該服務依賴這些服務,啟動本服務時會將依賴的服務也啟動,但是啟動順序無法得到保證。
目前本人實驗比較好的方案有兩種:
- 基於wait-for-it.sh實現,前提條件是本鏡像要支持bash
- 對於自己構建的鏡像時,讓工程本身帶一個監聽類,用於監聽依賴服務是否啟動,這種方式有侵入性,同時對於第3方的鏡像,不太好實現
1、wait-for-it.sh方案
wait-for-it.sh是GitHub中開源一個腳本,很輕量也很實用,以一個例子說明其的法:
本例子中定義了2個服務,一個mysql服務,一個cs2_serv服務,這個cs2_serv需要等mysql啟動好並做好初始化後才能啟動,要不然cs2_serv服務會由於沒法連接到數據庫而報錯。
version: "3" services: mysql: image: mysql:5.6 ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=jgyw@123 - MYSQL_USER=cs2 - MYSQL_PASS=cs2123 volumes: - ./db/mysql:/var/lib/mysql - ./db/init:/docker-entrypoint-initdb.d/ cs2_serv: image: cs2_serv:v1 ports: - "81:81" environment: - SERV_PORT=81 - MYSQL_IP=mysql - MYSQL_PORT=3306 - DB_USERNAME=root - DB_PASSWORD=jgyw@123 links: - mysql volumes: - ./wait-for-it.sh:/wait-for-it.sh entrypoint: "/wait-for-it.sh -t 0 mysql:3306 -- " command: - /bin/sh - -c - | sleep 10 java -Djava.security.egd=file:/dev/./urandom -jar /app.jar
此處最為核心的代碼就是:
entrypoint: "/wait-for-it.sh -t 0 mysql:3306 -- "
command:
- /bin/sh
- -c
- |
sleep 10
java -Djava.security.egd=file:/dev/./urandom -jar /app.jar
這2個配置的意思是,要等到mysql:3306服務可以用了,才去執行command對應的命令。
同時我在commad命令中再增加等待10s鐘,主要為了完全確保mysql服務啟動完成,還有就是初始化數據庫也完成,最後才去啟動cs2_serv服務。
2、自定義監聽類
這種方式有一定侵入性,但是配置起來會比較方便,在此以Spring Boot為例,寫了一個簡單的監聽類,即:
package com.swnote.cs2.common.listener;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Map;
import org.apache.log4j.Logger;
import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.context.ApplicationListener;
/**
* 依賴服務檢查
*/
public class DependsOnServiceCheckListener implements ApplicationListener<ApplicationStartingEvent> {
private Logger logger = Logger.getLogger(DependsOnServiceCheckListener.class);
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
// 獲取環境變量
Map<String, String> envs = System.getenv();
// 環境變量中DEPENDS_ON值,即是依賴的服務,值的內容格式為:host1:port1,host2:port2
if (envs.containsKey("DEPENDS_ON")) {
// 依賴服務是否啟動的標誌
boolean flag = false;
String val = envs.get("DEPENDS_ON");
String[] servs = val.split(",");
while (!flag) {
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
logger.warn("Wait depends on Service started...");
}
for (String serv : servs) {
flag = checkServ(serv);
if (!flag) {
break;
}
}
}
logger.info("Depends on Service started...");
}
}
/**
* 檢查服務是否啟動
*
* @param serv
* @return
*/
private boolean checkServ(String serv) {
String[] servs = serv.split(":");
String host = servs[0].trim();
int port = Integer.parseInt(servs[1].trim());
Socket socket = null;
try {
socket = new Socket();
socket.connect(new InetSocketAddress(host, port));
logger.info(serv + ": Service started...");
return true;
} catch (Exception e) {
logger.warn(serv + ": Service not started...");
return false;
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
這個監聽類,是將依賴的服務信息放到環境變量DEPENDS_ON中,即是依賴的服務,值的內容格式為:host1:port1,host2:port2,然後每隔5s去測試依賴的服務是否是通的,如果所有依賴的服務都是通的,那麽本服務就可以啟動,否則本服務一直處於等待狀態。
以一個實例說明使用方式,即:
cs2_web:
image: cs2_web:v1
ports:
- "82:82"
environment:
- WEB_PORT=82
- SERV_DOMAIN=cs2_serv
- DEPENDS_ON=cs2_serv:81
links:
- cs2_serv
這裏定義了一個cs2_web服務,該服務是依賴上面例子中的cs2_serv,但是它配置依賴關系是通過環境變量DEPENDS_ON來配置的。
3、參考資料
https://github.com/vishnubob/wait-for-it
關註我
以你最方便的方式關註我:
微信公眾號:
微服務架構 - 解決Docker-Compose服務編排啟動順序問題