Pod的调度流程

在k8s集群中kube-scheduler组件负责为Pod选择运行节点,并由对应节点上的kubelet创建Pod。对于每个未绑定至任何节点的Pod对象,无论是新建、被驱逐等,kube-scheduler都要使用调度算法从集群中挑选一个最佳节点来运行它。

kubs-schedulerd调度方式的发展主要分为两个阶段:在1.15版本之前使用经典调度器架构,1.15版本之后使用调度器框架(Scheduler Framework)

在经典调度架构中Pod的调度流程如下图所示,可以分为3个步骤:节点预选、节点优选和节点绑定。其中节点预选和节点优选是通过预选函数和优选函数完成。

  1. 节点预选:基于一系列预选规则对每个节点进行检查,将那些不符合筛选条件的节点过滤掉;没有节点满足Pod的资源需求时,该Pod被至于Pending状态,直到出现至少一个能满足条件的节点为止
  2. 节点优选:根据优选算法对预选出的节点进行打分,并根据最终得分进行优先级排序
  3. 节点选定:从优先级排序结果中挑选出优先级最高的节点运行Pod对象,最高优先级的节点数量多于一个时,从中随机选取一个运行Pod

k8s自1.1.5版本引入的调度框架重构了此前的经典调度器架构,它以插件化的方式在多个扩展点实现了调度器的绝大多数功能,替代了经典调度器中以预选函数(predicate)和优选函数(priority)为核心的调度载体。

如下图所示,调度框架将每次调度一个Pod的过程分为调度周期和绑定周期两个阶段,前者负责为Pod选择一个最佳运行节点,相当于之前的节点预选和优选;后者为完成Pod到节点的绑定执行必要的检测或初始化操作等。

调度器框架提供了多个扩展点,事实上其中的Filter相当于传统调度器上的Predicate(预选),Score相当于Priority(优选),Bind则保持原有的名称调度器插件可以根据自身的功能注册到一个或多个扩展点并由调度器进行调用。

在调度框架下,大部分的调度功能都以插件方式实现,便于扩展,还能让调度器核心程序保持简单且易于维护。因此,传统调度器中的节点预选、优选和绑定等相关的函数代码也都转而实现为新的调度框架下的插件。

节点选择器

Pod资源可以使用spec.nodeName直接指定要运行的目标节点,也可以基于spec.nodeSelector指定的标签选择器筛选符合条件的节点作为运行节点,最终选择则基于打分机制完成。nodeSeclctor也称为节点选择器,用户可以提前给节点打上不同的标签,然后通过节点选择器来选择想要运行Pod的节点,比如集群中的节点分属不同项目、节点不同硬件等情况可以使用节点选择器

如下图,目前集群中有3个node,下面分别演示一下nodeName和nodeSelector的效果

nodeName示例
创建2个Pod指定其运行在192.168.122.20这个node上,部署文件如下

apiVersion: apps/v1
kind: Deployment
metadata:name: pod-with-nodeName
spec:replicas: 2selector:matchLabels:app: pod-with-nodeNametemplate:metadata:lables:app: pod-with-nodeNamespec:nodeName: 192.168.122.20containers:- name: nginximage: nginximagePullPolicy: IfNotPresentports:- name: httpcontainerPort: 80resources:requests:cpu: 200mmemory: 256Milimits:cpu: 200mmemory: 256Mi

创建之后,查看Pod运行节点。如下图所示,两个Pod都被调度到了192.168.122.20这个node上

nodeSelector示例
假设集群中存在两个项目project1和project2,节点192.168.122.20和21属于project1,节点192.168.122.22属于project2,创建两个pod让其运行在project2拥有的节点上。

先为所有节点打上project标签

kubectl label node 192.168.122.20 project=project1
kubectl label node 192.168.122.21 project=project1
kubectl label node 192.168.122.22 project=project2

然后创建pod,部署文件如下:

apiVersion: apps/v1
kind: Deployment
metadata:name: pod-with-nodeselector
spec:replicas: 2selector:matchLabels:app: pod-with-nodeselectortemplate:metadata:labels:app: pod-with-nodeselectorspec:nodeSelector:project: project2containers:- name: nginximage: nginximagePullPolicy: IfNotPresentports:- name: httpcontainerPort: 80resources:requests:cpu: 200mmemory: 256Milimits:cpu: 200mmemory: 256Mi

创建之后,查看Pod运行节点,如下图,Pod都被调度到192.168.122.22这个node

node亲和

节点亲和是调度程序用来确定Pod对象调度位置的调度规则,这些规则基于节点上的自定义标签和Pod对象上指定的标签选择器进行定义。简单来说,节点亲和调度支持Pod资源定义自身对期望运行的某类节点的倾向性,倾向于运行的指定类型的节点即为亲和关系,否则即为反亲和关系

在Pod上定义节点亲和条件时有两种类型的亲和关系:强制(required)亲和首选(preferred)亲和,或者成为硬亲和和软亲和。强制亲和定义的规则在Pod调度时必选满足,无可用节点时Pod对象会被至于Pending状态,直到满足亲和条件的节点出现。首选亲和是非强制性的调度限制,它同样倾向于将Pod运行在符合亲和条件定义的节点上,但无法满足调度需求时,调度器会选择一个无法匹配规则的节点,而不是将Pod至于Pending状态

在Pod上定义亲和条件的关键点有两个:一是给节点规划并配置符合期望的标签;二是给Pod对象定义合理的标签选择器。需要注意的是,在Pod资源基于亲和条件调度到某节点之后,如果节点标签发生变动而不再符合Pod定义的亲和性规则时,调度器不会将Pod从此节点移出,因而亲和调度仅在调度执行的过程中进行一次即时的判断,而不是持续的监视亲和条件是否满足

虽然节点亲和和nodeSelector的目的都是控制Pod的调度结果,但是相对于nodeSelector,节点亲和的功能更加强大,具有以下优势:

  1. 节点亲和对目的标签的选择匹配不仅支持and,还支持In、NotIn、Exists、DoseNotExists、Gt(标签的值大于某个值)和Lt(标签的值小于某个值)
  2. 支持软亲和限制,在软亲和下,即使亲和条件不满足Pod也可以被调度到其它节点运行

node强制亲和

Pod规范中的spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution用于定义Pod和节点的强制亲和关系,它可以嵌套使用nodeSelectorTerm字段。nodeSelectorTerms用于定义节点选择器,其值是一个对象列表,支持使用matchExpressions和matchFields两种表达机制

  • matchExpressions:标签选择器表达式,根据节点标签值过滤节点;可以写多个matchExpressions以表示不同的匹配条件,它们之间为逻辑或关系
  • matchFields:以字段选择器表达的节点选择器,同样可以使用多个matchFields表示不同的匹配条件,它们之间为逻辑或关系

每个匹配条件下可以有一到多个匹配规则,例如一个matchExpressions条件下可以同时存在多个标签匹配规则,这些匹配规则之间是逻辑与关系。举例来说,如果一个nodeSelectorTerms下存在两个matchExpressions条件,只要满足其中一个即可,但满足指的是matchExpressions下的匹配规则要全部匹配成功

下面是一个强制亲和示例,它定义了Pod只能运行在具有project=project1 和ssd=true的节点上

此前已经为集群中的节点都打了project标签,现在再为其中一个属于projec1的节点打上ssd=true的标签

kubectl label node 192.168.122.21 ssd=true

pod部署文件如下:

apiVersion: apps/v1
kind: Deployment
metadata:name: pod-with-nodeaffinity
spec:replicas: 2selector:matchLabels:app: pod-with-nodeaffinitytemplate:metadata:labels:app: pod-with-nodeaffinityspec:containers:- name: nginximage: nginxports:- containerPort: 80resources:requests:cpu: 200mmemory: 256Milimits:cpu: 200mmemory: 256Miaffinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:    #此matchExpressions下的两个标签选择器都满足Pod才能调度成功- key: projectoperator: Invalues: ["project1"]- key: ssdoperator: Invalues: ["true"]

查看Pod,如下图,它们都调度到了192.168.122.21这个节点,符合亲和条件约束

假如修改部署文件中亲和条件定义,将一个标签匹配规则改为不存在的标签,然后删除重建Pod,那么此时Pod将无法被调度成功,一直处于Pending状态。如下所示:

      affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: projectoperator: Invalues: ["project1"]- key: gpu   #修改为匹配gpu=true的标签operator: Invalues: ["true"]


通过kubectl descibe输出可以看到所有节点都不能满足node affinity规则所以调度失败

node首选亲和

节点首选亲和为节点选择机制提供了一种柔性控制逻辑,被调度的Pod应该尽量放置在满足亲和条件的节点上,但亲和条件不满足时,该Pod也能接受被调度到其它不满足亲和条件的节点上。另外,多个软亲和条件并存时,还支持为亲和条件定义weight属性以区别它们的优先级,取值范围1-100,数字越大优先级越高,Pod越优先被调度到此节点上。

Pod规范中的spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution字段用于定义Pod和节点的首选亲和关系,它可以嵌套使用preference和weight字段。

  • weight:指定软亲和条件的优先级,取值范围1-100,数字越大优先级越高
  • preference:用于定义节点选择器,值是一个对象,支持matchExpressions和matchFields两种表达机制,它们的使用方式和逻辑与强制亲和一样

下面是一个软亲和示例:

apiVersion: apps/v1
kind: Deployment
metadata:name: pod-with-nodeaffinity-preferred
spec:replicas: 4selector:matchLabels:app: pod-with-nodeaffinity-preferredtemplate:metadata:labels:app: pod-with-nodeaffinity-preferredspec:containers:- name: nginximage: nginxports:- containerPort: 80resources:requests:cpu: 200mmemory: 256Milimits:cpu: 200mmemory: 256Miaffinity:nodeAffinity:preferredDuringSchedulingIgnoredDuringExecution:- weight: 60preference:matchExpressions:- key: projectoperator: Invalues: ["project2"]- weight: 30preference:matchExpressions:- key: ssdoperator: Invalues: ["true"]

在上面的示例中,定义了两个软亲和条件,第一个用于选择具有project=project2标签的节点,优先级为60;第二个用于选择具有ssd=true标签的节点,优先级为30。此时可以将集群中的节点分为4类:

  1. 第一类,同时具有project=project2和ssd=true标签的节点,优先级最高为60+30=90
  2. 第二类,只具有project=project2标签的节点,优先级为60
  3. 第三类,只具有ssd=true标签的节点,优先级为30
  4. 第四类,不具有project=project2和ssd=true标签的节点,优先级为0

Pod在调度时会优先选择第一类节点,直到第一类节点资源不足时再使用第二类节点,以此类推。

创建之后查看Pod,如下图,3个pod都运行在192.168.122.22节点,它具有project=project标签;剩余一个Pod运行在192.168.122.21节点,它具有ssd=true标签

假如修改示例中的软亲和条件,将两个标签匹配器都修改为不存在的标签,然后删除重建Pod,此时Pod也可以被调度运行,而不会被置于Pending状态,这就是和硬亲和的不同之处。如下所示:

      affinity:nodeAffinity:preferredDuringSchedulingIgnoredDuringExecution:- weight: 60preference:matchExpressions:- key: gpuoperator: Invalues: [""]- weight: 30preference:matchExpressions:- key: disktypeoperator: Invalues: ["hdd"]

综合示例

下面是一个强制亲和&首选亲和的示例,定义了Pod只能运行在具有project=project1标签的机器上,并且尽量运行在具有ssd=true标签的节点上

apiVersion: apps/v1
kind: Deployment
metadata:name: pod-nodeaffinity-demo
spec:replicas: 3selector:matchLabels:app: pod-nodeaffinity-demotemplate:metadata:labels:app: pod-nodeaffinity-demospec:containers:- name: nginximage: nginxports:- containerPort: 80resources:requests:cpu: 200mmemory: 256Milimits:cpu: 200mmemory: 256Miaffinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms:- matchExpressions:   #硬亲和条件1,Pod只能运行在属于project1的节点上- key: projectoperator: NotInvalues: ["project2"]preferredDuringSchedulingIgnoredDuringExecution:- weight: 50        #软亲和条件1,Pod尽量运行在具有ssd的节点上preference:     matchExpressions:- key: ssdoperator: Invalues: ["true"]

创建之后查看Pod,如下图,3个Pod都运行在192.168.122.21节点上,它同时具有project=project1和ssd=true标签

Pod亲和/反亲和

Pod的亲和与反亲和就能实现这类需求,它可以基于已经在node节点上运行的Pod来约束新创建的Pod可以调度到的目的节点。Pod的亲和/反亲和也都支持强制和首选两种形式

位置拓扑

Pod亲和调度的目的在于确保相关的Pod对象运行在同一位置,而反亲和调度则要求它们不能运行在同一位置。如何判断节点是否处于同一位置,取决于通过节点上的哪个标签来判断。

假设集群中有4个节点,如下图所示:

假如以kubernetes.io/hostname标签来判断,同一位置表示同一个节点,不同的节点表示不同的位置;假如以Kubernetes.io/rack标签来判断,node1和node2属于同一位置,node3和node4属于同一位置

因此,定义Pod的亲和/反亲和关系时,需要先借助标签选择器来选择出要参照的Pod对象,而后根据筛选出的Pod对象所在节点的标签来判定同一位置所指,而后针对亲和关系将新创建的Pod放置在同一位置优先级最高的节点,或根据反亲和关系将新创建的Pod放置在不同位置优先级最高的节点

Pod间的亲和关系通过spec.affinity.podAffinity字段定义,反亲和关系通过spec.affinity.podAntiAffinity字段定义,它们都支持强制和首选两种约束关系,都支持使用如下字段:

  • topologyKey:拓扑键,用来判断节点是否处于同一拓扑位置的标签,在指定的键上具有相同值的节点属于同一拓扑位置
  • labelSelector:Pod标签选择器,用于指定当前调度的Pod应该参照哪类现有的Pod来确定运行位置
  • namespace:指定的labelSelector生效的名称空间,默认为当前调度Pod所属的名称空间,可用来跨名称空间筛选Pod

Pod亲和

强制亲和

Pod间的强制亲和关系定义在spec.affinity.podAffinity.requiredSchedulingIgnoredDuringExecution字段中,其值是一个对象列表,支持嵌套使用labelSelector、namespaces和topologyKey字段。

下面是一个示例,首先定义了一个mysql应用,然后定义了一个依赖mysql的tomcat应用,tomcat上定义了Pod强制亲和约束,期望与mysql运行在同一位置,以project作为拓扑键。也就是说tomcat Pod与mysql Pod要运行在具有project标签且标签值相同的节点上。

apiVersion: apps/v1
kind: Deployment
metadata:name: mysql-deploy
spec:replicas: 1selector:matchLabels:app: mysqltemplate:metadata:labels:app: mysqlspec:containers:- name: mysqlimage: harbor-server.linux.io/n70/mysql:5.7.39imagePullPolicy: IfNotPresentports:- name: mysqlcontainerPort: 3306env:- name: MYSQL_ROOT_PASSWORDvalue: Passw0rd---
apiVersion: apps/v1
kind: Deployment
metadata:name: tomcat-deploy
spec:replicas: 2selector:matchLabels:app: tomcat-app1template:metadata:labels:app: tomcat-app1spec:containers:- name: tomcatimage: harbor-server.linux.io/n70/tomcat-myapp:v1affinity:podAffinity:  #Pod亲和定义requiredDuringSchedulingIgnoredDuringExecution: #pod强制亲和条件定义,多个列表项之间是逻辑与关系- labelSelector:   #Pod对象标签选择器,用于筛选放置当前Pod时要参考的PodmatchExpressions:- key: appoperator: Invalues: ["mysql"]namespaces:     #指定名称空间,表示在哪些名称空间下筛选Pod- defaulttopologyKey: project #拓扑键,用于确定节点拓扑位置

创建之后,查看Pod运行位置,如下图,mysql和tomcat都运行在节点192.168.122.22上,符合亲和规则约束

如果Pod的强制亲和规则不满足,Pod也会被置于Pending状态,这和node强制亲和的行为是一样的

首选亲和

Pod间的首选亲和通过spec.affinity.podAffinity.preferredDuringSchedulingIgnoredDuringExecution字段中,其值是一个对象列表,支持嵌套使用weight和podAffinityTerm字段

  • weight:数字,取值范围1-100,定义软亲和条件的权重
  • podAffinityTerm:Pod标签选择器定义,可以嵌套使用labelSelector、namespaces和topologyKey字段

下面是一个示例,它同样先定义一个mysql应用,之后定义的tomcat应用定义了Pod首选亲和约束,tomcat Pod期望尽量与mysql Pod运行在同一节点,但当条件无法满足时,则期望运行在同一project的节点上。如果都无法满足,也能接受运行在集群其他节点上

apiVersion: apps/v1
kind: Deployment
metadata:name: mysql-deploy
spec:replicas: 1selector:matchLabels:app: mysqltemplate:metadata:labels:app: mysqlspec:containers:- name: mysqlimage: harbor-server.linux.io/n70/mysql:5.7.39imagePullPolicy: IfNotPresentports:- name: mysqlcontainerPort: 3306env:- name: MYSQL_ROOT_PASSWORDvalue: Passw0rd---
apiVersion: apps/v1
kind: Deployment
metadata:name: tomcat-myapp-deploy
spec:replicas: 4selector:matchLabels:app: tomcat-myapptemplate:metadata:labels:app: tomcat-myappspec:containers:- name: tomcatimage: harbor-server.linux.io/n70/tomcat-myapp:v1resources:requests:cpu: 500mmemory: 512Milimits:cpu: 500mmemory: 512Miaffinity:podAffinity:preferredDuringSchedulingIgnoredDuringExecution:  #Pod软亲和定义- weight: 80       #软亲和条件1,权重为80podAffinityTerm:    #Pod标签选择器定义labelSelector:matchExpressions:- key: appoperator: Invalues: ["mysql"]namespaces:- defaulttopologyKey: kubernetes.io/hostname- weight: 40      #软亲和条件2,权重为40podAffinityTerm:labelSelector:matchExpressions:- key: appoperator: Invalues: ["mysql"]namespaces:- defaulttopologyKey: project

创建之后查看Pod运行位置:

如上图,mysql运行在192.168.122.22节点,有两个tomcat Pod和mysql运行在同一节点,另外两个Pod由于192.168.122.22节点资源不足会转而选择带project=project2标签的节点,但是只有192.168.122.22节点属于project2(关于节点上的标签设置,可以查看前面node亲和部分),所以它们被调度到其他节点

Pod反亲和

Pod的反亲和关系要实现的调度目标与亲和关系相反,它需要确保存在互斥关系的Pod不会运行在同一位置,因此反亲和调度一般用于分散同一类应用的Pod对象等,也包括把不同安全级别的Pod调度到不同的区域。同样的,Pod的反亲和也支持强制和首选两种形式。

强制反亲和

Pod的强制反亲和定义在spec.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution字段中,可嵌套使用的字段和强制亲和定义完全一致。

下面是一个示例,定义了属于同一Deployment但彼此互斥的Pod对象,它们必须运行在不同的节点上

apiVersion: apps/v1
kind: Deployment
metadata:name: fluentd
spec:replicas: 4selector:matchLabels:app: fluentdtemplate:metadata:labels:app: fluentdspec:containers:- name: fluentdimage: harbor-server.linux.io/n70/fluentd:v1.14-1affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:- labelSelector:matchExpressions:- key: appoperator: Invalues: ["fluentd"]namespaces:- defaulttopologyKey: kubernetes.io/hostname

如下图,创建之后只有3个Pod被调度成功,剩余一个Pod处于Pending状态,因为集群中只有3个节点

首选反亲和

Pod的首选反亲和定义在spec.affinity.podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution字段中,调度器尽量不会把互斥的Pod调度到同一位置,但约束条件无法满足时,也会将Pod放在同一位置,而不是将Pod至于Pending状态。

下面是一个示例,将上面的强制反亲和示例改为了首选反亲和:

apiVersion: apps/v1
kind: Deployment
metadata:name: fluentd
spec:replicas: 4selector:matchLabels:app: fluentdtemplate:metadata:labels:app: fluentdspec:containers:- name: fluentdimage: harbor-server.linux.io/n70/fluentd:v1.14-1affinity:podAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:- weight: 60podAffinityTerm:labelSelector:matchExpressions:- key: appoperator: Invalues: ["fluentd"]namespaces:- defaulttopologyKey: kubernetes.io/hostname

如下图,首选反亲和下Pod也可以运行在同一节点,而不是被至于Pending状态:

k8s调度之亲和/反亲和相关推荐

  1. k8s 亲和 反亲和介绍

    k8s 亲和 & 反亲和介绍 文章内容来自k8s文档翻译以及个人理解和实际使用过程中的实践内容 参考:https://kubernetes.io/docs/concepts/schedulin ...

  2. Kubernetes-调度、节点亲和反亲和、pod亲和反亲和、Taints污点的处理

    目录: 一.kubernetes调度介绍 二.nodename节点选择约束 三.nodeSelector 亲和 1.节点亲和 2.节点反亲和 1. pod亲和 2.pod反亲和 四.Taints(污点 ...

  3. k8s调度(nodeName、nodeSelect、节点、pod的亲和和反亲和、Taints)

    k8s调度 nodeName nodeSelector 亲和与反亲和 节点亲和 pod 亲和性和反亲和性 Taints(污点) 调度器通过 kubernetes 的 watch 机制来发现集群中新创建 ...

  4. 从零开始入门 K8s | 调度器的调度流程和算法介绍

    作者 | 汪萌海(木苏)  阿里巴巴技术专家 关注"阿里巴巴云原生"公众号,回复关键词**"入门"**,即可下载从零入门 K8s 系列文章 PPT. 导读:Ku ...

  5. 【项目实战23】k8s(9)—k8s调度(节点亲和性,Taint污点,pod容忍性)

    k8s调度 一.k8s调度背景介绍 二.nodeName方式调度 三.nodeSelector方式调度 (1).使用方式 (2).节点亲和性 四.Taints污点 (1).介绍 (2).使用 五.to ...

  6. 图解 K8S(07):调度利器之亲和与反亲和(服务容灾)

    本系列教程目录(已发布): 图解 K8S(01):基于ubuntu 部署最新版 k8s 集群 图解 K8S(02):认识 K8S 中的资源对象 图解 K8S(03):从 Pause 容器理解 Pod ...

  7. k8s(八)—调度因素(nodeName、nodeSelector、亲和与反亲和、Taints 污点)、影响pod调度的指令

    1 调度简介 [root@server2 ~]# kubectl get pod -n kube-system 2 影响kubernetes调度的因素 2.1 nodeName(针对节点) [root ...

  8. k8s 亲和、反亲和、污点、容忍

    目录 一.K8s调度 二.亲和与反亲和 2.1.Pod和Node 2.2.硬亲和和软亲和 三.污点与容忍 3.1  污点(Taint) 3.1.1  污点的组成 3.1.2  污点的设置和去除 3.2 ...

  9. K8S node亲和与反亲和:affinity应用

    简介: affinity是K8S 1.2版本后引入的新特性,类似于nodeSelector,允许使用者指定一些pod在Node间调度的约束,目前支持两种形式: 1. requireDuringSche ...

最新文章

  1. 通过Dockerfile构建Docker镜像
  2. 基于生成式深度学习方法设计潜在2019-nCoV蛋白酶抑制剂
  3. zabbix 二次开发之调用api接口获取历史数据
  4. 黑客演示通过空中电视信号DVB-T攻击智能电视机
  5. cocos2dx-lua之断点调试支持
  6. matlab fill 渐变,Matlab的渐变色填充(一)
  7. redis面试问题(一)
  8. Redis 分布式集群搭建2022版本+密码(linux环境)
  9. java并发初探ReentrantWriteReadLock
  10. C#如何测量字符串的高度宽度和精确取得字符串的高度宽度
  11. WinForm timer 控件
  12. 人工智能系列:AI 可视化训练平台
  13. IOS-App Store 提审应用步骤
  14. 流行编曲(6)副旋律&合声
  15. Borg和Kubernetes有什么不同?未来的云需要什么?
  16. CorelDRAW2022mac最新版本更新v24.0.0.301新增功能介绍
  17. 跑分软件测试的游戏是,扔掉跑分软件 开启游戏测评2.0时代
  18. 广度优先搜索算法1-已知若干个城市的路线,求从一个城市到另外一个城市的路径,要求路径中经过的城市最少。
  19. 010 面向对象编程
  20. Kinect结合Unity3D引擎开发体感游戏(二)

热门文章

  1. 手机网页图片自适应大小 background-size css 图片全屏 背景尺寸设置
  2. golang 实现 pdf 转高清晰度 jpeg
  3. ISTQB中的测试条件是什么?和测试用例的前置条件有什么区别?
  4. 2022/4/18 天梯赛刷题记录2022天梯赛热身赛
  5. dede首页调用全站tag
  6. 【QT】判断鼠标按键
  7. 银行项目外包专题系列之二:公司没提升打杂,裸辞后收到银行外包,到底去还是不去
  8. 只用200行Go代码写一个自己的区块链!
  9. 我的世界无限法则服务器怎么用,我的世界无限法则版
  10. 自动复制吱口令html,使用clipboard.js实现复制吱口令功能的示例代码