Discussion on the startup sequence of Docker Compose - springboot actual combat e-commerce project mall4j

springboot actual e-commerce project mall4j (https://gitee.com/gz-yami/mall4j)

java mall system source code

The following discussion is based on the V2 version of Docker Compose:

Nowadays, container orchestration is widely used. Everyone is using docker-compose for website deployment. During the deployment process, Mysql, Redis, JAVA back-end programs, PHP back-end programs, etc. will be used, which will involve the startup of a program The problem is that the general solution is to add the depends_on parameter in the Compose file, for example:

version: "2.4"
services:
  xxl-job-mysql:
    container_name: xxl-job-mysql
    environment:
      - TZ=Asia/Shanghai
      - MYSQL_ROOT_PASSWORD=123456
    image: mysql:5.7
    networks:
      xxl-job-network:
        ipv4_address: 192.168.189.2
    ports:
      - 3306:3306
    restart: always
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/initdb:/docker-entrypoint-initdb.d

  xxl-job-admin:
    container_name: xxl-job-admin
    environment:
      - PARAMS=--spring.datasource.url=jdbc:mysql://xxl-job-mysql:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 \
        --spring.datasource.username=root \
        --spring.datasource.password=123456 \
        --xxl.job.accessToken=
    image: xuxueli/xxl-job-admin:2.3.0
    networks:
      xxl-job-network:
        ipv4_address: 192.168.189.3
    ports:
      - 8080:8080
    restart: always
    # 依赖于数据库,先启动数据库再启动JAVA程序
    depends_on:
      - xxl-job-mysql

networks:
  xxl-job-network:
    driver: bridge
    ipam:
      config:
        - subnet: 192.168.189.0/24
    name: xxl-job-network

The above arrangement file does not seem to be a problem, but when it is actually executed, after the database is started, because of the depends_on parameter, the JAVA program will be started immediately after the database is started. Regardless of whether Mysql has been started or not. At this time, it may appear that the database has not been started yet, and the JAVA program has been started and completed, and try to connect to the database, resulting in a connection failure and the JAVA program reporting an error. So, depends_on does not really wait for the previous program to start to complete. This is also described in the official docker documentation: https://docs.docker.com/compose/startup-order/

里面有一句"However, for startup Compose does not wait until a container is “ready” (whatever that means for your particular application) - only until it’s running. There’s a good reason for this."

Snipaste_2021-12-09_11-17-18.png

Therefore, the official recommends some small tools for TCP detection to detect whether the program port is unblocked. Once the port is unblocked, it means that the program has been started.
Next, let's use the wait-for-it gadget: https://github.com/vishnubob/wait-for-it to detect whether the database has been started or not
. The dockerfile content of the java program of xxl-job-admin is as follows:

FROM openjdk:8-jre-slim
MAINTAINER xuxueli

ENV PARAMS=""

ENV TZ=PRC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

ADD target/xxl-job-admin-*.jar /app.jar

ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /app.jar $PARAMS"]
  xxl-job-admin:
    container_name: xxl-job-admin
    environment:
      - PARAMS=--spring.datasource.url=jdbc:mysql://xxl-job-mysql:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 \
        --spring.datasource.username=root \
        --spring.datasource.password=123456 \
        --xxl.job.accessToken=
    image: xuxueli/xxl-job-admin:2.3.0
    networks:
      xxl-job-network:
        ipv4_address: 192.168.189.3
    ports:
      - 8080:8080
    restart: always
    # 依赖于数据库,先启动数据库再启动JAVA程序
    depends_on:
      - xxl-job-mysql
    volumes:
      # 把wait-for-it脚本挂载到容器内根目录,
      # 因为查看xxl-job-admin的dockerfile得知app.jar也在根目录,所以放在同一个目录下
      - ./wait-for-it.sh:/wait-for-it.sh
    # 使用command命令,先执行wait-for-it等待数据库启动完成,然后执行java -jar app.jar
    command: ["/wait-for-it.sh", "xxl-job-mysql:3306", "--", "sh", "-c", "java -jar $JAVA_OPTS /app.jar $PARAMS"]

The complete code is as follows:

version: "2.4"
services:
  xxl-job-mysql:
    container_name: xxl-job-mysql
    environment:
      - TZ=Asia/Shanghai
      - MYSQL_ROOT_PASSWORD=123456
    image: mysql:5.7
    networks:
      xxl-job-network:
        ipv4_address: 192.168.189.2
    ports:
      - 3306:3306
    restart: always
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/initdb:/docker-entrypoint-initdb.d

  xxl-job-admin:
    container_name: xxl-job-admin
    environment:
      - PARAMS=--spring.datasource.url=jdbc:mysql://xxl-job-mysql:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 \
        --spring.datasource.username=root \
        --spring.datasource.password=123456 \
        --xxl.job.accessToken=
    image: xuxueli/xxl-job-admin:2.3.0
    networks:
      xxl-job-network:
        ipv4_address: 192.168.189.3
    ports:
      - 8080:8080
    restart: always
    # 依赖于数据库,先启动数据库再启动JAVA程序
    depends_on:
      - xxl-job-mysql
    volumes:
      # 把wait-for-it脚本挂载到容器内根目录,
      # 因为查看xxl-job-admin的dockerfile得知app.jar也在根目录,所以放在同一个目录下
      - ./wait-for-it.sh:/wait-for-it.sh
    # 使用command命令,先执行wait-for-it等待数据库启动完成,然后执行java -jar app.jar
    command: ["/wait-for-it.sh", "xxl-job-mysql:3306", "--", "sh", "-c", "java -jar $JAVA_OPTS /app.jar $PARAMS"]

networks:
  xxl-job-network:
    driver: bridge
    ipam:
      config:
        - subnet: 192.168.189.0/24
    name: xxl-job-network

In addition to the officially recommended gadgets for TCP detection, the official also provides a method, healthcheck, health check, and document connection: https://docs.docker.com/engine/reference/builder/#healthcheck

Snipaste_2021-12-09_11-28-34.png

According to the meaning of the document, we add the healthcheck parameter, the code is as follows:
xxl-job-admin:

    # 增加healthcheck参数:执行'mysqladmin ping -h localhost'命令
    # 执行时间设定为10秒,超时时常设定为20秒,重试次数为10次,如果能ping通,说明数据库启动完成
    healthcheck:
        test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
        interval: 10s
        timeout: 20s
        retries: 10

xxl-job-mysql:

    depends_on:
      xxl-job-mysql:
        # java容器在mysql容器状态为健康前不会启动
        condition: service_healthy

The complete code is as follows:

version: "2.4"
services:
  xxl-job-mysql:
    container_name: xxl-job-mysql
    environment:
      - TZ=Asia/Shanghai
      - MYSQL_ROOT_PASSWORD=123456
    image: mysql:5.7
    networks:
      xxl-job-network:
        ipv4_address: 192.168.189.2
    ports:
      - 3306:3306
    restart: always
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/initdb:/docker-entrypoint-initdb.d
    # 增加healthcheck参数:执行'mysqladmin ping -h localhost'命令
    # 执行时间设定为10秒,超时时常设定为20秒,重试次数为10次,如果能ping通,说明数据库启动完成
    healthcheck:
        test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
        interval: 10s
        timeout: 20s
        retries: 10

  xxl-job-admin:
    container_name: xxl-job-admin
    environment:
      - PARAMS=--spring.datasource.url=jdbc:mysql://xxl-job-mysql:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 \
        --spring.datasource.username=root \
        --spring.datasource.password=123456 \
        --xxl.job.accessToken=
    image: xuxueli/xxl-job-admin:2.3.0
    networks:
      xxl-job-network:
        ipv4_address: 192.168.189.3
    ports:
      - 8080:8080
    restart: always
    depends_on:
      xxl-job-mysql:
        # java容器在mysql容器状态为健康前不会启动,需要注意的是:ondition: service_healthy,在V3版本的Compose中已废弃  
        condition: service_healthy

networks:
  xxl-job-network:
    driver: bridge
    ipam:
      config:
        - subnet: 192.168.189.0/24
    name: xxl-job-network

So, so far, has the problem been solved? However, only a part of the solution is solved, because in the Mysql startup script, there is a command that will detect whether there is an initialization sql script in the /docker-entrypoint-initdb.d folder at startup. If it exists, it will be started for the first time. When executing sql, initialize the database, the link is as follows: https://github.com/docker-library/mysql/blob/master/5.7/docker-entrypoint.sh If the initialization script is very large, there will be a situation, The 3306 port of mysql can already be pinged, but the sql is still executing, the app container thinks it has been started, so as to connect to the database, so we need to improve the detection method: replace mysqladmin ping -h localhost with / usr/bin/mysql --user=root --password=123456 --execute "SHOW DATABASE;" The
code is as follows:

  xxl-job-mysql:
    container_name: xxl-job-mysql
    environment:
      - TZ=Asia/Shanghai
      - MYSQL_ROOT_PASSWORD=123456
    image: mysql:5.7
    networks:
      xxl-job-network:
        ipv4_address: 192.168.189.2
    ports:
      - 3306:3306
    restart: always
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/initdb:/docker-entrypoint-initdb.d
    # 增加healthcheck参数:执行'/usr/bin/mysql --user=root --password=123456 --execute "SHOW DATABASES;"'命令
    # 执行时间设定为10秒,超时时常设定为20秒,重试次数为10次,如果数据库能登陆并且可以查询时,说明数据库启动完成
    healthcheck:
        # 只有真正能执行查询命令,才算启动完成
        test: '/usr/bin/mysql --user=root --password=123456 --execute "SHOW DATABASES;"'
        interval: 10s
        timeout: 20s
        retries: 10

Summary:
1. It must be unreliable when only depends_on exists.
2. When the initialization script is relatively large, being able to ping does not necessarily mean that the database initialization is completed
. 3. When the database can be logged in and queried, the real startup is completed

springboot actual e-commerce project mall4j (https://gitee.com/gz-yami/mall4j)

java mall system source code

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324125813&siteId=291194637