【Kubernetes① 基础】一、容器基础
目录
- 一、进程
- 二、隔离与限制
- 三、容器镜像
- 总结
- 参考书籍
一、进程
- 容器技术的兴起源于PaaS技术(平台即服务)的普及;
- Docker公司发布的Docker项目具有里程碑式的意义;
- Docker项目通过“容器镜像”解决了应用打包这个根本性难题(CloudFoundry)。
容器本身的价值非常有限,真正有价值的是容器编排。
容器只是一种特殊的进程。
- 容器到底是怎么回事?
容器实际上就是一种沙盒技术,沙盒就是能够像集装箱一样把应用“装”起来,这样应用之间就会因为有了边界而不会相互干扰;被装进集装箱的应用也可以被方便地“搬来搬去”。这就是PaaS最理想的状态。
- 边界的实现手段
假设有一个计算加法的小程序,输入来自一个文件,计算结果输出到另一文件中。计算机只认识0和1,因此最终这段程序会被翻译为二进制文件,再加上所需的数据(输入文件中),放在磁盘上,就等于一个程序,也叫代码的“可执行镜像”,然后就可在计算机上运行这个程序。运行过程如下:
- os从程序中发现输入数据保存在文件中,会将这些数据加载到内存中待命;
- os读到加法指令,指示CPU完成加法操作;
- CPU与内存协作进行加法计算,会使用寄存器存放数值、内存堆栈保存执行的命令和变量;同时计算机里还有被打开的文件,以及各种I/O设备在不断的调用中修改自己的状态。
因此,程序一旦被执行,就从磁盘上的二进制文件变成了由 {计算机上内存数据+寄存器的值+堆栈中的指令+被打开的文件+各种设备的状态信息}
组成的一个集合。
因此,进程的静态表现 = 存储在磁盘中;动态表现 = 运行起来变成计算机中数据和状态的总和。
容器的核心功能就是:通过约束和修改进程的动态表现,为其创造一个“边界”。对于Docker等大多数Linux容器来说,Croups技术
是用来制造约束的主要手段;Namespace技术
是用来修改进程视图的主要方法。
- Namespace技术
假设我Linux上已拥有Docker,首先创建一个容器:
docker run -it gcc:9.2.0 /bin/bash
# 启动容器,在容器中执行命令/bin/bash,并为我分配一个终端来与这个容器交互
-it :告诉Docker在启动容器后为我们分配一个文本输入/输出环境(TTY),与容器的标准输入关联,这样就可以与Docker容器进行交互;
gcc:9.2.0: 我已拉取到Docker中的镜像文件;
/bin/bash:在Docker容器中运行的程序。
至此我的ubuntu变成了宿主机,容器在此宿主机上运行。再在容器中运行命令:ps
在容器外在运行ps命令:
可以看到,再Docker内最开始执行的是/bin/bash,因此他是容器内部的1号进程(PID=1),而在容器外部,他的PID不再是1。这就意味着容器中正在执行的这两个进程,已经被Docker隔离到了与宿主机完全不同的世界中。什么原理呢?
当我们在宿主机上运行一个/bin/bash程序,os会为其分配一个PID(如图5302),粗略地将/bin/bash理解为公司的第5302号员工,1号是CEO。现在我们想通过Docker在容器运行这个/bin/bash程序,这时,Docker会在这个第5302号员工入职时给他一个”障眼法“,让他永远看不到前面的5301位员工,因此他就会误以为自己是公司的第一位员工(对应容器内的PID=1)。
这种机制实际上就是对被隔离的应用的进程空间动了手脚,使这些进程只能看到重新计算过的PID(例如PID=1),实际上在宿主机中他还是第5302号进程。
这种技术就是Linux中的Namespace机制,他只是Linux创建进程的一个可选参数。如Linux中创建进程的系统调用(之一)是clone() ,这个系统调用会为我们创建一个新进程,并返回他的PID:
int pid = clone(main_function, stack_size, SIGCHLD, NULL);
而当我们使用clone()系统调用创建一个新进程时,可以在参数中指定CLONE_NEWPID参数:
int pid = clone(main_function, stack_size, SIGCHLD | CLONE_NEWPID, NULL);
此时这个新创建的进程会看见一个全新的进程空间,在此空间中,他的PID就是1,他既看不到宿主机中真正的进程空间,也看不到其他PID Namespace中的具体情况。而在真实的宿主机空间中,这个进程的PID是他的真实数值(比如5302)。
除PID Namespace外,Linux还提供了Mount、UTS、IPC、Network、User这些Namespace。
这就是Linux容器实现的最基本原理。因此,Docker实际在创建容器进程时,指定了该进程所需要启用的一组Namespace参数
,这样该容器只能看到当前Namespace所限定的资源、文件、设备、状态或配置了,而对于宿主机及其他不相关程序就完全不透明。
因此,容器实际上就是一种特殊的容器而已。
二、隔离与限制
虚拟机与容器均有“为进程划分一个独立的空间”思想。
- 对比虚拟机与容器
虚拟机的工作原理中,Hypervisor的软件是虚拟机最主要的部分,他通过硬件虚拟化功能模拟出了运行一个操作系统所需要的各种硬件,如CPU、内存等,然后在这些虚拟的硬件上安装了一个客户操作系统,这样用户的应用进程就可以在这个虚拟机中运行了。Docker使用一个名为Docker引擎的软件替换了Hypervisor,Docker不严谨的也可称为是轻量级虚拟化技术。
但二者拥有本质不同:Hypervisor会创建实体虚拟机,并对应用的隔离环境负责;而Docker并不会真正的在宿主机中创建一个Docker容器,他帮用户启动的还是原来的应用进程,只不过为其加上了各种Namespace参数,因此真正对隔离环境负责的还是宿主机本身,甚至Docker可以去掉。因此对比图应该如下:
因此容器相较于虚拟机的好处:
- 使用虚拟化作为沙盒必须由Hypervisor创建一个真实存在的虚拟机,且里面需要运行一个完整的客户操作系统,因此带来了额外的资源消耗和占用。实验表明,一个与逆行CentOS的KVM在不做任何优化的情况下,虚拟机本身要占用100~200MB内存。而Docker使用Namespace作为隔离手段,不需要完整的客户操作系统,使得容器额外的资源占用几乎可忽略不计;
- 用户应用对宿主机操作系统的系统调用会被Hypervisor拦截和处理,又是一层性能消耗,尤其对计算资源、网络、磁盘I/O的损耗很大;容器化后的用户应用仍为宿主机上的普通进程,因此不产生性能损耗。
summary:敏捷,高性能。
- 隔离
有利就有弊:Linux Namespace最主要的问题:隔离的不彻底。由于容器只是宿主机上的特殊进程,因此容器间共用同一宿主机的操作内核
,带来了一系列问题:
- 容器通过Mount Namespace挂载其他版本的操作系统文件有的会行不通,如Windows上运行Linux容器、低版本Linux宿主机上运行高版本Linux容器等。
- Linux内核中很多对象不能被Namespace化。例如时间,若在容器中使用settimeofday(2)系统调用修改时间,则整个宿主机时间会被随之修改,因此在容器中部署应用时需要考虑“什么能做、什么不能做”。尽管可以使用Seccomp技术对所有发起的系统调用进行过滤和甄别,但会拖累系统性能,同时也不知道要开启或禁用哪些系统调用。
- 限制
隔离之后为什么要进行限制?以PID Namespace为例,虽然容器内的1号进程在“障眼法”下无法看到容器外的其他信息,但在宿主机上,他作为第5302号进程与其他所有进程间存在平等的竞争关系。这意味着虽然第5302号进程表面被隔离起来了,但他所能够使用到的资源(如CPU、内存等)可随时被宿主机上其他进程或容器占用,同时也可能自己用光资源。显然这作为一个沙盒不合理。
- Cgroups
Linux Cgroups的主要作用是限制一个进程组能够使用的资源上限
(包括CPU、内存、网络带宽等)。此外还可进行优先级设置、审计、挂起和恢复进程等。
Linux、中,Cgroups向用户暴露出来的操作接口是文件系统
,他以文件和目录的形式组织在/sys/fs/cgroup路径下,使用如下命令可显示:
mount -t cgroup
如图所示,输出结果是一系列文件系统目录。/sys/fs/cgroup下有许多诸如cpu、memory等的子目录,也称子系统,这些都是这台机器当前可被Cgroups限制的资源种类。在子系统对应的资源种类下,可以看到这类资源具体可以被限制的方法。以CPU子系统为例:
可以注意输出中有cpu.cfs_quota_us、cpu.cfs_period_us等关键词,这两个参数组合使用可以限制进程长度在period一段时间内,只能分配到总量为quota的CPU时间。
这样的配置文件如何使用呢?(以CPU为例)
首先在对应的子系统下创建目录,这个目录称为一个控制组,操作系统会在新创建的目录下自动生成该子系统的资源限制文件。
然后运行一个死循环脚本(该程序的PID是3750),该进程将CPU占满(使用top命令查看)。
接下来查看demo_container目录下的文件quota,此时发现quota还没有任何限制(=-1),CPU的period默认是100ms(10000us)。
接下来,修改这些文件内容来设限:向quota文件写入20ms(20000us),这个操作意味着在每100ms(period)时间里被该控制组限制的进程只能使用20ms(quota)的CPU时间,即该进程只能使用到20%的CPU带宽。然后将该进程的PID写入tasks文件,如上设置即可生效。
注意命令执行过程为:
1. sudo -i
2. cd /sys/fs/cgroup/cpu/demo_container
3. echo 20000 > cpu.cfs_quota_us
4. cat cpu.cfs_quota_us
5. echo 3750 > tasks
6. cat tasks
不能直接echo,也不能sudo echo…,权限不够。
然后使用top命令查看cpu利用率下降到了20%。
我们自己创建的demo_container目录可以通过rmdir demo_container命令删除。
除了CPU子系统外,Cgroups的每一项子系统都有其独有的资源限制能力,如:
- blkio(block I/O):为块设备限定I/O限制,一般用于磁盘等设备;
- cpuset:为进程分配单独的CPU核以及对应的内存节点;
- memory:为进程设定内存使用限制。
- 等等
对于Docker等Linux容器来说,Cgroups只需在每个子系统下面为每个容器创建一个控制组(新建一个容器目录),启动容器之后,将这个容器的PID写入控制组的tasks文件即可。而为控制组中的资源文件赋值,通过docker run命令中的参数:
docker run -it --cpu-period=100000 --cpu-quota=20000 image_name /bin/bash # 以mysql镜像为例
然后查看Cgroups中该容器的控制组中资源配置文件信息:
左侧为容器内部文件系统信息,右侧为宿主机文件系统信息。可以发现,在容器内部的/sys/fs/cgroup/cpu子系统下没有其他目录,前面提到这是因为Namespace给他施了一个“障眼法”,使其只能看见自己沙盒内部的情况;而在宿主机的/sys/fs/cgroup/cpu子系统下新增了一个docker目录。进入这个docker目录,发现多了刚刚所创建的容器的控制组。
进入该容器的控制组,查看资源配置信息,与docker run命令中所指定的参数一致:这意味着,这个容器只能使用20%的CPU带宽。
- 总结
一个正在运行的容器其实就是一个启用了多个Namespace的应用进程,这个进程所能够使用的资源受Cgroups配置的限制。因此,容器是一个单进程
模型。因此用户的应用进程实际就是容器中PID=1的进程,也是其他后续所创建的所有进程的父进程,这意味着,一个容器中无法同时运行两个应用,除非找一个PID=1的程序来担任这两个进程的父进程(比如systemd或supervisord)。
与Namespace相似,Cgroups本身也有许多不完善之处,最典型的是/proc文件系统的问题。
/proc目录下存储的是记录当前内核运行状态的一系列特殊文件,用户可以通过访问这些文件查看系统以及当前正在运行的进程信息。如CPU使用情况、内存占用率等。这些文件也是top指令的主要数据来源。
若在容器中执行top命令会发现他显示的信息是宿主机中的信息,而非当前容器的数据。(目前已修正)
三、容器镜像
- 容器中的进程看到的文件系统是如何的?
这是一个关于Mount Namespace的问题,容器中的应用进程理应看到一套完全独立的文件系统,这样他在自己的容器目录下进行操作(例如/tmp),就完全不会受宿主机及其他容器的影响。
真实情况呢?
用如下程序验证:
#define _GNU_SOURCE
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];char* const container_args[] = {"/bin/bash",NULL
};int container_main(void *arg) {printf("Container - inside the container!\n");execv(container_args[0], container_args);printf("Something's wrong");return 1;
}int main() {printf("Parent - start a container!\n");pid_t container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWNS | SIGCHLD, NULL);printf("%d\n", container_pid);waitpid(container_pid, NULL, 0);printf("Parent - container stopped!\n");return 0;
}
编译运行:
1. gcc -o test_clone_func test_clone_func.c
2. ./test_clone_func
遇到的问题:
clone()函数返回的pid为-1,证明创建失败。查阅clone()参数使用发现,我们在函数中使用CLONE_NEWNS时需要管理员权限。
因此我们需要将第二步替换成如下:
2. sudo ./test_clone_func
这样就进入了这个“容器”中。在容器中执行ls命令,会发现/tmp目录与宿主机相同!
Monut Namespace修改的是容器进程对文件系统的“挂载点”的认知,即只有在“挂载”这个操作发生之后才能改变进程视图,在此之前会直接继承宿主机的各个挂载点。因此在创建新进程时除了要启用Mount Namespace外,还要告诉容器那些目录需要重新挂载(比如/tmp):
int container_main(void *arg) {printf("Container - inside the container!\n");mount("none", "/tmp", "tmpfs", 0, ""); # 以“tmpfs”内存盘格式重新挂载/tmp目录execv(container_args[0], container_args);printf("Something's wrong");return 1;
}
此时再查看容器中的/tmp文件,为空,意味挂载生效:
因此,Mount Namespace的使用与其他Namespace不同的是:对容器进程视图的修改一定要伴随挂载操作才能生效。
- 如何实现容器看到的文件系统是一个隔离了的隔离环境,而不是继承宿主机的文件系统呢(容器镜像:rootfs)?
只需在容器启动之前重新挂载他的整个根目录“/”即可,由于Namespace的存在,这个挂载操作对宿主机并不可见。
在Linux操作系统中有一个chroot命令(change root file system),可以在shell中改变进程的根目录到指定位置。假设有一个$HOME/test目录,想把它作为/bin/bash的根目录:
# 创建test目录和几个lib文件夹
1. mkdir -p $HOME/test
2. mkdir -p $HOME/test/{bin,lib64,lib} # 注意,之间一定不能有空格
3. cd $T
# 把bash命令复制到test目录下对应的bin文件夹中
4. cp -v /bin/{bash,ls} $HOME/test/bin
# 把bash命令所需要的so文件复制到test目录下对应的lib文件夹中
# 使用ldd命令查找so文件
5. T=$HOME/test
6. list="$(ldd /bin/ls | egrep -o '/lib.*\.[0-9]')"
7. for i in $list; do cp -v "$i" "${T}${i}"; done
# 执行chroot命令,将/bin/bash的根目录更改为$HOME/test
8. chroot $HOME/test /bin/bash
# 查看根目录是否被“修改”
9. ls /
# 此时发现输出内容是$HOME/test内容,而非宿主机。
Mount Namespace正是基于对chroot的改进才发明出的,也是第一个Namespace。
容器镜像:通常在容器根目录下挂载整个操作系统的文件系统(如Ubuntu 20.04的ISO),这个挂载在容器根目录上为容器进程提供隔离后执行环境的文件系统成为“容器镜像”,或rootfs(根文件系统)。
Docker项目最核心的原理:为待创建的用户进程:
- 启用Linux Namespace配置;
- 设置指定的Cgroups参数;
- 切换进程的根目录(change root)
但rootfs只是一个操作系统所包含的目录、配置和文件,并不包括操作系统的内核。操作系统只有在开机启动时才会加载指定版本的内核镜像。同一台机器上的所有容器共享宿主机的操作系统内核,因此如果应用需要配置内核参数实际上会修改宿主机的,一经修改则对所有容器来说都是被修改过的。
由于rootfs的存在,容器拥有一重要特性:一致性
。对于PaaS来说,由于云端与本地服务器环境不同,应用打包是一个极其麻烦的问题,而容器镜像(rootfs)将整个操作系统与应用一起打包,应用与他运行时所需的依赖都被封撞到了一起。(对于一个应用,操作系统本身才是他运行时所需要的最完整的依赖库)。
这种下沉到操作系统级别的运行环境的一致性,填平了应用在本地开发和远端执行环境之间难以逾越的鸿沟。
- 新问题:难道每开发一次或升级现有的就要重复制作一次rootfs吗?
例如我用Ubuntu操作系统的ISO做了一个rootfs并安装了Java环境,来部署我的Java应用,我的同事在发布他的java应用是我希望直接使用我的rootfs而不是再重复一遍这个步骤。
因此Docker在镜像设计中引入了“层(layer)”概念,这些修改都基于旧的rootfs,其他修改以增量方式添加,所有人只需要维护一个相对于base rootfs修改的增量内容。用户制作镜像的每一步操作都会生成一个层,即增量rootfs。这种想法使用了UnionFS(Union file system)联合文件系统的能力:
UnionFS:将不同位置的目录联合挂载到同一目录下,且相同文件名的文件合并。
1. mkdir A B C
2. touch ./A/a.txt ./A/common.txt ./B/b.txt ./B/common.txt
3. #向./A/common.txt写入"hello A",./B/common.txt写入"hello B"
# 通过联合挂载将A B两个目录挂载到同一目录C上
4. sudo mount -t aufs -o dirs=./A:./B none ./C
此时如果在目录C中对a.txt b.txt commom.txt做修改,也会在对应目录A B中生效。
若想取消挂载:
sudo umount A B C
那对于相同文件名且内容也相同的呢?将./A/common.txt与./B/common.txt内容都修改为"hello world"后在挂载到C上,再对./C/common.txt做修改:
同上,对于相同文件,只有A目录下的文件被修改了,说明默认是A文件夹下的。
目前docker支持的联合文件系统有很多种,包括:AUFS、overlay、overlay2、DeviceMapper、VSF等。查看本机Docker所使用的UnionFS:例如我的是overlay2。
docker info
- 容器镜像(rootfs)
容器的rootfs由如下三部分组成(以ubuntu镜像为例):
- 只读层
挂载方式为readonly+whiteout,这些层都以增量的方式包含了镜像的一部分。
- 可读可写层
容器rootfs的最上面一层,挂载方式为read write,写入文件之前,这个目录是空的,一旦容器有了修改操作,修改的内容就会以增量的方式出现在该层中。对于删除操作会在可读可写层生成一个whiteout文件,把只读层文件内容“遮挡”起来。如删除只读层中的一个foo文件,这个操作实际上是在可读可写层创建一个.wh.foo文件,对应于只读层ro+wh的挂载方式。
对于修改后的容器,可使用docker commit和push指令保存这个修改后的可读可写层,而原先只读层的内容不会发生任何改变。
- Init层
这是一个以-init结尾的层,存在于只读层和可读可写层之间,是由Docker项目单独生成的一个内部层,用来存放/etc/hosts、/etc/resolv.conf等信息。这些文件本来属于只读层的一部分,但是用户在启动容器时需要写入一些指定的值(比如hostname等),且这些修改只对当前容器有效,并不希望在docker commit时提交这些信息,因此设立一个Init层,单独挂载这些文件的修改。
docker镜像相同的层间可以共享
- Docker Volume(数据卷)
宿主机如何获取容器进程中新建的文件?容器中的进程怎么才能访问到宿主机上的文件和目录?
Volume机制允许将宿主机上的指定目录或文件挂载到容器中进行读取和修改。
docker run -v /test ...
在宿主机上创建一个临时目录/var/lib/docker/volumes/[VOLUME_ID]/_data,并将它挂载到容器的/test目录上。
docker run -v /home:/test
直接将宿主机的/home目录挂载到容器的/test目录上。
原理是什么?当容器进程被创建后,尽管开启了Mount Namespace,但在执行chroot(或pivot_root)之前容器进程可以看到宿主机上的整个文件系统,因此只需在容器rootfs准备好后chroot执行之前,将指定的宿主机目录挂载到指定的容器目录在宿主机上对应的目录
上(具体路径参照overlay2原理)即可。由于挂载操作时容器已经创建,即Mount Namespace已经开启,因此这个挂载事件只对容器可见,宿主机看不到容器内部的这个挂载点。这就是绑定挂载。实际上就是一个inode替换过程。
这里的容器进程
是指Docker创建的一个容器初始化进程(dockerinit),而非应用进程(ENTRYPOINT+CMD)。dockerinit会负责完成根目录的准备、挂载设备和目录、配置hostname等一系列需要在容器内进行的初始化操作,最后它通过execv()系统调用,让应用进程取代自己成为容器中PID=1的进程。
绑定挂载(bind mount):将一个目录或文件而非整个设备挂载到指定目录上,在挂载点上进行的任何操作只发生在被挂载的目录或文件上,原挂载点的内容会被隐藏起来且不受影响。(例如对容器/test目录的实际操作会发生在宿主机/home上,而原/test内容不会被影响。)
容器的/test目录挂载在rootfs的可读可写层,但不会被docker commit提交。(因为docker commit发生在宿主机空间,而该挂载操作对宿主机不可见,始终以为容器/test目录对应在宿主机上的路径始终为空。但新建目录不是挂载操作,因此commit的镜像中会多出一个/test空目录。)
总结
容器实际上是由Linux Namespace、Linux Cgroups和rootfs这三种技术构建出来的进程隔离环境。一个正在运行的Linux容器可一分为二:
- 容器镜像(container image):一组联合挂载的rootfs;
- 容器运行时(container runtime):一个由Namespace+Cgroups构成的隔离环境。
参考书籍
《深入剖析Kubernetes》 张磊著。
相关文章:

【Kubernetes① 基础】一、容器基础
目录 一、进程二、隔离与限制三、容器镜像总结参考书籍 一、进程 容器技术的兴起源于PaaS技术(平台即服务)的普及;Docker公司发布的Docker项目具有里程碑式的意义;Docker项目通过“容器镜像”解决了应用打包这个根本性难题(CloudFoundry)。 容器本身的价…...

计算机网络第1章(概述)万字笔记详细版
1.1、计算机网络在信息时代的作用 计算机网络已由一种通信基础设施发展成为一种重要的信息服务基础设施计算机网络已经像水,电,煤气这些基础设施一样,成为我们生活中不可或缺的一部分 我国互联网发展状况 中国互联网络信息中心CNNIC 1.2、…...

每日一练算法题(堆串的基本操作StrReplace(S, T, V))
6-2 堆串的基本操作StrReplace(S, T, V) 编写算法,实现堆串的基本操作StrReplace(S, T, V)。 初始条件: 串S, T和 V 均已存在,且 V 是非空串。 操作结果: 用V替换主串S中出现的所有与(模式串)T相等的不重叠的子串。输入格式: 第一行:S 第二行&#…...

IRP默认最小流程
IRP是Windows内核中的一种非常重要的数据结构。上层应用程序与底层驱动程序通信时,应用程序会发出I/O请求,操作系统将相应的I/O请求转换成相应的IRP,不同的IRP会根据类型被分派到不同的派遣例程中进行处理。 irp相当于R3下的消息,…...

【全网最全】AI产品经理面试高频100题答案解析
详细的目录如下,需要的小伙伴可以详细看一下~ 第一章:机器学习和深度学习的关系 第二章:机器学习7大经典算法 算法一:K近邻算法【分类算法】 1.1 KNN 算法的实现原理 1.2 KNN应用场景举例:预测候选人能不能拿到 O…...

VLLM实现大模型服务的部署
文章目录 安装离线推理适配openAI-API的API服务使用python命令行部署使用docker部署调用启动成功的API 安装 # (Recommended) Create a new conda environment. conda create -n myenv python3.9 -y conda activate myenv# Install vLLM with CUDA 12.1. pip install vllm -i …...

Java 基数排序
基数排序(Radix Sort)是一种非比较型整数排序算法,通常用于对数字进行排序。它按照数字的每一位(从最低有效位到最高有效位或从最高有效位到最低有效位)进行排序,每次使用一个稳定的排序算法(如…...

红帽发送邮件操作
一.将/mnt挂在至/run/media mount /dev/sr0 /mnt 二.查看下载时间 ll /etc/yum.repos.d/ 三.下载安装包 dnf install s-nail -y 四.配置邮件服务 在最下面一行输入######################### 接着输入邮件 set from18013844913163.com set smtpsmtp.163.com set smt…...

学习记录:js算法(六十一):添加与搜索单词 - 数据结构设计
文章目录 添加与搜索单词 - 数据结构设计思路一思路二 添加与搜索单词 - 数据结构设计 请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。 实现词典类 WordDictionary : ● WordDictionary() 初始化词典对象 ● voi…...

Jetpack-ObservableField实现双向绑定
ObservableField是Android Data Binding库中的一个类,用于实现双向绑定。双向绑定意味着当数据模型中的数据发生变化时,UI会自动更新;同时,当用户在UI上进行操作时,数据模型也会相应地更新。 1.在你的项目中添加Data …...

STARnak, LTR 模型笔记
未完成. 1. 简述 CIKM 23 的一篇论文, 任务为 Learning To Rank, 输入为 候选集合, 输出为 有序列表, 用于 top-n 推荐场景. 思考: 它是要替代 ctr 预估么?它跟 mind 这种召回, 有啥大的不一样么? 2. 网络结构 u u u: 将用户(或 query) 记为 u H q d X , d Y , . . . H…...

【数据结构】:破译排序算法--数字世界的秩序密码(二)
文章目录 前言一.比较排序算法1.Bubble Sort冒泡排序1.1.冒泡排序原理1.2.冒泡排序过程1.3.代码实现1.4.复杂度和稳定性 2.Quick Sort快速排序2.1递归快速排序2.1.1.递归快速排序原理2.1.2.递归快速排序过程2.1.3.代码实现 2.2.非递归快速排序2.2.1.非递归快速排序原理2.2.2.非…...

2024年《生成式ai大模型》都学什么内容呢?
近期大家都在关注的2024 2024年10月25日 — 2024年10月29日 在成都举办的第八期《新质技术之生成式AI、大模型、多模态技术开发与应用研修班》都学什么内容呢?下面我们来看看: 1.了解AIGC发展现状与核心技术。 2.掌握Transformer核心开发技术。 3.掌握…...

kubernetes自定义pod启动用户
一、kubernetes自定义pod启动用户 一)以root用户启动pod containers:- name: ...image: ...securityContext:runAsUser: 0 二)以普通用户启动pod 1、从构建镜像角度修改 # RUN命令执行创建用户和用户组(命令创建了一个用户newuser设定ID为1…...

C4T避风型电动采光排烟天窗(图集09J621-2)
C4T避风型电动采光排烟天窗是09J621-2《电动采光排烟天窗》图集中的一种窗型。也是一种现代化的建筑消防排烟通风采光设备,被广泛应用于多风地区厂房。 C4T避风型电动采光排烟天窗配有成品避风罩,该避风置由钢制骨架和彩色钢板构成,固定在电动…...

多态常见面试问题
1、什么是多态? 多态(Polymorphism)是面向对象编程中的一个重要概念,它允许同一个接口表现出不同的行为。在C中,多态性主要通过虚函数来实现,分为编译时多态(静态多态)和运行时多态…...

案例-登录认证(上)
案例-登录认证 在前面的课程中,我们已经实现了部门管理、员工管理的基本功能,但是大家会发现,我们并没有登 录,就直接访问到了Tlias智能学习辅助系统的后台。 这是不安全的,所以我们今天的主题就是登录 认证。 最终我…...

对BSV区块链下一代节点Teranode的答疑解惑(上篇)
发表时间:2024年8月7日 2024年初BSV区块链研发团队揭晓了即将到来的Teranode更新的突破性特性,这些特性将显著提升网络的效率和处理速度,使BSV区块链能够达到百万级TPS。 Teranode的项目主管Siggi Oskarsson强调:“当你阅读这…...

vue父子组件传参的方法
在Vue.js中,父子组件之间的参数传递是常见的需求。Vue提供了几种方法来实现这一点,主要包括使用props传递数据给子组件,以及使用事件(如自定义事件)从子组件向父组件发送数据。以下是详细的说明: 父组件向…...

关于this指针
在普通成员函数里 1.this指针不能显式说明,但能显示使用,是个常指针,只能改变指针指向的对象的内容,不能改变指针存储的对象的地址。 2.this指针一般不用特别写上,只有在(我目前的知识范围内)类…...

机器学习西瓜书
绪论 1.1绪论1.2课程定位 科学:是什么,为什么; 技术:怎么做; 工程:做的多快好省; 应用: 1.3机器学习 经典定义:利用经验改善系统自身的性能 1.4典型的机器学习过程 1.5计算学习理论 机器学习有坚实的理论基础,由Leslie Valiant的计算学习理论现在有一个数据样本x,现在…...

如何使用 Puppeteer 和 Browserless 运行自动化测试?
Puppeteer:什么是 Puppeteer 及其功能 Puppeteer 是一个 Node.js 库。使用 Puppeteer,您可以在所有基于 Chromium 的浏览器上测试您的网站,包括 Chrome、Microsoft Edge Chrome 和 Chromium。此外,Puppeteer 可用于网页抓取、自动…...

python菜鸟知识
去除空格 str 这是 含 空格 print(f去除两端空格{str.strip()}) print(f去除左端空格{str.lstrip()}) print(f去除右端空格{str.rstrip()}) print(f去除全部空格{str.replace(" ", "")}) 方法返回对象yield yield :.join([ip, port])yield {ranking…...

GPT4o,GPTo1-preview, 拼
兄弟们GPT刚开的 需要上车的扣,工作用 大家一起PIN分摊点压力。 在当今数字化的时代,程序员这一职业已经从幕后走到了前台,成为推动科技进步和社会变革的关键力量。编写代码、解决问题、不断学习新技术,程序员们的日常充满了挑战与…...

论文笔记:Pre-training to Match for Unified Low-shot Relation Extraction
论文来源:ACL 2022 论文地址:https://aclanthology.org/2022.acl-long.397.pdf 论文代码:https://github.com/fc-liu/MCMN (笔记不易,请勿恶意转载抄袭!!!) 目录 A…...

一篇文章带你快速了解linux中关于信号的核心内容
1. 信号概念 信号是操作系统用来通知进程某个特定事件已经发生的一种方式。它们是一种软件中断,可以被发送到进程以对其进行异步通知。 2. 信号处理的三种方式 执行默认动作执行自定义动作忽略 signal() 函数:将信号处理设置为 SIG_IGN,可…...

openEuler、Linux操作系统常见操作-(6)如何登录Linux
如何登录Linux Linux登陆方式主要有如下两种: 。本地登陆 。一个典型的Linux系统将运行六个虚拟控制台和一个图形控制台,openEuler目前暂未支持图形化界面; 可以通过CtrlAltF[1-6]在6个虚拟控制台之间进行切换。 远程登录 。默认情况下openEuler支持远程登录&…...

Python基础语法条件
注释 注释的作用 通过用自己熟悉的语言,在程序中对某些代码进行标注说明,这就是注释的作用,能够大大增强程序的可读性。 注释的分类及语法 注释分为两类:单行注释 和 多行注释。 单行注释 只能注释一行内容,语法如下…...

006-MAVEN 的使用
MAVEN 的使用 一、依赖范围二、依赖的传递性三、依赖的原则四、依赖的排除 一、依赖范围 在引入log4j 依赖的时候,有一个scope设置,这个scope设置的值就是对应的依赖范围(因为compile 是默认的依赖范围,所以有时也可以省略)。 Maven 提供了…...

npm使用时报错:Could not retrieve https://npm.taobao.org/mirrors/node/index.json.
在使用npm时报错,报错信息如下: 报错的原因:是原来的淘宝镜像地址过期了 解决办法:修改镜像地址。打开nvm的安装地址 -->settings.txt文件 -->配置下载源 1、将settings.txt文件中的 node_mirror: https://npm.taobao.or…...