微服务架构 - 解决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
关注我
以你最方便的方式关注我:
微信公众号: