Docker:关于 Dockerfile 编写优化的一些笔记整理
写在前面
- 分享一些
Dickerfile
构建镜像优化方式的笔记 - 理解不足小伙伴帮忙指正
对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是对大众理想的懦弱回归,是随波逐流,是对内心的恐惧 ——赫尔曼·黑塞《德米安》
简单介绍
在 Docker 中,常用的自定义构建新镜像的方式有两种:
- 通过当运行的容器来构建新的镜像
- 通过
Dockerfile
文件依托基础镜像来构建新的镜像
不管是那种方式,自定义镜像
的原理都是一样,通过镜像的分层设计,创建读写层
,修改配置
,重新打包
这里和小伙伴们分享一些 Dockerfile
构建自定义镜像的优化方式,所谓优化,也可以理解为相对较优的构建方式,对于 第一种,我们这里简单介绍
通过当运行的容器来构建新的镜像,一般在运行的镜像中做一些预制的操作,比如内网环境没有依赖库,没办法直接拉取需要的依赖,我们可以在有网络的环境下拉取对应的依赖,然后做成有依赖的基础镜像。
比如一个 python 镜像,我们要在内网中使用,但是内网环境没有 pip
源,所以我们只能把对应的包先在外网环境下载做成镜像。
运行的容器来构建新的镜像
┌──[root@vms100.liruilongs.github.io]-[~]
└─$docker run -d python python -m http.server 33333
009033ff4c0155f81647b857c0bf8975ee750a13d7aa2584638af032aafa758b
然后进入容器下载相关的依赖包,之后生成镜像导出
┌──[root@vms100.liruilongs.github.io]-[~]
└─$docker commit bcdd82ca5b48 my-python:latest
sha256:cb7c9965c541dfc794f78eb06ae1c4af0c77bb87c92e5e6e768c7770eb61a5bb
┌──[root@vms100.liruilongs.github.io]-[~]
└─$docker save my-python:latest -o ./my-python.tar
在操作上有些繁琐,使用 Dockerfile
的方式可能方便一点
┌──[root@vms100.liruilongs.github.io]-[~]
└─$docker build -<<EOF
> FROM python
> RUN python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
> RUN python -m pip install psycopg2
> EOF
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM python---> a5d7930b60cc
Step 2/3 : RUN python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple---> Running in 88140ad45551
Writing to /root/.config/pip/pip.conf
Removing intermediate container 88140ad45551---> df41fddd2cd2
Step 3/3 : RUN python -m pip install psycopg2---> Running in 1eddfbf7fa58
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting psycopg2Downloading https://pypi.tuna.tsinghua.edu.cn/packages/89/d6/cd8c46417e0f7a16b4b0fc321f4ab676a59250d08fce5b64921897fb07cc/psycopg2-2.9.5.tar.gz (384 kB)
Building wheels for collected packages: psycopg2Building wheel for psycopg2 (setup.py): started
.....14/44f32ab3b3f40f2e9a1a9ab8281a40ff4a911a930121c928b1
Successfully built psycopg2
Installing collected packages: psycopg2
.......
Removing intermediate container 1eddfbf7fa58---> 8791cb1dc692
Successfully built 8791cb1dc692
┌──[root@vms100.liruilongs.github.io]-[~]
└─$
忘记打标签了。这里我们手动打一下。可以在 build
的时候通过 -t
命令指定
┌──[root@vms100.liruilongs.github.io]-[~]
└─$docker tag 8791cb1dc692 my-python:latest
┌──[root@vms100.liruilongs.github.io]-[~]
└─$docker images my-python
REPOSITORY TAG IMAGE ID CREATED SIZE
my-python latest 8791cb1dc692 8 minutes ago 927MB
Dockerfile 自定义镜像
构建常用指令温习
FROM
:基础镜像RUN
:制作镜像时执行的命令,可以有多个,每个命令一层ADD
:复制文件到镜像,自动解压 (文件类型为: tar.gz 或 tar.bz2)COPY
:复制文件到镜像,不解压MAINTAINER
:镜像创建者信息EXPOSE
:开放的端口ENV
:设置变量WORKDIR
:定义容器默认工作目录CMD
: 容器启动时执行的命令,仅可以有一条CMD
.ENTRYPOINT
:类似CMD
指令的功能,用于为容器指定默认运行程序,从而使得容器像是一具单独的可执行程序
一些需要注意的事项:
-
当
docker run
命令中声明了参数时,Docker 守护程序会忽略 CMD 命令。 -
与
CMD
不同的是,由ENTRYPOINT
启动的程序不会被docker run
命令行指定的参数所覆盖,而且,这些命令行参数会被当作参数传递给ENTRYPOINT
指定的程序。不过,docker run
命令的–entrypoint
选项的参数可覆盖ENTRYPOINT
指令指定的程序 -
Dockfile
中,如果没有使用CMD
指定启动命令,则会继承上一个镜像的默认启动命令;CMD
容器的默认启动命令,有且只能有一条
;
根据Dockerfile
生成新的镜像命令中,build
创建新的镜像;-t
指定新镜像的名字和标签;.
指定Dockerfile
文件所在的目录
docker build -t imagename:latest Dockerfile所在目录
容器和镜像之间的主要区别在于顶部 可写层
。对容器的所有添加新数据或修改现有数据的写入都存储在此 可写层
中。删除容器时,可写层也会被删除。基础镜像保持不变。
这里利用了 写时复制
技术(COW
,copy on write) , 对于开发的小伙伴,可以结合 享元设计模式
理解,对于运维的小伙伴,可以结合 Openstack
组件 Glance
原理来理解
用通俗的话讲,当修改时,会把数据复制到容器层修改。当新增的时候直接在 容器层新增,当删除时,会屏蔽镜像层。
Docker 通过读取给定的指令来自动构建镜像。遵循特定的格式和指令集,其中的 每一条指令在容器镜像中创建一个层。这些层是堆叠的,每个层都是与前一层相比的变化的增量
这里我们以 redis:7
这个官方镜像为例,看看一个标准的 Dockerfile
如何书写,可以看到镜像构建了 16 层。
┌──[root@vms107.liruilongs.github.io]-[/etc/systemd]
└─$docker history --human=true redis:7
IMAGE CREATED CREATED BY SIZE COMMENT
19c51d4327cf 6 weeks ago /bin/sh -c #(nop) CMD ["redis-server"] 0B
<missing> 6 weeks ago /bin/sh -c #(nop) EXPOSE 6379 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B
<missing> 6 weeks ago /bin/sh -c #(nop) COPY file:e873a0e3c13001b5… 661B
<missing> 6 weeks ago /bin/sh -c #(nop) WORKDIR /data 0B
<missing> 6 weeks ago /bin/sh -c #(nop) VOLUME [/data] 0B
<missing> 6 weeks ago /bin/sh -c mkdir /data && chown redis:redis … 0B
<missing> 6 weeks ago /bin/sh -c set -eux; savedAptMark="$(apt-m… 32MB
<missing> 6 weeks ago /bin/sh -c #(nop) ENV REDIS_DOWNLOAD_SHA=06… 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ENV REDIS_DOWNLOAD_URL=ht… 0B
<missing> 6 weeks ago /bin/sh -c #(nop) ENV REDIS_VERSION=7.0.8 0B
<missing> 7 weeks ago /bin/sh -c set -eux; savedAptMark="$(apt-ma… 4.13MB
<missing> 7 weeks ago /bin/sh -c #(nop) ENV GOSU_VERSION=1.14 0B
<missing> 7 weeks ago /bin/sh -c groupadd -r -g 999 redis && usera… 329kB
<missing> 7 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 7 weeks ago /bin/sh -c #(nop) ADD file:e2398d0bf516084b2… 80.5MB
┌──[root@vms107.liruilongs.github.io]-[/etc/systemd]
└─$docker history --human=true redis:7 | wc -l
17
涉及 两个 Dockerfile
文件构建的镜像
基础镜像构建
FROM scratch
ADD rootfs.tar.xz /
CMD ["bash"]
reids 镜像构建
FROM debian:bullseye-slim# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r -g 999 redis && useradd -r -g redis -u 999 redis# grab gosu for easy step-down from root
# https://github.com/tianon/gosu/releases
ENV GOSU_VERSION 1.16
RUN set -eux; \savedAptMark="$(apt-mark showmanual)"; \apt-get update; \apt-get install -y --no-install-recommends ca-certificates dirmngr gnupg wget; \rm -rf /var/lib/apt/lists/*; \dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \export GNUPGHOME="$(mktemp -d)"; \gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \gpgconf --kill all; \rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \apt-mark auto '.*' > /dev/null; \[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \chmod +x /usr/local/bin/gosu; \gosu --version; \gosu nobody trueENV REDIS_VERSION 7.0.9
ENV REDIS_DOWNLOAD_URL http://download.redis.io/releases/redis-7.0.9.tar.gz
ENV REDIS_DOWNLOAD_SHA f77135c2a47c9151d4028bfea3b34470ab4d324d1484f79a84c6f32a3cfb9f65RUN set -eux; \\savedAptMark="$(apt-mark showmanual)"; \apt-get update; \apt-get install -y --no-install-recommends \ca-certificates \wget \\dpkg-dev \gcc \libc6-dev \libssl-dev \make \; \rm -rf /var/lib/apt/lists/*; \\wget -O redis.tar.gz "$REDIS_DOWNLOAD_URL"; \echo "$REDIS_DOWNLOAD_SHA *redis.tar.gz" | sha256sum -c -; \mkdir -p /usr/src/redis; \tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1; \rm redis.tar.gz; \\
# disable Redis protected mode [1] as it is unnecessary in context of Docker
# (ports are not automatically exposed when running inside Docker, but rather explicitly by specifying -p / -P)
# [1]: https://github.com/redis/redis/commit/edd4d555df57dc84265fdfb4ef59a4678832f6dagrep -E '^ *createBoolConfig[(]"protected-mode",.*, *1 *,.*[)],$' /usr/src/redis/src/config.c; \sed -ri 's!^( *createBoolConfig[(]"protected-mode",.*, *)1( *,.*[)],)$!\10\2!' /usr/src/redis/src/config.c; \grep -E '^ *createBoolConfig[(]"protected-mode",.*, *0 *,.*[)],$' /usr/src/redis/src/config.c; \
# for future reference, we modify this directly in the source instead of just supplying a default configuration flag because apparently "if you specify any argument to redis-server, [it assumes] you are going to specify everything"
# see also https://github.com/docker-library/redis/issues/4#issuecomment-50780840
# (more exactly, this makes sure the default behavior of "save on SIGTERM" stays functional by default)\
# https://github.com/jemalloc/jemalloc/issues/467 -- we need to patch the "./configure" for the bundled jemalloc to match how Debian compiles, for compatibility
# (also, we do cross-builds, so we need to embed the appropriate "--build=xxx" values to that "./configure" invocation)gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \extraJemallocConfigureFlags="--build=$gnuArch"; \
# https://salsa.debian.org/debian/jemalloc/-/blob/c0a88c37a551be7d12e4863435365c9a6a51525f/debian/rules#L8-23dpkgArch="$(dpkg --print-architecture)"; \case "${dpkgArch##*-}" in \amd64 | i386 | x32) extraJemallocConfigureFlags="$extraJemallocConfigureFlags --with-lg-page=12" ;; \*) extraJemallocConfigureFlags="$extraJemallocConfigureFlags --with-lg-page=16" ;; \esac; \extraJemallocConfigureFlags="$extraJemallocConfigureFlags --with-lg-hugepage=21"; \grep -F 'cd jemalloc && ./configure ' /usr/src/redis/deps/Makefile; \sed -ri 's!cd jemalloc && ./configure !&'"$extraJemallocConfigureFlags"' !' /usr/src/redis/deps/Makefile; \grep -F "cd jemalloc && ./configure $extraJemallocConfigureFlags " /usr/src/redis/deps/Makefile; \\export BUILD_TLS=yes; \make -C /usr/src/redis -j "$(nproc)" all; \make -C /usr/src/redis install; \\
# TODO https://github.com/redis/redis/pull/3494 (deduplicate "redis-server" copies)serverMd5="$(md5sum /usr/local/bin/redis-server | cut -d' ' -f1)"; export serverMd5; \find /usr/local/bin/redis* -maxdepth 0 \-type f -not -name redis-server \-exec sh -eux -c ' \md5="$(md5sum "$1" | cut -d" " -f1)"; \test "$md5" = "$serverMd5"; \' -- '{}' ';' \-exec ln -svfT 'redis-server' '{}' ';' \; \\rm -r /usr/src/redis; \\apt-mark auto '.*' > /dev/null; \[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \find /usr/local -type f -executable -exec ldd '{}' ';' \| awk '/=>/ { print $(NF-1) }' \| sort -u \| xargs -r dpkg-query --search \| cut -d: -f1 \| sort -u \| xargs -r apt-mark manual \; \apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \\redis-cli --version; \redis-server --versionRUN mkdir /data && chown redis:redis /data
VOLUME /data
WORKDIR /dataCOPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]EXPOSE 6379
CMD ["redis-server"]
运行此镜像并创建一个容器时,我们实际上在底层之上添加了一个新的可写层(容器层)。对正在运行的容器所做的所有更改(例如写入新文件、修改现有文件和删除文件)都将写入此可写容器层。
可以看到上面的 Dockerfile
文件很庞大,通过这个文件我们来总结一些 在编写时需要注意的地方
Dockerfile 编写优化
使用小的基础镜像
在上面的 Dockerfile
中基础镜像使用有FROM debian:bullseye-slim
, FROM scratch
scratch
是一个空镜像,一般用不到,在构建最基础的镜像的时候会用到。debian:bullseye-slim
是一个debian
系统的bullseye
版本的精简版镜像。看的出来,使用的镜像很小。
使用较小的镜像可以更快地构建、推送和拉取
镜像。往往更安全,因为只包含运行应用程序所需的必要库和系统依赖项
。 尤其是在 CI/CD
等流水线中,庞大的 基础镜像
在每个环节都要消耗一些时间,从而使流水线的时间变得很长。镜像之间的区别主要在于底层的操作系统
镜像选择类型
Official Image
:官方镜像,或者叫标准镜像,一般由官方维护的镜像,它是正确的选择,但是可能不是最优的。镜像基于最新的稳定 Debian 操作系统发行版,上面的 有 Dockerfile
构建完成的镜像即为 redis:7 的一个官网镜像。
Debian(bullseye/buster/stretch/jessie)
:不同的 Debian Linux 发行版镜像,jessie(8.0),stretch(9.0) 是比较老旧的版本,buster(10.0) ,bullseye(11.0)为较新的版本
slim
:精简版,它通常会安装运行特定工具所需的最小包
alpine
:基于 Alpine Linux
项目,专门为在容器内部使用而构建的操作系统。相比较 Debian 来说 Alpine 很小很小,但是需要考虑一些时区,兼容性问题。
scratch
: 一个明确的空镜像,特别是用于建立 “从头开始” 的镜像。
在选择最小基础镜像的同时,要尽量避免安装不必要的软件包
指令链式运行
可以很明显的发现,上面 Dockerfile
中的 RUN
指令很长
这是由于每个指令都会创建一个可缓存单元并构建一个新的中间镜像层。所以可以通过链接所有命令来避免过多层级。此外,尽量避免链接过多的可缓存 RUN 命令,因为这会导致创建大型缓存并最终导致缓存突发。
RUN set -eux; \\savedAptMark="$(apt-mark showmanual)"; \apt-get update; \apt-get install -y --no-install-recommends \ca-certificates \wget \\dpkg-dev \gcc \libc6-dev \libssl-dev \make \; \rm -rf /var/lib/apt/lists/*; \\wget -O redis.tar.gz "$REDIS_DOWNLOAD_URL"; \..................
变动的指令放到最后
Dockerfile
编写往往需要重复的构建,每个层的构建都比较耗时,但是 Docker
为了加快后续构建的速度,会自动缓存每一层的构建,当对应的层指令以及前面的指令没有发生变动时,会直接使用缓存。当步骤对应指令更改时,缓存不仅会针对该特定步骤失效,还会使所有后续步骤失效。
所以 始终将最常更改的指令放在末尾
。 会提高构建速度.
FROM python:3.9-slimWORKDIR /app
COPY requirements.txt .
RUN pip install -r /requirements.txt
COPY app.py .
首选数组而不是字符串语法
我们可以通过两种不同的方式编写 最后的进程启动 命令 ENTRYPOINT
- 数组:
ENTRYPOINT ["python","-m","http.server","33333"]
- 字符串:
ENTRYPOINT "python -m http.server 33333"
数组形式是首选。这是因为使用字符串形式会导致 Docker 使用 bash 运行您的进程
,这无法正确处理信号
。由于大多数 shell 不处理子进程的信号,因此如果使用 shell 格式,CTRL-C(生成 SIGTERM)可能不会停止子进程。
COPY 而不是 ADD
如果有多个步骤使用上下文中的不同文件,请 单独复制
它们,而不是一次全部复制。这可确保每个步骤的生成缓存仅失效
,并在特别需要的文件发生更改时强制重新运行该步骤。
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/
使用 .dockerignore
.dockerignore
文件的作用类似于 git 工程中的 .gitignore
。不同的是 .dockerignore 应用于 docker 镜像的构建,它存在于 docker 构建上下文的根目录,用来忽略不需要打入镜像的文件
.dockerignore
文件的写法和 .gitignore
类似,支持正则和通配符,具体规则如下:
- 每行为一个条目;
- 以 # 开头的行为注释;
- 空行被忽略;
- 构建上下文路径为所有文件的根路径;
.git
script
static
!README*.md
从 stdin 标准输入构建
Docker 引擎能够通过本地或远程构建上下文通过 stdin 管道传输 Dockerfile 来构建镜像
在 Dockerfile 不需要将文件复制到镜像中(COPY/ADD 将失败)的情况下,省略构建上下文非常有用,并且可以提高构建速度,因为不会将任何文件发送到 Docker 守护程序。适用于单纯的镜像构建
┌──[root@vms107.liruilongs.github.io]-[/etc/systemd]
└─$docker build -<<EOF
> FROM busybox
> RUN echo "hello world"
> EOF
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM busybox
latest: Pulling from library/busybox
5cc84ad355aa: Pull complete
Digest: sha256:5acba83a746c7608ed544dc1533b87c737a0b0fb730301639a0179f9344b1678
Status: Downloaded newer image for busybox:latest---> beae173ccac6
Step 2/2 : RUN echo "hello world"---> Running in c56fb8343c72
hello world
Removing intermediate container c56fb8343c72---> 95bc7e444353
Successfully built 95bc7e444353
┌──[root@vms107.liruilongs.github.io]-[/etc/systemd]
└─$
利用多阶段构建
多阶段构建使我们能够通过利用构建缓存大幅减小最终镜像的大小,而无需努力减少中间层和文件的数量。例如,让我们看一下以下内容:Dockerfile
FROM golang:1.18-alpine AS prebuild# Install tools required for project
RUN go get github.com/golang/dep/cmd/dep
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
RUN go build -o /bin/project# This results in a single layer image
FROM scratch
COPY --from=prebuild /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
可以使用多个语句。每个指令都可以使用不同的基础,并且每个指令都开始构建的新阶段。我们可以有选择地将伪影从一个阶段复制到另一个阶段,在最终镜像中留下我们不想要的所有内容。
#syntax=docker/dockerfile:1.4
FROM … AS build1
COPY –from=app1 . /srcFROM … AS build2
COPY –from=app2 . /srcFROM …
COPY –from=build1 /out/app1 /bin/
COPY –from=build2 /out/app2 /bin/
博文部分内容参考
文中涉及参考链接内容版权归原作者所有,如有侵权请告知
https://www.docker.com/
https://docs.docker.com/engine/reference/builder/
https://www.docker.com/blog/dockerfiles-now-support-multiple-build-contexts/
https://blog.devgenius.io/devops-in-k8s-write-dockerfile-efficiently-37eaedf87163
© 2018-2023 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)
相关文章:

Docker:关于 Dockerfile 编写优化的一些笔记整理
写在前面 分享一些 Dickerfile 构建镜像优化方式的笔记理解不足小伙伴帮忙指正 对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式&#…...

个性化营销:您需要知道的信息
个性化营销在现代企业中风靡一时。我们将剖析您需要了解的有关个性化营销的信息,一起来了解一下吧。 什么是个性化营销? 个性化营销是一种一对一营销形式,它使用实时用户数据和分析来传递品牌信息并针对特定潜在客户。 它与传统营销不同&…...
栈和队列的相互实现
文章目录一、用栈实现队列入队:出队:Java代码实现:二、用队列实现栈入栈:出栈:Java代码实现:附:C版代码1、用栈实现队列2、用队列实现栈栈(stack):先进后出&a…...

iTab新标签页重磅更新 |这些功能绝对有你想要的新体验!
01 写在前面 csdn的朋友们,你好哦,我是iTab 插件的独立开发者,今天给大家安利一下我做的这款桌面插件。 首先要告诉大家一个好消息: 最近iTab新标签页被Edge 浏览器商店官方热门🔥推荐啦。 在此,特别感谢…...

【改机教程】iOS系统去除小黑条,改拍照声、拨号音、键盘音,不用越狱,支持所有机型
大家好,上次给大家分享了几个iOS系统免越狱改机教程 今天带来最新的教程,这次修改利用的是同一个漏洞,由外网大神 tamago 开发,国内大神冷风 进行汉化和优化 可以修改的地方包括 去除底部小黑条 dock栏透明 桌面文件夹透明 桌面…...
Android10开机向导中复用设置中的Wifi界面
很多时候我们需要定制开机向导,在开机向导界面我们一般会实现联网和设置时间等功能,考虑复用与稳定性问题,我们最好复用设置中的WiFi设置和日期设置。但是设置中的wifi设置界面默认是没有下一步按钮的,这会让用户感觉很奇怪。在以…...

川农机械专业小伙转行Java开发,年薪20w
本期学员就业故事,知了姐邀请到一位“特别”的同学,一位从知了堂就业成功近两年的学员再度接受我们的采访。 来自四川农业大学的曾同学,一个本来学机械开挖掘机的粗犷男人,因为不断地努力学习编程,最终成为一个性格闷…...
华为OD机试题 - 打印文件(JavaScript)| 机考必刷
更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解:打印文件题目输入输出示例一输入输出示例二输入输出Code代码解析…...
免费常用API大全,程序员必备
淘宝接口 淘宝开放平台 http://open.taobao.com/?spma219a.7395905.1.1.YdFDV6 APISpace 生活常用 今天吃什么:随机返回一顿美味食物,解决你今天吃什么的难题。 星座查询:根据日期或星座名称,查询星座详细信息,包…...

MySQL主从复制,读写分离
目录 一、MySQL主从复制介绍 MySQL复制过程分成三步 二、主库配置master 1、步骤1 2、第二步:重启Mysql服务 3、第三步:登录Mysql数据库,执行下面SQL 4、第四步:登录Mysql数据库,执行下面SQL,记录下结果中File和…...

什么是UEFI签名认证?UEFI签名有什么好处?
为了防御恶意软件攻击,目前市面上所有电脑设备启动时默认开启安全启动(Secure Boot)模式。安全启动(Secure Boot)是UEFI扩展协议定义的安全标准,可以确保设备只使用OEM厂商信任的软件启动。UEFI签名认证就是对运行在 UEFI 系统下的 efi 驱动和通过 UEFI …...

案例14-课程推送页面逻辑整理--vue
目录一级目录二级目录三级目录一、背景介绍二、问题分析问题1:逻辑边界不清晰,封装意识缺乏问题问题2:展示效果上的问题三、解决过程问题一 代码结构混乱问题解决问题二 代码结构混乱问题解决问题三 展示效果上的细微问题四、总结一级目录 二…...

5大GPU厂商共建 | openKylin社区GPU SIG首次例会召开!
3月8日,openKylin社区GPU SIG首次例会以线上形式召开。此次会议由长沙景美集成电路设计有限公司、摩尔线程智能科技(北京)有限责任公司、格兰菲智能科技有限公司、象帝先计算技术(重庆)有限公司等GPU厂商的多位SIG Mai…...
SpringBoot读取配置文件
目录一、简介1、SpringBoot 中常用读取配置方法2、 ConfigurationProperties和Value的区别二、使用 ConfigurationProperties 读取配置三、使用 Value 读取配置一、简介 在日常开发使用 SpringBoot 框架时,经常有一些配置信息需要放置到配置文件中,我们…...

51驱动NRF24L01通信,NRF24L01与TTL转NRF24L01模块通信
51驱动NRF24L01通信,NRF24L01与TTL转NRF24L01模块通信NRF24L01一、简介二、引脚功能描述程序设计一、对 24L01 的程序编程的基本思路如下:二、Tx 与 Rx 的配置过程1、Tx 模式初始化过程:2、Rx 模式初始化过程:三、基本程序函数通信…...
C++友元
欢迎来观看温柔了岁月.c的博客 目前 设有C学习专栏 C语言项目专栏 数据结构与算法专栏 目前主要更新C学习专栏,C语言项目专栏不定时更新 待C专栏完毕,会陆续更新C项目专栏和数据结构与算法专栏 一周主要三更,星期三,星期五&#x…...

MySQL内置函数
文章目录日期函数字符串函数数学函数其他函数日期函数 获取年月日: mysql> select current_date(); ---------------- | current_date() | ---------------- | 2023-02-01 | ---------------- 1 row in set (0.00 sec)获得时分秒: mysql> se…...

mysql数据库之innodb存储引擎架构之内存架构
一、逻辑存储结构 mysql5.5版本开始,默认使用innodb存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发中使用非常广泛。 架构图(左侧为内存架构,右侧为磁盘架构) 二、 内存架构。 1、缓冲…...

Vue:(三十五)路由vue-router
今天,我们开始学习vue中一个很关键的知识点,路由。理解vue的一个插件库,专门用来实现SPA应用单页web应用整个应用只有一个完整的页面点击页面中的导航连接不会刷新页面,只会做页面的局部更新数据需要通过ajax请求获取下来…...

Dynabook笔记本电脑无法开机怎么重装新系统?
Dynabook笔记本电脑无法开机怎么重装新系统?有用户使用Dynabook笔记本电脑出现了无法正常开机的情况。遇到这样的问题是我们的电脑系统出现了损坏,可以尝试进行系统修复。如果无法修复的话,就需要进行系统重装了。以下为大家带来Dynabook笔记…...

idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...