Basics of Container Isolation 容器隔离的实现原理
目录
容器隔离的实现原理
1. 使用cgroups实现资源隔离
自定义一个cgroup
设置进程的内存使用
启动一个docker 容器,观察cgroup的创建情况
2. 使用Namespaces进行资源分区
namespace继承关系引发的问题
3. 结合来使用Namespaces 和chroot
4. 结论
参考文档
容器隔离的实现原理
docker 是一个神奇的工具,它能够轻松创建 docker 镜像并快速启动一个完全隔离资源的容器,但它是如何实现资源隔离的呢?
大致来说,docker通过Linux的cgroups 、namespaces、chroot实现容器隔离,在这篇文章中,我们将通过使用linux基础命令来构造进程隔离,就像docker容器中那样
1. 使用cgroups实现资源隔离
在 Linux 中,控制组 (cgroups) 是一项内核功能,它允许隔离一组进程的资源使用,例如 CPU、内存、磁盘 IO、网络等。
Linux 中的每种类型的资源都有一个 cgroup 层次结构。 这些层次结构表示为 /sys/fs/cgroup 中的伪文件系统。
在这个目录下可以看到很多cgroups:
allen@localhost:/sys/fs/cgroup$ ls
blkio cpu cpu,cpuacct cpuacct cpuset devices freezer hugetlb memory net_cls net_cls,net_prio net_prio perf_event pids rdma systemd unified
正如文件夹名称所固有的那样,这些条目中的每一个都负责一种类型的资源。 如果你进一步查看其中一个文件夹,可以看到可以各种属性控制文件。 例如,让我们看一下内存 cgroup:
allen@localhost:/sys/fs/cgroup$ ls memorycgroup.procs
memory.soft_limit_in_bytes memory.limit_in_bytes memory.max_usage_in_bytes memory.usage_in_bytes
如上面命令的输出所示,每一个文件都控制一个特定的属性。 例如,memory.limit_in_bytes 控制此 cgroup 中的进程可以使用的最大内存, cgroup.procs 的文件包含属于该 cgroup 的所有进程的列表。 你可以修改其中一些文件来改变 cgroup 的行为(如调整memory.soft_limit_in_bytes使这组cgroup有更大的内存使用率),其中一些文件由内核编写,用于维护cgroup 当前的状态。
自定义一个cgroup
你可以为特定资源(例如内存)创建一个属于自己的 cgroup,方法是在该资源的 cgroup 目录下创建一个目录。
allen@localhost:~# mkdir /sys/fs/cgroup/memory/allen
Linux kernel会自动在这个目录下创建一些必要的文件
allen@localhost:~# ls /sys/fs/cgroup/memory/allen
cgroup.clone_children memory.kmem.failcnt memory.kmem.tcp.limit_in_bytes memory.max_usage_in_bytes memory.move_charge_at_immigrate memory.stat tasks cgroup.event_control memory.kmem.limit_in_bytes memory.kmem.tcp.max_usage_in_bytes memory.memsw.failcnt memory.numa_stat memory.swappiness cgroup.procs memory.kmem.max_usage_in_bytes memory.kmem.tcp.usage_in_bytes memory.memsw.limit_in_bytes memory.oom_control memory.usage_in_bytes memory.failcnt memory.kmem.slabinfo memory.kmem.usage_in_bytes memory.memsw.max_usage_in_bytes memory.pressure_level memory.use_hierarchy memory.force_empty memory.kmem.tcp.failcnt memory.limit_in_bytes memory.memsw.usage_in_bytes memory.soft_limit_in_bytes notify_on_release
设置进程的内存使用
我们可以改写memory.limit_in_bytes 来限制内存使用大小,比如100kbs(表示最多使用100kbs)
echo 100000 > /sys/fs/cgroup/memory/sushil/memory.limit_in_bytes
通过将 shell 的 PID 写入 /sys/fs/cgroup/memory/allen/cgroup.procs,来将当前 shell 添加到此 cgroup。此操作的意义在于,当你用在继续使用bash时(属于当前用户的),它能够调用的内存资源是100kbs
allen@localhost:/sys/fs/cgroup# ps
PID TTY TIME CMD
1973 pts/1 00:00:00 sudo
1974 pts/1 00:00:00 su
1975 pts/1 00:00:00 bash
1983 pts/1 00:00:00 ps
allen@localhost:/sys/fs/cgroup# echo 1975 > /sys/fs/cgroup/memory/allen/cgroup.procs
启动一个docker 容器,观察cgroup的创建情况
我们启动一个 docker 容器,看看它是否创建了一个单独的 cgroup。
allen@localhost:~$ sudo docker run -d -m 100m nginx Unable to find image 'nginx:latest' locally latest: Pulling from library/nginx c229119241af: Pull complete 2215908dc0a2: Pull complete 08c3cb2073f1: Pull complete 18f38162c0ce: Pull complete 10e2168f148a: Pull complete c4ffe9532b5f: Pull complete Digest: sha256:2275af0f20d71b293916f1958f8497f987b8d8fd8113df54635f2a5915002bf1 Status: Downloaded newer image for nginx:latest 17fa32e039f2dcd8ad7dfbb0a9eb192ff01f38f47a8befd7cbf3040ef5a4d20a
在这里,我们启动了一个内存限制为 100 MB 的 nginx container, 接下来我们将检查这个容器是否有内存 cgroup(cgroup 将使用容器 ID 创建)
allen@localhost:~$ lscgroup memory:/ | grep 17fa32e039f2dcd8ad7dfbb0a9eb192ff01f38f47a8befd7cbf3040ef5a4d20a memory:/docker/17fa32e039f2dcd8ad7dfbb0a9eb192ff01f38f47a8befd7cbf3040ef5a4d20a
确实有创建内存的cgroup, 我们检查 memory.limit_in_bytes 文件看看内存限制是多少(该值是通过将 100MB 转换为 KB 来设置的):
allen@localhost:~$ cat /sys/fs/cgroup/memory/docker/17fa32e039f2dcd8ad7dfbb0a9eb192ff01f38f47a8befd7cbf3040ef5a4d20a/memory.limit_in_bytes 104857600
通过以上操作我们可以了解到,容器可以通过cgroup 来限制每个容器的资源使用, 当你启动容器时,运行时会创建一个单独的 cgroup 来设置资源限制。
任何超过 cgroup 限制的行为,都会使内核终止该容器来处理,并且容器运行时将抛出 OOM 错误。接下来让我们了解一下Linux Namespaces的用法。
2. 使用Namespaces进行资源分区
Namespaces 是 Linux 内核的一项功能,它允许为一组进程划分资源。 简单来说,如果 cgroups 限制了资源的使用,Namespaces 则限制了进程可以看到的资源。 通过将进程放在Namespaces 中,你可以限制它可以看到的资源,它对内核资源进行分区,以便一组进程看到一组资源,而另一组进程看到另一组资源。Linux通过将一组资源和进程放在相同Namespaces 来工作(比如pid 101 ,对应资源net101、cpu101、memory101)。
以下是不同类型的Namespaces(请查看文章下方的参考文档了解更多):
- Mount (
mnt
) - Process ID (
pid
) - Network (
net
) - Interprocess Communication (
ipc
) - Unix Timesharing System (
uts
) - User ID (
user
) - Control Group (
cgroup
)
每个进程属于以上7种类型中的其中一种,你可以使用 lsns 命令在系统上查看不同的Namespaces ,每个进程都与一个命名空间关联,并且只能查看或使用与该命名空间关联的资源。
allen@localhost:~$ lsnsNS TYPE NPROCS PID USER COMMAND 4026531835 cgroup 3 1588 allengcp /lib/systemd/systemd --user 4026531836 pid 3 1588 allengcp /lib/systemd/systemd --user 4026531837 user 3 1588 allengcp /lib/systemd/systemd --user 4026531838 uts 3 1588 allengcp /lib/systemd/systemd --user 4026531839 ipc 3 1588 allengcp /lib/systemd/systemd --user 4026531840 mnt 3 1588 allengcp /lib/systemd/systemd --user 4026531992 net 3 1588 allengcp /lib/systemd/systemd --user
namespace继承关系引发的问题
容器使用namespaces 来划分不同的资源。 例如,每个容器都有自己的主机名、自己的网络堆栈、自己的一组 cgroup,并且只能看到在其中运行的进程。 这是通过为每个容器创建单独的namespaces 来实现的。 namespace的一个特性是,子进程可以继承父进程的namespace。
这个继承的特性在容器技术中是要想办法屏蔽的,为了帮助读者理解这一点,请看下面的一个操作实例。
当你从当前的 shell(shl1) 启动另一个 shell(shl2),shl2将成为当前 shl1 的子进程,并将继承其namespaces 。 我们先执行ps获取当前 shell 进程可以看到的进程列表, 接下来使用 sh命令启动一个新的 shell (shl2),并调用ps查看进程表.
allen@localhost:~$ psPID TTY TIME CMD1815 pts/1 00:00:00 bash16787 pts/1 00:00:00 ps allen@localhost:~$ sh $psPID TTY TIME CMD1815 pts/1 00:00:00 bash16788 pts/1 00:00:00 sh16789 pts/1 00:00:00 ps
你可以看到,在shl2中,可以看到bash 1815,这个进程是属于父进程shl1的,也就是说子 shell 进程可以看到父进程里所有的进程,这一特性对docker 容器来说,是不可接受的。 每个容器应该只能看到自己的子进程,仅此而已。
我们继续实验,使用 unshare 命令在新的 pid 命名空间中启动子进程,--fork 参数是为了sh进程继承unshare 进程,而不是继承 sudo 进程(否则它会继承sudo)
补充:unshare用于取消共享的命名空间,或切断命名空间的继承关系
通过sudo lsns 命令来验证 sh 进程是否在新的 PID 命名空间中运行。
allen@localhost:~$ sudo unshare --pid --fork sh # ps PID TTY TIME CMD 26168 pts/1 00:00:00 sudo 26169 pts/1 00:00:00 unshare 26170 pts/1 00:00:00 sh 26171 pts/1 00:00:00 ps #sudo lsns NS TYPE NPROCS PID USER COMMAND 4026532203 mnt 1 200 root /lib/systemd/systemd-udevd 4026532204 uts 1 200 root /lib/systemd/systemd-udevd 4026532251 mnt 1 427 systemd-network /lib/systemd/systemd-networkd 4026532252 mnt 1 432 systemd-resolve /lib/systemd/systemd-resolved 4026532253 mnt 2 1245 _chrony /usr/sbin/chronyd -F -1 4026532314 mnt 1 916 root /lib/systemd/systemd-logind 4026532315 uts 1 916 root /lib/systemd/systemd-logind 4026532326 pid 1 26170 root shallen@localhost:~$ sudo lsns NS TYPE NPROCS PID USER COMMAND 4026532203 mnt 1 200 root /lib/systemd/systemd-udevd 4026532204 uts 1 200 root /lib/systemd/systemd-udevd 4026532251 mnt 1 427 systemd-network /lib/systemd/systemd-networkd 4026532252 mnt 1 432 systemd-resolve /lib/systemd/systemd-resolved 4026532253 mnt 2 1245 _chrony /usr/sbin/chronyd -F -1 4026532314 mnt 1 916 root /lib/systemd/systemd-logind 4026532315 uts 1 916 root /lib/systemd/systemd-logind 4026532326 pid 1 26170 root sh
很显然,新的 shell 进程仍然可以看到系统范围内的进程与其namespace,也就是说即使使用unshare隔绝了共享空间,但子shell任然可以看到父shell的内容,为什么呢?
这是由于ps本身的性质所导致的,如果你对这个命令底层比较了解的话,你会发现它读取的是主机/proc 目录中的文件,如果你从子 shell 进程中列出 proc 目录,你会发现它与主机的信息是一模一样的
补充:/proc 目录是一个位于内存中的伪文件系统。该目录下保存的并不是真正的文件和目录,而是一些【运行时】的信息,如 CPU 信息、负载信息、系统内存信息、磁盘 IO 信息等。
allen@localhost:~$ sudo unshare --pid --fork sh
# ls /proc
1 115 128 16190 17 20 25 26286 3 321 4 579 79 87 941 buddyinfo diskstats interrupts kmsg misc schedstat sysrq-trigger vmallocinfo
......
这样就说明,需要为我们的子 shell 进程提供一个单独的根目录,因为 /proc 文件位于根目录中。 就像容器看不到整个主机文件系统一样,我们必须为我们的进程创建一个新根以限制它读取主机的 /proc 文件或任何文件。
这里就需要用到 chroot 这个命令。 chroot 允许更改任何进程的根目录,一旦完成,进程将失去对新根目录之上任何内容的访问权限,因为根目录是任何进程的最顶层目录
命令格式为:sudo chroot NEW_ROOT_DIR RUN_COMMAND
chroot 运行一条命令,并将根设置为新根。 如果未给出 RUN_COMMAND,则默认为 ${SHELL}。
allen@localhost:~$ echo $SHELL
/bin/bash
allen@localhost:~$ mkdir new_root
allen@localhost:~$ sudo chroot new_root
chroot: failed to run command ‘/bin/bash’: No such file or directory
这里报错了,是因为 new_root 目录是空的并且没有 /bin/bash 文件。 新根目录下没有命令和文件。容器可以解决这个问题,当你运行容器时,新进程获取新根目录,docker image中包含新根路径下的内容和执行命令,并会对这个新根目录进行填充
下载 new_root 目录下的 alpine linux,看看我们的新进程是否可以使用它。
$ mkdir alpine & cd alpine
$ curl -o alpine.tar.gz http://dl-cdn.alpinelinux.org/alpine/v3.10/releases/x86_64/alpine-minirootfs-3.10.0-x86_64.tar.gz$ tar -xvf alpine.tar.gz
我们现在将 alpine 目录作为根目录。
allen@localhost:~$ sudo chroot alpine ls bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
新进程现在可以看到由 alpine linux 实例化的根文件系统。 使用这个新的根创建一个 shell ,发出 ps 命令,看到一个空列表。 你可以通过 /proc 目录来验证这一点(也是空的)。 现在我们的新进程有一个独立于主机的 /proc 目录。 现在,新进程可以在单独的 PID 命名空间中使用该目录来写入进程信息。
allen@localhost:~$ sudo chroot alpine sh / # ps PID USER TIME COMMAND / #
3. 结合来使用Namespaces 和chroot
现在让我们结合使用 unshare 和 chroot 为这个进程提供一个单独的 /proc 目录。
allen@localhost:~$ sudo unshare --pid --fork chroot alpine sh / # ls bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var / # ps PID USER TIME COMMAND / #
结果你仍然看不到任何进程信息。 那是因为您需要将 /proc 目录挂载为 proc 类型的伪文件系统。
mount -t proc /proc /proc
此命令指示将 /proc 目录挂载到 /proc,类型为 proc, 完成后再次发出 ps 命令,你将开始看到进程信息。
/ # mount -t proc /proc /proc / # ps PID USER TIME COMMAND1 root 0:00 sh5 root 0:00 ps
可以看到 PID 已在此 shell 中重置,这说明这一进程确实位于单独的 PID 命名空间中。
4. 结论
我们来进行一下复盘,首先我们了解了cgroups的原理,即对资源进行隔离,它允许隔离一组进程的资源使用,我们将这个概念具体化: 假设一台主机的资源CPU、内存、磁盘 IO、网络分别为100%,现在我们有4个进程,需要均分这些资源,显然每个进程将分配到25%的资源。那这里会产生一个问题,怎么确保进程间不能互相访问资源呢?
于是我们就需要Namespaces 命名空间来隔绝进程间的资源访问, 即 cgroups 限制了资源的使用,Namespaces 则限制了进程可以看到的资源。 通过将进程放在Namespaces 中,你可以限制它可以看到的资源,它对内核资源进行分区,以便一组进程看到一组资源,而另一组进程看到另一组资源。Namespaces无法解决的一个问题是,子进程可以继承父进程的namespace,也就是说,你通过宿主机的docker run命令运行了容器,但这个容器是可以看到父进程即宿主机的进程内容的,这是非常大的安全隐患,于是我们想到使用unshare命令。
Linux unshare 命令可以阻断进程对命名空间的继承关系,但是却无法阻断Linux 中文件的存放关系,ls /proc就是一个很好的例子,这个目录保存了系统【运行时】的状态信息,它是位于根下的,我们使用命令来查询状态,本质上是读取文件目录下的数据信息,这里我用指针的概念来阐述一下,假设有p1,p2都指向rom1,现在从逻辑上将p2执指向rom2,然而rom1和rom2使用的任然是同一块物理内存,所以结果不会改变
于是又引入了 chroot 这个命令。 chroot 允许更改任何进程的根目录,一旦完成,进程将失去对新根目录之上任何内容的访问权限,这样的话,我们就可以为rom2开设一块新的地址,从而在逻辑与实际意义上都切断联系,现在,新进程可以在单独的 PID 命名空间中使用该目录来写入进程信息。
参考文档
https://en.wikipedia.org/wiki/Linux_namespaces
https://blog.devgenius.io/basics-of-container-isolation-5eabdb258409
相关文章:

Basics of Container Isolation 容器隔离的实现原理
目录 容器隔离的实现原理 1. 使用cgroups实现资源隔离 自定义一个cgroup 设置进程的内存使用 启动一个docker 容器,观察cgroup的创建情况 2. 使用Namespaces进行资源分区 namespace继承关系引发的问题 3. 结合来使用Namespaces 和chroot 4. 结论 参考文档…...

EBS R12.1 注册客户化应用的步骤
创建客户化应用目录 登录成 applxxx 用户 -- applxxx 改成所需用户名 # 以标准INV模块作为客户化应用目录的模板 cd $APPL_TOP mkdir -p cust cp -r inv cust/template cd cust # 删除template 目录下的文件,只保留目录结构 cd $APPL_TOP/cust for rm_list in …...

算法记录 | Day38 动态规划
对于动态规划问题,将拆解为如下五步曲 确定dp数组(dp table)以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 509.斐波那契数 思路: 确定dp数组(dp table)以及下标的含义&#x…...

PMP项目管理-[第六章]进度管理
进度管理知识体系: 规划进度管理: 定义活动: 排列活动顺序: 估算活动持续时间: 制定进度计划: 6.1 规划进度管理 定义:为规划、编制、管理、执行和控制项目进度而制定政策、程序和文档的过程 作…...

Python变量
一、变量的定义 变量名的命名规范:变量名是标识符的一种,变量名不能随便起,要遵守 Python 标识符命名规范。 ## 常用的命名规范有以下几种: 1. 变量名为单个单词的话全部小写 name "张三" 2. 多个单词组成的话&#…...

准备换工作的看过来~
大家好,最近有不少小伙伴在后台留言,得准备面试了,又不知道从何下手!为了帮大家节约时间,特意准备了一份面试相关的资料,内容非常的全面,真的可以好好补一补,希望大家在都能拿到理想…...

免费AI人工智能在线写作伪原创-百度ai自动写文章
免费伪原创洗稿工具 免费伪原创洗稿工具现在终于推出了!你是否在写作的时候,经常因为缺乏灵感而苦恼?或者,你在撰写文章的时候,发现自己的语言表述不够丰富,缺乏变化,语句重复率太高?…...

互联网摸鱼日报(2023-04-21)
互联网摸鱼日报(2023-04-21) InfoQ 热门话题 3年不用云能节省4亿美元!想知道我们为什么敢不用AWS吗? 华为周红:通过行业大模型促进AI价值创造 建设业务规划、交付和反馈闭环| BizDevOps 公开课 云原生时…...

5.3、web服务器简介HTTP协议
代码地址 5.3、web服务器简介HTTP协议 1.Web-Server(网页服务器)2.HTTP协议(应用层的协议)①简介②概述③工作原理④HTTP请求报文格式⑤HTTP响应报文格式⑥HTTP请求方法⑦HTTP状态码 1.Web-Server(网页服务器) 一个 Web Server …...

【观察】华为:新一代楼宇网络,使能绿建智慧化
“碳达峰”、“碳中和”目标是我国生态文明建设和高质量可持续发展的重要战略安排,将推动全社会加速向绿色低碳转型。作为全球既有建筑和每年新建建筑量最大的国家,大力发展绿色建筑对中国全方位迈向低碳社会、实现高质量发展具有重要意义。 《“十四五”…...

【C# .NET】chapter 13 使用多任务改进性能和可扩展性
目录 一、物理内存和虚拟内存使用(Recorder 类) 二、 对比 string的“”操作与stringbuilder 操作 的处理效率,内存消耗情况, 三、异步运行任务、三种启动任务方法、将上一任务方法处理结果作为参数传给下一任务方法 四、嵌套…...

CA(证书颁发机构)
CA 根证书路径/csk-rootca/csk-ca.pem; ~ 签发数字证书,颁发者信息:(仅包含如下信息) C CN ST China L BeiJing O skills OU Operations Departments CN CSK Global Root CA 1.修改证书的路径以及相关配置 vi /etc/pki/tls/op…...

辛弃疾最有代表性的十首词
辛弃疾的词,风格多样,题材广阔,几乎涉及到生活中的各个方面,从爱国情怀到日常生活,甚至连戒酒这种事都能写入词中。辛弃疾也是两宋词人中,存词最多的作家之一,现存的六百多首作品。 辛弃疾的词…...

MC9S12G128开发板—实现按键发送CAN报文指示小车移动功能
实验环境:MC9S12G128开发板 基本功能:控制开发板上的按键,模拟车辆移动的上下左右四个方位,通过can通信告诉上位机界面,车辆轨迹的移动方位。 1. 1939报文发送的示例代码 MC9S12G128开发板1939协议发送can报文数据的…...

尚融宝22-提交借款申请
目录 一、需求介绍 二、图片上传 (一)前端页面 (二)实现图片上传 三、数据字典展示 (一)后端 (二)前端 四、表单信息提交 (一)后端 1、VO对象&…...

机器学习在生态、环境经济学中的实践技术应用及论文写作
近年来,人工智能领域已经取得突破性进展,对经济社会各个领域都产生了重大影响,结合了统计学、数据科学和计算机科学的机器学习是人工智能的主流方向之一,目前也在飞快的融入计量经济学研究。表面上机器学习通常使用大数据…...

Android硬件通信之 WIFI通信
一,简介 1.1 随着网络的普及和通信技术的发展,网络的传输速度也越来越快,wifi技术也还成为手机设备最基本的配置。我们可以通过wifi实现手机与手机之前的信息传输,当然也可以与任意一台有wifi模块的其它设备传输。 1.2 wifi与蓝…...

面试官:“请描述一下Android系统的启动流程”
作者:OpenGL 前言 什么是Android启动流程呢?其实指的就是我们Android系统从按下电源到显示界面的整个过程。 当我们把手机充好电,按下电源,手机会弹出相应启动界面,在等了一段时间之后,会弹出我们熟悉的主…...

k8s delete node 后 重启kubelet会自己加入到集群 ?
原因 当执行kubectl delete node命令时,Kubernetes API服务器会收到该节点的删除请求,并将其从集群中删除。此时,kubelet服务在该节点上仍然在运行,但已经不再与集群通信。 当您重启kubelet服务时,它会重新向API服务…...

REXROTH液压方向阀安装须知
安装规程 阀安装到系统之前,应该对照订货型号比较其型号说明。 确认阀的连接表面和底板无水分,没有油。 - 清洁: ‧ 安装元件时,确认工业阀和周围干净 ‧ 油箱须密闭,以防止外部污染 ‧ 安装之前&…...

【数据结构实验】哈夫曼树
【数据结构实验】哈夫曼树 简介: 为一个信息收发站编写一个哈夫曼码的编/译码系统。文末贴出了源代码。 需求分析 完整的系统需要具备完整的功能,包含初始化、编码、译码、印代码文件和印哈夫曼树,因此需要进行相应的文件操作进行配合。哈…...

浏览器不好用?插件来帮忙
一、目的 浏览器本身具备的功能并不完善,不同的用户可以为自己浏览器增加想要功能,使得浏览器更能符合自己的需求,提高浏览器使用的舒适度 二、推荐插件 AdblockPlus LastPass(密码记录,全平台通用) Dar…...

Qt Quick - 容器控件综述
Qt Quick - 容器控件综述 一、概述二、ApplicationWindow Control三、Frame Control四、GroupBox Control五、Page Control六、Pane Control七、ScrollView Control八、StackView Control九、SwipeView Control十、TabBarControl十一、ToolBar控件 一、概述 Qt Quick Controls…...

面试题30天打卡-day06
1、什么是反射机制?说说反射机制的优缺点、应用场景? 反射机制:Java的反射机制是在运行状态,对于任意一个类,都能够动态的获得这个类的属性和方法;对于一个对象,都能动态的调用它当中的方法和属…...

Spring Boot的基础使用和< artifactId>spring-boot-maven-plugin</ artifactId>爆红的处理
Spring Boot的基础使用和< artifactId>spring-boot-maven-plugin</ artifactId>爆红的处理 Spring Boot概述 微服务概述 微服务Microservices是一种软件架构风格,他是以专注于单一责任与功能的小型功能区块Small Building Blocks 为基础,…...

项目管理中的必不可少的强大工具有哪些?
在项目管理中,我们总是想寻求一套功能强大的工具,来满足我们多样化的需求。但往往事与愿违,这样强大的工具总是费用高,操作复杂,需安装多个插件。下面,我就给大家推荐一款项目管理软件 ~Zoho Projects&…...

嵌入式学习笔记——SPI通信的应用
SPI通信的应用 前言屏幕分类1.3OLED概述驱动芯片框图原理图通信时序显示的方式页地址、列地址初始化指令 程序设计初始化代码初始化写数据与写命令清屏函数 初始化代码字符显示函数 总结 前言 上一篇中介绍了STM32的SPI通信,并根据框图和寄存器进行了SPI通信的初始…...

.Net下企业应用系统架构构建心得
在开始架构设计之前,需要了解一下架构是什么,按照IEEE标准的定义是: Architecture 是一个系统的基本组织,它蕴含于系统的组件中、组件之间的相互关系中、组件与环境的相互关系中、以及呈现于其设计和演进的原则中。 (The embodied…...

【社区图书馆】关于Mybatis原理学习的读后感
1、为什么会看原理书籍 Mybatis是我们Java后端开发中的主流ORM框架,基本都会在工作中用到。所以,是既熟悉,又陌生。熟悉是因为一直都在使用,而陌生则是对于其内部原理还不够深入。刚好近期的工作中,又遇到了一个需求&a…...

C++ Primer阅读笔记--表达式和运算符的使用
1--左值和右值 C 的表达式有右值(rvalue, are-value)和左值(lvalue, ell-value)两个形式;当一个对象被用作右值时,使用的是对象的值(内容);当对象被用作左值时࿰…...