一文详解kubernetes 中资源分配的那些事

目录
  • 概要
  • 一个nginx的配置
  • 我们进入nginx容器所在目录看下
    • cpu.shares
    • cpu.cpu.cfs_period_us、cpu.cfs_quota_us
  • 资源使用率数据来源
  • 下kubelet相关配置:**

概要

在k8s中,kube-scheduler是Kubernetes中的调度器,用于将Pod调度到可用的节点上。在调度过程中,kube-scheduler需要了解节点和Pod的资源需求和可用性情况,其中CPU和内存是最常见的资源需求。那么这些资源的使用率是怎么来的呢?当Pod调度到节点上后,系统是如何约束Pod的资源使用而不影响其他Pod的?当资源使用率达到了申请的资源时,会发生什么?下面,我们就这些问题,详细展开说说。阅读本文,你将了解到

  • k8s调度Pod时,节点的资源使用率是怎么来的
  • k8s中配置的cpu的limit, request在节点上具体是通过什么参数来约束Pod的资源使用的
  • 什么是empheral-storage资源,有什么用
  • kubelet配置中关于资源管理的那些参数该怎么配置

用过k8s的同学应该都知道,我们在配置deployment的时候,我们一般都会为cpu和内存配置limit和request,那么这个配置具体在节点上是怎么限制的呢?

一个nginx的配置

cpu的request、limit分别是1个核和4个核,内存的request、limit分别是1Gi和4Gi(Gi=1024Mi,G=1000Mi)。我们都知道,资源的限制时使用cgroup实现的,那么Pod的资源是怎么实现的呢?我们去Pod所在的节点看下。

k8s的cpu限制的cgroup目录在 /sys/fs/cgroup/cpu/kubepods ,该目录内容如下

我们能看到besteffort 和 burstable两个目录,这两个目录涉及Pod的QoS

QoS(Quality of Service),大部分译为“服务质量等级”,又译作“服务质量保证”,是作用在 Pod 上的一个配置,当 Kubernetes 创建一个 Pod 时,它就会给这个 Pod 分配一个 QoS 等级,可以是以下等级之一:

  • Guaranteed:Pod 里的每个容器都必须有内存/CPU 限制和请求,而且值必须相等。
  • Burstable:Pod 里至少有一个容器有内存或者 CPU 请求且不满足 Guarantee 等级的要求,即内存/CPU 的值设置的不同。
  • BestEffort:容器必须没有任何内存或者 CPU 的限制或请求。

这个东西的作用就是,当节点上出现资源压力的时候,会根据QoS的等级顺序进行驱逐,驱逐顺序为Guaranteed<Burstable<BestEffort。

我们的nginx对资源要求的配置根据上面的描述可以看到是Burstable类型的

我们进该目录看下:

里面的目录表示属于Burstable类型的Pod的cpu cgroup都配置在这个目录,再进到Pod所在目录,可以看到有2个目录,每个目录是容器的cpu cgroup目录,一个是nginx本身的,另外一个Infra容器(沙箱容器)。

我们进入nginx容器所在目录看下

我们重点看下红框内的三个文件的含义。

cpu.shares

cpu.shares用来设置CPU的相对值,并且是针对所有的CPU(内核),默认值是1024等同于一个cpu核心。 CPU Shares将每个核心划分为1024个片,并保证每个进程将按比例获得这些片的份额。如果有1024个片(即1核),并且两个进程设置cpu.shares均为1024,那么这两个进程中每个进程将获得大约一半的cpu可用时间。

当系统中有两个cgroup,分别是A和B,A的shares值是1024,B 的shares值是512, 那么A将获得1024/(1024+512)=66%的CPU资源,而B将获得33%的CPU资源。shares有两个特点:

  • 如果A不忙,没有使用到66%的CPU时间,那么剩余的CPU时间将会被系统分配给B,即B的CPU使用率可以超过33%。
  • 如果添加了一个新的cgroup C,且它的shares值是1024,那么A的限额变成了1024/(1024+512+1024)=40%,B的变成了20%。

从上面两个特点可以看出:

在闲的时候,shares不起作用,只有在CPU忙的时候起作用。

由于shares是一个绝对值,单单看某个组的share是没有意义的,需要和其它cgroup的值进行比较才能得到自己的相对限额,而在一个部署很多容器的机器上,cgroup的数量是变化的,所以这个限额也是变化的,自己设置了一个高的值,但别人可能设置了一个更高的值,所以这个功能没法精确的控制CPU使用率。从share这个单词(共享的意思)的意思,我们也能够体会到这一点。

cpu.shares对应k8s内的resources.requests.cpu字段,值对应关系为:resources.requests.cpu * 1024 = cpu.share

cpu.cpu.cfs_period_us、cpu.cfs_quota_us

cpu.cfs_period_us用来配置时间周期长度,cpu.cfs_quota_us用来配置当前cgroup在设置的周期长度内所能使用的CPU时间数。 两个文件配合起来设置CPU的使用上限。两个文件的单位都是微秒(us),cfs_period_us的取值范围为1毫秒(ms)到1秒(s),cfs_quota_us的取值大于1ms即可,如果cfs_quota_us的值为-1(默认值),表示不受cpu时间的限制。

cpu.cpu.cfs_period_us、cpu.cfs_quota_us对应k8s中的resources.limits.cpu字段:resources.limits.cpu = cpu.cfs_quota_us/cpu.cfs_period_us

可以看到上面的nginx的这两个比值正好是4,表示nginx最多可以分配到4个CPU。此时,就算系统很空闲,上面说的share没有发挥作用,也不会分配超时4个CPU,这就是上限的限制。

在平时配置的时候,limit和request两者最好不要相差过大,否则节点CPU容易出现超卖情况,如limit/request=4,那么在调度的时候发现节点是有资源的,一旦调度完成后,Pod可能会由于跑出超过request的CPU,那么节点其他Pod可能就会出现资源”饥饿“情况,反映到业务就是请求反应慢。CPU 属于可压缩资源,内存属于不可压缩资源。当可压缩资源不足时,Pod 会饥饿,但是不会退出;当不可压缩资源不足时,Pod 就会因为 OOM 被内核杀掉。

资源使用率数据来源

这个问题还得从源码入手,首先我们看看kube-scheduler在调度的时候对于资源的判断都做了哪些事。kube-scheduler会使用informer监听集群内node的变化,如果有变化(如Node的状态,Node的资源情况等),则调用事件函数写入本地index store(cache)中,代码如下:

func addAllEventHandlers{
  ...
    informerFactory.Core().V1().Nodes().Informer().AddEventHandler(
      cache.ResourceEventHandlerFuncs{
        AddFunc:    sched.addNodeToCache,
        UpdateFunc: sched.updateNodeInCache,
        DeleteFunc: sched.deleteNodeFromCache,
      },
    )
  ...

如上,如果集群内加入了新节点,则会调用addNodeToCache函数将Node信息加入本地缓存,那么咱们来看看addNodeToCache函数:

func (sched *Scheduler) addNodeToCache(obj interface{}) {
  node, ok := obj.(*v1.Node)
  if !ok {
    klog.ErrorS(nil, "Cannot convert to *v1.Node", "obj", obj)
    return
  }
  nodeInfo := sched.Cache.AddNode(node)
  klog.V(3).InfoS("Add event for node", "node", klog.KObj(node))
  sched.SchedulingQueue.MoveAllToActiveOrBackoffQueue(queue.NodeAdd, preCheckForNode(nodeInfo))
}

从上面的代码我们看到,在把该节点加入cache后,还会调用MoveAllToActiveOrBackoffQueue 函数,对在 unschedulablePods (还没有调度的Pod队列)中的Pod进行一次Precheck ,如果MoveAllToActiveOrBackoffQueue** 函数如下

func (p *PriorityQueue) MoveAllToActiveOrBackoffQueue(event framework.ClusterEvent, preCheck PreEnqueueCheck) {
  p.lock.Lock()
  defer p.lock.Unlock()
  unschedulablePods := make([]*framework.QueuedPodInfo, 0, len(p.unschedulablePods.podInfoMap))
  for _, pInfo := range p.unschedulablePods.podInfoMap {
    if preCheck == nil || preCheck(pInfo.Pod) {
      unschedulablePods = append(unschedulablePods, pInfo)
    }
  }
  p.movePodsToActiveOrBackoffQueue(unschedulablePods, event)
}

如果上述的Precheck通过后,则会把Pod移到相应的队列等待下一次调度。这里的重点来了,本文是讲关于资源相关的,那么Precheck中到底做了什么检查呢?

func preCheckForNode(nodeInfo *framework.NodeInfo) queue.PreEnqueueCheck {
  // Note: the following checks doesn't take preemption into considerations, in very rare
  // cases (e.g., node resizing), "pod" may still fail a check but preemption helps. We deliberately
  // chose to ignore those cases as unschedulable pods will be re-queued eventually.
  return func(pod *v1.Pod) bool {
    admissionResults := AdmissionCheck(pod, nodeInfo, false)
    if len(admissionResults) != 0 {
      return false
    }
    _, isUntolerated := corev1helpers.FindMatchingUntoleratedTaint(nodeInfo.Node().Spec.Taints, pod.Spec.Tolerations, func(t *v1.Taint) bool {
      return t.Effect == v1.TaintEffectNoSchedule
    })
    return !isUntolerated
  }
}
func AdmissionCheck(pod *v1.Pod, nodeInfo *framework.NodeInfo, includeAllFailures bool) []AdmissionResult {
  var admissionResults []AdmissionResult
  insufficientResources := noderesources.Fits(pod, nodeInfo)
  if len(insufficientResources) != 0 {
    for i := range insufficientResources {
      admissionResults = append(admissionResults, AdmissionResult{InsufficientResource: &insufficientResources[i]})
    }
    if !includeAllFailures {
      return admissionResults
    }
  }
  if matches, _ := corev1nodeaffinity.GetRequiredNodeAffinity(pod).Match(nodeInfo.Node()); !matches {
    admissionResults = append(admissionResults, AdmissionResult{Name: nodeaffinity.Name, Reason: nodeaffinity.ErrReasonPod})
    if !includeAllFailures {
      return admissionResults
    }
}
  if !nodename.Fits(pod, nodeInfo) {
    admissionResults = append(admissionResults, AdmissionResult{Name: nodename.Name, Reason: nodename.ErrReason})
    if !includeAllFailures {
      return admissionResults
    }
  }
  if !nodeports.Fits(pod, nodeInfo) {
    admissionResults = append(admissionResults, AdmissionResult{Name: nodeports.Name, Reason: nodeports.ErrReason})
    if !includeAllFailures {
      return admissionResults
    }
  }
  return admissionResults
}

preCheckForNode 调用了AdmissionCheck,在AdmissionCheck中分别做了资源检查、亲和性检查、nodeName检查、端口检查。这里我们只关注资源的检查

func Fits(pod *v1.Pod, nodeInfo *framework.NodeInfo) []InsufficientResource {
  return fitsRequest(computePodResourceRequest(pod), nodeInfo, nil, nil)
}
func fitsRequest(podRequest *preFilterState, nodeInfo *framework.NodeInfo, ignoredExtendedResources, ignoredResourceGroups sets.String) []InsufficientResource {
  insufficientResources := make([]InsufficientResource, 0, 4)
  allowedPodNumber := nodeInfo.Allocatable.AllowedPodNumber
  if len(nodeInfo.Pods)+1 > allowedPodNumber {
    insufficientResources = append(insufficientResources, InsufficientResource{
      ResourceName: v1.ResourcePods,
      Reason:       "Too many pods",
      Requested:    1,
      Used:         int64(len(nodeInfo.Pods)),
      Capacity:     int64(allowedPodNumber),
    })
  }
  if podRequest.MilliCPU == 0 &&
    podRequest.Memory == 0 &&
    podRequest.EphemeralStorage == 0 &&
    len(podRequest.ScalarResources) == 0 {
    return insufficientResources
  }
  if podRequest.MilliCPU > (nodeInfo.Allocatable.MilliCPU - nodeInfo.Requested.MilliCPU) {
    insufficientResources = append(insufficientResources, InsufficientResource{
      ResourceName: v1.ResourceCPU,
      Reason:       "Insufficient cpu",
      Requested:    podRequest.MilliCPU,
      Used:         nodeInfo.Requested.MilliCPU,
      Capacity:     nodeInfo.Allocatable.MilliCPU,
    })
  }
  if podRequest.Memory > (nodeInfo.Allocatable.Memory - nodeInfo.Requested.Memory) {
    insufficientResources = append(insufficientResources, InsufficientResource{
      ResourceName: v1.ResourceMemory,
      Reason:       "Insufficient memory",
      Requested:    podRequest.Memory,
      Used:         nodeInfo.Requested.Memory,
      Capacity:     nodeInfo.Allocatable.Memory,
    })
}
  if podRequest.EphemeralStorage > (nodeInfo.Allocatable.EphemeralStorage - nodeInfo.Requested.EphemeralStorage) {
    insufficientResources = append(insufficientResources, InsufficientResource{
      ResourceName: v1.ResourceEphemeralStorage,
      Reason:       "Insufficient ephemeral-storage",
      Requested:    podRequest.EphemeralStorage,
      Used:         nodeInfo.Requested.EphemeralStorage,
      Capacity:     nodeInfo.Allocatable.EphemeralStorage,
    })
  }
  for rName, rQuant := range podRequest.ScalarResources {
    if v1helper.IsExtendedResourceName(rName) {
      // If this resource is one of the extended resources that should be ignored, we will skip checking it.
      // rName is guaranteed to have a slash due to API validation.
      var rNamePrefix string
      if ignoredResourceGroups.Len() > 0 {
        rNamePrefix = strings.Split(string(rName), "/")[0]
      }
      if ignoredExtendedResources.Has(string(rName)) || ignoredResourceGroups.Has(rNamePrefix) {
        continue
      }
    }
    if rQuant > (nodeInfo.Allocatable.ScalarResources[rName] - nodeInfo.Requested.ScalarResources[rName]) {
      insufficientResources = append(insufficientResources, InsufficientResource{
        ResourceName: rName,
        Reason:       fmt.Sprintf("Insufficient %v", rName),
        Requested:    podRequest.ScalarResources[rName],
        Used:         nodeInfo.Requested.ScalarResources[rName],
        Capacity:     nodeInfo.Allocatable.ScalarResources[rName],
      })
    }
  }
  return insufficientResources
}

fitsRequest 首先会调用computePodResourceRequest函数计算出这个Pod需要多少资源,然后跟目前节点还能分配的资源做比较,如果还能够分配出资源,那么针对于资源检查这一项就通过了。如果Precheck所有项都能通过,那么该Pod会被放入active队列,该队列里的Pod就会被kube-scheduler取出做调度。前面所说的目前节点资源情况是从哪里来的呢?kubelet会定期(或者node发生变化)上报心跳到Kube-apiserver,因为kube-scheduler监听了node的变化,所以能感知到节点的资源使用情况。

当Kube-scheduler从队列取到Pod后,会进行一系列的判断(如PreFilter),还会涉及资源的检查,这个资源使用情况也是kubelet上报的。

当我们describe 一个node的时候,可以看到能够显示资源allocatable的信息,那这个信息就是实时的资源使用情况吗?答案是否定的,我们看下

kubectl describe node xxxx
Capacity:
 cpu:                64
 ephemeral-storage:  1056889268Ki
 hugepages-1Gi:      0
 hugepages-2Mi:      0
 memory:             263042696Ki
 pods:               110
Allocatable:
 cpu:                63
 ephemeral-storage:  1044306356Ki
 hugepages-1Gi:      0
 hugepages-2Mi:      0
 memory:             257799816Ki
 pods:               110

注:本机是64U256G的机器

其中capacity是本机的硬件一共可以提供的资源,allocatable是可以分配的,那么为什么allocatable为什么会和capacity不一样呢?这里就涉及到了预留资源和驱逐相关内容

下kubelet相关配置:**

systemReserved:
  cpu: "0.5"
  ephemeral-storage: 1Gi
  memory: 2Gi
  pid: "1000"
kubeReserved:
  cpu: "0.5"
  ephemeral-storage: 1Gi
  memory: 2Gi
  pid: "1000
evictionHard:
  imagefs.available: 10Gi
  memory.available: 1Gi
  nodefs.available: 10Gi
  nodefs.inodesFree: 5%

systemReserved, kubeReserved分别 *表示预留给操作系统和kubernetes组件的资源,kubelet在上报可用资源的时候需要减去这部分资源;evictionHard表示资源只剩下这么多的时候,就会启动Pod的驱逐,所以这部分资源也不能算在可分配里面的。这么算起来,上面的capacity减去上述三者相加正好是allocatable的值,也就是该节点实际可分配的资源。他们的关系可以用下图表示
*

但是这里有个值得注意的点,上面通过describe出来的allocatable的值是一个静态的值,表示该节点总共可以分配多少资源,而不是此时此刻节点可以分配多少资源,kube-scheduler依据Kubelet动态上报的数据来判断某个节点是否能够调度。

还需要注意,要使systemReserved, kubeReserved配置的资源不算在可分配的资源里面,还需要配置如下配置:

# 该配置表示,capacity减去下面的配置的资源才是节点d当前可分配的
# 默认是pods,表示只减去pods占用了的资源
enforceNodeAllocatable:
- pods
- kube-reserved
- system-reserved
# 如果你使用的是systemd作为cgroup驱动,你还需要配置下面的配置
# 否则kubelet无法正常启动,因为找不到cgroup目录
# k8s官方推荐s会用systemd
kubeReservedCgroup: /kubelet.slice
systemReservedCgroup: /system.slice/kubelet.service

到这里,我们就简单讲了scheduler是如何在调度的时候,在资源层面是如何判断的。当然了,上面只简单讲了调度的时候pod被移到可调度队列的情况,后面还有prefilter、filter、score等步骤,但是这些步骤在判断资源情况时,跟上面是一样的。

以上就是一文详解kubernetes 中资源分配的那些事的详细内容,更多关于kubernetes 资源分配的资料请关注我们其它相关文章!

(0)

相关推荐

  • Kubernetes scheduler启动监控资源变化解析

    目录 确立目标 run Scheduler NodeName Informer Shared Informer PodInformer Reflect Summary 确立目标 理解kube-scheduler启动的流程 了解Informer是如何从kube-apiserver监听资源变化的情况 理解kube-scheduler启动的流程 代码在cmd/kube-scheduler run // kube-scheduler 类似于kube-apiserver,是个常驻进程,查看其对应的Run函

  • kubernetes 使用jq命令对资源配置查看方式

    目录 使用jq命令对资源配置查看 kubernetes常用命令总结 kubectl常用命令 kubectl格式化输出 kubernetes集群管理指南 使用jq命令对资源配置查看 有图形化的直接从图形化可以看到各种资源,如Deployment.Pod等资源的配置 这里写一个 jq 命令 jq命令允许针对json进行操作,如过滤 jq命令centos环境下安装 # yum -y install jq 假设我们有个文件 # cat pod-yaml   {     "apiVersion"

  • 如何使用Kubernetes自定义资源(CRD)详解

    目录 什么是CRD CRD使用 小结 总结 什么是CRD CRD的全称为 CustomResourceDefinitions,即自定义资源.k8s拥有一些内置的资源,比如说Pod,Deployment,ReplicaSet等等,而CRD则提供了一种方式,使用户可以自定义新的资源,以扩展k8s的功能. 使用CRD可以在不修改k8s源代码的基础上方便的扩展k8s的功能,比如腾讯云TKE使用CRD:logcollectors.ccs.cloud.tencent.com以添加日志收集服务,而Istio也

  • kubernetes k8s CRD学习笔记

    目录 CustomResourceDefinition简介: 目前扩展Kubernetes API的常用方式有3种: 配置规范 示例1: 创建自定义CRD 示例2: etcd Operator 部署 (该项目已不在维护) CustomResourceDefinition简介: 在 Kubernetes 中一切都可视为资源,Kubernetes 1.7 之后增加了对 CRD 自定义资源二次开发能力来扩展 Kubernetes API,通过 CRD 我们可以向 Kubernetes API 中增加新

  • kubernetes(k8s)安装metrics-server实现资源使用情况监控方式详解

    1. Metrics Server 与 kubenetes版本 Metrics Server    Metrics API group/version    Supported Kubernetes version0.6x    metrics.k8s.io/v1beta1    *1.19+0.5x    metrics.k8s.io/v1beta1    *1.8+0.4x    metrics.k8s.io/v1beta1    *1.8+0.3x    metrics.k8s.io/v1

  • 一文详解kubernetes 中资源分配的那些事

    目录 概要 一个nginx的配置 我们进入nginx容器所在目录看下 cpu.shares cpu.cpu.cfs_period_us.cpu.cfs_quota_us 资源使用率数据来源 下kubelet相关配置:** 概要 在k8s中,kube-scheduler是Kubernetes中的调度器,用于将Pod调度到可用的节点上.在调度过程中,kube-scheduler需要了解节点和Pod的资源需求和可用性情况,其中CPU和内存是最常见的资源需求.那么这些资源的使用率是怎么来的呢?当Pod调

  • 一文详解JS中的事件循环机制

    目录 前言 1.JavaScript是单线程的 2.同步和异步 3.事件循环 前言 我们知道JavaScript 是单线程的编程语言,只能同一时间内做一件事,按顺序来处理事件,但是在遇到异步事件的时候,js线程并没有阻塞,还会继续执行,这又是为什么呢?本文来总结一下js 的事件循环机制. 1.JavaScript是单线程的 JavaScript 是一种单线程的编程语言,只有一个调用栈,决定了它在同一时间只能做一件事.在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行.在

  • 一文详解Java中的类加载机制

    目录 一.前言 二.类加载的时机 2.1 类加载过程 2.2 什么时候类初始化 2.3 被动引用不会初始化 三.类加载的过程 3.1 加载 3.2 验证 3.3 准备 3.4 解析 3.5 初始化 四.父类和子类初始化过程中的执行顺序 五.类加载器 5.1 类与类加载器 5.2 双亲委派模型 5.3 破坏双亲委派模型 六.Java模块化系统 一.前言 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最 终形成可以被虚拟机直接使用的Java类型,这个过程

  • 一文详解C++中运算符的使用

    目录 一.算术运算符 二.关系运算符 三.逻辑运算符 四.位运算符 五.赋值运算符 六.杂项运算符 一.算术运算符 运算符 描述 + 把两个操作数相加 - 从第一个操作数中减去第二个操作数 * 把两个操作数相乘 / 分子除以分母 % 取模运算符,整除后的余数 ++ 自增运算符,整数值增加 1 – 自减运算符,整数值减少 1 通过下面的例子可以让我们更好的理解C++中的运算符的意义与使用方法. #include <iostream> using namespace std; int main()

  • 一文详解Python中生成器的原理与使用

    目录 什么是生成器 迭代器和生成器的区别 创建方式 生成器表达式 基本语法 生成器函数 yield关键字 yield和return yield的使用方法 生成器函数的基本使用 send的使用 可迭代对象的优化 总结 我们学习完推导式之后发现,推导式就是在容器中使用一个for循环而已,为什么没有元组推导式? 原因就是“元组推导式”的名字不是这样的,而是叫做生成器表达式. 什么是生成器 生成器表达式本质上就是一个迭代器,是定义迭代器的一种方式,是允许自定义逻辑的迭代器.生成器使用generator表

  • 一文详解Java中Stream流的使用

    目录 简介 操作1:创建流 操作2:中间操作 筛选(过滤).去重 映射 排序 消费 操作3:终止操作 匹配.最值.个数 收集 规约 简介 说明 本文用实例介绍stream的使用. JDK8新增了Stream(流操作) 处理集合的数据,可执行查找.过滤和映射数据等操作. 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询.可以使用 Stream API 来并行执行操作. 简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式. 特点 不是数据结构

  • 一文详解Python中PO模式的设计与实现

    目录 什么是PO模式 PO 三层模式 PO 设计模式的优点 将改写的脚本转为PO设计模式 构建基础的 BasePage 层 构建首页的 Page 层(HomePage) 构建登录页的 Page 层(LoginPage) 构建 首页 - 订单 - 支付 流程的 Page 层(OrderPage) PO 设计模式下测试Case的改造 在使用 Python 进行编码的时候,会使用自身自带的编码设计格式,比如说最常见的单例模式,稍微抽象一些的抽象工厂模式等等… 在利用 Python 做自动化测试的时候,

  • 一文详解C#中方法重载的底层玩法

    目录 一:为什么 C 不支持 二:C++ 符号表突破 三:C#如何实现突破 最近在看 C++ 的方法重载,我就在想 C# 中的重载底层是怎么玩的,很多朋友应该知道 C 是不支持重载的,比如下面的代码就会报错. #include <stdio.h> int say() { return 1; } int say(int i) { return i; } int main() { say(10); return 0; } 从错误信息看,它说 say 方法已经存在了,尴尬... 一:为什么 C 不支

  • 一文详解JavaScript中prototype的使用

    目录 prototype初步认识 函数有prototype属性,函数创建的对象没有 获得当前对象的属性 父和子的扩展 子的proto和prototype的区别 扩展得到的东西到底从哪来的 prototype初步认识 在学习JavaScript中,遇到了prototype,经过一番了解,知道它是可以进行动态扩展的 function Func(){}; var func1 = new Func; console.log(func1.var1) //undefined Func.prototype.v

  • 一文详解Python中的重试机制

    目录 介绍 1. 最基本的重试 2. 设置停止基本条件 3. 设置何时进行重试 4. 重试后错误重新抛出 5. 设置回调函数 介绍 为了避免由于一些网络或等其他不可控因素,而引起的功能性问题.比如在发送请求时,会因为网络不稳定,往往会有请求超时的问题. 这种情况下,我们通常会在代码中加入重试的代码.重试的代码本身不难实现,但如何写得优雅.易用,是我们要考虑的问题. 这里要给大家介绍的是一个第三方库 - Tenacity (标题中的重试机制并并不准确,它不是 Python 的内置模块,因此并不能称

随机推荐