什么!!容器化启动swoole项目需要几分钟,一招教你秒级启动

起因

这几年在公司开发我都是用4年前买的mac笔记本的,4年他终究是顶不住了,时不时出现内存不足提示,严重影响工作效率,于是跟公司申请了一台Windows台式电脑。 因为旧的项目使用的是php的swoole开发,新电脑不想把本机环境搞乱,直接使用docker来部署,也很省事,可没想到通过容器文件挂载的方式启动swoole程序,启动时间长达2分钟,这在代码调试的时候简直不能忍受。

思考

前面我们说到使用到了挂载文件的方式在容器内启动swoole程序,那么为什么光是启动就这么慢呢?

docker-compose.yml

yaml 复制代码
version: "3.8"
services:
  user_service:
    image: xxxx.com/right/swoft-base:local
    container_name: user_service
    volumes:
      - ./user_service:/app/user_service
    ports:
      - 1003:1003
    working_dir: /app/user_service
    command: sh -c "php bin/swoft http:start"
    network_mode: host

分析

我们可以看到,在启动容器时,docker-compose会将本地的user_service目录挂载到容器的/app/user_service目录下,然后启动swoft程序。

理论上文件都拷贝进去容器了,文件都在容器内的操作系统,启动不就应该像宿主主机一样吗?

在宿主主机操作系统中我们读取文件是这样的(容器内也可以理解为一个操作系统)

应用程序发起请求

应用程序通过系统调用(如read())请求读取文件,将请求传递给系统内核。

内核处理请求

内核接收到请求后,根据文件描述符等信息,找到对应的文件,并确定要读取数据的位置。

文件系统层读取文件

内核请求文件系统层去读取文件。文件系统层会根据文件的存储位置等信息,从磁盘等存储设备中读取文件数据。如果数据在页缓存中,直接从缓存读取;否则,从磁盘读取数据到页缓存,再从缓存中读取。

数据返回

文件系统层将读取到的数据返回给内核,内核再将数据返回给应用程序,应用程序便可以对这些数据进行后续处理。

难道因为挂载的原因,在启动程序读取项目文件时跟在宿主主机不一样?带着这个因为我去查阅了资料

原理解析

经过查阅资料可知,当容器内程序发起读操作文件时,会发生这些事情:

容器内发起读请求

容器内的应用程序(如一个运行在容器中的Python脚本)发起对挂载文件的读请求。例如,应用程序执行open('/container/path/file.txt', 'r')来打开文件,并准备读取内容。

容器文件系统层处理

容器的文件系统层(通常是联合文件系统,如OverlayFS)接收到读请求。它会检查挂载点信息,确定这个文件实际上映射到了宿主机的哪个路径。例如,容器内的/container/path/file.txt映射到了宿主机的/host/path/file.txt。

请求传递给Docker守护进程

容器文件系统层将读请求传递给Docker守护进程。Docker守护进程是Docker架构中的核心组件,负责管理容器的生命周期和资源分配等。

Docker守护进程与宿主机内核交互

Docker守护进程将读请求转发给宿主机的内核。内核根据挂载点的映射关系,找到宿主机文件系统中对应的文件。内核会调用文件系统驱动程序(如ext4驱动程序,如果宿主机使用ext4文件系统)来处理读请求。

宿主机文件系统读取数据

宿主机的文件系统驱动程序从磁盘(或缓存中,如果数据已经在缓存中)读取文件的内容。这个过程可能涉及到磁盘I/O操作,数据从磁盘的特定扇区读取到内存中。

数据回传给容器

读取到的数据首先传递回宿主机内核,然后通过Docker守护进程返回给容器的文件系统层。最终,数据被传递给容器内的应用程序,应用程序就可以读取到文件的内容了。

原来如此,挂载文件的读取还需要经过docker守护进程转发给宿主机内核,再经过文件系统层,最后才到达应用程序。这就导致了读取一个文件相比于直接在宿主机上读取文件,需要多花费一倍的时间。

实验

为了验证上述原理,我们可以做一个实验。循环10000次去读取同一个文件,打印出这挂载的文件和容器的文件不同读取时间。

/home/README.md 为容器内的文件

/app/README.md 为挂载的文件

shell脚本

bash 复制代码
    #!/bin/bash

    # 检查是否提供了文件路径参数
    if [ -z "$1" ]; then
        echo "Usage: $0 <file_path>"
        exit 1
    fi

    # 获取文件路径参数
    file_path="$1"

    # 检查文件是否存在
    if [ ! -f "$file_path" ]; then
        echo "File not found: $file_path"
        exit 1
    fi

    # 初始化总耗时
    total_elapsed_time=0

    # 打开文件1万次
    i=1
    while [ $i -le 10000 ]; do
        # 获取开始时间(毫秒)
        start_time=$(date +%s%3N)

        # 读取文件内容
        cat "$file_path" > /dev/null

        # 获取结束时间(毫秒)
        end_time=$(date +%s%3N)

        # 计算耗时(毫秒)
        elapsed_time=$((end_time - start_time))

        # 累加总耗时
        total_elapsed_time=$((total_elapsed_time + elapsed_time))

        # 增加计数器
        i=$((i + 1))
    done

    # 输出总耗时和平均耗时
    echo "Total elapsed time: $total_elapsed_time milliseconds"

实验结果与分析

看到这个结果,我们可以得出结论,在容器内读取挂载文件,需要经过docker守护进程、文件系统层、内核等多个步骤,耗时比直接在宿主机上读取文件要多。 挂载文件(挂载进容器的文件)的访问速度比容器文件(容器内的文件)要慢,这也是为什么容器内程序启动时间长的原因。 既然这样,我们直接通过容器文件来启动程序不就好了吗?

解决方法

我们可以直接通过容器文件来启动程序,这样就不用挂载文件了。

修改docker-compose.yml

yaml 复制代码
version: "3.8"
services:
    user_service:
        image: XXXX.com/right/swoft-base:local
        container_name: user_service
        volumes:
        - ./user_service:/app/user_service
        - ./rsync:/app/rsync
        ports:
        - 1003:1003
        working_dir: /app/user_service2
        command: sh -c "sh /app/rsync/cron.sh &&php bin/swoft http:start"
        network_mode: host

cron.sh

bash 复制代码
#!/bin/bash
rsync -azv --delete --exclude-from=/app/rsync/exclude /app/user_service/ /app/user_service2
(
    while true; do
    rsync -azv --delete --exclude-from=/app/rsync/cron_exclude /app/user_service/ /app/user_service2
    sleep 2
done
) &

exclude文件

.git
.idea
.vscode

cron_exclude文件

bash 复制代码
.git
.idea
.vscode
vendor
runtime
*.log

在这个示例,我将项目user_service挂载进/app/user_service目录,同时在容器内通过rsync工具复制到user_service2目录,使用容器内user_service2来启动服务,避免的挂载文件的读写,读写文件更高效。 然后启动一个shell脚本监听挂载文件目录的文件变化并同步给启动目录user_service2中。

定时同步的脚本是cron.sh,它会每隔2秒同步一次,同步时会排除掉.git、.idea、.vscode、vendor、runtime、*.log等目录,这样可以避免同步过多无用的文件。

注意当前前提是镜像内已安装好rsync包,如果没有安装,可以安装一下。

容器内的程序启动命令也修改为sh /app/rsync/cron.sh &&php bin/swoft http:start,这样容器内的程序会先同步一次,然后再启动swoft程序。

这样,容器内的程序就能直接访问宿主机的目录了,启动时间也大幅度缩短。直接从几分钟变成几秒钟,虽然还需要几秒钟但这个速度足够可以进行本地的开发调试了。

怎么说呢?也算是曲线救国了

总结

通过容器文件挂载的方式启动swoole程序,启动时间长达2分钟,这在代码调试的时候简直不能忍受。

通过实验,我们可以得出结论,在容器内读取文件,需要经过docker守护进程、文件系统层、内核等多个步骤,耗时比直接在宿主机上读取文件要多。

既然这样,我们直接通过容器文件来启动程序不就好了吗?

通过定时同步的方式,我们可以将宿主机的user_service目录同步到容器的user_service2目录,这样容器内的程序就能直接访问宿主机的目录了。

这样,容器内的程序就能直接访问宿主机的目录了,启动时间也大幅度缩短。

相关推荐
编程星空20 分钟前
css主题色修改后会多出一个css吗?css怎么定义变量?
开发语言·后端·rust
程序员侠客行1 小时前
Spring事务原理 二
java·后端·spring
sjsjsbbsbsn2 小时前
Spring Boot定时任务原理
java·spring boot·后端
计算机毕设指导62 小时前
基于Springboot学生宿舍水电信息管理系统【附源码】
java·spring boot·后端·mysql·spring·tomcat·maven
计算机-秋大田2 小时前
基于Spring Boot的兴顺物流管理系统设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·spring·课程设计
羊小猪~~4 小时前
MYSQL学习笔记(九):MYSQL表的“增删改查”
数据库·笔记·后端·sql·学习·mysql·考研
豌豆花下猫4 小时前
Python 潮流周刊#90:uv 一周岁了,优缺点分析(摘要)
后端·python·ai
橘猫云计算机设计4 小时前
基于SSM的《计算机网络》题库管理系统(源码+lw+部署文档+讲解),源码可白嫖!
java·数据库·spring boot·后端·python·计算机网络·毕设
熬夜苦读学习5 小时前
Linux文件系统
linux·运维·服务器·开发语言·后端
坚定信念,勇往无前6 小时前
Spring Boot 如何保证接口安全
spring boot·后端·安全