Docker 与 Linux Cgroups:资源隔离的魔法之旅

news2024/9/29 13:14:38

docker-logo

这篇文章主要介绍了 Docker 如何利用 Linux 的 Control Groups(cgroups)实现容器的资源隔离和管理。

最后通过简单 Demo 演示了如何使用 Go 和 cgroups 交互。


如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。

搜索公众号【探索云原生】即可订阅


1.Docker 是如何使用 Cgroups 的

我们知道 Docker 是通过 Cgroups 实现容器资源限制和监控的,那么具体是怎么用的呢?

演示

包含以下步骤:

  • 1)创建容器,指定内存限制
  • 2)查看 cgroup 情况
  • 3)停止容器
  • 4)再次查看 cgroup 情况

先启动一个容器:

[root@iZ2zefmrr626i66omb40ryZ memory]# docker run -itd -m 128m nginx
da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416

这里通过-m参数设置了内存限制为 128M。

该命令执行后 docker 会在 memory cgroup 上(也就是 /sys/fs/cgroup/memory 路径下)创建一个叫 docker 的子 cgroup,具体如下:

$ ls -l /sys/fs/cgroup/memory/docker/
-rw-r--r-- 1 root root 0 Jan  6 19:53 cgroup.clone_children
--w--w--w- 1 root root 0 Jan  6 19:53 cgroup.event_control
-rw-r--r-- 1 root root 0 Jan  6 19:53 cgroup.procs
# 可以发现这一长串ID和创建容器时打印的是一致的
drwxr-xr-x 2 root root 0 Jan  6 19:56 da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416
# 省略其他文件

内部除了 cgroup 相关的文件外,还有很多目录,使用容器 ID 作为目录名,其中每个目录即对应一个容器

其中,da82f9e...这个目录名称和容器 ID 一致,说明 docker 是为每个容器创建了一个子 cgroup 来单独限制。

查看一下里面的具体配置:

[root@iZ2zefmrr626i66omb40ryZ docker]# cd da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416/
[root@iZ2zefmrr626i66omb40ryZ da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416]# cat memory.limit_in_bytes
134217728

可以发现,memory.limit_in_bytes 中配置的值为 134217728,转换一下134217728/1024/1024=128M, 刚好就是我们指定的 128M。

然后我们停止该容器

docker stop da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416

再次查看 cgroup 情况

ls -l /sys/fs/cgroup/memory/docker/|grep da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416

发现目录已经被删除,说明容器对应的子 cgroup 也同步被回收。

把停止的容器 start 一下看看

docker start da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416

再次查看 cgroup 情况

[root@docker ~]# ls -l /sys/fs/cgroup/memory/docker/|grep da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416
drwxr-xr-x 2 root root 0 Jan  6 19:58 da82f9ebd384730dda7f831b4331c9e55893c100c83c0c9b0ce112436aa93416

可以看到,同名目录又被创建出来了。

至此,演示完成。

结论:Docker 容器启动时创建容器 ID 同名子 group 以实现资源控制,容器停止时删除该子 cgroup。

Demo 中只演示了内存限制,其他资源也是类似的

小结

所以 docker 使用 cgroup 其实很简单,

  • 1)为每个容器创建一个子 cgroup
  • 2)根据 docker run 时提供的参数调整 cgroup 中的配置
  • 3)容器被删除时同步删除对应子 cgroup

2.Cgroups 相关操作命令

这里记录一下 cgroups 的一些常用操作命令。

hierarchy

创建

由于 Linux Cgroups 是基于内核中的 cgroup virtual filesystem 的,所以创建 hierarchy 其实就是将其挂载到指定目录。

语法为: mount -t cgroup -o subsystems name /cgroup/name

  • 其中 subsystems 表示需要挂载的 cgroups 子系统
  • /cgroup/name 表示挂载点(一般为具体目录)

这条命令同在内核中创建了一个 hierarchy 以及一个默认的 root cgroup。

例如:

$ mkdir cg1
$ mount -t cgroup -o cpuset cg1 ./cg1

比如以上命令就是挂载一个 cg1 的 hierarchy 到 ./cg1 目录,如果指定的 hierarchy 不存在则会新建。

hierarchy 创建的时候就会就会自动创建一个 cgroup 以作为 cgroup 树中的 root 节点。

删除

删除 hierarchy 则是卸载。

语法为:umount /cgroup/name

  • /cgroup/name 表示挂载点(一般为具体目录)

例如:

$ umount ./cg1

以上命令就是卸载 ./cg1 这个目录上挂载的 hierarchy,也就是前面挂载的 cg。

hierarchy 卸载后,相关的 cgroup 都会被删除。

不过 cg1 目录需要手动删除。

默认文件含义

hierarchy 挂载后会生成一些文件,具体如下:

为了避免干扰,未关联任何 subsystem

$ mkdir cg1
$ mount -t cgroup -o none,name=cg1 cg1 ./cg1
$ tree cg1
cg1
├── cgroup.clone_children
├── cgroup.procs
├── cgroup.sane_behavior
├── notify_on_release
├── release_agent
└── tasks

具体含义如下:

  • cgroup.clone_children:这个文件只对 cpuset subsystem 有影响,当该文件的内容为 1 时,新创建的 cgroup 将会继承父 cgroup
    的配置,即从父 cgroup 里面拷贝配置文件来初始化新
    cgroup,可以参考cgroup.clone_children
  • cgroup.procs:当前 cgroup 中的所有进程ID,系统不保证 ID 是顺序排列的,且 ID 有可能重复
  • cgroup.sane_behavior:这个文件只会存在于 root cgroup 下面,用于控制某些特性的开启和关闭。
    • 由于 cgroup 一直再发展,很多子系统有很多不同的特性,因此内核用CGRP_ROOT_SANE_BEHAVIOR来控制
  • notify_on_release:该文件的内容为 1 时,当 cgroup 退出时(不再包含任何进程和子 cgroup),将调用 release_agent 里面配置的命令。
    • 新 cgroup 被创建时将默认继承父 cgroup 的这项配置。
  • release_agent:里面包含了 cgroup 退出时将会执行的命令,系统调用该命令时会将相应 cgroup 的相对路径当作参数传进去。
    • 注意:这个文件只会存在于 root cgroup 下面,其他 cgroup 里面不会有这个文件。
    • 相当于配置一个回调用于清理资源。
  • tasks:当前 cgroup 中的所有线程 ID,系统不保证 ID 是顺序排列的

cgroup.procs 和 tasks 的区别见 cgroup 操作章节。

release_agent 演示

当一个 cgroup 里没有进程也没有子 cgroup 时,release_agent 将被调用来执行 cgroup 的清理工作。

具体操作流程:

  • 首先需要配置 notify_on_release 以开启该功能。
  • 然后将脚本内容写入到 release_agent 中去。
  • 最后 cgroup 退出时(不再包含任何进程和子 cgroup)就会执行 release_agent 中的命令。
#创建新的cgroup用于演示
dev@ubuntu:~/cgroup/demo$ sudo mkdir test
#先enable release_agent
dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 1 > ./test/notify_on_release'

#然后创建一个脚本/home/dev/cgroup/release_demo.sh,
#一般情况下都会利用这个脚本执行一些cgroup的清理工作,但我们这里为了演示简单,仅仅只写了一条日志到指定文件
dev@ubuntu:~/cgroup/demo$ cat > /home/dev/cgroup/release_demo.sh << EOF
#!/bin/bash
echo \$0:\$1 >> /home/dev/release_demo.log
EOF

#添加可执行权限
dev@ubuntu:~/cgroup/demo$ chmod +x ../release_demo.sh

#将该脚本设置进文件release_agent
dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo /home/dev/cgroup/release_demo.sh > ./release_agent'
dev@ubuntu:~/cgroup/demo$ cat release_agent
/home/dev/cgroup/release_demo.sh

#往test里面添加一个进程,然后再移除,这样就会触发release_demo.sh
dev@ubuntu:~/cgroup/demo$ echo $$
27597
dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 27597 > ./test/cgroup.procs'
dev@ubuntu:~/cgroup/demo$ sudo sh -c 'echo 27597 > ./cgroup.procs'

#从日志可以看出,release_agent被触发了,/test是cgroup的相对路径
dev@ubuntu:~/cgroup/demo$ cat /home/dev/release_demo.log
/home/dev/cgroup/release_demo.sh:/test

cgroup

创建

创建 cgroup 很简单,在父 cgroup 或者 hierarchy 目录下新建一个目录就可以了。

具体层级关系就和目录层级关系一样。

# 创建子cgroup cgroup-cpu
$ mkdir cgroup-cpu
$ cd cgroup-cpu
# 创建cgroup-cpu的子cgroup
$ mkdir cgroup-cpu-1

删除

删除也很简单,删除对应目录即可。

注意:是删除目录 rmdir,而不是递归删除目录下的所有文件。

如果有多层 cgroup 则需要先删除子 cgroup,否则会报错:

$ rmdir cgroup-cpu
# 如果cgroup中有进程正在本限制,也会出现这个错误,需要先停掉对应进程,或者把进程移动到另外的 cgroup 中(比如父cgroup)
rmdir: failed to remove 'cgroup-cpu': Device or resource busy

先删除子 cgroup 就可以了:

$ rmdir cg1
$ cd ../
$ rmdir cgroup-cpu

也可以借助 libcgroup 工具来创建或删除。

使用 libcgroup 工具前,请先安装 libcgroup 和 libcgroup-tools 数据包

redhat 系统安装:

$ yum install libcgroup
$ yum install libcgroup-tools

ubuntu 系统安装:

$ apt-get install cgroup-bin
# 如果提示cgroup-bin找不到,可以用 cgroup-tools 替换
$ apt-get install cgroup-tools

具体语法:

# controllers就是subsystem
# path可以用相对路径或者绝对路径
$ cgdelete controllers:path

例如:

$ cgcreate cpu:./mycgroup
$ cgdelete cpu:./mycgroup

添加进程

创建新的 cgroup 后,就可以往里面添加进程了。注意下面几点:

  • 在一颗 cgroup 树里面,一个进程必须要属于一个 cgroup
    • 所以不能凭空从一个 cgroup 里面删除一个进程,只能将一个进程从一个 cgroup 移到另一个 cgroup
  • 新创建的子进程将会自动加入父进程所在的 cgroup。
    • 这也就是 tasks 和 cgroup.proc 的区别。
  • 从一个 cgroup 移动一个进程到另一个 cgroup 时,只要有目的 cgroup 的写入权限就可以了,系统不会检查源 cgroup 里的权限。
  • 用户只能操作属于自己的进程,不能操作其他用户的进程,root 账号除外。
#--------------------------第一个shell窗口----------------------
#创建一个新的cgroup
dev@ubuntu:~/cgroup/demo$ sudo mkdir test
dev@ubuntu:~/cgroup/demo$ cd test

#将当前bash加入到上面新创建的cgroup中
dev@ubuntu:~/cgroup/demo/test$ echo $$
1421
dev@ubuntu:~/cgroup/demo/test$ sudo sh -c 'echo 1421 > cgroup.procs'
#注意:一次只能往这个文件中写一个进程ID,如果需要写多个的话,需要多次调用这个命令

#--------------------------第二个shell窗口----------------------
#重新打开一个shell窗口,避免第一个shell里面运行的命令影响输出结果
#这时可以看到cgroup.procs里面包含了上面的第一个shell进程
dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
1421

#--------------------------第一个shell窗口----------------------
#回到第一个窗口,随便运行一个命令,比如 top
dev@ubuntu:~/cgroup/demo/test$ top
#这里省略输出内容

#--------------------------第二个shell窗口----------------------
#这时再在第二个窗口查看,发现top进程自动加入了它的父进程(1421)所在的cgroup
dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
1421
16515
dev@ubuntu:~/cgroup/demo/test$ ps -ef|grep top
dev      16515  1421  0 04:02 pts/0    00:00:00 top
dev@ubuntu:~/cgroup/demo/test$

#在一颗cgroup树里面,一个进程必须要属于一个cgroup,
#所以我们不能凭空从一个cgroup里面删除一个进程,只能将一个进程从一个cgroup移到另一个cgroup,
#这里我们将1421移动到root cgroup
dev@ubuntu:~/cgroup/demo/test$ sudo sh -c 'echo 1421 > ../cgroup.procs'
dev@ubuntu:~/cgroup/demo/test$ cat cgroup.procs
16515
#移动1421到另一个cgroup之后,它的子进程不会随着移动

#--------------------------第一个shell窗口----------------------
##回到第一个shell窗口,进行清理工作
#先用ctrl+c退出top命令
dev@ubuntu:~/cgroup/demo/test$ cd ..
#然后删除创建的cgroup
dev@ubuntu:~/cgroup/demo$ sudo rmdir test

cgroup.procs vs tasks

#创建两个新的cgroup用于演示
dev@ubuntu:~/cgroup/demo$ sudo mkdir c1 c2

#为了便于操作,先给root账号设置一个密码,然后切换到root账号
dev@ubuntu:~/cgroup/demo$ sudo passwd root
dev@ubuntu:~/cgroup/demo$ su root
root@ubuntu:/home/dev/cgroup/demo#

#系统中找一个有多个线程的进程
root@ubuntu:/home/dev/cgroup/demo# ps -efL|grep /lib/systemd/systemd-timesyncd
systemd+   610     1   610  0    2 01:52 ?        00:00:00 /lib/systemd/systemd-timesyncd
systemd+   610     1   616  0    2 01:52 ?        00:00:00 /lib/systemd/systemd-timesyncd
#进程610有两个线程,分别是610和616

#将616加入c1/cgroup.procs
root@ubuntu:/home/dev/cgroup/demo# echo 616 > c1/cgroup.procs
#由于cgroup.procs存放的是进程ID,所以这里看到的是616所属的进程ID(610)
root@ubuntu:/home/dev/cgroup/demo# cat c1/cgroup.procs
610
#从tasks中的内容可以看出,虽然只往cgroup.procs中加了线程616,
#但系统已经将这个线程所属的进程的所有线程都加入到了tasks中,
#说明现在整个进程的所有线程已经处于c1中了
root@ubuntu:/home/dev/cgroup/demo# cat c1/tasks
610
616

#将616加入c2/tasks中
root@ubuntu:/home/dev/cgroup/demo# echo 616 > c2/tasks

#这时我们看到虽然在c1/cgroup.procs和c2/cgroup.procs里面都有610,
#但c1/tasks和c2/tasks中包含了不同的线程,说明这个进程的两个线程分别属于不同的cgroup
root@ubuntu:/home/dev/cgroup/demo# cat c1/cgroup.procs
610
root@ubuntu:/home/dev/cgroup/demo# cat c1/tasks
610
root@ubuntu:/home/dev/cgroup/demo# cat c2/cgroup.procs
610
root@ubuntu:/home/dev/cgroup/demo# cat c2/tasks
616
#通过tasks,我们可以实现线程级别的管理,但通常情况下不会这么用,
#并且在cgroup V2以后,将不再支持该功能,只能以进程为单位来配置cgroup

#清理
root@ubuntu:/home/dev/cgroup/demo# echo 610 > ./cgroup.procs
root@ubuntu:/home/dev/cgroup/demo# rmdir c1
root@ubuntu:/home/dev/cgroup/demo# rmdir c2
root@ubuntu:/home/dev/cgroup/demo# exit
exit

结论:将线程 ID 加到 cgroup1 的 cgroup.procs 时,会把线程对应进程 ID 加入 cgroup.procs 且还会把当前进程下的全部线程 ID 加入到
tasks 中。

这里看起来,进程和线程好像效果是一样的。

区别来了,如果此时把某个线程 ID 移动到另外的 cgroup2 的 tasks 中,会自动把 线程 ID 对应的进程 ID 加入到 cgroup2 的
cgroup.procs 中,且只把对应线程加入 tasks 中。

此时 cgroup1 和 cgroup2 的 cgroup.procs 都包含了同一个进程 ID,但是二者的 tasks 中却包含了不同的线程 ID。

这样就实现了线程粒度的控制。但通常情况下不会这么用,并且在 cgroup V2 以后,将不再支持该功能,只能以进程为单位来配置
cgroup。

3.如何使用 Go 和 Cgroups 交互

其实挺简单的,就是用 Go 翻译了一遍上面的命令。

后续则是按照这个流程实现自己的 docker。

具体代码如下:

// cGroups cGroups初体验
func cGroups() {
// /proc/self/exe是一个符号链接,代表当前程序的绝对路径
if os.Args[0] == "/proc/self/exe" {
// 第一个参数就是当前执行的文件名,所以只有fork出的容器进程才会进入该分支
fmt.Printf("容器进程内部 PID %d\n", syscall.Getpid())
// 需要先在宿主机上安装 stress 比如 apt-get install stress
cmd := exec.Command("sh", "-c", `stress --vm-bytes 200m --vm-keep -m 1`)
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
} else {
// 主进程会走这个分支
cmd := exec.Command("/proc/self/exe")
cmd.SysProcAttr = &syscall.SysProcAttr{Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWNS | syscall.CLONE_NEWPID}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
fmt.Println(err)
os.Exit(1)
}
// 得到 fork 出来的进程在外部namespace 的 pid
fmt.Println("fork 进程 PID:", cmd.Process.Pid)
// 在默认的 memory cgroup 下创建子目录,即创建一个子 cgroup
err := os.Mkdir(filepath.Join(cgroupMemoryHierarchyMount, "testmemorylimit"), 0755)
if err != nil {
fmt.Println(err)
}
// 	将容器加入到这个 cgroup 中,即将进程PID加入到cgroup下的 cgroup.procs 文件中
err = ioutil.WriteFile(filepath.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "cgroup.procs"),
[]byte(strconv.Itoa(cmd.Process.Pid)), 0644)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// 	限制进程的内存使用,往 memory.limit_in_bytes 文件中写入数据
err = ioutil.WriteFile(filepath.Join(cgroupMemoryHierarchyMount, "testmemorylimit", "memory.limit_in_bytes"),
[]byte("100m"), 0644)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
cmd.Process.Wait()
}
}

首先是一个 if 判断,区分主进程和子进程,分别执行不同逻辑。

  • 主进程:fork 出子进程,并创建 cgroup,然后将子进程加入该 cgrouop
  • 子进程:执行 stress 命令,以消耗内存,便于查看 memory cgroup 的效果

运行并测试:

lixd  ~/projects/docker/mydocker main $ go build main.go
lixd  ~/projects/docker/mydocker main $ sudo ./main
fork 进程 PID: 21827
当前进程 pid 1
stress: info: [7] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

根据输出可以知道,我们 fork 出的进程,pid 为 21827。

通过pstree -pl查看进程关系:

$pstree -pl
init(1)─┬─init(8)───init(9)───fsnotifier-wsl(10)
        ├─init(12)───init(13)─┬─exe(20618)─┬─sh(20623)───stress(20624)───stress(20625)
        │                     │            ├─{exe}(20619)
        │                     │            ├─{exe}(20620)
        │                     │            ├─{exe}(20621)
        │                     │            └─{exe}(20622)
└─zsh(14)───sudo(21821)───main(21822)─┬─exe(21827)─┬─sh(21832)───stress(21833)───stress(21834)

可以看到 21827 进程 最终启动了一个 21834 的 stress 进程。

top查看以下内存占用:

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
21834 root      20   0  208664 101564    272 D  35.2   1.3   0:14.38 stress

可以看到 RES 101564,也就是刚好 100M,说明我们的 cgroup 是有效果的。

4. 小结

本文主要介绍了

  • 1)Docker 是如何使用 cgroups 的;
  • 2) hierarchy 和 cgroup 相关的操作,如创建删除等;
  • 3)最后则是简单演示了如何使用 Go 和 cgroups 进行交互。

至此,cgroups 的相关内容就告一段落了,加上本文一共包括 3 篇文章:
初探 Linux Cgroups:资源控制的奇妙世界
深入剖析 Linux Cgroups 子系统:资源精细管理

包括以下内容:

  • 1)cgroups 怎么实现资源控制的
  • 2)相关 subsystem 演示
  • 3)docker 怎么使用 cgroups 的
  • 4)go 怎么操作 cgroups

后续可以使用 go 实现 docker 的时候,资源控制就会使用 go 和 cgroups 交互来实现。


如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。

搜索公众号【探索云原生】即可订阅


5.参考

cgroups(7) — Linux manual page

Linux Cgroup 系列(02):创建并管理 cgroup

cgroup 源码分析 6——cgroup 中默认控制文件的内核实现分析

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

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

相关文章

Python OpenCV 影像处理:影像二值化

► 前言 本篇将介绍使用OpenCV Python对于图像上的二值化操作&#xff0c;二值化主要用途包括图像分割、物体侦测、文字识别等。这种转换可以帮助检测图像中的物体或特定特征&#xff0c;并提取有用的信息。透过程式码的说明&#xff0c;让各位了解OpenCV Python于图像处理上的…

mysql安装及部署

1.在/usr/local下创建mysql目录 cd /usr/local mkdir /mysql 2.在mysql目录中下载 cd mysql/ wget https://cdn.mysql.com/archives/mysql-8.0/mysql-8.0.34-1.el9.x86_64.rpm-bundle.tar 3.解压 tar xvf mysql-8.0.34-1.el9.x86_64.rpm-bundle.tar 4.安装 dnf localinst…

JS-WebAPIs-元素尺寸与位置(三)

使用场景&#xff1a; 前面案例滚动多少距离&#xff0c;都是我们自己算的&#xff0c;最好是页面滚动到某个元素&#xff0c;就可以做某些事。简单说&#xff0c;就是通过js的方式&#xff0c;得到元素在页面中的位置这样我们可以做&#xff0c;页面滚动到这个位置&#xff0…

137基于matlab的面和线接触的滑块润滑

基于matlab的面和线接触的滑块润滑&#xff0c;基于有限差分法求解面接触滑块润滑的油膜厚度、油膜压力&#xff0c;输出三维可视化结果。程序已调通&#xff0c;可直接运行。 137 matlab油膜压力油膜厚度 (xiaohongshu.com)

商用软件方案的多种交付方式有什么优势?

商用软件方案在交付上&#xff0c;往往存在多种模式&#xff0c;包括SaaS模式、私有化部署、SDK嵌入式等等&#xff0c;SaaS模式讲究一个标准化&#xff0c;旨在最大限度的降低部署成本&#xff0c;但对于一些定制化程度比较高的需求&#xff0c;往往企业仍然需要采用私有化部署…

暴雨信息与英特尔联合发布全球首个全液冷冷板服务器参考设计

科技之家 1 月 19 日消息&#xff0c;据暴雨服务器官方消息&#xff0c;1 月 18 日&#xff0c;暴雨信息与英特尔联合发布全球首个全液冷冷板服务器参考设计&#xff0c;并面向业界开放&#xff0c;推动全液冷冷板解决方案在全球数据中心的大规模部署应用。 基于该参考设计&am…

10个常考的前端手写题,你全都会吗?

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热爱技术和分享&#xff0c;欢迎大家交流&#xff0c;一起学习进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 今天来分享一下10个常见的JavaScript手写功能。 目录 1.实现new 2.call、apply、…

区域入侵检测AI边缘计算智能分析网关V4如何通过ssh进行服务器远程运维

智能分析网关V4是一款高性能、低功耗的AI边缘计算硬件设备&#xff0c;它采用了BM1684芯片&#xff0c;集成高性能8核ARM A53&#xff0c;主频高达2.3GHz&#xff0c;并且INT8峰值算力高达17.6Tops&#xff0c;FB32高精度算力达到2.2T&#xff0c;每个摄像头可同时配置3种算法&…

SD-WAN保障服务质量的五大核心功能

基于 MPLS 的传统专用网络极度结构化、僵化&#xff0c;缺乏灵活性和变通性。SD-WAN让传统的MPLS专用网络显得相形见绌。这种创新的网络架构不仅在管理灵活性和成本效益方面取得显著进展&#xff0c;更能够随着业务的扩张和新分支机构的增设而高效扩展。在SD-WAN中&#xff0c;…

Linux操作系统----gdb调试工具(配实操图)

绪论​ “不用滞留采花保存&#xff0c;只管往前走去&#xff0c;一路上百花自会盛开。 ——泰戈尔”。本章是Linux工具篇的最后一章。gdb调试工具是我们日常工作中需要掌握的一项重要技能我们需要基本的掌握release和debug的区别以及gdb的调试方法的指令。下一章我们将进入真正…

探索设计模式的魅力:“感受单例模式的力量与神秘” - 掌握编程的王牌技巧

在软件开发的赛场上&#xff0c;单例模式以其独特的魅力长期占据着重要的地位。作为设计模式中的一员&#xff0c;它在整个软件工程的棋盘上扮演着关键性角色。本文将带你深入探索单例模式的神秘面纱&#xff0c;从历史渊源到现代应用&#xff0c;从基础实现到高级技巧&#xf…

数据结构实验6:图的应用

目录 一、实验目的 1. 邻接矩阵 2. 邻接矩阵表示图的结构定义 3. 图的初始化 4. 边的添加 5. 边的删除 6. Dijkstra算法 三、实验内容 实验内容 代码 截图 分析 一、实验目的 1&#xff0e;掌握图的邻接矩阵的存储定义&#xff1b; 2&#xff0e;掌握图的最短路径…

Idea 开发环境不断切换git代码分支导致冲掉别人代码

问题分析 使用git reflog查看执行命令&#xff0c;以下是发生事故的切换和提交动作 46f72622e1 HEAD{41}: commit: feat: 【Sales - 6.3】小程序端不登录也可以录入客户线索 c5e7d9f6e1 HEAD{42}: fetch origin feature/20240102_Sales6.3_xingang:feature/20240102_Sales6.3…

基于 IDEA 进行 Maven 依赖管理

一、依赖管理概念 Maven 依赖管理是 Maven 软件中最重要的功能之一。Maven 的依赖管理能够帮助开发人员自动解决软件包依赖问题&#xff0c;使得开发人员能够轻松地将其他开发人员开发的模块或第三方框架集成到自己的应用程序或模块中&#xff0c;避免出现版本冲突和依赖缺失等…

旧衣物回收小程序搭建,旧衣回收利润有多高?

在当下人们的生活水平日益提高&#xff0c;淘汰的旧衣物逐渐增加。面对这些旧衣物&#xff0c;很多人舍不得丢弃&#xff0c;既浪费又污染环境&#xff0c;只能常年堆积在家里。为了解决这一问题&#xff0c;旧衣回收成为了大众的选择。大家能够将淘汰的衣物进行回收&#xff0…

Java-NIO篇章(4)——Selector选择器详解

Selector介绍 选择器&#xff08;Selector&#xff09;是什么呢&#xff1f;选择器和通道的关系又是什么&#xff1f;这里详细说明&#xff0c;假设不用选择器&#xff0c;那么一个客户端请求数据传输那就需要建立一个连接&#xff0c;为了避免线程阻塞&#xff0c;那么每个客…

微分方程(1)微分方程的历史

总之就是学习吧~ Introduction Without knowing something about differential equations and methods of solving them, it is difficult to appreciate the history of this important branch of mathematics. Further, the development of differential equations is inti…

如何用GOWIN创建FPGA工程

高云FPGA如何创建工程 第一步&#xff1a;安装Gowin软件&#xff0c;这个在高云的官网是可以下载的 第二步&#xff1a;点开这个软件&#xff0c;点击“New Project…” 点击“Next” 选择自己对应的器件型号&#xff1a; 工程创建成功&#xff0c;如下图&#xff1a; 最后将…

给程序加个进度条吧!1行Python代码,快速搞定~

你在写代码的过程中&#xff0c;有没有遇到过以下问题&#xff1f; 已经写好的程序&#xff0c;想看看程序执行的进度&#xff1f; 在写代码批量处理文件的时候&#xff0c;如何显示现在处理到第几个文件了&#xff1f; &#x1f446;如上图所示的进度条是一个最好的解决方法…

如何解决分支机构无法连入总部采购管理系统的难题

案例背景&#xff1a; 某企业业务规模不断壮大&#xff0c;内部采购流程越发复杂&#xff0c;供应商资质情况各异难以管理&#xff0c;为提高内部采购效率和采购品质&#xff0c;优化供应链管理&#xff0c;确保采购环节公正透明可溯&#xff0c;该企业集中化部署了采购管理系…