关于docker的一些深入了解

本文将深入介绍一下docker方面的知识,不尽完全,慢慢完善。

进程

进程的概念

在介绍docker的相关知识前,先了解一下相关概念。进程就是系统中正在运行的程序,进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就是创建了一个进程,在这个过程中操作系统对进程资源的分配和释放,可以认为进程就是一个程序的一次执行过程。

Linux下的三个特殊进程

Linux下有三个特殊的进程idle进程(PID=0),init进程(PID=1),和kthreadd(PID=2)

  • idle进程由系统自动创建,运行在内核态。idle进程的pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换,是其他所有进程的祖先进程。
  • kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间,负责所有内核进程的调度和管理。
    它的任务就是管理和调度其他内核线程kernel_thread,会循环执行一个kthread的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程 。
  • init进程由idle通过kernel_thread创建,在内核空间完成初始化后,加载init程序。

init进程

在这里我们就主要讲解下init进程,init进程由idle进程创建,完成系统的初始化。在Linux操作系统启动时,首先从 BIOS 开始,接下来进入 boot loader,由 bootloader 载入内核,进行内核初始化。内核初始化的最后一步就是启动 pid 为 1 的 init 进程,这个进程是系统的第一个进程,它负责产生其他所有用户进程。由此我们可以看出,整个系统的用户进程,是一棵由init进程作为根的进程树。(注意是用户进程

init 的一些特点

  • init 以守护进程方式存在,是所有其他进程的祖先。init 进程非常独特,能够完成其他进程无法完成的任务。
    init系统能够定义、管理和控制 init 进程的行为。它负责组织和运行许多独立的或相关的始化工作(因此被称为 init 系统),从而让计算机系统进入某种用户预订的运行模式。仅仅将内核运行起来是毫无实际用途的,必须由 init 系统将系统代入可操作状态。比如启动外壳 shell 后,便有了人机交互,这样就可以让计算机执行一些预订程序完成有实际意义的任务。

  • init进程有一个非常厉害的地方,就是SIGKILL信号对它无效。很显然,如果我们将一棵树的树根砍了,那么这棵树就会分解成很多棵子树,这样的最终结果是导致整个操作系统进程杂乱无章,无法管理。所以为了防止用户误操作init进程是无法kill掉的。
    init(PID 1)进程的发展也是一段非常有趣的过程,从最早的sysvinit,到upstart,再到systemd。我们可以用 pstree -p查看PID 1的 进程是谁。
    init(PID 1)的作用是负责清理那些被抛弃的进程(孤儿和僵尸进程)所留下来的痕迹,有效的回收的系统资源,保证系统长时间稳定的运行,可谓是功不可没。在理解了它的重要性之后,我们今天主要探讨一下在容器中的PID 1是怎么回事。

僵尸进程

僵尸进程指的是:进程退出后,到其父进程还未对其调用wait/waitpid之间的这段时间所处的状态。一般来说,这种状态持续的时间很短,我们一般很难在系统中捕捉到。但是,一些粗心的程序员可能会忘记调用wait/waitpid,或者由于某种原因未执行该调用等等,那么这个时候就会出现长期驻留的僵尸进程了。如果大量的产生僵尸进程,其进程号就会一直被占用,可能导致系统不能产生新的进程。(子进程挂了,如果父进程不给子进程“收尸”(调用 wait/waitpid),那这个子进程小可怜就变成了僵尸进程。)

孤儿进程

父进程先于子进程退出,那么子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)接管,并由init进程对它完成状态收集(wait/waitpid)工作。

容器中的PID 1

对Docker有一定使用经验的童鞋应该知道,容器并不是一个完整的操作系统,它也没有什么内核初始化过程,更没有像init(1)这样的初始化过程。在容器中被标志为PID 1的进程实际上就是一个普普通通的用户进程,也就是我们制作镜像时在Dockerfile中指定的ENTRYPOINT(CMD)的那个进程。而这个进程在宿主机上有一个普普通通的进程ID,而在容器中之所以变成PID 1,是因为linux内核提供的PID namespaces功能,如果宿主机的所有用户进程构成了一个完整的树型结构,那么PID namespaces实际上就是将这个ENTRYPOINT进程(包括它的后代进程)从这棵大树剪下来,很显然,剪下来的这部分东西本身也是一个树型结构,它完全可以自己长成一棵苍天大树(不断地fork),当然子namespaces里面是看不到整棵树的原貌的,但是父级的namespaces确可以看到完整的子树。

pid1 的测试

创建一个测试镜像

[root@k8s-m1 k8s-total]# cat Dockerfile 
From centos:7
CMD ["/bin/sh","-c","sleep 3600"]

[root@k8s-m1 k8s-total]# docker build -t lifecycle:v1 .
Sending build context to Docker daemon  3.223MB
Step 1/2 : From centos:7
 ---> eeb6ee3f44bd
Step 2/2 : CMD ["/bin/sh","-c","sleep 3600"]
 ---> Running in 5c9b2704c6cd
Removing intermediate container 5c9b2704c6cd
 ---> 8d208d2b880b
Successfully built 8d208d2b880b
Successfully tagged lifecycle:v1

[root@k8s-m1 k8s-total]# docker run -idt lifecycle:v1 
3971502e8f0410f779da7ed4a79aab8260754d9b2211b248b1153cd4ef6dd45e

[root@k8s-m1 k8s-total]# docker ps  -l
3971502e8f04   lifecycle:v1                                        "/bin/sh -c 'sleep 3…"   9 seconds ago    Up 8 seconds                          friendly_hodgkin

[root@k8s-m1 k8s-total]# docker exec -it 39 /bin/bash
[root@3971502e8f04 /]# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4364   356 pts/0    Ss+  02:15   0:00 sleep 3600
root         6  1.6  0.0  11828  1892 pts/1    Ss   02:16   0:00 /bin/bash
root        19  0.0  0.0  51732  1704 pts/1    R+   02:16   0:00 ps aux
[root@3971502e8f04 /]# 

从上面我们可以看到pid为1的是一个/bin/sh的进程,容器是单独一个pid namespaces的。通过下图可以更方便理解。由于子namespaces无法看到父级的namespaces,所以容器里第一个进程(也就是cmd)认为自己是pid为1,容器里其余进程都是它的子进程。
在这里插入图片描述

#在容器内部使用kill -9杀pid为1的进程发现是杀不掉的
[root@3971502e8f04 /]# kill -9 1
[root@3971502e8f04 /]# ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4364   356 pts/0    Ss+  02:15   0:00 sleep 3600
root         6  0.0  0.0  11828  1892 pts/1    Ss   02:16   0:00 /bin/bash
root        20  0.0  0.0  51732  1704 pts/1    R+   02:19   0:00 ps aux
[root@3971502e8f04 /]# exit

宿主机上测试是否能杀掉容器内部pid为1 的进程

#查看容器内部pid为1的进程在宿主机上的pid号,通过docker top查看
[root@k8s-m1 k8s-total]# docker top 3971502e8f04
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                29298               29279               0                   10:34               pts/0               00:00:00            sleep 3600

#或者ps也可以查看
[root@k8s-m1 k8s-total]# ps aux|grep sleep
root     29298  0.4  0.0   4364   356 pts/0    Ss+  10:34   0:00 sleep 3600
root     29676  0.0  0.0 112812   976 pts/0    S+   10:35   0:00 grep --color=auto sleep

[root@k8s-m1 k8s-total]# kill -9 29298
[root@k8s-m1 k8s-total]# docker ps -a|grep 3971502e8f04
3971502e8f04   lifecycle:v1                                        "/bin/sh -c 'sleep 3…"   20 minutes ago       Exited (137) 7 seconds ago                friendly_hodgkin
#可以看到在宿主机上已经将容器内部pid为1的容器杀掉

容器存活时间

[root@k8s-m1 k8s-total]# docker run -d centos:7 ls
b80e296e4667f106cc5c71a39050841cc8a87917de16c3d582118b9818a8fd7b
[root@k8s-m1 k8s-total]# docker run -d centos:7 sleep 3600
5665a9169f9a1bfbdb0e38f5fb9bdd828fb7c79cd2edbba56be14abcbd4251c8

[root@k8s-m1 k8s-total]# docker ps -a|grep centos
5665a9169f9a   centos:7                                            "sleep 3600"             10 seconds ago   Up 9 seconds                              wonderful_germain
b80e296e4667   centos:7                                            "ls"                     21 seconds ago   Exited (0) 20 seconds ago                 peaceful_ishizaka

[root@k8s-m1 k8s-total]# 

docker run 后面镜像后面的command和arg会覆盖掉镜像的CMD(注意entrypoint一般不能被覆盖,注意二者区别)。上面例子通过命令行添加了cmd覆盖掉centos镜像默认的CMD bash。我们可以看到ls的容器直接退出了,但是sleep 3600的容器会运行3600s后才会退出。这也说明了容器不是虚拟机,容器是个隔离的进程。

而且容器的存活是容器里pid为1的进程运行时长决定的。所以nginx的官方镜像里就是用的exec格式让nginx充当pid为1的角色
CMD [“nginx”, “-g”, “daemon off;”]

JDK无法识别cgroup限制

首先Docker容器本质是宿主机上的一个进程,它与宿主机共享一个/proc目录,也就是说我们在容器内看到的/proc/meminfo,/proc/cpuinfo与直接在宿主机上看到的一致。
如下:

[root@8baf80228c25 /]# head -n3 /proc/meminfo 
MemTotal:        8007180 kB
MemFree:         2976140 kB
MemAvailable:    5661936 kB
[root@8baf80228c25 /]# exit
[root@k8s-m1 k8s-total]# head -n3 /proc/meminfo 
MemTotal:        8007180 kB
MemFree:         2991332 kB
MemAvailable:    5677132 kB
[root@k8s-m1 k8s-total]# 

jvm也是读取/proc目录,会导致无法识别cgroup限制。默认情况下,JVM的Max Heap Size是系统内存的1/4,假如我们系统是8G,那么JVM将的默认Heap≈2G。

Docker通过CGroups完成的是对内存的限制,而/proc目录是已只读形式挂载到容器中的,由于默认情况下Java压根就看不见CGroups的限制的内存大小,而默认使用/proc/meminfo中的信息作为内存信息进行启动,这种不兼容情况会导致,如果容器分配的内存小于JVM的内存,JVM进程申请超过限制的内存会被docker认为oom杀掉。

测试用例(OPENJDK)
在JDK8u212版本之前,JVM在容器里面识别到的是宿主机的内存。如果没有手动调整堆大小的话JVM默认会使用1/4的宿主机内存。这样会远远大于容器规格限制的内存,导致oom之后容器自动重启。这里我用我们生产用的openjdk8做演示,jdk8也是一个长期维护版本。测试机器为8G内存,给容器限制内存为4G,看JDK默认参数下的最大堆为多少。

jdk 212版本之前

[root@k8s-m1 k8s-total]# docker run -m 4GB --rm  openjdk:8u181 java -XshowSettings:vm  -version
VM settings:
    Max. Heap Size (Estimated): 1.70G
    Ergonomics Machine Class: server
    Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_181"
OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-2~deb9u1-b13)
OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)

未能正确识别CGroup限制,使用的是/proc/meminfo 里面的值

jdk 212版本之后

[root@k8s-m1 k8s-total]# docker run -m 4GB --rm  openjdk:8-jdk java -XshowSettings:vm  -version
VM settings:
    Max. Heap Size (Estimated): 910.50M
    Ergonomics Machine Class: server
    Using VM: OpenJDK 64-Bit Server VM

openjdk version "1.8.0_312"
OpenJDK Runtime Environment (build 1.8.0_312-b07)
OpenJDK 64-Bit Server VM (build 25.312-b07, mixed mode)
[root@k8s-m1 k8s-total]# 

能正确识别CGroup限制

总结,OpenJDK8老版本无法识别容器限制,我们在选jdk的时候可以选取高版本的OpenJDK8或adoptopenjdk。如果不想指定-Xmx,而让Java进程自动的发现容器限制,那么请选择JDK8u212之后的版本。如果你想要指定-Xmx,那么你选什么版本都可以。

Docker容器优雅终止方案

作为一名系统可靠工程师(SRE),你可能经常需要重启容器,毕竟 Kubernetes 的优势就是快速弹性伸缩和故障恢复,遇到问题先重启容器再说,几秒钟即可恢复,实在不行再重启系统,这就是系统重启工程师的杀手锏。然而现实并没有理论上那么美好,某些容器需要花费 10s 左右才能停止,这是为啥?有以下两种可能性:

  • 容器中的进程收到了信号,但不能进行处理。
  • 容器中应用的关闭时间确实就是这么长。
    对于第二种可能性这个还是主要需要开发对代码进行优化,本文主要解决第一种

如果要构建一个新的 Docker 镜像,肯定希望镜像越小越好,这样它的下载和启动速度都很快,一般我们都会选择一个瘦了身的操作系统(例如 Alpine,Busybox 等)作为基础镜像。
问题就在这里,这些基础镜像的 init 系统 也被抹掉了,这就是问题的根源!

init 系统有以下几个特点:

  • 它是系统的第一个进程,负责产生其他所有用户进程。
  • init 以守护进程方式存在,是所有其他用户进程的先祖。
    它主要负责:
    • 启动守护进程
    • 回收孤儿进程
    • 将操作系统信号转发给子进程

Docker 容器停止过程
对于容器来说,init 系统不是必须的,当你通过命令 docker stop mycontainer 来停止容器时,docker CLI 会将 TERM 信号发送给 mycontainer 的 PID 为 1 的进程。

  • 如果 PID 1 是 init 进程 ,那么 PID 1 会将 TERM 信号直接转发给子进程,然后子进程开始关闭,最后容器终止。
  • 如果没有 init 进程 - 那么容器中的应用进程(Dockerfile 中的 ENTRYPOINT 或 CMD 指定的应用,新版的docker应该做了优化,不管是使用ENTRYPOINT 或 CMD,shell模式在制作镜像时都会转为exec模式)就是 PID 1,不需要转发。应用进程直接负责响应 TERM 信号。这时又分为两种情况:
    • 应用收到不处理 SIGTERM - 如果应用没有监听 SIGTERM 信号,或者应用中没有实现处理 SIGTERM 信号的逻辑,应用就不会停止,容器也不会终止。
    • 应用收到 SIGTERM 信号并处理信号
      第一种会导致容器停止时间很长 运行命令 docker stop mycontainer 之后,Docker 会等待 10s,如果 10s 后容器还没有终止,Docker 就会绕过容器应用直接向内核发送 SIGKILL,内核会强行杀死应用,从而终止容器。

容器进程收不到 SIGTERM 信号?
如果容器中的进程没有收到 SIGTERM 信号,很有可能是因为应用进程不是 PID 1,PID 1 是 shell,而应用进程只是 shell 的子进程。而 shell 不具备 init 系统的功能,也就不会将操作系统的信号转发到子进程上,这也是容器中的应用没有收到 SIGTERM 信号的常见原因。

解决方案 :使用 init 系统

如果容器中的应用默认无法处理 SIGTERM 信号,又不能修改代码,可以在容器中添加一个 init 系统。init 系统有很多种,这里使用tini测试,它是专用于容器的轻量级 init 系统,使用方法也很简单:

  • 安装 tini
  • 将 tini 设为容器的默认应用
  • 将 test.sh 作为 tini 的参数

具体的 Dockerfile 如下:

[root@k8s-m1 tmp]#  cat Dockerfile
FROM alpine:3.18
COPY test.sh .
RUN chmod +x test.sh
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--", "./test.sh"]

[root@k8s-m1 tmp]# docker build -t test:v1 .
Sending build context to Docker daemon  24.06kB
Step 1/5 : FROM alpine:3.18
 ---> c1aabb73d233
Step 2/5 : COPY test.sh .
 ---> 7fd1ff5ff917
Step 3/5 : RUN chmod +x test.sh
 ---> Running in 772972a29e18
Removing intermediate container 772972a29e18
 ---> 8336d2d63696
Step 4/5 : RUN apk add --no-cache tini
 ---> Running in 2461ef80c3f4
fetch https://dl-cdn.alpinelinux.org/alpine/v3.18/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.18/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tini (0.19.0-r1)
Executing busybox-1.36.1-r0.trigger
OK: 7 MiB in 16 packages
Removing intermediate container 2461ef80c3f4
 ---> b54a49cb55aa
Step 5/5 : ENTRYPOINT ["/sbin/tini", "--", "./test.sh"]
 ---> Running in fcd4c55b5ebd
Removing intermediate container fcd4c55b5ebd
 ---> 69039ca9c5a1
Successfully built 69039ca9c5a1
Successfully tagged test:v1

[root@k8s-m2 tmp]# docker  run -d test:v1 
9c6fbeae5422b62ee0cac50cc240d1ef83280045c302c730a373566ca6c13b8b

[root@k8s-m2 tmp]# docker ps -l
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS     NAMES
9c6fbeae5422   test:v1   "/sbin/tini -- ./tes…"   3 seconds ago   Up 2 seconds             vigorous_tereshkova

[root@k8s-m2 tmp]# docker exec -it 9c /bin/sh
/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /sbin/tini -- ./test.sh
    6 root      0:00 {test.sh} /bin/sh ./test.sh
   21 root      0:00 /bin/sh
   28 root      0:00 ps aux
/ # 

从上面结果可以看到现在 tini 就是 PID 1,它会将收到的系统信号转发给子进程 test.sh。

如果你想直接通过 docker 命令来运行容器,可以直接通过参数 --init 来使用 tini,其实不需要在镜像中安装 tini。如果是 Kubernetes 就不行了,还得老老实实安装 tini。

其实在k8s中的ingress-nginx就使用了dumb-init,一样的效果

    "Cmd": [
        "/nginx-ingress-controller"
    ],
    "ArgsEscaped": true,
    "Image": "sha256:f3745c5705f1617a2b44c28ee7f5637257b9ca281b9df0a2069c6c8d30ebbba8",
    "Volumes": null,
    "WorkingDir": "/etc/nginx",
    "Entrypoint": [
        "/usr/bin/dumb-init",
        "--"
    ]

而如果安装了tini,应用test.sh 中是否还需要类似如下脚本中对 SIGTERM 信号的处理逻辑。也就是trap "exit" TERM

cat test.sh
#!/bin/sh
# catch the TERM signal and then exit
trap "exit" TERM   
while true 
do
	date
	sleep 1 
done

大家可以自行测试,是不影响的。也就是可以不需要在对 SIGTERM 信号再进行处理。

更多关于docker容器和运维相关的知识,请前往博客主页。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【unity】Pico VR 开发笔记(视角移动)

【unity】Pico VR 开发笔记(视角移动) 视角移动是简单的基础功能,这里区别于头显定位获得的小范围位移,是长距离不影响安全边界的位移方式。的常见的位移方式有两种,其一是触发后瞬间传送到指定位置,其次是…

Linux: 设置qmake的Qt版本

Qt开发,qmake会对应一个Qt版本,有时候需要切换这个版本,例如把qmake从Qt5.12切换到Qt5.9, 怎么操作呢? 案例如下: 银河麒麟V10系统,下载安装了Qt5.9.8,但是检查qmake发现它使用的是5.12.8&…

OPC DA 客户端与服务器的那点事

C#开发OPC客户端,使用OPCDAAuto.dll。在开发过程中偶遇小坎坷,主要记录一下问题解决办法。 1、建立客户端,参考链接。建立WinFrom工程,将博客中代码全部复制即可运行: https://www.cnblogs.com/kjgagaga/p/17011730.…

Java阶段五Day19

Java阶段五Day19 问题解析 需求单查询列表功能的bug 业务逻辑: 需要用户登录,师傅入驻,审核入驻通过 查询师傅详情(areaIds,categoryIds) demand-server-dao-impl 包含持久层实现 requestOrderMappe…

Vue——formcreate表单设计器自定义组件实现(二)

前面我写过一个自定义电子签名的formcreate表单设计器组件,那时初识formcreate各种使用也颇为生疏,不过总算套出了一个组件不是。此次时隔半年又有机会接触formcreate,重新熟悉和领悟了一番各个方法和使用指南。趁热打铁将此次心得再次分享。…

身体原来是一份宝贵的“情绪地图”, 疾病都在教导我们如何与世界相处

当我们生病时 很多时候,是一个契机 让我们来倾听自己内心的压抑的真实 聆听身体的声音 身体能够教会我们如何对待情绪 进而教导我们如何与世界相处 -1- 身体上,有你的情绪地图 皮肤是身体的镜子,身体则是心灵的镜子。生病&#xff0c…

什么是微服务

微服务的架构特征: 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责自治:团队独立、技术独立、数据独立,独立部署和交付面向服务:服务提供统一标准的接口&#xff0…

《Java-SE-第二十八章》之CAS

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页:KC老衲爱尼姑的博客主页 博主的github,平常所写代码皆在于此 共勉:talk is cheap, show me the code 作者是爪哇岛的新手,水平很有限&…

AI绘图实战(十二):让AI设计LOGO/图标/标识 | Stable Diffusion成为设计师生产力工具

S:AI能取代设计师么? I :至少在设计行业,目前AI扮演的主要角色还是超级工具,要顶替?除非甲方对设计效果无所畏惧~~ 预先学习: 安装及其问题解决参考:《Windows安装Stable Diffusion …

Kali部署dvwa和pikachu靶场

kali换源 进入 vim /etc/apt/sources.list deb https://mirrors.aliyun.com/kali kali-rolling main non-free contrib deb-src https://mirrors.aliyun.com/kali kali-rolling main non-free contrib替换完后更新源 apt-get upadteDVWA靶场环境搭建 使用git从github上把DV…

项目中引用svg图标,公共组件SvgIcon使用,注册全局组件,使用自定义插件注册全局组件

在开发项目的时候经常会用到svg矢量图,而且我们使用SVG以后,页面上加载的不再是图片资源, 这对页面性能来说是个很大的提升,而且我们SVG文件比img要小的很多,放在项目中几乎不占用资源。 1、安装依赖插件 pnpm install vite-plugin-svg-ic…

以产品经理的角度去讲解原型图---会议OA项目

目录 一.前言 二.原型图 2.1 原型图是什么 3.1 原型图的作用 三.演示讲解 3.1 项目背景 3.2 项目介绍 3.2.1 会议管理(会议的发起,通知) 3.2.2 投票管理(会议的流程重大决策记录) 3.2.3 会议室管理 3.2.4 系统管…

2023年第三届工业自动化、机器人与控制工程国际会议 | IET独立出版 | EI检索

会议简介 Brief Introduction 2023年第三届工业自动化、机器人与控制工程国际会议(IARCE 2023) 会议时间:2023年10月27 -30日 召开地点:中国成都 大会官网:www.iarce.org 2023年第三届工业自动化、机器人与控制工程国际…

Android 获取网络连接状态新方法

一. 问题背景 Android12上,有的app模块判断当前网络的类型和连接状态时,还是使用的旧的API,导致返回的结果不准确,影响代码逻辑判断,本篇文章就这一问题,整理一下判断网络类型和连接状态的新方法。 二. 原因…

Selenium上传文件有多少种方式?不信你有我全!

Selenium 封装了现成的文件上传操作。但是随着现代前端框架的发展,文件上传的方式越来越多样。而有一些文件上传的控件,要做自动化控制会更复杂一些,这篇文章主要讨论在复杂情况下,如何通过自动化完成文件上传 input 元素上传文件…

go env 配置(环境变量)说明

前提:已经安装好 golang 可正确的运行下面这段命令,来查看 go 的配置: go env 输出示例: 以上是我本地(windows)环境下输出的配置信息(环境变量) 我们这次就针对每个配置信息进行一个说明,具体到每个字段是什么意思…

前端(十一)——Vue vs. React:两大前端框架的深度对比与分析

😊博主:小猫娃来啦 😊文章核心:Vue vs. React:两大前端框架的深度对比与分析 文章目录 前言概述原理与设计思想算法生态系统与社区支持API与语法性能与优化开发体验与工程化对比总结结语 前言 在当今快速发展的前端领…

iOS——Block回调

先跟着我实现最简单的 Block 回调传参的使用,如果你能举一反三,基本上可以满足了 OC 中的开发需求。已经实现的同学可以跳到下一节。 首先解释一下我们例子要实现什么功能(其实是烂大街又最形象的例子): 有两个视图控…

性能测试怎么做?测试工具怎么选择?

在当前软件测试行业,熟练掌握性能测试已经是测试工程师们面试的敲门砖了,当然还有很多测试朋友们每天的工作更多的是点点点,性能方面可能也只是做过简单的并发测试,对于编写脚本,搭建环境方面也比较陌生。今天这篇文章…

【力扣刷题 | 第二十四天】

目录 前言: 416. 分割等和子集 - 力扣(LeetCode) 总结 前言: 今晚我们爆刷动态规划类型的题目。 416. 分割等和子集 - 力扣(LeetCode) 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这…