Kubernetes Pod 底层实现原理

news2024/12/24 9:02:56

文章目录

  • 前言
  • 一、探索 Container
        • 1.1 设置实验环境(playground)
        • 1.2 探索容器的 namespace
        • 1.3 探索容器的 cgroups
  • Check the memory limit.
  • 二、探索 Pod
        • 2.1 设置实验环境(playground)
        • 2.2 探索 Pod 的容器
        • 2.3 探索 Pod 的命名空间
        • 2.4 探索 Pod 的 cgroups
        • 三、利用 Docker 实现 Pod
  • 四、总结


前言

刚开始接触 Kubernetes 时,你学到的第一件事就是每个 Pod 都有一个唯一的 IP 和主机名,并且在同一个 Pod 中,容器可以通过 localhost 相互通信。所以,显而易见,一个 Pod 就像一个微型的服务器。

  • 但是,过段时间,你会发现 Pod 中的每个容器都有一个隔离的文件系统,并且从一个容器内部,你看不到在同一 Pod 的其他容器中运行的进程。好吧!也许 Pod 不是一个微型的服务器,而只是一组具有共享网络堆栈的容器。

  • 但随后你会了解到,Pod 中的容器可以通过共享内存进行通信!所以,在容器之间,网络命名空间不是唯一可以共享的东西……

基于最后的发现,所以,我决定深入了解:

  • Pod 是如何在底层实现的
  • Pod 和 Container 之间的实际区别是什么
  • 如何使用 Docker 创建 Pod

在此过程中,我希望它能帮助我巩固我的 Linux、Docker 和 Kubernetes 技能。

一、探索 Container

OCI 运行时规范并不将容器实现仅限于 Linux 容器,即使用 namespace 和 cgroup 实现的容器。但是,除非另有明确说明,否则本文中的容器一词指的是这种相当传统的形式。

1.1 设置实验环境(playground)

在了解构成容器的 namespace 和 cgroups 之前,让我们快速设置一个实验环境:

$ cat > Vagrantfile <<EOF
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "debian/buster64"
  config.vm.hostname = "docker-host"
  config.vm.define "docker-host"
  config.vagrant.plugins = ['vagrant-vbguest']

  config.vm.provider "virtualbox" do |vb|
    vb.cpus = 2
    vb.memory = "2048"
  end

  config.vm.provision "shell", inline: <<-SHELL
    apt-get update
    apt-get install -y curl vim
  SHELL

  config.vm.provision "docker"
end
EOF

$ vagrant up
$ vagrant ssh

最后让我们启动一个容器:

$ docker run --name foo --rm -d --memory='512MB' --cpus='0.5' nginx

1.2 探索容器的 namespace

首先我们来看一下,当容器启动后,哪些隔离原语(primitives)被创建了:

# Look up the container in the process tree.
$ ps auxf
USER       PID  ...  COMMAND
...
root      4707       /usr/bin/containerd-shim-runc-v2 -namespace moby -id cc9466b3e...
root      4727        \_ nginx: master process nginx -g daemon off;
systemd+  4781            \_ nginx: worker process
systemd+  4782            \_ nginx: worker process

# Find the namespaces used by 4727 process.
$ sudo lsns
        NS TYPE   NPROCS   PID USER    COMMAND
...
4026532157 mnt         3  4727 root    nginx: master process nginx -g daemon off;
4026532158 uts         3  4727 root    nginx: master process nginx -g daemon off;
4026532159 ipc         3  4727 root    nginx: master process nginx -g daemon off;
4026532160 pid         3  4727 root    nginx: master process nginx -g daemon off;
4026532162 net         3  4727 root    nginx: master process nginx -g daemon off;

我们可以看到用于隔离以上容器的命名空间是以下这些:

  • mnt(挂载):容器有一个隔离的挂载表。
  • uts(Unix 时间共享):容器拥有自己的 hostname 和 domain。
  • ipc(进程间通信):容器内的进程可以通过系统级 IPC 和同一容器内的其他进程进行通信。
  • pid(进程 ID):容器内的进程只能看到在同一容器内或拥有相同的 PID 命名空间的其他进程。
  • net(网络):容器拥有自己的网络堆栈。

注意,用户(user)命名空间没有被使用,OCI 运行时规范提及了对用户命名空间的支持。不过,虽然 Docker 可以将此命名空间用于其容器,但由于固有的限制,它默认情况下没有使用。因此,容器中的 root 用户很可能是主机系统中的 root 用户。谨防!

另一个没有出现在这里的命名空间是 cgroup。我花了一段时间才理解 cgroup 命名空间与 cgroups 机制(mechanism)的不同。Cgroup 命名空间仅提供一个容器的 cgroup 层次结构的孤立视图。同样,Docker 也支持将容器放入私有 cgroup 命名空间,但默认情况下没有这么做。

1.3 探索容器的 cgroups

Linux 命名空间可以让容器中的进程认为自己是在一个专用的机器上运行。但是,看不到别的进程并不意味着不会受到其他进程的影响。一些耗资源的进程可能会意外的过多消耗宿主机上面共享的资源。

这时候就需要 cgroups 的帮助!

可以通过检查 cgroup 虚拟文件系统中的相应子树来查看给定进程的 cgroups 限制。Cgroupfs 通常被挂在 /sys/fs/cgroup 目录,并且进程特定相关的部分可以在 /proc//cgroup 中查看:

PID=$(docker inspect --format '{{.State.Pid}}' foo)

# Check cgroupfs node for the container main process (4727).
$ cat /proc/${PID}/cgroup
11:freezer:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
10:blkio:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
9:rdma:/
8:pids:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
7:devices:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
6:cpuset:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
5:cpu,cpuacct:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
4:memory:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
3:net_cls,net_prio:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
2:perf_event:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
1:name=systemd:/docker/cc9466b3eb67ca374c925794776aad2fd45a34343ab66097a44594b35183dba0
0::/system.slice/containerd.service

似乎 Docker 使用 /docker/模式。
ID=$(docker inspect --format ‘{{.Id}}’ foo)

Check the memory limit.

$ cat /sys/fs/cgroup/memory/docker/${ID}/memory.limit_in_bytes
536870912  # Yay! It's the 512MB we requested!

# See the CPU limits.
ls /sys/fs/cgroup/cpu/docker/${ID}

有趣的是在不明确设置任何资源限制的情况下启动容器都会配置一个 cgroup。实际中我没有检查过,但我的猜测是默认情况下,CPU 和 RAM 消耗不受限制,Cgroups 可能用来限制从容器内部对某些设备的访问。

这是我在调查后脑海中呈现的容器:

在这里插入图片描述

二、探索 Pod

现在,让我们来看看 Kubernetes Pod。与容器一样,Pod 的实现可以在不同的 CRI 运行时(runtime)之间变化。例如,当 Kata 容器被用来作为一个支持的运行时类时,某些 Pod 可以就是真实的虚拟机了!并且正如预期的那样,基于 VM 的 Pod 与传统 Linux 容器实现的 Pod 在实现和功能方面会有所不同。

为了保持容器和 Pod 之间公平比较,我们会在使用 ContainerD/Runc 运行时的 Kubernetes 集群上进行探索。这也是 Docker 在底层运行容器的机制。

2.1 设置实验环境(playground)

这次我们使用基于 VirtualBox driver 和 Containd 运行时的 minikube 来设置实验环境。要快速安装 minikube 和 kubectl,我们可以使用 Alex Ellis 编写的 arkade 工具:

# Install arkade ()
$ curl -sLS https://get.arkade.dev | sh

$ arkade get kubectl minikube

$ minikube start --driver virtualbox --container-runtime containerd

实验的 Pod,可以按照下面的方式设置:

$ kubectl --context=minikube apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: foo
spec:
  containers:
    - name: app
      image: docker.io/kennethreitz/httpbin
      ports:
        - containerPort: 80
      resources:
        limits:
          memory: "256Mi"
    - name: sidecar
      image: curlimages/curl
      command: ["/bin/sleep", "3650d"]
      resources:
        limits:
          memory: "128Mi"
EOF

2.2 探索 Pod 的容器

实际的 Pod 检查应在 Kubernetes 集群节点上进行:

$ minikube ssh

让我们看看那里 Pod 的进程:

$ ps auxf
USER       PID  ...  COMMAND
...
root      4947         \_ containerd-shim -namespace k8s.io -workdir /mnt/sda1/var/lib/containerd/...
root      4966             \_ /pause
root      4981         \_ containerd-shim -namespace k8s.io -workdir /mnt/sda1/var/lib/containerd/...
root      5001             \_ /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
root      5016                 \_ /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
root      5018         \_ containerd-shim -namespace k8s.io -workdir /mnt/sda1/var/lib/containerd/...
100       5035             \_ /bin/sleep 3650d

基于运行的时间,上述三个进程组很有可能是在 Pod 启动期间创建。这很有意思,因为在清单文件中,只有两个容器,httpbin 和 sleep。
可以使用名为 ctr 的 ContainerD 命令行来交叉检查上述的发现:

$ sudo ctr --namespace=k8s.io containers ls
CONTAINER      IMAGE                                   RUNTIME
...
097d4fe8a7002  docker.io/curlimages/curl@sha256:1a220  io.containerd.runtime.v1.linux
...
dfb1cd29ab750  docker.io/kennethreitz/httpbin:latest   io.containerd.runtime.v1.linux
...
f0e87a9330466  k8s.gcr.io/pause:3.1                    io.containerd.runtime.v1.linux

的确是三个容器被创建了。同时,使用另一个和 CRI 运行时监控的命令行 crictl 检测发现,仅仅只有两个容器:

$ sudo crictl ps
CONTAINER      IMAGE          CREATED            STATE    NAME     ATTEMPT  POD ID
097d4fe8a7002  bcb0c26a91c90  About an hour ago  Running  sidecar  0        f0e87a9330466
dfb1cd29ab750  b138b9264903f  About an hour ago  Running  app      0        f0e87a9330466

但是注意,上述的 POD ID 字段和 ctr 输出的 pause:3.1 容器 id 一致。好吧,看上去这个 Pod 是一个辅助容器。

我还没有注意到在 OCI 运行时规范中有和 Pod 相对应的东西。因此,当我对 Kubernetes API 规范提供的信息不满意时,我通常直接进入 Kubernetes Container Runtime 接口(CRI)Protobuf 文件中查找相应的信息:

// kubelet expects any compatible container runtime
// to implement the following gRPC methods:

service RuntimeService {
    ...
    rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {}    
    rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {}    
    rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {}    
    rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {}
    rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {}

    rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {}
    rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {}    
    rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {}    
    rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {}
    rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {}    
    rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {}    
    rpc UpdateContainerResources(UpdateContainerResourcesRequest) returns (UpdateContainerResourcesResponse) {}    
    rpc ReopenContainerLog(ReopenContainerLogRequest) returns (ReopenContainerLogResponse) {}

    // ...    
}

message CreateContainerRequest {
    // ID of the PodSandbox in which the container should be created.
    string pod_sandbox_id = 1;
    // Config of the container.
    ContainerConfig config = 2;
    // Config of the PodSandbox. This is the same config that was passed
    // to RunPodSandboxRequest to create the PodSandbox. It is passed again
    // here just for easy reference. The PodSandboxConfig is immutable and
    // remains the same throughout the lifetime of the pod.
    PodSandboxConfig sandbox_config = 3;
}

所以,Pod 实际上就是由沙盒以及在沙盒中运行的容器组成的。沙盒管理 Pod 中所有容器的常用资源,pause 容器会在 RunPodSandbox() 调用中被启动。简单的互联网搜索就发现了该容器仅仅是一个 idle 进程。

2.3 探索 Pod 的命名空间

下面就是集群节点上的命名空间:

$ sudo lsns
        NS TYPE   NPROCS   PID USER            COMMAND
4026532614 net         4  4966 root            /pause
4026532715 mnt         1  4966 root            /pause
4026532716 uts         4  4966 root            /pause
4026532717 ipc         4  4966 root            /pause
4026532718 pid         1  4966 root            /pause
4026532719 mnt         2  5001 root            /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
4026532720 pid         2  5001 root            /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
4026532721 mnt         1  5035 100             /bin/sleep 3650d
4026532722 pid         1  5035 100             /bin/sleep 3650d

前面第一部分很像 Docker 容器,pause 容器有五个命名空间:net、mnt、uts、ipc 以及 pid。但是很明显,httpbin 和 sleep 容器仅仅有两个命名空间:mnt 和 pid。

事实证明,lsns 不是检查进程名称空间的最佳工具。相反,要检查某个进程使用的命名空间,可以参考 /proc/${pid}/ns 位置:

# httpbin container
sudo ls -l /proc/5001/ns
...
lrwxrwxrwx 1 root root 0 Oct 24 14:05 ipc -> 'ipc:[4026532717]'
lrwxrwxrwx 1 root root 0 Oct 24 14:05 mnt -> 'mnt:[4026532719]'
lrwxrwxrwx 1 root root 0 Oct 24 14:05 net -> 'net:[4026532614]'
lrwxrwxrwx 1 root root 0 Oct 24 14:05 pid -> 'pid:[4026532720]'
lrwxrwxrwx 1 root root 0 Oct 24 14:05 uts -> 'uts:[4026532716]'

# sleep container
sudo ls -l /proc/5035/ns
...
lrwxrwxrwx 1 100 101 0 Oct 24 14:05 ipc -> 'ipc:[4026532717]'
lrwxrwxrwx 1 100 101 0 Oct 24 14:05 mnt -> 'mnt:[4026532721]'
lrwxrwxrwx 1 100 101 0 Oct 24 14:05 net -> 'net:[4026532614]'
lrwxrwxrwx 1 100 101 0 Oct 24 14:05 pid -> 'pid:[4026532722]'
lrwxrwxrwx 1 100 101 0 Oct 24 14:05 uts -> 'uts:[4026532716]'

虽然不太容易去注意到,但 httpbin 和 sleep 容器实际上重用了 pause 容器的 net、uts 和 ipc 命名空间!

我们可以用 crictl 交叉检测验证:

# Inspect httpbin container.
$ sudo crictl inspect dfb1cd29ab750
{
  ...
  "namespaces": [
    {
      "type": "pid"
    },
    {
      "type": "ipc",
      "path": "/proc/4966/ns/ipc"
    },
    {
      "type": "uts",
      "path": "/proc/4966/ns/uts"
    },
    {
      "type": "mount"
    },
    {
      "type": "network",
      "path": "/proc/4966/ns/net"
    }
  ],
  ...
}

# Inspect sleep container.
$ sudo crictl inspect 097d4fe8a7002
...

我认为上述发现完美的解释了同一个 Pod 中容器具有的能力:

  • 能够互相通信
    • 通过 localhost 和/或
    • 使用 IPC(共享内存,消息队列等)
  • 共享 domain 和 hostname

有趣的是,CRI API 规范似乎更加灵活。至少在语法上,它允许将 net、pid 和 ipc 命名空间限定为 CONTAINER、POD 或 NODE。因此,可以构建一个 Pod 使其容器无法通过 localhost 相互通信 。

2.4 探索 Pod 的 cgroups

Pod 的 cgroups 是什么样的?systemd-cgls 可以很好地可视化 cgroups 层次结构:

$ sudo systemd-cgls
Control group /:
-.slice
├─kubepods
│ ├─burstable
│ │ ├─pod4a8d5c3e-3821-4727-9d20-965febbccfbb
│ │ │ ├─f0e87a93304666766ab139d52f10ff2b8d4a1e6060fc18f74f28e2cb000da8b2
│ │ │ │ └─4966 /pause
│ │ │ ├─dfb1cd29ab750064ae89613cb28963353c3360c2df913995af582aebcc4e85d8
│ │ │ │ ├─5001 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ │ │ │ └─5016 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ │ │ └─097d4fe8a7002d69d6c78899dcf6731d313ce8067ae3f736f252f387582e55ad
│ │ │   └─5035 /bin/sleep 3650d
...

所以,Pod 本身有一个父节点(Node),每个容器也可以单独调整。这符合我的预期,因为在 Pod 清单中,可以为 Pod 中的每个容器单独设置资源限制。

此刻,我脑海中的 Pod 看起来是这样的:

加粗样式

三、利用 Docker 实现 Pod

如果 Pod 的底层实现是一组具有共同 cgroup 父级的半融合(emi-fused)容器,是否可以使用 Docker 生产类似 Pod 的构造?

最近我尝试做了一些类似的事情来让多个容器监听同一个套接字,我知道 Docker 可以通过 docker run —network container:语法来创建一个可以使用已存在的网络命名空间容器。但我也知道 OCI 运行时规范只定义了 create 和 start 命令。

因此,当你使用 docker exec在现有容器中执行命令时,实际上是在运行(即 create 然后 start)一个全新的容器,该容器恰好重用了目标容器的所有命名空间(证明 1[1] 和 2[2])。这让我非常有信心可以使用标准 Docker 命令生成 Pod。

我们可以使用仅仅安装了 Docker 的机器作为实验环境。但是这里我会使用一个额外的包来简化使用 cgroups:

$ sudo apt-get install cgroup-tools
  1. 首先,让我们配置一个父 cgroup 条目。为了简洁起见,我将仅使用 CPU 和内存控制器:
sudo cgcreate -g cpu,memory:/pod-foo

# Check if the corresponding folders were created:
ls -l /sys/fs/cgroup/cpu/pod-foo/
ls -l /sys/fs/cgroup/memory/pod-foo/
  1. 然后我们创建一个沙盒容器:
$ docker run -d --rm \
  --name foo_sandbox \
  --cgroup-parent /pod-foo \
  --ipc 'shareable' \
  alpine sleep infinity
  1. 最后,让我们启动重用沙盒容器命名空间的实际容器:
# app (httpbin)
$ docker run -d --rm \
  --name app \
  --cgroup-parent /pod-foo \
  --network container:foo_sandbox \
  --ipc container:foo_sandbox \
  kennethreitz/httpbin

# sidecar (sleep)
$ docker run -d --rm \
  --name sidecar \
  --cgroup-parent /pod-foo \
  --network container:foo_sandbox \
  --ipc container:foo_sandbox \
  curlimages/curl sleep 365d

你注意到我省略了哪个命名空间吗?没错,我不能在容器之间共享 uts 命名空间。似乎目前在 docker run 命令中没法实现。嗯,是有点遗憾。但是除开 uts 命名空间之外,它是成功的!

cgroups 看上去很像 Kubernetes 创建的:

$ sudo systemd-cgls memory
Controller memory; Control group /:
├─pod-foo
│ ├─488d76cade5422b57ab59116f422d8483d435a8449ceda0c9a1888ea774acac7
│ │ ├─27865 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ │ └─27880 /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
│ ├─9166a87f9a96a954b10ec012104366da9f1f6680387ef423ee197c61d37f39d7
│ │ └─27977 sleep 365d
│ └─c7b0ec46b16b52c5e1c447b77d67d44d16d78f9a3f93eaeb3a86aa95e08e28b6
│   └─27743 sleep infinity

全局命名空间列表看上去也很相似:

$ sudo lsns
        NS TYPE   NPROCS   PID USER    COMMAND
...
4026532157 mnt         1 27743 root    sleep infinity
4026532158 uts         1 27743 root    sleep infinity
4026532159 ipc         4 27743 root    sleep infinity
4026532160 pid         1 27743 root    sleep infinity
4026532162 net         4 27743 root    sleep infinity
4026532218 mnt         2 27865 root    /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
4026532219 uts         2 27865 root    /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
4026532220 pid         2 27865 root    /usr/bin/python3 /usr/local/bin/gunicorn -b 0.0.0.0:80 httpbin:app -k gevent
4026532221 mnt         1 27977 _apt    sleep 365d
4026532222 uts         1 27977 _apt    sleep 365d
4026532223 pid         1 27977 _apt    sleep 365d

httpbin 和 sidecar 容器看上去共享了 ipc 和 net 命名空间:

# app container
$ sudo ls -l /proc/27865/ns
lrwxrwxrwx 1 root root 0 Oct 28 07:56 ipc -> 'ipc:[4026532159]'
lrwxrwxrwx 1 root root 0 Oct 28 07:56 mnt -> 'mnt:[4026532218]'
lrwxrwxrwx 1 root root 0 Oct 28 07:56 net -> 'net:[4026532162]'
lrwxrwxrwx 1 root root 0 Oct 28 07:56 pid -> 'pid:[4026532220]'
lrwxrwxrwx 1 root root 0 Oct 28 07:56 uts -> 'uts:[4026532219]'

# sidecar container
$ sudo ls -l /proc/27977/ns
lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 ipc -> 'ipc:[4026532159]'
lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 mnt -> 'mnt:[4026532221]'
lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 net -> 'net:[4026532162]'
lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 pid -> 'pid:[4026532223]'
lrwxrwxrwx 1 _apt systemd-journal 0 Oct 28 07:56 uts -> 'uts:[4026532222]'

四、总结

Container 和 Pod 是相似的。在底层,它们主要依赖 Linux 命名空间和 cgroup。但是,Pod 不仅仅是一组容器。Pod 是一个自给自足的高级构造。所有 Pod 的容器都运行在同一台机器(集群节点)上,它们的生命周期是同步的,并且通过削弱隔离性来简化容器间的通信。这使得 Pod 更接近于传统的 VM,带回了熟悉的部署模式,如 sidecar 或反向代理。

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

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

相关文章

UDP协议重点总结(附实例)

文章目录前言一、网络的原生情况二、UDP协议2.1 UDP的特点2.1.1 不可靠性2.1.2 无连接&#xff08;不是缺点&#xff09;2.1.3 面向数据报&#xff08;优点&#xff09;2.1.4 缓冲区2.1.5 大小受限2.2 UDP协议端格式2.3 关于校验和2.4 基于UDP的应用层协议三、UDP总结&#xff…

P2279 [HNOI2003]消防局的设立

[HNOI2003]消防局的设立题目描述2020 年&#xff0c;人类在火星上建立了一个庞大的基地群&#xff0c;总共有 n 个基地。起初为了节约材料&#xff0c;人类只修建了 n-1 条道路来连接这些基地&#xff0c;并且每两个基地都能够通过道路到达&#xff0c;所以所有的基地形成了一个…

HTML5本地存储详解

html5 本地存储。前言一、localStorage 对象二、sessionStorage 对象三、localstorage 与 cookie 的区别四、localStorage 和 sessionStorage 二者的区别总结前言 ☀️本地存储是指在客户端存储数据&#xff0c;HTML5 为我们提供了两种 API&#xff0c;分别是 localStorage 与 …

算法是如何炼成的?

一、算 法 简 史算法可以追溯到古代埃及人和古希腊人使用的算术方法。在古代埃及&#xff0c;人们使用简单的加减法来解决基本的数学问题&#xff0c;而在古希腊&#xff0c;人们开始使用更加复杂的算术方法&#xff0c;比如平方、立方、平方根和立方根。随着数学的发展&#x…

css元素转换(旋转函数、rotateX 和 rotateY 的使用、移动函数、缩放函数、过渡、动画)详解

文章目录旋转函数rotateX 和 rotateY 的使用移动函数缩放函数过渡transition-timing-function 属性动画旋转函数 在 CSS3 中&#xff0c;使用 rotate 函数能够让指定的元素对象绕原点旋转&#xff0c;主要在二维空间内进行操作。 其语法格式如下所示&#xff1a; transform: …

【C++常用算法】STL基础语法学习 | 查找算法

目录 ●find ●find_if ●adjacent_find ●binary_ search ●count ●count_if ●find 1.功能描述&#xff1a; 查找指定元素&#xff0c;如果找到则放回指定元素的迭代器&#xff0c;若未找到则返回结束迭代器。 2.查看find定义下底层代码的函数原型&#xff1a; 3.…

Day849.ThreadLocal线程本地存储模式 -Java 性能调优实战

ThreadLocal线程本地存储模式 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于ThreadLocal线程本地存储模式的内容。 民国年间某山东省主席参加某大学校庆演讲&#xff0c;在篮球场看到十来个人穿着裤衩抢一个球&#xff0c;观之实在不雅&#xff0c;于是怒斥学校的…

用于安全医疗保健系统的基于机器学习的可伸缩区块链架构

文章目录背景相关技术简介区块链扩张性电子病历数据安全安全医疗保健的架构基于可扩展区块链架构的机器学习概述基于可扩展区块链架构的机器学习工作流程小结摘要从3.0到4.0的工业革命已经改变了医疗保健环境。患者电子健康记录(EHR)与医学研究机构共享&#xff0c;用于临床研究…

12月榜单丨B站UP主排行榜(飞瓜数据B站)发布!

飞瓜轻数发布2022年12月飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B站UP主。飞瓜…

Python:python简介

1&#xff1a;特点 一种解释型&#xff0c;面向对象&#xff0c;动态数据类型的开源高级程序设计语言 其特点就是&#xff1a;优雅&#xff0c;明确&#xff0c;简单&#xff0c;完善的基础代码库和大量的第三方库。 2&#xff1a;解释VS解释 3&#xff1a;应用场景 python…

基于androidstudio校园快递APP系统的设计与实现

1.课题背景及研究的目的和意义 1.1 课题背景 在其发展速度可谓一日千里的电子商务时代&#xff0c;大学生群体已成为网络购物群体中不可或缺的一部分。因此&#xff0c;高校师生对网购的需求也愈来愈强烈&#xff0c;校园快递的问题也成为了焦点&#xff0c;其中校园快递代理…

98%的数据被浪费,企业该如何释放数据价值?

在数字经济时代&#xff0c;对于广大企业来说&#xff0c;数据就是生产资料&#xff0c;算力则是生产力。飞速增长的业务数据&#xff0c;为现代企业提供了最具价值的资产。然而另一方面&#xff0c;如何存储、清理、管理、挖掘、运用数据&#xff0c;也给广大企业提出了艰巨的…

4天带你上手HarmonyOS ArkUI开发——《HarmonyOS ArkUI入门训练营之健康生活实战》

《HarmonyOS ArkUI入门训练营之健康饮食应用》是面向入门开发者打造的实战课程系列。特邀华为终端BG高级开发工程师作为本次训练营讲师&#xff0c;以健康饮食为例&#xff0c;开展技术教学及实战案例分享&#xff0c;助力入门开发者快速提升技能实力进阶。 目标学员 入门开发者…

apache httpClient关于cookie解析的报错处理

报错信息&#xff1a;o.a.h.c.p.ResponseProcessCookies - Invalid cookie header: "Set-Cookie: account"xxxxx"; expiresFri, 03 Feb 2023 06:02:40 GMT; httponly; Path/". Invalid expires attribute: Fri, 03 Feb 2023 06:02:40 GMThttpClient版本&am…

4年翻4倍年薪30W+的测试工程师个人成长之路

欢迎同行来交流&#xff0c;wx 群二维码应该过不了审核&#xff0c;私聊要把。税收图保证真实性。 一、何为测试 简单做一下科普。测试简而言之就是应用上线前&#xff0c;验证应用是否存在bug&#xff0c;是否满足产品的需求。大家津津乐道的程序员&#xff0c;也就是开发&am…

stm32 的 ESP8266 wifi 模块 (ESP - 12s) 的使用

1. ESP8266 的器件介绍 2. ESP2866外设 的引脚 3. 我所用的的ESP2866 的引脚图 4. 代码 编程的串口 5.wifi 的指令 1. AT 测试指令 2. ATRST 重启模块 3. ATGMR 查看版本信息 4. ATRESTORE 恢复出厂设置 5. ATUART115200,8,1,0,0 串口设置 串口号&#xff…

【SpringBoot应用篇】SpringBoot 业务代码中常用技巧

【SpringBoot应用篇】SpringBoot 业务代码中常用技巧自定义拦截器自定义过滤器过滤器和拦截器的区别获取Spring容器对象BeanFactoryAware接口ApplicationContextAware接口ApplicationListener接口全局异常处理类型转换器参数解析器Import导入配置普通类配置类ImportSelectorImp…

异地旁路组网:zerotier

有这么一个需求&#xff1a;需要远程访问内网的nas。然后现成的解决方案有蒲公英这个方案&#xff0c;但是个人版的话限了只能3个设备&#xff0c;因此找了半天&#xff0c;最后选择了功能类似的zerotier. 创建网络 zerotier的使用很简单&#xff0c;首先去官网http://zeroti…

vue 时间栏选择

效果图&#xff1a; 用el-carousel 的轮播组件 将样式修改 添加change事件 区分左右点击 获取当前年 和 当前月 <el-carouseltrigger"click"height"36px":autoplay"false"arrow"always"change"carouselChange"><e…

Leetcode.189 轮转数组

题目链接 Leetcode.189 轮转数组 题目描述 给你一个数组&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步:[7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5…