Kubernetes基础(十四)-Cluster Autoscaler

news2024/11/15 13:30:20

Kubernetes 给出的解决方案就是:自动伸缩(auto-scaling),通过自动伸缩组件之间的配合,可以 7*24 小时的监控着k8s集群,动态变化负载,以适应用户需求。

1 自动伸缩组件

1.1 自动伸缩类型

1.1.1 水平自动伸缩(Horizontal Pod Autoscaler,HPA)

HPA 可以基于实时的 CPU 利用率自动伸缩 Replication Controller、Deployment 和 Replica Set 中的 Pod 数量。也可以通过搭配 Metrics Server 基于其他的度量指标。

1.1.2 垂直自动伸缩(Vertical Pod Autoscaler,VPA)

VPA 可以基于 Pod 的使用资源来自动设置 Pod 所需资源并且能够在运行时自动调整资源。

1.1.3 集群自动伸缩(Cluster Autoscaler,CA)

CA 是一个可以自动伸缩集群 Node 的组件。如果集群中有未被调度的 Pod,它将会自动扩展 Node 来使 Pod 可用,或是在发现集群中的 Node 资源使用率过低时,删除 Node 来节约资源。

1.2 插件伸缩(Addon Resizer)

Addon Resizer是一个小插件,它以 Sidecar 的形式来垂直伸缩与自己同一个部署中的另一个容器,目前唯一的策略就是根据集群中节点的数量来进行线性扩展。通常与 Metrics Server 配合使用,以保证其可以负担不断扩大的整个集群的 metrics API 服务。

通过 HPA 伸缩无状态应用,VPA 伸缩有状态应用,CA 保证计算资源,它们的配合使用,构成了一个完整的自动伸缩解决方案。

2 Cluster Autoscaler 详细介绍

上面介绍的四个组件中,HPA 是在 kubernetes 代码仓库中的,随着 kubernetes 的版本进行更新发布,不需要部署,可以直接使用。其他的三个组件都在官方社区维护的仓库中,Cluster Autoscaler 的 v1.0(GA) 版本已经随着 kubernetes 1.8 一起发布,剩下两个则还是 beta 版本。

CA由以下几个模块组成:

  • autoscaler:核心模块,负责整体扩缩容功能
  • Estimator:负责评估计算扩容节点
  • Simulator:负责模拟调度,计算缩容节点
  • CA Cloud-Provider:与云交互进行节点的增删操作。社区目前仅支持AWS和GCE,其他云厂商需要自己实现CloudProvider和NodeGroup相关接口。

CA的架构如下:

2.1 云厂商

Cluster Autoscaler 通常需要搭配云厂商使用,它提供了 Cloud Provider 接口供各个云厂商接入,云厂商通过伸缩组(Scaling Group)或节点池(Node Pool)的功能对 ECS 类产品节点进行增加删除等操作。

目前(v1.18.1)已接入的云厂商:

Alicloud:https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/alicloud/README.md

Aws:https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md

Azure:https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/azure/README.md

Baiducloud:https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/baiducloud/README.md

Digitalocean:https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/digitalocean/README.md

GoogleCloud GCE:https://kubernetes.io/docs/tasks/administer-cluster/cluster-management/#upgrading-google-compute-engine-clusters

GoogleCloud GKE:https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-autoscaler

OpenStack Magnum:https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/magnum/README.md

Packet:https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/packet/README.md

启动参数:https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md#what-are-the-parameters-to-ca

部署yaml:

直接在集群中部署即可,简化的yaml如下所示,启动参数按需添加,其中{{MIN}}是最小节点数,{{MAX}}是最大节点数。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: cluster-autoscaler
  labels:
    k8s-app: cluster-autoscaler
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: cluster-autoscaler
  template:
    metadata:
      labels:
        k8s-app: cluster-autoscaler
    spec:
      containers:
        - image: cluster-autoscaler:latest
          name: cluster-autoscaler
          command:
            - ./cluster-autoscaler
            - --nodes={{MIN}}:{{MAX}}:k8s-worker-asg-1

2.2 工作原理

Cluster Autoscaler 抽象出了一个 NodeGroup 的概念,与之对应的是云厂商的伸缩组服务。Cluster Autoscaler 通过 CloudProvider 提供的 NodeGroup 计算集群内节点资源,以此来进行伸缩。

在启动后,Cluster Autoscaler 会定期(默认 10s)检查未调度的 Pod 和 Node 的资源使用情况,并进行相应的 Scale UPScale Down 操作。

2.2.1 Scale UP

当 Cluster Autoscaler 发现有 Pod 由于资源不足而无法调度时,就会通过调用 Scale UP 执行扩容操作。

Scale UP 中会只会计算在 NodeGroup 中存在的 Node,我们可以将 Worker Node 统一交由伸缩组进行管理。并且由于伸缩组非同步加入的特性,也会考虑到 Upcoming Node。

为了业务需要,集群中可能会有不同规格的 Node,我们可以创建多个 NodeGroup,在扩容时会根据 --expander 选项配置指定的策略,选择一个扩容的节点组,支持如下五种策略:

  • random:随机选择一个 NodeGroup。如果未指定,则默认为此策略。
  • most-pods:选择能够调度最多 Pod 的 NodeGroup,比如有的 Pod 未调度是因为 nodeSelector,此策略会优先选择能满足的 NodeGroup 来保证大多数的 Pod 可以被调度。
  • least-waste:为避免浪费,此策略会优先选择能满足 Pod 需求资源的最小资源类型的 NodeGroup
  • price:根据 CloudProvider 提供的价格模型,选择最省钱的 NodeGroup
  • priority:通过配置优先级来进行选择,用起来比较麻烦,需要额外的配置,可以看文档。

如果有需要,也可以平衡相似 NodeGroup 中的 Node 数量,避免 NodeGroup 达到 MaxSize 而导致无法加入新 Node。通过 --balance-similar-node-groups 选项配置,默认为 false

再经过一系列的操作后,最终计算出要扩容的 Node 数量及 NodeGroup,使用 CloudProvider 执行 IncreaseSize 操作,增加云厂商的伸缩组大小,从而完成扩容操作。

文字表达能力不足,如果有不清晰的地方,可以参考下面的 ScaleUP 源码解析。

2.2.2 Scale Down

缩容是一个可选的功能,通过 --scale-down-enabled 选项配置,默认为 true

在 Cluster Autoscaler 监控 Node 资源时,如果发现有 Node 满足以下三个条件时,就会标记这个 Node 为 unneeded

  • Node 上运行的所有的 Pod 的 Cpu 和内存之和小于该 Node 可分配容量的 50%。可通过 --scale-down-utilization-threshold 选项改变这个配置。
  • Node 上所有的 Pod 都可以被调度到其他节点。
  • Node 没有表示不可缩容的 annotaition。

如果一个 Node 被标记为 unneeded 超过 10 分钟(可通过 --scale-down-unneeded-time 选项配置),则使用 CloudProvider 执行 DeleteNodes 操作将其删除。一次最多删除一个 unneeded Node,但空 Node 可以批量删除,每次最多删除 10 个(通过 ----max-empty-bulk-delete 选项配置)。

实际上并不是只有这一个判定条件,还会有其他的条件来阻止删除这个 Node,比如 NodeGroup 已达到 MinSize,或在过去的 10 分钟内有过一次 Scale UP 操作(通过 --scale-down-delay-after-add 选项配置)等等,更详细可查看文档。

Cluster Autoscaler 的工作机制很复杂,但其中大部分都能通过 flags 进行配置,如果有需要,请详细阅读文档:https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md

2.3 如何实现 CloudProvider

如果使用上述中已实现接入的云厂商,只需要通过 --cloud-provider 选项指定来自哪个云厂商就可以,如果想要对接自己的 IaaS 或有特定的业务逻辑,就需要自己实现 CloudProvider InterfaceNodeGroupInterface。并将其注册到 builder 中,用于通过 --cloud-provider 参数指定。

buildercloudprovider/builder 中的 builder_all.go 中注册,也可以在其中新建一个自己的 build,通过 go 文件的 +build 编译参数来指定使用的 CloudProvider

CloudProvider 接口与 NodeGroup 接口在 cloud_provider.go 中定义,其中需要注意的是 Refresh 方法,它会在每一次循环(默认 10 秒)的开始时调用,可在此时请求接口并刷新 NodeGroup 状态,通常的做法是增加一个 manager 用于管理状态。有不理解的部分可参考其他 CloudProvider 的实现。


type CloudProvider interface {
    // Name returns name of the cloud provider.
    Name() string

    // NodeGroups returns all node groups configured for this cloud provider.
    // 会在一此循环中多次调用此方法,所以不适合每次都请求云厂商服务,可以在 Refresh 时存储状态
    NodeGroups() []NodeGroup

    // NodeGroupForNode returns the node group for the given node, nil if the node
    // should not be processed by cluster autoscaler, or non-nil error if such
    // occurred. Must be implemented.
    // 同上
    NodeGroupForNode(*apiv1.Node) (NodeGroup, error)

    // Pricing returns pricing model for this cloud provider or error if not available.
    // Implementation optional.
    // 如果不使用 price expander 就可以不实现此方法
    Pricing() (PricingModel, errors.AutoscalerError)

    // GetAvailableMachineTypes get all machine types that can be requested from the cloud provider.
    // Implementation optional.
    // 没用,不需要实现
    GetAvailableMachineTypes() ([]string, error)

    // NewNodeGroup builds a theoretical node group based on the node definition provided. The node group is not automatically
    // created on the cloud provider side. The node group is not returned by NodeGroups() until it is created.
    // Implementation optional.
    // 通常情况下,不需要实现此方法,但如果你需要 ClusterAutoscaler 创建一个默认的 NodeGroup 的话,也可以实现。
    // 但其实更好的做法是将默认 NodeGroup 写入云端的伸缩组
    NewNodeGroup(machineType string, labels map[string]string, systemLabels map[string]string,
        taints []apiv1.Taint, extraResources map[string]resource.Quantity) (NodeGroup, error)

    // GetResourceLimiter returns struct containing limits (max, min) for resources (cores, memory etc.).
    // 资源限制对象,会在 build 时传入,通常情况下不需要更改,除非在云端有显示的提示用户更改的地方,否则使用时会迷惑用户
    GetResourceLimiter() (*ResourceLimiter, error)

    // GPULabel returns the label added to nodes with GPU resource.
    // GPU 相关,如果集群中有使用 GPU 资源,需要返回对应内容。 hack: we assume anything which is not cpu/memory to be a gpu.
    GPULabel() string

    // GetAvailableGPUTypes return all available GPU types cloud provider supports.
    // 同上
    GetAvailableGPUTypes() map[string]struct{}

    // Cleanup cleans up open resources before the cloud provider is destroyed, i.e. go routines etc.
    // CloudProvider 只会在启动时被初始化一次,如果每次循环后有需要清除的内容,在这里处理
    Cleanup() error

    // Refresh is called before every main loop and can be used to dynamically update cloud provider state.
    // In particular the list of node groups returned by NodeGroups can change as a result of CloudProvider.Refresh().
    // 会在 StaticAutoscaler RunOnce 中被调用
    Refresh() error
}
// NodeGroup contains configuration info and functions to control a set
// of nodes that have the same capacity and set of labels.
type NodeGroup interface {
    // MaxSize returns maximum size of the node group.
    MaxSize() int

    // MinSize returns minimum size of the node group.
    MinSize() int

    // TargetSize returns the current target size of the node group. It is possible that the
    // number of nodes in Kubernetes is different at the moment but should be equal
    // to Size() once everything stabilizes (new nodes finish startup and registration or
    // removed nodes are deleted completely). Implementation required.
    // 响应的是伸缩组的节点数,并不一定与 kubernetes 中的节点数保持一致
    TargetSize() (int, error)

    // IncreaseSize increases the size of the node group. To delete a node you need
    // to explicitly name it and use DeleteNode. This function should wait until
    // node group size is updated. Implementation required.
    // 扩容的方法,增加伸缩组的节点数
    IncreaseSize(delta int) error

    // DeleteNodes deletes nodes from this node group. Error is returned either on
    // failure or if the given node doesn't belong to this node group. This function
    // should wait until node group size is updated. Implementation required.
    // 删除的节点一定要在该节点组中
    DeleteNodes([]*apiv1.Node) error

    // DecreaseTargetSize decreases the target size of the node group. This function
    // doesn't permit to delete any existing node and can be used only to reduce the
    // request for new nodes that have not been yet fulfilled. Delta should be negative.
    // It is assumed that cloud provider will not delete the existing nodes when there
    // is an option to just decrease the target. Implementation required.
    // 当 ClusterAutoscaler 发现 kubernetes 节点数与伸缩组的节点数长时间不一致,会调用此方法来调整
    DecreaseTargetSize(delta int) error

    // Id returns an unique identifier of the node group.
    Id() string

    // Debug returns a string containing all information regarding this node group.
    Debug() string

    // Nodes returns a list of all nodes that belong to this node group.
    // It is required that Instance objects returned by this method have Id field set.
    // Other fields are optional.
    // This list should include also instances that might have not become a kubernetes node yet.
    // 返回伸缩组中的所有节点,哪怕它还没有成为 kubernetes 的节点
    Nodes() ([]Instance, error)

    // TemplateNodeInfo returns a schedulernodeinfo.NodeInfo structure of an empty
    // (as if just started) node. This will be used in scale-up simulations to
    // predict what would a new node look like if a node group was expanded. The returned
    // NodeInfo is expected to have a fully populated Node object, with all of the labels,
    // capacity and allocatable information as well as all pods that are started on
    // the node by default, using manifest (most likely only kube-proxy). Implementation optional.
    // ClusterAutoscaler 会将节点信息与节点组对应,来判断资源条件,如果是一个空的节点组,那么就会通过此方法来虚拟一个节点信息。
    TemplateNodeInfo() (*schedulernodeinfo.NodeInfo, error)

    // Exist checks if the node group really exists on the cloud provider side. Allows to tell the
    // theoretical node group from the real one. Implementation required.
    Exist() bool

    // Create creates the node group on the cloud provider side. Implementation optional.
    // 与 CloudProvider.NewNodeGroup 配合使用
    Create() (NodeGroup, error)

    // Delete deletes the node group on the cloud provider side.
    // This will be executed only for autoprovisioned node groups, once their size drops to 0.
    // Implementation optional.
    Delete() error

    // Autoprovisioned returns true if the node group is autoprovisioned. An autoprovisioned group
    // was created by CA and can be deleted when scaled to 0.
    Autoprovisioned() bool
}

2.4 ScaleUP 源码解析


func ScaleUp(context *context.AutoscalingContext, processors *ca_processors.AutoscalingProcessors, clusterStateRegistry *clusterstate.ClusterStateRegistry, unschedulablePods []*apiv1.Pod, nodes []*apiv1.Node, daemonSets []*appsv1.DaemonSet, nodeInfos map[string]*schedulernodeinfo.NodeInfo, ignoredTaints taints.TaintKeySet) (*status.ScaleUpStatus, errors.AutoscalerError) {
    
    ......
    // 验证当前集群中所有 ready node 是否来自于 nodeGroups,取得所有非组内的 node
    nodesFromNotAutoscaledGroups, err := utils.FilterOutNodesFromNotAutoscaledGroups(nodes, context.CloudProvider)
    if err != nil {
        return &status.ScaleUpStatus{Result: status.ScaleUpError}, err.AddPrefix("failed to filter out nodes which are from not autoscaled groups: ")
    }

    nodeGroups := context.CloudProvider.NodeGroups()
    gpuLabel := context.CloudProvider.GPULabel()
    availableGPUTypes := context.CloudProvider.GetAvailableGPUTypes()

    // 资源限制对象,会在 build cloud provider 时传入
    // 如果有需要可在 CloudProvider 中自行更改,但不建议改动,会对用户造成迷惑
    resourceLimiter, errCP := context.CloudProvider.GetResourceLimiter()
    if errCP != nil {
        return &status.ScaleUpStatus{Result: status.ScaleUpError}, errors.ToAutoscalerError(
            errors.CloudProviderError,
            errCP)
    }

    // 计算资源限制
    // nodeInfos 是所有拥有节点组的节点与示例节点的映射
    // 示例节点会优先考虑真实节点的数据,如果 NodeGroup 中还没有真实节点的部署,则使用 Template 的节点数据
    scaleUpResourcesLeft, errLimits := computeScaleUpResourcesLeftLimits(context.CloudProvider, nodeGroups, nodeInfos, nodesFromNotAutoscaledGroups, resourceLimiter)
    if errLimits != nil {
        return &status.ScaleUpStatus{Result: status.ScaleUpError}, errLimits.AddPrefix("Could not compute total resources: ")
    }

    // 根据当前节点与 NodeGroups 中的节点来计算会有多少节点即将加入集群中
    // 由于云服务商的伸缩组 increase size 操作并不是同步加入 node,所以将其统计,以便于后面计算节点资源
    upcomingNodes := make([]*schedulernodeinfo.NodeInfo, 0)
    for nodeGroup, numberOfNodes := range clusterStateRegistry.GetUpcomingNodes() {
        ......
    }
    klog.V(4).Infof("Upcoming %d nodes", len(upcomingNodes))

    // 最终会进入选择的节点组
    expansionOptions := make(map[string]expander.Option, 0)
    ......
    // 出于某些限制或错误导致不能加入新节点的节点组,例如节点组已达到 MaxSize
    skippedNodeGroups := map[string]status.Reasons{}
    // 综合各种情况,筛选出节点组
    for _, nodeGroup := range nodeGroups {
    ......
    }
    if len(expansionOptions) == 0 {
        klog.V(1).Info("No expansion options")
        return &status.ScaleUpStatus{
            Result:                 status.ScaleUpNoOptionsAvailable,
            PodsRemainUnschedulable: getRemainingPods(podEquivalenceGroups, skippedNodeGroups),
            ConsideredNodeGroups:   nodeGroups,
        }, nil
    }

    ......
    // 选择一个最佳的节点组进行扩容,expander 用于选择一个合适的节点组进行扩容,默认为 RandomExpander,flag: expander
    // random 随机选一个,适合只有一个节点组
    // most-pods 选择能够调度最多 pod 的节点组,比如有 noSchedulerPods 是有 nodeSelector 的,它会优先选择此类节点组以满足大多数 pod 的需求
    // least-waste 优先选择能满足 pod 需求资源的最小资源类型的节点组
    // price 根据价格模型,选择最省钱的
    // priority 根据优先级选择
    bestOption := context.ExpanderStrategy.BestOption(options, nodeInfos)
    if bestOption != nil && bestOption.NodeCount > 0 {
    ......
        newNodes := bestOption.NodeCount

        // 考虑到 upcomingNodes, 重新计算本次新加入节点
        if context.MaxNodesTotal > 0 && len(nodes)+newNodes+len(upcomingNodes) > context.MaxNodesTotal {
            klog.V(1).Infof("Capping size to max cluster total size (%d)", context.MaxNodesTotal)
            newNodes = context.MaxNodesTotal - len(nodes) - len(upcomingNodes)
            if newNodes < 1 {
                return &status.ScaleUpStatus{Result: status.ScaleUpError}, errors.NewAutoscalerError(
                    errors.TransientError,
                    "max node total count already reached")
            }
        }

        createNodeGroupResults := make([]nodegroups.CreateNodeGroupResult, 0)
    
        // 如果节点组在云服务商端处不存在,会尝试创建根据现有信息重新创建一个云端节点组
        // 但是目前所有的 CloudProvider 实现都没有允许这种操作,这好像是个多余的方法
        // 云服务商不想,也不应该将云端节点组的创建权限交给 ClusterAutoscaler
        if !bestOption.NodeGroup.Exist() {
            oldId := bestOption.NodeGroup.Id()
            createNodeGroupResult, err := processors.NodeGroupManager.CreateNodeGroup(context, bestOption.NodeGroup)
        ......
        }

        // 得到最佳节点组的示例节点
        nodeInfo, found := nodeInfos[bestOption.NodeGroup.Id()]
        if !found {
            // This should never happen, as we already should have retrieved
            // nodeInfo for any considered nodegroup.
            klog.Errorf("No node info for: %s", bestOption.NodeGroup.Id())
            return &status.ScaleUpStatus{Result: status.ScaleUpError, CreateNodeGroupResults: createNodeGroupResults}, errors.NewAutoscalerError(
                errors.CloudProviderError,
                "No node info for best expansion option!")
        }

        // 根据 CPU、Memory及可能存在的 GPU 资源(hack: we assume anything which is not cpu/memory to be a gpu.),计算出需要多少个 Nodes
        newNodes, err = applyScaleUpResourcesLimits(context.CloudProvider, newNodes, scaleUpResourcesLeft, nodeInfo, bestOption.NodeGroup, resourceLimiter)
        if err != nil {
            return &status.ScaleUpStatus{Result: status.ScaleUpError, CreateNodeGroupResults: createNodeGroupResults}, err
        }

        // 需要平衡的节点组
        targetNodeGroups := []cloudprovider.NodeGroup{bestOption.NodeGroup}
        // 如果需要平衡节点组,根据 balance-similar-node-groups flag 设置。
        // 检测相似的节点组,并平衡它们之间的节点数量
        if context.BalanceSimilarNodeGroups {
        ......
        }
        // 具体平衡策略可以看 (b *BalancingNodeGroupSetProcessor) BalanceScaleUpBetweenGroups 方法
        scaleUpInfos, typedErr := processors.NodeGroupSetProcessor.BalanceScaleUpBetweenGroups(context, targetNodeGroups, newNodes)
        if typedErr != nil {
            return &status.ScaleUpStatus{Result: status.ScaleUpError, CreateNodeGroupResults: createNodeGroupResults}, typedErr
        }
        klog.V(1).Infof("Final scale-up plan: %v", scaleUpInfos)
        // 开始扩容,通过 IncreaseSize 扩容
        for _, info := range scaleUpInfos {
            typedErr := executeScaleUp(context, clusterStateRegistry, info, gpu.GetGpuTypeForMetrics(gpuLabel, availableGPUTypes, nodeInfo.Node(), nil), now)
            if typedErr != nil {
                return &status.ScaleUpStatus{Result: status.ScaleUpError, CreateNodeGroupResults: createNodeGroupResults}, typedErr
            }
        }
        ......
    }
    ......
}

2.5 使用场景

在以下情况下,集群自动扩容或者缩放:

  • 扩容:由于资源不足,某些Pod无法在任何当前节点上进行调度
  • 缩容: Node节点资源利用率较低时,且此node节点上存在的pod都能被重新调度到其他node节点上运行。

2.6 什么时候集群节点不会被 CA 删除?

  • 节点上有pod被 PodDisruptionBudget 控制器限制。
  • 节点上有命名空间是 kube-system 的pods。
  • 节点上的pod不是被控制器创建,例如不是被deployment, replica set, job, stateful set创建。
  • 节点上有pod使用了本地存储
  • 节点上pod驱逐后无处可去,即没有其他node能调度这个pod
  • 节点有注解:"cluster-autoscaler.kubernetes.io/scale-down-disabled": "true"(在CA 1.0.3或更高版本中受支持)

2.7 Horizontal Pod Autoscaler 如何与 Cluster Autoscaler 一起使用?

Horizontal Pod Autoscaler 会根据当前CPU负载更改部署或副本集的副本数。如果负载增加,则HPA将创建新的副本,集群中可能有足够的空间,也可能没有足够的空间。如果没有足够的资源,CA将尝试启动一些节点,以便HPA创建的Pod可以运行。如果负载减少,则HPA将停止某些副本。结果,某些节点可能变得利用率过低或完全为空,然后CA将终止这些不需要的节点。

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

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

相关文章

VsCode中常用的正则表达式操作

在vscode中可以使用正则表达式来进行搜索内容&#xff0c;极大的方便了我们对大量数据中需要查看的信息进行筛选&#xff0c;使用正则搜索时点击 .* 此文章会持续补充常用的正则操作 1.光标选中搜索到的内容 将搜索的内容进行全选&#xff0c;举例&#xff1a;在如下文件中我需…

基于Linux的HTTP代理服务器搭建与配置实战

在数字化世界中&#xff0c;HTTP代理服务器扮演着至关重要的角色&#xff0c;它们能够帮助我们管理网络请求、提高访问速度&#xff0c;甚至在某些情况下还能保护我们的隐私。而Linux系统&#xff0c;凭借其强大的功能和灵活性&#xff0c;成为了搭建HTTP代理服务器的理想选择。…

Android Camera2 API 后台服务

最近在搞CameraAPP需要将Camera2弄成一个后台服务&#xff0c;发现跟预览的Activity没多大变动只是加了Service&#xff0c;和一些简单的修改。之前的公司也用到Camera2&#xff0c;发现用到的时候还是蛮多的所以记录一下&#xff0c;代码在文章末尾 camera2的结构如下&#x…

常用的前端模块化标准总结

1、模块化标准出现以前使用的模块化方案&#xff1a; 1&#xff09;文件划分&#xff1a; 将不同的模块定义在不同的文件中&#xff0c;然后使用时通过script标签引入这些文件 缺点&#xff1a; 模块变量相当于是定义在全局的&#xff0c;容易造成变量名冲突&#xff08;即不…

代码随想录算法训练营第25天 | 216.组合总和III ,17.电话号码的字母组合

回溯章节理论基础&#xff1a; https://programmercarl.com/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 216.组合总和III 题目链接&#xff1a;https://leetcode.cn/problems/combination-sum-iii/ 思路: 本题就是在[1,2,3,4,5,6,7,…

Git简单了解

文章目录 1、Git概述2、Git下载与安装3、Git代码托管服务3.1、使用码云托管服务 1、Git概述 什么是Git Git是一个分布式版本控制工具&#xff0c;主要用于管理开发过程中的源代码文件&#xff08;Java类、xml文件、html页面等&#xff09;&#xff0c;在软件开发过程中被广泛使…

免费文字转语音工具,一款优秀且永久免费的文字转语音工具,同时拥有多种类型男声女声,支持多国语言转换,支持语速调节和下载!

一、软件简介 该工具只有一个功能&#xff0c;就是将输入框内的纯文本内容转换为指定语言的音频&#xff0c;并且可以自由调节语速及音色&#xff08;男声/女声&#xff09;&#xff0c;其内置了多种语音包&#xff0c;包含男声、女声、普通话、粤语以及方言&#xff0c;并且支…

Ubuntu安装SVN服务并结合内网穿透实现公网访问本地存储文件

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&…

Visual Studio 2022中创建的C++项目无法使用万能头<bits/stdc++.h>解决方案

目录 发现问题 解决办法 第一步 第二步 第三步 第四步 最后一步 问题解决 发现问题 如果大家也遇到下面这种问题&#xff0c;可能是没有include文件夹中没有bits/stdc.h 解决办法 第一步 打开一个C项目&#xff0c;鼠标移动至头文件上右击&#xff0c;选择转到文档或…

c#: 表达式树的简化

环境&#xff1a; .net 6 一、问题&#xff1f; 有下面的表达式&#xff1a; var nums new List<int> { 1, 2, 3 }; Expression<Func<int, bool>> exp i > i > nums.Max();我们知道&#xff0c;它其实就是&#xff1a;exp i > i > 3; 那么…

06-OpenFeign-使用HtppClient连接池

默认下OpenFeign使用URLConnection 请求连接&#xff0c;每次都需要创建、销毁连接 1、添加ApacheHttpClient依赖 <!-- 使用Apache HttpClient替换Feign原生httpclient--><dependency><groupId>org.apache.httpcomponents</groupId><artifact…

编曲学习:旋律创作基础概念 和弦进行作曲 和弦外音使用 作曲技巧

旋律创作基础概念 和弦进行作曲 和弦外音使用 作曲技巧https://app8epdhy0u9502.pc.xiaoe-tech.com/live_pc/l_65be1ba7e4b064a83b92a3d7?course_id=course_2XLKtQnQx9GrQHac7OPmHD9tqbv文档https://app8epdhy0u9502.pc.xiaoe-tech.com/p/t_pc/course_pc_detail/camp_pro/cour…

【GAMES101】Lecture 19 相机

目录 相机 视场 Field of View (FOV) 曝光&#xff08;Exposure&#xff09; 感光度&#xff08;ISO&#xff09; 光圈 快门 相机 成像可以通过我们之前学过的光栅化成像和光线追踪成像来渲染合成&#xff0c;也可以用相机拍摄成像 今天就来学习一下相机是如何成像的…

Golang数据库编程详解 | 深入浅出Go语言原生数据库编程

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 Golang学习专栏&#xff1a;https://blog.csdn.net/qq_35716689/category_12575301.html 前言 对数据库…

【Java八股面试系列】JVM-常见参数设置

目录 堆内存相关 显式指定堆内存–Xms和-Xmx 显式新生代内存(Young Generation) 显式指定永久代/元空间的大小 垃圾收集相关 垃圾回收器 GC 日志记录 处理 OOM JDK监控和故障处理工具总结 堆内存相关 Java 虚拟机所管理的内存中最大的一块&#xff0c;Java 堆是所有线…

【JavaScript + CSS】随机生成十六进制颜色

效果图 实现 <template><div class"year_area"><div class"year_list"><el-row :span"24"><div :class"showAll"><el-col :span"5" v-for"(item, index) in defaulList" :key&…

代码随想录算法训练营DAY16 | 二叉树 (3)

一、LeetCode 104 二叉树的最大深度 题目链接&#xff1a;104.二叉树的最大深度https://leetcode.cn/problems/maximum-depth-of-binary-tree/ 思路&#xff1a;采用后序遍历递归求解。 class Solution {int ans 0;public int maxDepth(TreeNode root) {if(root null){retur…

【Linux】Linux开发工具(yum、gdb、git)详解

一、软件包管理器 yum 1、什么是软件包 在 Linux 下安装软件&#xff0c;通常的办法是下载到程序的源代码&#xff0c;并进行编译&#xff0c;得到可执行程序。但这样太麻烦了&#xff0c;于是有些人把一些常用的软件提前编译好&#xff0c;做成软件包&#xff08;可以理解成…

mac电脑flutter环境配置,解决疑难问题

准备工作 首先搭建flutter的环境需要使用到flutter的sdk&#xff0c;可以直接跳去官网下载&#xff1a;Choose your first type of app - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter&#xff0c;下载时要注意你电脑所使用的芯片是Intel的还是苹果的芯片。 下载好的…

邮箱营销软件推荐?企业邮箱群发会限制吗?

邮箱营销平台怎么选择&#xff1f;哪种邮箱适合做外贸邮件群发&#xff1f; 邮箱营销凭借其精准触达、低成本和高回报的特点&#xff0c;依然是许多企业不可或缺的营销手段。该如何选择一款适合自己的工具呢&#xff1f;蜂邮EDM将为您推荐几款优秀的邮箱营销软件&#xff0c;并…