Dockerfile编写实践篇

Docker通过一种打包和分发的软件,完成传统容器的封装。这个用来充当容器分发角色的组件被称为镜像。Docker镜像是一个容器中运行程序的所有文件的捆绑快照。当使用Docker分发软件,其实就是分发这些镜像,并在接收的机器上创建容器。镜像在Docker生态系统中是可交付的基本单位。
镜像的创建方式有很多种,如使用docker commit命令根据当前容器的更改创建一个新的镜像,也可以使用docker save保存镜像或使用docker export导出镜像。但是,最常用、最推荐的方式,还是使用Dockerfile定义镜像,然后使用docker build命令创建镜像。
Dockerfile使用基本的基于DSL语法的指令来定义一个Docker镜像,之后就可以使用docker build命令基于该Dockerfile中的指令构建一个新的镜像。Dockerfile具有信息表达性,且易于理解,这些要归功于Dockerfile支持注释的简洁语法。Dockerfile构建程序自身使用缓存技术来解决快速开发和迭代带来的问题。这个构建过程可追踪且可重用。它们能够很简单地和现有的构建系统、持续构建和集成工具一起工作。

Dockerfile编写及使用

Dockerfile是一个文件,它由构建镜像的指令组成,用户可以使用Dockerfile来快速创建自定义的镜像。指令由Docker镜像构建者自上而下排列,能够被用来修改镜像的任何信息。
Dockerfile具有信息表达性,且易于理解,这些都要归功于Dockerfile支持注释的简洁语法。开发者可以使用任何版本控制工具来跟踪Dockerfile文件的变动。维护多个版本的镜像就和管理多个Dockerfile一样简单。

基本结构

Dockerfile由一系列指令和其参数组成,并且支持以#开头的注释行。每条指令都建议使用大写字母,且后面跟随一个参数。Dockerfile中指令的基本使用可以参考笔者Dockerfile指令大全一文,更详细的指令使用说明建议参考官网Dockerfile reference一文。
Dockerfile中的指令会按顺序从上到下执行,所以应根据需要合理安排指令的顺序。Docker大体上按照如下流程执行Dockerfile中的指令:
(1) Docker从基础镜像运行一个容器。
(2) 执行一条指令,对容器做出修改。
(3) 执行类似docker commit的操作指令,提交一个新的镜像层。
(4) Docker再基于刚提交的镜像运行一个新容器。
(5) 执行Dockerfile中的下一条指令,直到所有指令都执行完毕。
如果Dockerfile由于某些原因(如指令执行失败)没有正常结束,那么仍将得到一个可用的镜像,只是最后一条指令执行失败,这对日常的开发和调试是有帮助的。
一般而言, Dockerfile 主体内容分为四部分:基础镜像信息、制作者信息、镜像操作指令和容器启动时执行指令。但是,考虑到容器安全,还有必要对其进行安全加固,如配置用户或用户组等。此外,构建基础镜像时,有时还需要考虑继承的镜像需要执行的一些默认操作,如创建一组默认的用户和用户组供子镜像使用。

基础镜像信息

在编写Dockerfile文件时,第一个要考虑的事情就是创建一个基础镜像供他人使用,或是基于基础镜像去构建一个新的镜像。这些诉求都可以通过FROM指令实现。一般情况下,FROM指令是一个Dockerfile的第一条指令。
FROM指令用来指定一个父镜像,以开始新的构建阶段。Dockerfile支持在一个文件中使用多个FROM指令,以创建多个镜像。FROM的指令格式如下:

FROM [–platform=] [:|@|-] [AS ]

其中,–platform参数用来指定镜像应用的平台,主要应用于多平台场景,如linux/amd64或linux/arm64等;tag和digest用来指定需要引用的镜像的tag,如果不指定,则使用latest。如果一个Dockerfile中需要创建多个镜像,会使用到多个FROM。如果下一个FROM指令中需要使用上一个FROM指令构建的镜像,可以现在上一个FROM指令中定义别名,也即使用AS 。
某些场景下,不需要父镜像,如构建一个操作系统镜像,这时可以使用"FROM scratch"这个指令来表示不需要父镜像。

制作者信息

对于一个镜像,无论是镜像开发者还是镜像的继承者,有时需要直到这个镜像的作者相关的信息。对于这个需求,可以通过MAINTAINER指令或LABEL指令来指定镜像作者信息。这里不推荐使用MAINTAINER指令,更推荐LABEL指令。相比MAINTAINER指令,LABEL指令会将这部分信息保存到元数据,这个就可以通过docker image inspect命令方便的查询该部分信息。示例如下:

$ docker image inspect --format='{{json .Config.Labels}}' target-image-name-or-id

使用LABEL记录镜像制作者信息的示例如下:

# Base image to use, this must be set as the first line
FROM ubuntu:18.04# Maintainer: docker user <docker_user@email.com>
LABEL maintainer docker_user<docker_user@email.com>...

上述示例中,首先使用FROM指令指明所基础镜像的名称,接下来就是使用LABEL指令说明制作者信息。这里,制作者信息并不是一个必须的信息,但建议都添加上。注意,这个Dockerfile只是编写了一部分,还不算一个完整的Dockerfile。

镜像操作指令

Dockerfile文件的主体部分就是基于基础镜像的进一步操作,如使用RUN指令执行特定的命令,使用ADD/COPY指令将特定的产物复制到容器文件系统,等等。如这里使用RUN指令下载软件包,然后将构建上下文中的基于Java源码生成的JAR包复制到容器文件系统的示例如下:

# Base image to use, this must be set as the first line
FROM ubuntu:18.04# Maintainer: docker user <docker_user@email.com>
LABEL maintainer docker_user<docker_user@email.com>RUN apt-get update \
&& apt-get install -y libsnappy-dev \ 
&& rm -rf /var/cache/apt...

上述示例中,使用RUN指令下载特定的软件包,并在下载完毕后,删除了遗留的缓存。每运行一条RUN指令,镜像添加新的一层,所以这里是将在多个RUN指令执行的命令合并成了一行。同样的,到这个阶段,Dockerfile也只是编写了一部分,还不算一个完整的Dockerfile。

容器启动时执行指令

容器启动时,可以指定默认执行的命令或指定默认的可执行文件。这对于提供后端服务的应用来说很有必要。如需要在容器中运行一个Java Web应用,可以在容器启动时指定可执行的shell脚本,以运行一个Java应用。对于这个需求,可以通过CMD指令或ENTRYPOINT指令来实现。
CMD指令和ENTRYPOINT指令均用来定义启动容器时需要执行的命令,推荐优先使用ENTRYPOINT指令。对于同时出现CMD指令和ENTRYPOINT指令的场景,遵循如何规则:

No ENTRYPOINTENTRYPOINT exec_entry p1_entryENTRYPOINT [“exec_entry”, “p1_entry”]
No CMDerror, not allowed/bin/sh -c exec_entry p1_entryexec_entry p1_entry
CMD [“exec_cmd”, “p1_cmd”]exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry exec_cmd p1_cmd
CMD exec_cmd p1_cmd/bin/sh -c exec_cmd p1_cmd/bin/sh -c exec_entry p1_entryexec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

上表中,对同时存在CMD指令和ENTRYPOINT指令的场景,要根据指令使用的exec模式或shell模式,进行如下划分:如果ENTRYPOINT指令是shell模式,则会忽略CMD指令;如果ENTRYPOINT指令是exec模式,CMD指令是exec模式,则CMD指令的命令失效,参数会追加到ENTRYPOINT指令上;如果ENTRYPOINT指令是exec模式,CMD指令是shell模式,则先执行ENTRYPOINT指令,再执行CMD指令。
使用示例如下:

# Base image to use, this must be set as the first line
FROM ubuntu:18.04# Maintainer: docker user <docker_user@email.com>
LABEL maintainer docker_user<docker_user@email.com># COPY and rename application jar file to the container's filesystem
COPY app-*.jar /app.jar# EXECUTE jar file
ENTRYPOINT ["java", "-jar", "/app.jar"]

上述示例中,使用COPY指令将构建上下文中的软件包复制到容器的文件系统。然后,使用ENTRYPOINT声明容器启动时需要执行的命令。这样当容器启动时,就可以运行JAR包,从而启动这个应用。到这个阶段,一个基本的Dockerfile文件就编写完了。

用户权限加固

默认情况下,容器以root用户运行,root用户权限太高,对于业务应用来说,存在安全风险,如常见的容器逃离手段,都是依赖于获得容器的root权限。对于用户权限分配来说,最佳实践就是尽可能地消减用户的特权,也即遵循最小权限原则。当一个Docker用户创建容器时,是能够覆盖镜像的默认配置的。因此,并不存在某个方法来完全防止容器以root用户运行。但是,镜像的制作者可以创建非root用户,并以非root用户来执行应用。
Dockerfile通过提供USER指令、docker run指令或docker create指令来设置用户和用户组,从而限制用户的访问。推荐使用USER指令设置用户和用户组,使用docker run命令后docker create指令来更改设置用户和用户组。这里重点介绍下使用USER指令设置用户和用户组。示例如下:

# Base image to use, this must be set as the first line
FROM ubuntu:18.04# Maintainer: docker user <docker_user@email.com>
LABEL maintainer docker_user<docker_user@email.com># Creat application directory
RUN mkdir -p /opt/app# COPY and rename application jar file and set new owner and mode to the container's filesystem
COPY --chown=1000:1000 --chmod=644 app-*.jar /opt/app/app.jar# SET new user id and group id
USER 1000:1000# EXECUTE jar file
ENTRYPOINT ["java", "-jar", "/opt/app/app.jar"]

这里,要注意把握设置用户UID和GID的时机。如果过早地设置,可能导致当前用户没有权限完成Dockerfile中的其他指令。如需要执行一个root目录下的文件,但是先使用USER指令设置了用户UID和GID,则会导致当前用户权限过低,导致root目录下的文件执行失败。示例如下:

# Base image to use, this must be set as the first line
FROM ubuntu:18.04# SET new user id and group id
USER 1000:1000# Creat application directory
RUN mkdir -p /opt/app

因为/opt目录属于root用户,所以不能在UID是1000的用户中在/opt目录创建新目录。
注意,如果是编写一个基础镜像的Dockerfile文件,则不建议遵循最小权限原则,而是尽量提供root权限,让业务镜像去考虑最小权限的问题。

注入下游镜像在构建时发生的操作

在构建基础镜像时,有时需要考虑继承的镜像需要执行的一些默认操作,如创建一组默认的用户和用户组供子镜像使用。对于这个需求,可以通过ONBUILD指令实现。ONBUILD指令不会在包含它们的Dockerfile被构建时执行。这些指令会被记录在生成镜像的元数据ContainerConfig.OnBuild下。这个元数据会一直被保留,直到生成的镜像被另外的Dockerfile作为基础镜像。这样,当子镜像构建时,ONBUILD后跟随的指令将会在FROM指令后,下一条指令前被执行。示例如下:

# Base image to use, this must be set as the first line
FROM ubuntu:18.04# Maintainer: docker user <docker_user@email.com>
LABEL maintainer docker_user<docker_user@email.com># ONBUILD command 
ONBUILD RUN echo "This is an ONBUILD trigger."  # 其他的设置或命令
...

编写Dockerfile文件

介绍完了编写Dockerfile的基本结构,接下来就根据真实的业务场景,介绍下如何编写特定于业务场景的Dockerfile文件。由于笔者目前主要参与的是Java Web后端应用的开发,所以这里重点介绍下一个商用Java Web后端应用的Dockerfile的编写示例,其他场景的Dockerfile编写示例还请自行学习。
首先是基础镜像的选择。对于一个业务服务来说,为了保证业务服务不依赖内核的操作系统,在基础镜像的选择上,可以使用一个简化版本的操作系统。这个操作系统是一个精简版本,只包含操作系统自带的一些功能。一些软件包的安装,如python、jdk等,均留给业务镜像去按需补充。接着是JRE的版本选择。注意,在Java应用运行时,只有JRE即可。目前,主流的Java版本还是Java 8和Java 11。不同公司根据业务需要选择合适的JDK版本。对于基于Java的业务服务来说,可以使用精简操作系统+JRE构成的基础镜像。
Java Web后端应用依赖的基础镜像已经选择完毕,接下来就是把Java Web后端应用依赖的工具安装到操作系统。对于不同的Java应用,可能会使用到一些实用工具。如对网络服务来说,可能使用nmap来进行网络发现。
考虑完Java Web后端应用依赖的工具,下面要考虑的就是Java Web后端应用的lib的依赖。使用Maven进行打包时,默认情况下不会将依赖包打入jar中。但是,可以通过配置Maven插件来将依赖包和项目本身打包到一个jar文件中。这样,无论是二方库,还是三方库,这部分依赖都会作为Java Web后端应用构建产物的一部分。对于基于Spring Boot构建的Java Web后端应用来说,其依赖会自动打包到jar文件中,无需特殊处理。
对于Java Web后端应用来说,容器启动后,需要启动一个进程在运行Java Web后端应用。这个应用会一直活跃,直到容器意外中止或容器正常终止为止。也即是说,容器启动后,要立即执行Java Web后端应用。为了聚合启动Java Web后端应用相关的操作,使用shell脚本来编写启动相关的命令。这样,在容器启动时,只需要执行下shell脚本即可。
编写一个Java Web后端应用的主要事项都已考虑完毕,接下来简单描述下构建上下文的目录结构:

--docker              # docker目录,存储docker镜像制作相关资源
----package             # package目录,存储业务应用打包相关资源
------start.sh            # start.sh,用来启动业务服务
------service.jar         # service.jar,业务服务代码构建的jar
--  app.dockerfile      # app.dockerfile,业务服务的Dockerfile,描述构建镜像的过程

介绍完构建上下文的目录结构,接下来说明下app.dockerfile文件中的内容:

# 使用centos操作系统 + jre(Java 1.8)的基础镜像,镜像仓库并不包含该镜像,可以基于centos镜像制作一个包含jre的镜像
FROM centos_jre_1.8:centos8_jar8# 记录镜像的制作者,方便后期的运维
LABEL maintainer custom_java_web<custom_java_web@email.com># jar包和运行jar包的shell脚本等复制到容器中
## 将构建上下文的package目录下的内容拷贝到/opt/app目录下,注意这里并没有预先在容器的文件系统创建/opt/app/目录
COPY package  /opt/app/  
## 对于可执行文件,在使用前,赋予可执行权限
RUN chmod 544 /opt/app/start.sh \
&& chmod 544 /opt/app/start.jar# 设置ENTRYPOINT来直接执行脚本  
ENTRYPOINT ["/opt/app/start.sh"]

这样,一个Java Web应用的Dockerfile文件就编写完毕了。考虑到安全问题,还有必要调整下用户,使用USER指令改造后的Dockerfile文件如下:

# 使用centos操作系统 + jre(Java 1.8)的基础镜像,镜像仓库并不包含该镜像,可以基于centos镜像制作一个包含jre的镜像
FROM centos_jre_1.8:centos8_jar8# 记录镜像的制作者,方便后期的运维
LABEL maintainer custom_java_web<custom_java_web@email.com># jar包和运行jar包的shell脚本等复制到容器中
## 将构建上下文的package目录下的内容拷贝到/opt/app目录下,注意这里并没有预先在容器的文件系统创建/opt/app/目录
COPY package  /opt/app/  
## 对于可执行文件,在使用前,赋予可执行权限
RUN chown -R 1000:1000 /opt/app \  
&& chmod 644 /opt/app \
&& chmod 544 /opt/app/start.sh \
&& chmod 544 /opt/app/start.jar# 设置用户及用户组
USER 1000:1000# 设置ENTRYPOINT来直接执行脚本  
ENTRYPOINT ["/opt/app/start.sh"]

在ENTRYPOINT指令中,使用exec格式执行可执行文件时,不会启动一个shell进程,docker会直接运行可执行文件,这里是start.sh。需要注意的是,这里的start.sh脚本的第一行应该是#!/bin/sh或者其它希望用来执行脚本的解释器路径。start.sh脚本的示例如下:

#!/bin/bash# 指定JAR包的路径
JAR_PATH="/opt/app/service.jar"# 使用nohup在后台启动JAR包,并将输出重定向到日志文件
nohup java -jar "$JAR_PATH" &

使用docker build命令生成镜像

编写完Dockerfile文件后,接下来就是使用docker build命令构建镜像。docker build命令的基本格式如下:

docker image build [OPTIONS] PATH | URL | -

该命令将读取指定路径下(包括子目录)的Dockerfile,并将该路径下所有数据作为构建上下文(Context)发送给 Docker服务器端。Docker服务端在校验Dockerfile格式通过后,逐条执行其中定义的指令,如果碰到ADD、COPY和RUN指令则会生成一层新的镜像。最终如果创建镜像成功,会返回最终镜像的ID。
在构建过程中会有新层被加入到要产生的镜像中。这不仅意味着开发者能够从任意一步开始创建分支,更重要的是构建过程能够缓存每一步的结果,当运行完几个指令后,如果下一条指令出现问题,构建过程能够在问题被修复后,从同一步重新启动。如果构建过程需要下载资源或包含某些需要消耗大量时间的任务,那么缓存可以起到节省时间的效果。如果需要从零开始构建,可以使用–no-cache选项来禁止缓存的使用。

将执行目录切换到上述目录结构的docker目录下,上述示例的dockerfile文件,就可以使用如下的docker build命令生成镜像:

$ docker build -f app.docerfile -t service-image:202403102100 .

在docker build命令执行完毕后,如果镜像制作者可以看见"Successfully built XXX"字样,则说明镜像构建成功。且会生成一个service-image的镜像,镜像的版本是202403102100。可以使用docker images查看该镜像,或使用docker start命令启动该镜像。

Dockerfile最佳实践

所谓Dockerfile最佳实践,就是从需求出发,来定制适合当前业务场景、高效方便的镜像。首先,要尽量吃透每个指令的含义和执行效果,多编写一些简单的例子进行测试,弄清楚了再撰写正式的Dockerfile文件。此外,Docker Hub官方仓库中提供了大量的优秀镜像和对应的Dockefile,可以通过阅读它们来学习如何撰写高效的Dockerfile。
这里梳理一些实践经验。建议读者在生成镜像过程中,尝试从如下角度进行思考,完善所生成镜像:
(1) 精简镜像用途:尽量让每个镜像的用途都比较集中单一,避免构造大而复杂、多功能的镜像。
(2) 选用合适的基础镜像:容器的核心是应用。选择过大的父镜像(如Ubuntu系统镜像)会造成最终生成应用镜像的膝肿,推荐选用瘦身过的应用镜像(如node:slim),或者较为小巧的系统镜像(如alpine、busybox或debian)。
(3) 提供必要的注释和维护者信息: Dockerfile也是一种代码,需要考虑方便后续的扩展和他人的使用。
(4) 正确使用版本号:使用明确的版本号信息,如1.0,2.0,而非依赖于默认的latest。通过版本号可以避免环境不一致导致的问题。
(5) 减少镜像层数:如果希望所生成镜像的层数尽量少,则要尽量合并RUN、ADD和COPY指令。通常情况下,多个RUN指令可以合并为一条RUN指令。
(6) 恰当使用多步骤创建:通过多步骤创建,可以将编译和运行等过程分开,保证最终生成的镜像只包括运行应用所需要的最小化环境。当然,用户也可以通过分别构造编译镜像和运行镜像来达到类似的结果,但这种方式需要维护多个Dockerfile。
(7) 使用.dockerignore文件:使用该文件可以标记在执行docker build时忽略的路径和文件,避免发送不必要的数据内容,从而加快整个镜像创建过程。
(8) 及时删除临时文件和缓存文件:特别是在执行 apt-get 指令后,/var/cache/apt下面会缓存了一些安装包。
(9) 提高生成速度:如合理使用 cache, 减少内容目录下的文件,或使用.dockerignore 文件指定等。
(10) 调整合理的指令顺序:在开启cache的情况下,内容不变的指令尽量放在前面,这样可以尽量复用。
(11) 减少外部源的干扰:如果确实要从外部引入数据,需要指定持久的地址,并带版本信息等,让他人可以复用而不出错。

参考

《Docker技术入门与实战》 杨保华 戴王剑 曹亚仑 著
《Docker实战》 Jeff Nickoloff 著, 胡震,杨润青 黄帅 译
https://yiyan.baidu.com/ 文心一言
https://docs.docker.com/reference/dockerfile/ Dockerfile reference
http://www.dockerinfo.net/3328.html 7 步精简 Docker 镜像
https://www.runoob.com/docker/docker-install-ubuntu.html Docker 安装 Ubuntu
https://blog.csdn.net/u014163312/article/details/127330574 Maven打包所有依赖到一个可执行jar中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/736033.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux:线程互斥与同步

目录 线程互斥 锁的初始化 加锁 解锁 锁的初始化 锁的原理 死锁 线程同步 方案一&#xff1a;条件变量 条件变量初始化 等待 唤醒 条件变量的代码示例 基于阻塞队列的生产消费模型 方案二&#xff1a;POSIX信号量 初始化信号量&#xff1a; 销毁信号量 等待信…

JAVA基础-数据结构一(线性表、链表、栈、队列)

一、数组线性表&#xff08;ADT&#xff09; 线性表&#xff1a;又称动态数组&#xff0c;核心是动态数组&#xff0c;可以自行扩容&#xff0c;支持增删改查四种功能 java中有ArrayList也可以自行扩容&#xff0c;二者功能较为相似&#xff0c;且ArrayList也支持转换为数组。 …

中国大学生计算机设计大赛--智慧物流挑战赛基础

文章目录 一、Ubuntu基础1.1 基本操作1.2 文本编辑 二、ROS基础介绍2.1 概念与特点2.2 基本结构2.3 创建工程2.4 节点和节点管理器2.5 启动文件 三、ROS通信机制3.1 话题3.2 服务3.3 动作3.4 参数服务器 四、ROS可视化工具4.1 rviz4.2 rqt4.3 tf 五、Python实现简单的ROS节点程…

01-分析同步通讯/异步通讯的特点及其应用

同步通讯/异步通讯 微服务间通讯有同步和异步两种方式 同步通讯: 类似打电话场景需要实时响应(时效性强可以立即得到结果方便使用),而且通话期间不能响应其他的电话(不支持多线操作)异步通讯: 类似发邮件场景不需要马上回复并且可以多线操作(适合高并发场景)但是时效性弱响应…

MQ高可用相关设置

文章目录 前言MQ如何保证消息不丢失RabbitMQRocketMQKafkaMQ MQ如何保证顺序消息RabbitMQRocketMQKafka MQ刷盘机制/集群同步RabbitMQRocketMQKafka 广播消息&集群消息RabbitMQRocketMQ MQ集群架构RabbitMQRocketMQKafka 消息重试RabbitMQRockeMqKafka 死信队列RocketMQKaf…

Claude3横空出世:颠覆GPT-4,Anthropic与亚马逊云科技共启AI新时代

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

洛谷P3853路标设置

题目背景 B 市和 T 市之间有一条长长的高速公路&#xff0c;这条公路的某些地方设有路标&#xff0c;但是大家都感觉路标设得太少了&#xff0c;相邻两个路标之间往往隔着相当长的一段距离。为了便于研究这个问题&#xff0c;我们把公路上相邻路标的最大距离定义为该公路的“空…

车载电子电器架构 —— 汽车电子电气系统分解

车载电子电器架构 —— 汽车电子电气系统分解 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何…

【JavaWeb】【瑞吉外卖】分页操作数据传输转换

瑞吉day3 搞定了分页以及数据传输的问题 mybatis-plus分页接口实现 分页主要是通过mybatis提供的接口实现的。这篇笔记只是记录如何实现这个接口&#xff0c;并不会深究原理。 博主也比较菜&#xff0c;目前还没有手撕mybatis代码&#xff0c;后续有机会研究一下&#xff08;…

【操作系统概念】第14章:系统保护

文章目录 0. 前言14.1 保护目标14.2 保护原则14.3 保护域14.3.1 域结构14.3.2 实例&#xff1a;UNIX14.3.3 实例&#xff1a;MUTICS 14.4 访问矩阵14.5 访问矩阵的实现14.5.1 全局表14.5.2 对象的访问列表14.5.3 域的能力(权限)列表14.5.4 锁-钥匙机制*14.5.5 比较* 14.6 访问控…

Github 2024-03-10php开源项目日报Top10

根据Github Trendings的统计,今日(2024-03-10统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目10Blade项目1Laravel:表达力和优雅的 Web 应用程序框架 创建周期:4631 天开发语言:PHP, BladeStar数量:75969 个Fork数量:24281 次…

网络层学习常见问题及答案整理

问题0&#xff1a;ARP解析协议的定义和特点 ARP&#xff08;地址解析协议&#xff09;高速缓存表用于存储IP地址到MAC地址的映射关系。当一台主机需要将IP数据包发送到同一局域网中的另一台主机时&#xff0c;它需要知道目标主机的MAC地址&#xff0c;以便在以太网帧中使用。AR…

Vue脚手架

Vue脚手架 学习目标&#xff1a; 理解Node.js基本使用方法理解包资源管理器NPM的使用理解webpack的作用理解 vue-cli 脚手架 (重点)Element-UI 组件库 1.vue的格式&#xff1a;new Vue({//作用的视图el:"id选择器",//vue中的数据/*data:{key:value,key:value,...}…

Mysql实现分布式锁

Mysql实现分布式锁 Mysql实现分布式锁 Mysql实现分布式锁 通过数据库的唯一索引和事务的特性来实现分布式锁。 自定义一个表 -- 创建分布式锁表 CREATE TABLE DistributedLock(lock_key VARCHAR(64) NOT NULL,lock_value VARCHAR(255),PRIMARY KEY (lock_key) );-- 尝试获取…

Java 基于微信小程序的快递柜小程序

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

harmony 鸿蒙安全和高效的使用N-API开发Native模块

简介 N-API 是 Node.js Addon Programming Interface 的缩写&#xff0c;是 Node.js 提供的一组 C API&#xff0c;封装了V8 引擎的能力&#xff0c;用于编写 Node.js 的 Native 扩展模块。通过 N-API&#xff0c;开发者可以使用 C 编写高性能的 Node.js 模块&#xff0c;同时…

【python3】线程同步机制 Condition

threading.Condition 是 Python 中用于线程同步的一种机制&#xff0c;它提供了一个条件变量&#xff0c;允许一个或多个线程等待某个条件变为真时再继续执行。Condition 对象内部包含一个锁对象&#xff0c;线程可以在调用 wait() 方法时释放这个锁&#xff0c;并在条件满足时…

devops-Jenkins【内网环境部署及插件安装】

1、准备工作 外网Linux机器一台&#xff0c;内网Linux机器一台。硬件环境要求&#xff1a;至少1GB的可用内存空间&#xff0c;至少50GB的可用硬盘空间。软件环境需求&#xff1a;需要安装好Java8&#xff0c;Java的运行环境JRE1.8或者Java的开发工具包JDK1.8都可以。 2、外网安…

结构指针的使用

结构指针的使用 指针类型变量&#xff1a; 指针类型&#xff0c;是变量类型的一种&#xff0c;它是专门用来存储变量的地址的。 例如 int *p; 表示p是一个指针变量&#xff0c;它用来存储某个整型变量的地址。 int a5; int *p&a; 这样&#xff0c;就将整型变量a的地…

体系班第十三节

1判断完全二叉树递归做法 有四种情况&#xff1a;1 左树完全&#xff0c;右数满&#xff0c;且左高为右高加一 2左满 &#xff0c;右满&#xff0c;左高为右高加一 3左满&#xff0c;右完全&#xff0c;左右高相等 4左右均满且高相等 #include<iostream> #include&l…