微服务架构 – 解决Docker-Compose服务编排启动顺序问题

服务器

浏览数:170

2019-7-8

AD:资源代下载服务

基于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

关注我

以你最方便的方式关注我:
微信公众号:

作者:架构与我