K8s 集群巡检

news2024/9/23 11:13:15

K8s 集群巡检

上次发文 K8s 无备份,不运维,文章开篇,插入了一张 K8s 集群巡检的图片,好多小伙伴私信留言,问我要开源地址。由于其通用性不高,大多数公司需要结合自身的架构情况进行不同的巡检,所以我没有开源。

今天发现有小伙伴还在群里讨论,有没有类似的工具/平台,虽然没有开源,我把其关键的 巡检指标后端核心伪代码 分享出来,供各位同行参考。

什么是平台巡检

平台巡检是一种监测和评估底层系统运行状况的工具,可帮助您快速发现系统中存在的潜在风险并给出相应修复建议。

该工具可用于扫描集群中的各个方面,包括系统性能瓶颈、业务组件运行状态、资源使用情况和配置问题等,以提高系统的性能、稳定性和可用性。

巡检的意义

我反复思考,有了 metrics/logs/traces + grafana + alert ,还需要巡检做什么?以下是我总结巡检的意义:

  • 是监控的补充,比如证书过期、Pod CIDR 使用情况、Etcd 备份情况、Velero 备份情况,通过脚本更方便查看状态,编写 exporter 周期较长
  • 可以监控 Prometheus、VictorMetric 等组件的状态,拉取最新数据情况,监控是否收集了各个组件的 metrics
  • 是主动式的发现问题,能迅速了解整个集群的核心指标的状态,集中式检查,不用一个个 Grafana 图标检查

K8s 巡检关键指标

分三类

  • 集群总览
  • 核心组件状态
  • 节点状态

里面的 Promql 和 Bash 脚本内容,需要根据实际情况进行配置!

集群总览

巡检项名称:Node 使用情况

描述:旨在查看集群 是否有备用资源

动作来源:bash

具体动作:

#!/bin/bash
#
# Node 数量巡检脚本

set -o errexit
set -o nounset

node_sum=$(kubectl get nodes | awk 'NR>1' | grep -v master -c)
node_ready=$(kubectl get nodes | awk 'NR>1' | grep -v master | grep -v SchedulingDisabled -c)
echo "| " "${node_ready}/${node_sum}"

if [[ $node_sum -gt $node_ready ]]; then
  echo "success"
else
  echo "warning"
fi

巡检项名称:Pod 剩余情况

描述:旨在查看 有无 Pod 资源可供分配

动作来源:prometheus

具体动作:

sum(kube_node_status_capacity{resource='pods'} * on(node) group_left(label_env) kube_node_labels{label_env=~"prod",cluster="core",zone=~"shanghai"} unless on(node) kube_node_role) -
sum(kube_pod_info *on (node) group_left(label_env) kube_node_labels{label_env=~"prod",cluster="core",zone=~"shanghai"} unless on(node) kube_node_role)

阈值:["<", 90]

巡检项名称:Pod CIDR 使用情况

描述:Pod 剩余可分配 IP 数量

动作来源:bash

具体动作:

#!/bin/bash
#
# Pod IP 剩余数量 巡检脚本

set -o errexit
set -o nounset

pod_ip_free=$(calicoctl ipam show | grep '%' | awk '{print $12}')
echo '| IP 剩余数量:' ${pod_ip_free}

if [[ $pod_ip_free -gt 500 ]]; then
  echo "success"
elif [[ $pod_ip_free -gt 100 ]]; then
  echo "warning"
else
  echo "error"
fi

巡检项名称:集群 CPU 使用率

动作来源:prometheus

具体动作:

(1 - avg(label_replace(rate(node_cpu_seconds_total{mode="idle", cluster="core",zone=~"shanghai"}[60s]), "internal_ip", "$1", "instance", "(.+):(\\d+)") and on(internal_ip) kube_node_labels{cluster="core",zone=~"shanghai"} * on(node) group_left(internal_ip) kube_node_info)) * 100

阈值:[">", 50]

巡检项名称:集群 MEM 使用率

动作来源:prometheus

具体动作:

(1 - sum(label_replace(node_memory_MemAvailable_bytes{cluster="core",zone=~"shanghai"}, "internal_ip", "$1", "instance", "(.+):(\\d+)") and on(internal_ip) kube_node_labels{cluster="core",zone=~"shanghai"} * on(node) group_left(internal_ip) kube_node_info) / sum(label_replace(node_memory_MemTotal_bytes{cluster="core",zone=~"shanghai"}, "internal_ip", "$1", "instance", "(.+):(\\d+)") and on(internal_ip) kube_node_labels{cluster="core",zone=~"shanghai"} * on(node) group_left(internal_ip) kube_node_info)) * 100

阈值:[">", 85]

巡检项名称:证书过期时间

动作来源:bash

具体动作:

#!/bin/bash
#
# 证书过期时间 巡检脚本

set -o errexit
set -o nounset

ct=$(date -d "$(openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates |awk -F '=' '/notAfter/{print $2}'|awk '{print $1,$2,$3,$4}')" +%s)
dt=$(date +%s)
expired=$(($(($ct-$dt))/(60*60*24)))
echo "| " $expired "天后过期"

if [[ $expired -gt 60 ]]; then
  echo "success"
elif [[ $expired -gt 15 ]]; then
  echo "warning"
else
  echo "error"
fi

巡检项名称:ectd 备份情况

描述:是否有最新备份

动作来源:bash

具体动作:

#!/bin/bash
#
# Etcd 备件检查脚本

set -o nounset

result=$(find /var/lib/docker/etcd_backup/ -mmin -120)
if [[ -n ${result} ]]; then
  echo "正常"
  echo "success"
else
  echo "异常"
  echo "error"
fi

巡检项名称:velero备份情况

描述:是否有最新备份

动作来源:bash

具体动作:

#!/bin/bash
#
# Velero 备件检查脚本

set -o nounset
current_date=$(date +%F)
backup_date=$(velero backup get | grep core-shanghai | awk '{print $5}' | sort -nr | head -1)
backup_date_2d=$(date -d "$backup_date +2 days" +%F)
if [[ $backup_date_2d > $current_date && $backup_date != "" ]]; then
  echo "正常"
  echo "success"
else
  echo "异常"
  echo "error"
fi

核心组件状态

etcd

巡检项名称:etcd 集群节点是否不足

动作来源:prometheusOr

具体动作:

sum by(job) (up{job=~".*etcd.*",cluster="core",zone="shanghai"} == bool 1) < ((count by(job) (up{job=~".*etcd.*",cluster="core",zone="shanghai"}) + 1) / 2)

阈值:是

巡检项名称:etcd 集群是否有主节点

动作来源:prometheusOr

具体动作:

etcd_server_has_leader{job=~".*etcd.*",cluster="core",zone="shanghai"} == 1

阈值:否

巡检项名称:etcd 主从是否切换频繁

动作来源:prometheusOr

具体动作:

rate(etcd_server_leader_changes_seen_total{job=~".*etcd.*",cluster="core",zone="shanghai"}[15m])  > 3

阈值:是

巡检项名称:etcd 请求成功率

动作来源:prometheus

具体动作:

100 - max(sum(rate(grpc_server_handled_total{grpc_type="unary",grpc_code!="OK",cluster="core",zone="shanghai"}[1m])) by (grpc_service) / sum(rate(grpc_server_started_total{grpc_type="unary",cluster="core",zone="shanghai"}[1m])) by (grpc_service) * 100.0)

阈值:["<", 99]

巡检项名称:etcd 磁盘操作延迟情况

动作来源:prometheus

具体动作:

max(histogram_quantile(0.99, sum(rate(etcd_disk_wal_fsync_duration_seconds_bucket{cluster="core",zone="shanghai"}[1m])) by (instance,le))) * 1000

阈值:[">", 10]

kube-apiserver

巡检项名称:apiserver 健康状态

动作来源:prometheus

具体动作:

sum(up{job="apiserver",cluster="core",zone="shanghai"}) / count(up{job="apiserver",cluster="core",zone="shanghai"})  *100

阈值:["<", 90]

巡检项名称:apiserver QPS

动作来源:prometheus

具体动作:

sum(rate(apiserver_request_total{cluster="core",zone="shanghai"}[1m]))

阈值:[">", 3000]

巡检项名称:apiserver 请求成功率

动作来源:prometheus

具体动作:

apiserver_request:availability30d{verb="all",cluster="core",zone="shanghai"} * 100

阈值:["<", 99]

巡检项名称:apiserver 请求延迟

动作来源:prometheus

具体动作:

max(cluster_quantile:apiserver_request_duration_seconds:histogram_quantile{cluster="core",zone="shanghai"})

阈值:[">", 1]

kube-controller-manager/kube-scheduler 巡检项同 apiserver

coredns/ingress 只巡检了健康状态

节点状态

kubelet

巡检项名称:kubelet 节点不可用列表

动作来源:prometheusList

具体动作:

sum by(node) (kube_node_status_condition{condition="Ready",job="kube-state-metrics",status="true",cluster="core",zone="shanghai"})  ==  0

巡检项名称:PLEG relist 耗时过高列表

动作来源:prometheusList

具体动作:

histogram_quantile(0.99, sum(rate(kubelet_pleg_relist_duration_seconds_bucket{job="kubelet", metrics_path="/metrics",cluster="core",zone="shanghai"}[1m])) by (node,le)) * 1000 > 1000
资源使用情况

**巡检项名称:CPU 使用率大于50%的列表 **

动作来源:prometheusList

具体动作:

(1 - avg by(internal_ip) (label_replace(rate(node_cpu_seconds_total{mode="idle", cluster="core",zone=~"shanghai"}[60s]), "internal_ip", "$1", "instance", "(.+):(\\d+)")) and on(internal_ip) kube_node_labels{cluster="core",zone=~"shanghai",label_env=~"prod"} * on(node) group_left(internal_ip) kube_node_info) * 100 > 50

巡检项名称:MEM 使用率大于80%的列表

动作来源:prometheusList

具体动作:

sum by (internal_ip) (label_replace(1 - (node_memory_MemAvailable_bytes{cluster="core",zone=~"shanghai"} / node_memory_MemTotal_bytes{cluster="core",zone=~"shanghai"}), "internal_ip", "$1", "instance", "(.+):(\\d+)") and on(internal_ip) kube_node_labels{cluster="core",zone=~"shanghai",label_env=~"prod"} * on(node) group_left(internal_ip) kube_node_info) * 100 > 80

巡检项名称:磁盘 / 使用率大于80%的列表

动作来源:prometheusList

具体动作:

sum by (internal_ip) (label_replace(100 - ((node_filesystem_avail_bytes{job="node-exporter",mountpoint="/",fstype!="rootfs",cluster="core",zone="shanghai"} * 100) / node_filesystem_size_bytes{job="node-exporter",mountpoint="/",fstype!="rootfs",cluster="core",zone="shanghai"}), "internal_ip", "$1", "instance", "(.+):(\\d+)") and on(internal_ip) kube_node_labels{cluster="core",zone=~"shanghai",label_env=~"prod"} * on(node) group_left(internal_ip) kube_node_info) > 80

巡检项名称:PID 使用率大于80%的列表

动作来源:prometheusList

具体动作:

label_replace(node_processes_threads{cluster="core",zone="shanghai"} / on(instance) min by(instance) (node_processes_max_processes or node_processes_max_threads{cluster="core",zone="shanghai"}),"internal_ip", "$1", "instance", "(.+):(\\d+)") * 100 > 80

巡检项名称:FD 使用率大于70%的列表

动作来源:prometheusList

具体动作:

sum by(internal_ip) (label_replace(node_filefd_allocated{job="node-exporter",cluster="core",zone="shanghai"} * 100 / node_filefd_maximum{job="node-exporter",cluster="core",zone="shanghai"}, "internal_ip", "$1", "instance", "(.+):(\\d+)")) > 70

巡检项名称:时间不同步列表

动作来源:prometheusList

具体动作:

min_over_time(node_timex_sync_status{cluster="core",zone="shanghai"}[5m]) == 0 and node_timex_maxerror_seconds{cluster="core",zone="shanghai"} >= 16

巡检项名称:dockerHung 列表

动作来源:prometheusList

具体动作:

sum by(node) (rate(problem_counter{reason="DockerHung",cluster="core",zone="shanghai"}[1m])) > 0

kube-proxy/calico只巡检了健康状态

内核发生错误列表 同 dockerHung 列表 通过 NPD 收集的指标进行暴露判断

巡检平台(自动化)

细心的小伙伴可能已经发现,上文巡检项中的 “动作来源” 分为 bash、prometheus、prometheusOr、prometheusList 四种

  • bash 对应放置在 K8s Master 节点上指定目录下的 bash 脚本,脚本中有两行返回值,一行是具体结果,一行是正常 Or 异常

  • prometheus 对应通过 Promql 查询出来的结果再与 具体的阈值 做比较判断,最后得出是否正常

  • prometheusOr 逻辑类似,只不过阈值是 是 或 否

  • prometheusList 一样,只不过列表不为空就代表有异常

所有的执行语句、执行脚本名称,都放到了 mysql 表里进行了固定,想要新增 巡检项,只需在 mysql 表中插入一条规则即可

注意: 要将 promql 进行 URL 编码

核心伪代码如下

var mu sync.Mutex

type ScannerRequest struct {
	CheckKeys       []int `json:"check_keys"`
	SelectedCluster int   `json:"selected_cluster"`
}

// ScannerStart 执行巡检
func (s *ScannerController) ScannerStart(g *gin.Context) {
	mu.Lock()
	defer mu.Unlock()

	// 先更新数据库状态 status: running 巡检中 |  finish 巡检完成
	s.store.UpdateAllStatus()

	// 解析数据
	var r ScannerRequest
	if err := g.ShouldBindJSON(&r); err != nil {
		v2api.AbnormalJsonResponse(g, "", "body parse error: "+err.Error())
		return
	}

	var scannerItems []int

	// 定义 Cluster 结构体
	type Cluster struct {
		ID          int    `json:"id"`
		ClusterName string `json:"cluster_name"`
		Zone        string `json:"zone"`
		Hosts       string `json:"hosts"`
	}
	// 初始化数据
	clusterData := []string{
		`{"id": 11, "cluster_name": "core", "zone": "shanghai", "hosts": "10.10.10.10"}`,
		`{"id": 12, "cluster_name": "other", "zone": "beijing", "hosts": "10.10.10.20"}`,
	}

	// 创建映射
	clusterMap := make(map[int]Cluster)

	// 解析JSON数据并填充映射
	for _, d := range clusterData {
		var cluster Cluster
		err := json.Unmarshal([]byte(d), &cluster)
		if err != nil {
			fmt.Println("Error decoding JSON:", err)
			return
		}
		clusterMap[cluster.ID] = cluster
	}
	for _, item := range r.CheckKeys {
		if item > 300 {
			scannerItems = append(scannerItems, item/100)
		}
	}

	var wg sync.WaitGroup
	wg.Add(len(scannerItems))
	for _, scannerItem := range scannerItems {
		go func(scannerItem int) {
			defer wg.Done() // 在 Goroutine 完成时减少 WaitGroup 的计数
			query, err := s.store.GetItem(scannerItem, "action_type", "action_detail", "threshold")
			if err != nil {
				fmt.Println("查询数据失败" + err.Error())
			}
			promql := strings.ReplaceAll(query.ActionDetail, "%22core%22", "%22"+clusterMap[r.SelectedCluster].ClusterName+"%22")
			promql = strings.ReplaceAll(promql, "%22shanghai%22", "%22"+clusterMap[r.SelectedCluster].Zone+"%22")
			switch query.ActionType {
			case "prometheus":
				value, err := prometheusQuery(promql)
				if err != nil {
					fmt.Println("查询 Prometheus 失败" + err.Error())
				}

				var thresholdArr []interface{}
				err = json.Unmarshal([]byte(query.Threshold), &thresholdArr)
				if err != nil {
					fmt.Println("Error:", err)
				}

				var status string
				intValue, err := strconv.ParseFloat(value, 64)
				if err != nil {
					// 处理转换错误
					fmt.Println("Error converting string to int:", err)
				}

				switch v := thresholdArr[1].(type) {
				case float64:
					if thresholdArr[0] == "<" {
						if intValue > v {
							status = "success"
						} else {
							status = "warning"
						}
					}
					if thresholdArr[0] == ">" {
						if intValue < v {
							status = "success"
						} else {
							status = "warning"
						}
					}
				default:
					fmt.Println("Type of thresholad", reflect.TypeOf(thresholdArr[1]))
				}

				err = s.store.UpdateItem(scannerItem, value, status)
				if err != nil {
					fmt.Println("更新数据失败" + err.Error())
				}
			case "prometheusOr":
				value, err := prometheusQueryOr(promql)
				if err != nil {
					fmt.Println("查询 Prometheus 失败" + err.Error())
				}
				if value == query.Threshold {
					err = s.store.UpdateItem(scannerItem, value, "error")
					if err != nil {
						fmt.Println("更新数据失败" + err.Error())
					}
				} else {
					err = s.store.UpdateItem(scannerItem, value, "success")
					if err != nil {
						fmt.Println("更新数据失败" + err.Error())
					}
				}
			case "prometheusList":
				value, err := prometheusQueryList(promql)
				if err != nil {
					fmt.Println("查询 Prometheus 失败" + err.Error())
				}
				if value == "空" {
					err = s.store.UpdateItem(scannerItem, value, "success")
					if err != nil {
						fmt.Println("更新数据失败" + err.Error())
					}
				} else {
					err = s.store.UpdateItem(scannerItem, value, "warning")
					if err != nil {
						fmt.Println("更新数据失败" + err.Error())
					}
				}

			case "bash":
				res, err := executeSSHCommand(clusterMap[r.SelectedCluster].Hosts+":22", "root", "/root/.ssh/id_rsa", query.ActionDetail)
				if err != nil {
					fmt.Println(err)
				}
				resArray := strings.Split(res, "\n")
				value := resArray[0]
				status := resArray[1]
				err = s.store.UpdateItem(scannerItem, value, status)
				if err != nil {
					fmt.Println("更新数据失败" + err.Error())
				}
			}

		}(scannerItem)
	}

	v2api.NormalJsonResponse(g, "开始巡检", "")
}


type Result struct {
	ResultType string `json:"resultType"`
	ResultData []struct {
		Metric map[string]interface{} `json:"metric"`
		Value  []interface{}          `json:"value"`
	} `json:"result"`
}

type Response struct {
	Status    string `json:"status"`
	IsPartial bool   `json:"isPartial"`
	Data      Result `json:"data"`
}

var promURL = "http://victoria-select.xxx.xxx/select/1/prometheus/api/v1/query?query="

func prometheusQuery(promql string) (string, error) {

	var res Response
	currentTimestamp := time.Now().Unix()
	reqURL := promURL + promql + "&start=" + strconv.Itoa(int(currentTimestamp))

	// 发起 GET 请求
	response, err := http.Get(reqURL)
	if err != nil {
		return "", err
	}
	defer response.Body.Close()
	// 读取响应内容解析
	body, err := io.ReadAll(response.Body)
	if err != nil {
		return "", err
	}
	if err := json.Unmarshal(body, &res); err != nil {
		return "", err
	}
	if len(res.Data.ResultData) == 0 {
		fmt.Println(reqURL)
		fmt.Println(res.Data.ResultData)
		return "", err
	}
	value, ok := res.Data.ResultData[0].Value[1].(string)
	if !ok {
		return "", errors.New("conversion failed")
	}
	return value, nil
}

func prometheusQueryOr(promql string) (string, error) {
	var res Response
	currentTimestamp := time.Now().Unix()
	reqURL := promURL + promql + "&start=" + strconv.Itoa(int(currentTimestamp))

	// 发起 GET 请求
	response, err := http.Get(reqURL)
	if err != nil {
		return "", err
	}
	defer response.Body.Close()
	// 读取响应内容解析
	body, err := io.ReadAll(response.Body)
	if err != nil {
		return "", err
	}
	if err := json.Unmarshal(body, &res); err != nil {
		return "", err
	}
	if len(res.Data.ResultData) == 0 {
		return "否", nil
	}
	return "是", nil
}

func prometheusQueryList(promql string) (string, error) {
	var res Response
	currentTimestamp := time.Now().Unix()
	reqURL := promURL + promql + "&start=" + strconv.Itoa(int(currentTimestamp))

	// 发起 GET 请求
	response, err := http.Get(reqURL)
	if err != nil {
		return "", err
	}
	defer response.Body.Close()
	// 读取响应内容解析
	body, err := io.ReadAll(response.Body)
	if err != nil {
		return "", err
	}
	if err := json.Unmarshal(body, &res); err != nil {
		return "", err
	}
	if len(res.Data.ResultData) == 0 {
		return "空", nil
	}
	var value string
	for _, v := range res.Data.ResultData {
		switch s := v.Metric["node"].(type) {
		case string:
			value = value + " " + s
		}
	}
	if len(value) == 0 {
		for _, v := range res.Data.ResultData {
			switch s := v.Metric["internal_ip"].(type) {
			case string:
				value = value + " " + s
			}
		}
	}
	return value, nil
}

func executeSSHCommand(serverAddr, username, privateKeyPath, command string) (string, error) {
	key, err := ioutil.ReadFile(privateKeyPath)
	if err != nil {
		return "", err
	}

	signer, err := ssh.ParsePrivateKey(key)
	if err != nil {
		return "", err
	}

	config := &ssh.ClientConfig{
		User: username,
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(signer),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}

	client, err := ssh.Dial("tcp", serverAddr, config)
	if err != nil {
		return "", err
	}
	defer client.Close()

	session, err := client.NewSession()
	if err != nil {
		return "", err
	}
	defer session.Close()

	output, err := session.CombinedOutput(command)
	if err != nil {
		return "", err
	}

	return string(output), nil
}

页面展示

巡检有没有必要?大家关心哪些巡检指标,欢迎在留言区 讨论

我是 Clay,下期见 👋


  • 欢迎订阅我的公众号「SRE运维进阶之路」或关注我的 Github https://github.com/clay-wangzhi/SreGuide 查看最新文章

  • 欢迎加我微信sre-k8s-ai,与我讨论云原生、稳定性相关内容

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

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

相关文章

互联网应用主流框架整合之Redis配置

在实际的商用系统中&#xff0c;Redis常用的机制包括备份、回收策略、主从复制、哨兵模式、集群模式等&#xff0c;在保证性能的同时还要保证其高可用 首先要熟悉一下Redis的配置文件&#xff0c;如果实在linux系统中&#xff0c;配置文件是redis.conf&#xff0c;而在windows…

linux系统编程:网络通信

1.网络 1.粘包 tcp特点 面向连接 字节流&#xff08;TCP 将数据视为连续的字节流&#xff0c;没有明确的消息边界。会发生粘包问题。 避免粘包 特殊分隔符&#xff1a;在消息间加入特殊的分隔符&#xff08;如换行符或其他特殊字符&#xff09;&#xff0c;接收方根据分…

404炫酷单页面html5源码

源码介绍 404炫酷单页面html5源码&#xff0c;感觉应该符合一些人的感觉&#xff01;可以用来做404页面。记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 源码下载 分…

Java基础再学习

作为一名实习生&#xff0c;为了夯实基础、查缺补漏&#xff0c;我跟随 B 站学习了以下知识&#xff0c;欲将其留存纪念&#xff0c;故对其中内容进行优化&#xff0c;以使其更为优雅。 面向对象 面向对象编程的定义&#xff1a;借助合适的对象来解决问题&#xff08;如运用 …

可视化表单设计生成器可以实现流程化办公吗?

实现流程化办公一直都是很多客户朋友追求的目标。那么&#xff0c;如何才能实现这一目标&#xff1f;如何为客户提供更优良的产品助力提质、降本、增效&#xff1f;低代码技术平台拥有可视化操作界面、更灵活、更高效、更可靠&#xff0c;是专注于职场办公&#xff0c;助力流程…

牛客笔试小题

目录 牛客.小红取数 牛客.哈夫曼编码​编辑 牛客.字符编码(上一道题的资料) 牛客.最小的完全平方数 牛客.小红取数 01背包问题:在满足总和必须为k的倍数的情况下&#xff0c;选择最大的总和 1.状态表示: dp[i][j]:表示从前面i个数字中挑选&#xff0c;总和%k等于j时候,最大的…

Java SpringBoot+Vue实战教程:如何一步步实现Cosplay论坛设计与实现

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

JWT-JSON Web Token

JSON Web Token&#xff08;缩写 JWT&#xff09;是目前最流行的跨域认证解决方案。 1 跨域认证的问题 互联网服务离不开用户认证。一般流程是下面这样。 用户向服务器发送用户名和密码。服务器验证通过后&#xff0c;在当前对话(session)里面保存相关数据&#xff0c;比如用…

8.23题目:矩阵数字查找

一些闲话&#xff1a; 小编打算给这个刷题系列改个名&#xff0c;以后就用日期做标题吧&#xff0c;哪一天写了就写哪一天的日期&#xff0c; 不然就跑题了&#xff0c;你说是吧&#xff01;啊啊啊&#xff0c;根本做不到每日一题&#xff01;&#xff01;~~>_<~~ 一、…

Mix|使用VS2017CMake构建Qt工程 仿照MVS(仅用于学习)

MVS下载链接&#xff1a;https://www.hikrobotics.com/cn/machinevision/service/download/?module0 CMake工程构建参考&#xff1a;CMake|VS2017CMake3.8搭建Qt项目 文章目录 效果图整体结构实现代码最外层CMakeLists.txt代码实现及CMakeLists.txt搭建CMakeLists.txt搭建主函…

适应CLIP作为图像去雾的聚合指导

Adapt CLIP as Aggregation Instructor for Image Dehazing 2408.12317 (arxiv.org) 大多数去雾方法都存在感受野有限的问题&#xff0c;并且没有探索视觉-语言模型中蕴含的丰富语义先验&#xff0c;这些模型已在下游任务中被证明是有效的。 本文介绍了CLIPHaze&#xff0c;这…

如何使用ssm实现汽车养护管理系统

TOC ssm038汽车养护管理系统jsp 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;规范化管理。…

NAND闪存制造商Kioxia申请IPO,预计市值达103亿美元

据《日本经济新闻》报道&#xff0c;全球第三大NAND闪存制造商Kioxia已向东京证券交易所提交了首次公开募股&#xff08;IPO&#xff09;申请&#xff0c;计划于10月上市。根据《金融时报》的消息&#xff0c;Kioxia希望通过此次IPO筹集至少5亿美元的资金&#xff0c;并可能获得…

Circuitjs web 在线电路模拟器应用简介

Circuitjs 是一款 web 在线电路模拟器, 可以在浏览器上方便地模拟各种模拟或数字的电路, 用户无需安装各种软件, 生成的电路也支持在线分享给其它用户. 访问地址 下述地址我部署的一个版本: https://cc.xiaogd.net/ 此版本的特色是界面翻译成中文时尽量保留了英文, 因此在某…

VBA语言専攻T3学员领取资料通知0823

T3学员领取资料通知0823 各位学员∶本周MF系列VBA技术资料增加706-725讲&#xff0c;T3学员看到通知后请免费领取,领取时间8月23日晚上19:00-8月24日上午11:00。本次增加内容&#xff1a; MF706:在同一页面上打印多个范围 MF707:通过合并行打印多个范围 MF708:将多表中的多…

mathtype7永久激活码密钥及2024最新序列号附安装教程

MathType7是一款强大的数学公式编辑器&#xff0c;它可以在各种文档中插入复杂的数学公式和符号。它的特点包括&#xff1a; MathType最新mac官方版本下载如下: https://wm.makeding.com/iclk/?zoneid61764 MathType最新win官方版本下载如下: https://wm.makeding.com/icl…

代谢组数据分析(十八):随机森林构建代谢组诊断模型

本文参考Metabolomic machine learning predictor for diagnosis and prognosis of gastric cancer撰写。 使用随机森林算法和LASSO特征选择构建了一种胃癌(GC)诊断预测模型。参与者(队列1,n=426)通过随机分层抽样分为发现数据集(n=284)和测试集(n=142)。接下来,在发…

社区流浪动物救助系统-计算机毕设Java|springboot实战项目

&#x1f393; 作者&#xff1a;计算机毕设小月哥 | 软件开发专家 &#x1f5a5;️ 简介&#xff1a;8年计算机软件程序开发经验。精通Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等技术栈。 &#x1f6e0;️ 专业服务 &#x1f6e0;️ 需求定制化开发源码提…

Linux下进程间的通信--信号

信号的概念&#xff1a; 在Linux操作系统中&#xff0c;信号是一种软件中断机制&#xff0c;用于通知进程某个事件已经发生。信号是Linux进程间通信&#xff08;IPC&#xff09;的一种简单且快速的方式&#xff0c;它可以用来处理各种异步事件&#xff0c;如用户输入、硬件事件…

DAG计算框架:实现业务编排

文章目录 DAG如何实现DAG计算框架Node的实现Engine的实现Graph的实现具体某个节点如何使用 在工作几年之后&#xff0c;大部分人如果还在继续做着 CRUD的简单重复工作&#xff0c;即使领导不提出对你更高的期望&#xff0c;自身也会感到焦虑吧。学如逆水行舟不进则退&#xff…