K8S系列三:单服务部署

在这里插入图片描述

写在前面

本文是K8S系列第三篇,主要面向对K8S新手同学,阅读本文需要读者对K8S的基本概念,比如Pod、Deployment、Service、Namespace等基础概念有所了解。尚且不熟悉的同学推荐先阅读本系列的第一篇文章《K8S系列一:概念入门》[1]。

本文旨在讲述如何基于K8S集群部署一个web服务,包括如何更优雅地获取配置信息、如何保存日志以便排查问题。本文将重点介绍以下的K8S对象(API Resources,可能翻译成“资源”更好?不过笔者更喜欢API对象的称呼,如果有特别不妥,后面再换回来):

  • Pod的容器 - Container
  • Pod的存储 - Volume
  • Pod的配置 - ConfigMap和Secret
  • Pod的网络 - Service

I. 单服务部署概要

在前文中提到过,传统的服务部署方式一般是:

(1).首先准备好“完整的程序文件包”,其内容包括脚本文件、可执行程序、依赖库、配置文件等。
(2).接着把这个“完整的程序文件包”分发到物理机器的某个固定位置,比如/opt/、/usr/local/services/等。
(3).最后通过脚本文件或者直接命令启动程序入口的可执行程序,程序就会运行起来。
当然,运行程序还有可能需要网络顺畅,比如需要下载数据;还需要物理机上磁盘有足够空间,用以存储日志和中间结果等。

从上面的过程中摘取核心要点,不难发现,一个服务的部署需要满足几个要素:

在这里插入图片描述

  • 环境。一个完整的服务可能是由可执行程序组成的(这也是一种“微服务”架构),每一个可执行程序对应了Pod内部的Container;显然,在这块k8s的做法更具备优势,因为不同程序的依赖库和版本不同,如果在同一个环境中往往需要解决版本冲突问题,而放到不同Container之后,冲突问题就不存在了。
  • 存储。私以为在一个Pod中多Container之间存储共享的方式是不方便的,但这个也是由于环境隔离造成的,总体来说利大于弊。k8s的解决办法是引入了Volume资源对象,而Container通过挂载Volume方式来共享存储资源。这种也是解决Container没办法持久化存储的最有效办法。
  • 配置。其实配置文件完全可以和可执行程序一起打包/挂载到镜像的手段来完成。但是k8s也提供了一种比较好的做法,即通过ConfigMap或Secret管理配置内容,再通过挂载到Pod的具体Container内部暴露给Container内的服务。
  • 网络。k8s集群有自己的一套网络机制,通过Service、Ingress甚至ISTIO组件来做k8s集群内部、内外通信。

而k8s把以上几个要素都考虑到了。这里,我们需要达成统一的观点:对于k8s而言,我们平时经常说的一个“服务”实际上对应是一个Pod。k8s中提出的措施机制是:Container(环境)、Volume(存储)、ConfigMap和Secret(配置)、Service(网络)。本章将详细展开每一个API对象的使用办法、以及实际使用的小tip。下图可以表述它们之间的关系:
在这里插入图片描述
本文介将围绕下面这个Pod展开:

apiVersion: v1
kind: Pod
metadata:name: demonamespace: test
spec:containers:- name: mainimage: polinux/stressresources:limits:memory: "200Mi"requests:memory: "100Mi"command: ["stress"]args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]volumeMounts:- name: redis-storagemountPath: /data/redisvolumes:- name: redis-storageemptyDir: {}

II. Container

其实,Container并不是k8s的API对象,但是Container可以说是Pod非常重要的元素,因此单独拎出来介绍。此外,我们常用的容器一般是docker,本节也会以docker为例。

首先,来看下Container基础的用法。上面名为“demo”的Pod的yaml文件中,与Container相关的参数几乎占了一半,把相关的拎出来并给出注释:

 containers:- name: main              # 容器名称image: polinux/stress   # 容器的镜像地址resources:              # 容器所需的机器资源limits:memory: "200Mi"requests:memory: "100Mi"command: ["stress"]     # 容器的入口commandargs: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"] # 容器的入口command的参数volumeMounts:           # 容器的挂载点- name: redis-storagemountPath: /data/redis

容器的名称、镜像地址没什么可说的。‘container.resources’这块只要记住’container.resources.requests’<=‘container.resources.limits’,容器实际占用一定是[‘container.resources.requests’, ‘container.resources.limits’],前者是容器对机器的资源要求的下限,后者是容器占用机器资源的上限。而’container.command’和’container.args’则是容器入口命令,本文不做展开。'container.volumeMounts’是容器的挂载点,这个非常关键!k8s外部的存储盘、配置文件都是通过这个挂载到容器内的,这块的具体使用在下一节会提到。

接着,再来看点相对不基础,但是很常用的知识。

1. 如何拉取需要密钥的镜像?

通过Secret配置!官方有非常详细的指导手册《从私有仓库拉取镜像》[2],总结来说是:

  • 首先,把拉取镜像的鉴权信息保存成k8s的Secret对象;
  • 接着,在Pod的yaml文件中的imagePullSecrets指定到上面的Secret即可;

2. 拉取镜像的策略?

这个非常坑!笔者曾经遇到过一次,修改了镜像内容后没有修改tag直接push回镜像仓库,回到k8s集群重启pod对象,但是镜像没有更新。后来才知道需要指定imagePullPolicy为Always。不过根据《k8s-in-action》[3]书籍中所述:

You need to be aware that the default imagePullPolicy depends on the
image tag. If a container refers to the latest tag (either explicitly
or by not specifying the tag at all), imagePullPolicy defaults to
Always, but if the container refers to any other tag, the policy
defaults to IfNotPresent.
你需要注意imagePullPolicy的默认值视镜像的tag不同。如果容器镜像的tag是latest(显示指定或者缺省),imagePullPolicy的默认值是Always;否则,imagePullPolicy的默认值是IfNotPresent。

总之为了保险起见,笔者建议指定imagePullPolicy为Always

3. 如何登陆镜像排查问题?

  • 线上经常遇到服务出现问题之类的,因此避免不了登录容器排查问题。情况分为几种: 服务还在。这种情况处理起来比较简单,使用kubectl -n
    ${namespace} exec -it ${pod-name} -c ${container-name}
    /bin/bash就能以交互式bash进入容器操作;使用kubectl -n ${namespace} exec ${pod-name}
    -c ${container-name} – ${command} 则可以不进入容器做一些简单的操作,比如查看日志。当然,直接用kubectl -n ${namespace} logs
    ${pod-name} -c ${container-name} 也能不用登录容器查看日志。
  • 服务挂了。这种情况相对比较麻烦,登录容器肯定是不可能的了,但是仍旧可以查看日志,用法是加一个-p或者–previous=true,具体命令为kubectl
    -n ${namespace} logs -c ${pod-name} -c ${container-name}。不过,只有因为异常退出的容器才能使用该命令,如果是被Terminated的容器则无法查看。可以参考How
    to see logs of terminated pods[4]的讨论。

更好的建议是,对于部署到k8s上的服务,最好有集中的日志管理系统来统一管理服务日志,比如Elasticsearch[5]。

4. 容器分类?

  • k8s支持一个Pod内包括多个Container,并且,k8s把Container也分了类型:
    InitContainer。初始化容器,这个最为特别,它是通过’initContainers’而非’containers’指定的,k8s会确保’initContainers’下的容器首先启动,结束后再启动’containers’下的容器。

  • StandardContainer。标准容器,即我们常见常用的容器,无特别之处,不提。

  • SidecarContainer。边车容器,u1s1,sidecarContainer和mainContainer没有什么不同,仅是从业务角度来说,这个容器内做的事情与主业务无关、往往辅助类事情。据说,在k8s
    1.8版本之后新增了’container.lifecycle.type’来和标准容器进行区分[6]。

  • EphemeralContainer。临时容器,笔者还没有用过这类容器,因此特性并不清楚。

在真正使用中,initContainer可以做些前提准备工作,比如下载数据;sidecarContainer则做些辅助工作,比如收集日志。

4. 多容器的使用?

在前文中提到过,虽然由于不同Container的文件系统隔离关系,它们无法互相访问彼此的数据(但是k8s仍然给出了解决办法,具体措施在下一节),但是其他各个方面,譬如网络、IPC等,同个Pod内各个Container之间就如同部署在同一台“物理机”上。
那么,k8s究竟做了什么使得同个Pod内的Container之间能够共享网络、IPC等,而不同Pod的Container之间完全隔离呢?这个是unix提供的namespacecgroup技术。前者欢迎阅读文章《Multi-Container Pods in Kubernetes》[7];后者也可以自行Google了解,对于docker的虚拟化会有更深的了解。
(unix提供的namespacecgroup技术笔者也不十分了解,暂且在这里挖个坑,后面填。)

III. Volume

Volume是k8s中十分基础且重要,但也很容易被忽视的一个API对象,就像我们在部署服务时经常忽视磁盘一样,因为实在太基础且越来越便宜。但是如果不做好Volume的功课,简单把它等同于磁盘mount的话,k8s的Volume是会服务部署带来不少困扰的。

Volume的语法格式其实也相对比较简单,把"demo"的yaml文件中和Volume有关的拎出来:

volumes:

  • name: redis-storage
    emptyDir: {}

嗯,Volume的语法基本是由’name’和具体类型组成的。请注意:'volumes’单单做了Volume的声明,真正使用的话,还是要在’container.volumeMounts’通过Volume的’name’挂载到Container中

1.Volume分类?

k8s的Volume提供了非常丰富的类型,可以涵盖大部分应用场景。关于Volume类型的介绍,网上的博文非常多,而且都写的非常棒,本文就不再赘述,推荐几篇:《Volumes》[8]、《 Kubernetes 存储卷》[9]、《[Kubernetes] Volume Overview》[10]。这里对常用到的做个简单总结:

类型使用办法
emptyDirPod临时空文件夹,随着Pod的销毁而销毁
nfs同NFS类似hostPath
configMap/secret?依赖于ConfigMap/SecretPod销毁数据还在ConfigMap/Secret

2. hostPath有坑?

2.1. 慎重选择hostPath的type

第一个坑是k8s为hostPath提供了多种挂载的type,从官网[8]可以看到目前提供的类型:
在这里插入图片描述
坑就出现在第一个取值为空的type上,或许读者会问“这个功能不是挺好的么,不对挂载盘做检查,使用起来非常自由,哪里坑了?”
分享笔者一次遇到的问题:通过hostPath和type为空的方式挂载了本地的一个压缩包文件${tar},但是事先出了意外导致{tar}没有放到正确的位置,最后Pod正常启动但是程序响应异常了。排查后知道是压缩包文件没放上去,企图重新放压缩包文件,再重启Pod,但是失败了!原因是:当type为空且k8s发现挂载的路径/文件不存在,它会创建一个同名的目录!!!(当然上传压缩包文件的程序写的也不够鲁邦)

2.2. 小心hostPath把本地磁盘打满

Pod会通过’container.resources.limit’来约束容器内程序对CPU和内存等资源的占用,但是目前并没有约束对磁盘的占用。 特别在一台机器上会被分配到多个Pod,一定小心多个Pod把本地磁盘占满的情况。而磁盘满的话,k8s会把这个worker node上的Pod都驱逐掉,分配到其他woker node上;如果k8s集群的node资源紧张,那么这些Pod的服务就一直起不来了。
分享笔者项目中遇到的问题:有几次发现线上多个服务突然不工作了,定位发现是一台机器磁盘满了,再进一步定义发现多个Pod都在往同一个数据盘存日志,日积月累就把磁盘打满了;还有好几次k8s整个集群不能工作,结果也是把机器上的系统盘等打满了。
虽然“磁盘满”这个问题听上去是新手小白才会犯的错误,但是实际在管理一个庞大集群的时候,是非常容易遇到的。

3.configMap/secret热加载/同步?

通过Volume挂载ConfigMap和Secret,在实际场景中非常常见。譬如把服务所需的配置文件记录在ConfigMap或者Secret中,通过挂载就变成容器内的一个配置文件。
但是使用中需要注意热加载,或者说是同步的问题:即**修改了ConfigMap和Secret的内容,已经挂载到Container的配置文件内容会同步修改么?**答案是:不会。为此笔者做了几次实验,除非重建Pod否则并不会做同步,并且k8s似乎也没有提供热加载/同步的机制。

IV. ConfigMap和Secret

ConfigMap和Secret是k8s中经常被使用到的API对象,他俩本质都是存储kv键值对的,只是前者存储的数据是明文的;后者存储的是base64加密后的数据。ConfigMap比Secret更简单,纯粹的kv键值对,借用官方《ConfigMaps》[11]中的例子,一个ConfigMap的yaml文件长下面这个样子:

apiVersion: v1
kind: ConfigMap
metadata:name: game-demo
data:# 简单的键值对player_initial_lives: "3"ui_properties_file_name: "user-interface.properties"# 如果期望作为配置文件game.properties: |enemy.types=aliens,monstersplayer.maximum-lives=5    user-interface.properties: |color.good=purplecolor.bad=yellowallow.textmode=true    

而Secret更复杂些,因为它的初衷是用于存储一些敏感的数据,如密钥、密码等。Secret分为多种类型,在官方文档《Secrets》[12]中有详细介绍,本文不再赘述:

在这里插入图片描述
这里插一句题外话:base64加密的级别实在太弱了,直接用base64 decode命令base64 -d ${data-string}就看到了(捂脸)。

再借用官方[12]的例子,一个Secret的yaml文件长下面这个样子:

apiVersion: v1
kind: Secret
metadata:name: secret-sa-sampleannotations:kubernetes.io/service-account.name: "sa-name"
type: kubernetes.io/service-account-token
data:# You can include additional key value pairs as you do with Opaque Secretsextra: YmFyCg==

ConfigMap和Secret的yaml文件内容极其相似,真正的数据都在’data’项目下;而且它俩在使用上也极为相似,总结来说可以分为:

  • 配置文件。正如上一节最后提到的用法,通过Volume挂载到Container中。而且ConfigMap/Secret的挂载非常有趣,k8s不是简单把ConfigMap/Secret作为文件挂载进去,而是对于’data’下的每个kv键值对,以key作为文件名,value作为文件内容得到一个文件,挂载到Container内指定的路径。
  • 环境变量。这也是实际场景中常见的用法之一,通过’container.envFrom’进行指定,既可以只指定几个键值对,也可以ConfigMap/Secret所有的键值对都加载进来。这里有一个事情需要注意:通过环境变量加载Secret到Container之后,在Container内部看到的value值是经过base64 decode后的数据。至于上面配置文件,笔者没有试验过,有清楚的朋友欢迎留言告知。此外,配置文件挂载的方式不支持热加载/同步,环境变量加载的方式同样也不支持热加载/同步

V. Service

前文[1]已经解释过k8s中Service的意思:k8s的Service并不是传统意义上的“服务”,而更像是网关层,是若干个Pod的流量入口、流量均衡器。
第一点. 可以看到,配置好Container环境、Volume数据盘、ConfigMap/Secret配置文件或者环境变量之后,一个服务已经可以独立跑起来了!但是真正部署好服务之后会发现,在k8s集群外部似乎访问不了部署到k8s集群中的服务?(请注意,k8s的网络模型有underlay和overlay两种,本文默认k8s使用overlay。k8s的网络模型笔者也没完全搞懂,这里继续挖个坑。)如下图,笔者在k8s上部署了一个开放8080端口的服务,发现通过IP根本访问不通!
在这里插入图片描述
第二点. 在实际场景中为了提供高可用服务,往往会在不同的机器上部署多个服务实例。在k8s中如果一个Pod部署多份(名字一定不同,比如kubia-1,kubia-2这种),那么这多份Pod一定得对外有统一的访问当时,即IP和Port,并且有流量负载均衡策略,把流量分配到多份Pod上。

以上这些,就是k8s的Service要做的事情:屏蔽服务细节,对外暴露统一的服务接口,真正做到了“(微)服务”。总的来说,Service带来的优势有:

  1. Service提供了几种类型/机制,分别对集群内可访问和对集群外可访问的服务接口
  2. Service通过label来绑定若干Pod,使得这若干的Pod能够对外暴露统一的服务接口。最重要的是,即使Pod重启后其IP改变了,也不会影响Service

可以说,Service是k8s非常重要的一个API对象,一定要掌握其使用办法!笔者阅读过很多博文,总是越看越糊涂,最后是阅读了《k8s-in-action》[5]后豁然开朗,强烈推荐!这里,笔者会详细Service的几种访问机制、排查Service不工作的手段。因为这些都是实际场景中部署和运维服务必备的。

在此之前,先来看一个Service的yaml文件(来自官网《Service》[13]):

apiVersion: v1
kind: Service
metadata:name: my-service
spec:selector:app: MyAppports:- protocol: TCPport: 80targetPort: 9376

内容非常简单,重点需要关注的有2点:

  • ‘selector’:这个是Service用以绑定Pod的关键语法。这里的’app:
    MyApp’被称为label,label可以有多个。‘selector’则会去同一个namespace下寻找匹配label的Pod做绑定。而Pod的lable则是在’metadata.labels’定义的,用户可以随便写,甚至可以写’foo:
    bar’。
  • ‘ports’:端口映射。其中’port’是Service对外(k8s集群内/外)暴露的端口,而’targetPort’则是匹配绑定了的Pod的端口,不要弄反了。看了下图会更清晰些:

在这里插入图片描述

5.1 Service的类型

常用的Service有三种类型/机制(也有说法还有headless等其他类型,这里不展开):ClusterIP、NodePort和LoadBalancer。第一点需要记住的是:ClusterIP只能提供对k8s集群内部可访问的IP和Port,NodePort和LoadBalancer则能够提供k8s集群外可访问的IP和Port。第二点需要记住的是:这三种类型/机制的关系不是互相排斥,而是层层递进。也就是说,NodePort机制包含ClusterIP,LoadBlanacer包含NodePort和CluterIP。相信看了下图会更明白:
在这里插入图片描述
借用《k8s-in-action》[3]的插图来解释这三种类型/机制:

  • CluterIP

ClusterIP是Service默认的类型/机制。如上面的yaml文件,并没有指定type,那执行kubectl create -f service-demo.yaml之后就会是一个CluterIP类型的Service。CluterIP类型的Service会提供一个’CLUSTER-IP’和’PORT(S)',能够允许k8s内部相同namespace下的任何Pod访问
在k8s集群中,用kubectl查看ClusterIP会得到如下图:
在这里插入图片描述

  • NodePort
    如果希望直接通过Service对集群外部提供访问方式,那么NodePort是一种勉强不错的方法,并且所有的k8s集群都能支持这种机制。创建时,需要指定’type: NodePort’。在k8s集群中,用kubectl查看NodePort你会得到:
    在这里插入图片描述
    会发现NodePort和ClusterIP似乎没有太大的不同,仅仅是在’PORT(S)'中多了一个数字30839。于是你尝试telnet ${CLUSTER-IP} 80,发现不行;你又尝试telnet ${CLUSTER-IP} 30839,哎还是不行。恼羞成怒:怎么回事,NodePort不能用啊,垃圾?!

因此,我们需要了解NodePort究竟做了什么使得集群外部能够访问。笔者认为,下图非常有助于理解(图片来自于《k8s-in-action》[3]):
在这里插入图片描述
NodePort实际是在Pod所在的物理机器上开了一个端口,让外部流量从这个端口先导流到自己的’port’,再导入’targetPort’(也就是Pod的端口)。
所以,在使用NodePort类型的时候,请记住正确的访问方式是telnet ${Pod所在的NODE} ${PORT(S)非定义的端口号}。这就意味着,你还需要查看绑定的Pod所在的物理机IP,可以使用kubectl get pod -o wide -l ${lable}:
在这里插入图片描述
那么,使用telnet 9.235.138.15 30839原则上应该是可以访问通的,我们来试试:
在这里插入图片描述
果然可以!
再回过头来看,NodePort的’CLUSTER-IP’其实是供k8s集群内访问的IP,这就是ClusterIP的机制,也就证明了NodePort类型/机制是包括ClusterIP的。

  • LoadBalancer
    NodePort类型/机制仅仅提供了对k8s集群外访问的方式,很快就会发现这种方法违背了Service的流量负载均衡的策略,因为通过Pod所在机器IP访问的流量,只能够导入到该机器上的Pod,其他机器上就不行了。因此就有了LoadBalancer。**LoadBalancer的做法是在NodePort基础上增加一个EXTERNAL-IP,集群外部调用该IP+自定义的port就能够访问Pod。**相信下图会更有助于对LoadBalancer的理解(图片来自于《k8s-in-action》[3]):
    在这里插入图片描述
    但是,并不是所有的k8s集群都能够支持LoadBalancer,笔者目前使用的k8s集群就不支持,在’EXTERNAL-IP’这一列一直处于状态:
    在这里插入图片描述

5.2 Service问题排查

一般来说,创建好一个Service之后,只要绑定了正常可服务的Pod就应该能够通过Service提供的访问方式访问到Pod了(k8s集群内或外)。具体绑定方式在上文中已经提到过,这里再强调一遍:通过label来关联Service和Pod。
但实际场景中,遇到访问不通的问题时有发生(这里指的是telnet不通的情况),本小节就介绍下怎么排查这个问题,以及Service和Pod究竟是怎么通过label绑定的。

首先需要了解k8s另一个API对象Endpoint,端点切片。Endpoint是随着Service的生成而被动生成的。此外,虽然从使用上来看,Service和Pod是通过label绑定的,但是实际上Service和Pod是通过Endpoint来做关联的。因此Endpoint是查看Service是否真正绑定到Pod上的有力工具!

一个正常的、可用的Service和Pod的状态应该如下图:
在这里插入图片描述
会看到Endpoint其实是一个如倒排表的结构,存储了Service名称和其绑定的一系列PodIP+Port。

但是,如果仅仅创建了一个Service而没有Pod的话,再查看Endpoint会发现其内容为:
在这里插入图片描述
再比如,Service和Pod都被创建了,但是Pod由于种种原因没有正常服务,这个时候EndPoint内容是空:
在这里插入图片描述
因此,实际应用中操作Service,一定记得把Endpoint用起来!

最后,k8s内部的网络策略/机制,包括DNS策略/机制,非常值得深挖和学习,笔者在这里继续挖个坑。

写在后面

本文是K8S系列文章第三篇,旨在前两篇入门介绍(概念入门和实践入门)基础上,介绍在实际应用中部署服务常见的用法和注意事项。如果文章中有纰漏,非常欢迎留言或者私信指出;有理解错误的地方,更是欢迎留言或者私信告知。

非常欢迎大家留言或者私信交流更多K8S的问题。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图解 Paxos 算法

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱写博客的嗯哼&#xff0c;爱好Java的小菜鸟 &#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44d;三连支持&#x1f44d;一下博主哦 &#x1f4dd;个人博客&#xff1a;敬请期待 &#x1f4d5;系列…

ROS学习笔记(二)---使用 VScode 开发 ROS 的Python程序(简例)

一、任务介绍 本篇作为ROS学习的第二篇&#xff0c;是关于如何在Ubuntu18.04中使用VSCode编写一个Python程序&#xff0c;输出“Hello&#xff01;”的内容介绍。 首先我们来了解下ROS的文件系统&#xff0c;ROS文件系统级指的是在硬盘上ROS源代码的组织形式&#xff0c;其结构…

东方晶源亮相第十一届半导体设备年会,共话发展“芯”机遇

8月11日&#xff0c;以“协力同芯抢机遇&#xff0c;集成创新造设备”为主题的第十一届&#xff08;2023年&#xff09;中国电子专用设备工业协会半导体设备年会暨产业链合作论坛&#xff08;CSEAC&#xff09;在无锡太湖国际博览中心圆满闭幕。为期3天的CSEAC&#xff0c;通过…

安装Linux操作系统CentOS 6详细图文步骤

为满足业务对Linux操作系统部署的要求&#xff0c;本文档主要提供CentOS 6操作系统的最小化安装和基本配置, 安装本系统建议最少1GB内存和2GB磁盘空间。 1、 使用光盘或者挂载ISO镜像&#xff0c;在出现如下图形界面时选择【Install or upgrade an existing system】并按Ent…

Redis 缓存过期及删除

一、Redis缓存过期策略 物理内存达到上限后&#xff0c;像磁盘空间申请虚拟内存(硬盘与内存的swap),甚至崩溃。 内存与硬盘交换 (swap) 虚拟内存&#xff0c;频繁I0 性能急剧下降&#xff0c;会造成redis内存急剧下降&#xff1b; 一般设置物理内存的3/4&#xff0c;在redis…

【C/C++】STL queue 非线程安全接口,危险!

STL 中的 queue 是非线程安全的&#xff0c;一个组合操作&#xff1a;front(); pop() 先读取队首元素然后删除队首元素&#xff0c;若是有多个线程执行这个组合操作的话&#xff0c;可能会发生执行序列交替执行&#xff0c;导致一些意想不到的行为。因此需要重新设计线程安全的…

每天一道leetcode:剑指 Offer 13. 机器人的运动范围(中等广度优先遍历剪枝)

今日份题目&#xff1a; 地上有一个m行n列的方格&#xff0c;从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0]的格子开始移动&#xff0c;它每次可以向左、右、上、下移动一格&#xff08;不能移动到方格外&#xff09;&#xff0c;也不能进入行坐标和列坐标的数位之…

jmeter返回值中的中文显示为????问号处理解决方案

jmeter返回值中的中文显示为????问号 查找解决方案时&#xff0c;发现了以下两种解决方案&#xff1a; 一、1.打开jmter配置文件bin/jmeter.properties 2.修改配置文件&#xff0c;查找“sampleresult.default.encoding”将其改为utf8&#xff0c;注意要去掉“#”号 sample…

opencv带GStreamer之Windows编译

目录 1、下载GStreamer和安装2. GSTReamer CMake配置3. 验证是否配置成功 1、下载GStreamer和安装 下载地址如下&#xff1a; gstreamer-1.0-msvc-x86_64-1.18.2.msi gstreamer-1.0-devel-msvc-x86_64-1.18.2.msi 安装目录无要求&#xff0c;主要是安装完设置环境变量 xxx\1…

CVPR 2023 | 用户可控的条件图像到视频生成方法(基于Diffusion)

注1:本文系“计算机视觉/三维重建论文速递”系列之一&#xff0c;致力于简洁清晰完整地介绍、解读计算机视觉&#xff0c;特别是三维重建领域最新的顶会/顶刊论文(包括但不限于 Nature/Science及其子刊; CVPR, ICCV, ECCV, NeurIPS, ICLR, ICML, TPAMI, IJCV 等)。 本次介绍的论…

S7-200 Smart 的多种端口及通讯方式

每个S7-200 SMART CPU都提供一个以太网端口和一个RS485端口(端口0)&#xff0c;标准型CPU额外支持SB CM01信号板(端口1)&#xff0c;信号板可通过STEP 7-Micro/WIN SMART软件组态为RS232通信端口或RS485通信端口。 CPU 通信端口引脚分配 1.S7-200 SMART CPU 集成的 RS485 通信…

见证马斯克的钞能力,AI.com再次易主,OpenAI投掷1100万美金购买AI.com刚满五个月

我们又一次见证了马斯克的钞能力。上次是去年他用440亿美元买下推特。 高价值的AI.com域名在2021年易主后&#xff0c;闲置过一段时间&#xff0c;今年2月份突然重定向到ChatGPT。 对于ChatGPT用户来说&#xff0c;每次访问都要在浏览器里敲这些字符&#xff1a;https://chat.o…

实践-CNN卷积层

实践-CNN卷积层 1 卷积层构造2 整体流程3 BatchNormalization效果4 参数对比5 测试效果 1 卷积层构造 2 整体流程 根据网络结构来写就可以了。 池化 拉平 训练一个网络需要2-3天的时间。用经典网络来&#xff0c;一些细节没有必要去扣。 损失函数&#xff1a; fit模型&…

checkbox post参数接收

checkbox 定义 <div class"check-box"> <label for"ck1">batchInsert:</label><input type"checkbox" id"ck1" checkedname"ckFn" value"batchInsert" > </div> <div class&qu…

QGIS3.28的二次开发五:VS使用QT插件创建UI界面

前面我们说了在创建项目时创建的是一个空项目&#xff0c;即不使用 Qt 提供的综合开发套件 Qt Creator&#xff0c;也不使用 Qt Visual Studio Tools 这类工具。 但是后面发现&#xff0c;如果我想要有更加满意的界面布局&#xff0c;还是要自己写一个UI文件&#xff0c;如果不…

Word(1):文章页码设置

1.需求 在文档的封皮页不设置页码&#xff0c;在目录页页码设置为罗马数字&#xff0c;在正文使用阿拉伯数字。 2.解决方法 step1&#xff1a; 在封皮页的最后&#xff0c;点击”插入“-分隔符-分节符&#xff08;下一页&#xff09; step2&#xff1a;在目录页的最后&…

Python学习笔记_基础篇(二)_数据类型之字符串

一.基本数据类型 整数&#xff1a;int 字符串&#xff1a;str(注&#xff1a;\t等于一个tab键) 布尔值&#xff1a; bool 列表&#xff1a;list 列表用[] 元祖&#xff1a;tuple 元祖用&#xff08;&#xff09; 字典&#xff1a;dict 注&#xff1a;所有的数据类型都存在想对应…

Python Opencv实践 - 图像放射变换

import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/pomeranian.png", cv.IMREAD_COLOR) rows,cols img.shape[:2] print(img.shape[:2])#使用getAffineTransform来获得仿射变换的矩阵M #cv.getAffineTransform(…

[JavaScript游戏开发] 绘制Q版地图、键盘上下左右地图场景切换

系列文章目录 第一章 2D二维地图绘制、人物移动、障碍检测 第二章 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示) 第三章 绘制冰宫宝藏地图、人物鼠标点击移动、障碍检测 第四章 绘制Q版地图、键盘上下左右地图场景切换 文章目录 系列文章目录前言一、本章节…

企业直播MR虚拟直播(MR混合现实直播技术)视频介绍

到底什么是企业直播MR虚拟直播&#xff08;MR混合现实直播技术&#xff09;&#xff1f; 企业直播MR虚拟直播新玩法&#xff08;MR混合现实直播技术&#xff09; 我的文章推荐&#xff1a; [视频图文] 线上研讨会是什么&#xff0c;企业对内对外培训可以用线上研讨会吗&#x…