client-go初级篇,从操作kubernetes到编写单元测试

news2024/12/23 13:07:22

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 尽管长篇系列《client-go实战》的内容足够丰富,然而内容太多每个知识点也有一定深度,对于打算快速学习并开始kubernetes开发的新手并不友好,因此本篇的目标读者就是client-go初学者,重点解决两个基础问题:
  1. 如何编码操作kubernetes?
  2. 对应的单元测试代码怎么写,运行单元测试时可是没有kubernetes环境的,这时咱们写的那些操作kubernetes的代码能运行吗?
  • 注意一:本篇写的代码是Go语言
  • 注意二:文末有源码下载地址,对应本篇的完整工程源码

环境信息

  • 以下是本篇实战涉及的软件的版本信息,作为您的参考
  1. go:1.19.3
  2. kubernetes:1.22.8
  3. client-go:v0.22.8
  4. 开发机:Ubuntu 20.04.4 LTS
  5. 编码环境:Windows 11 家庭中文版 + vs code 1.79.2
  • 这里顺便提一下,编码环境不重要,我这里使用vs code的Remote Explorer插件远程连接到开发机上进行操作,也就是说写代码用的是windows编码环境,实际编译和运行都在开发机Ubuntu上面,如下图
    在这里插入图片描述
  • 本篇的主题是编码操作kubernetes,因此请确保kubernetes环境已经就绪

如何编码操作kubernetes?

  • 想要编码操作kubernetes,需要使用client-go库,因此本篇主要演示的就是如何使用该库
  • 首先要确定client-go的版本,这和您自己的kubernetes环境有关,在确定了kubernetes版本后如何确定client-go的版本呢?来看client-go官方说明,如下图
    在这里插入图片描述
  • 简单解释一下如何确定版本
  1. client-go的版本一共有两类:旧版的kubernetes-1.x.y和新版v0.x.y
  2. 如果kubernetes版本大于或等于1.17.0,client-go版本请选择新版,举例:如果kubernetes版本是1.20.4,client-go版本就是v0.20.4
  3. 如果kubernetes版本小于1.17.0,client-go版本请选择旧版,举例:如果kubernetes版本是1.20.4,client-go版本就是kubernetes-1.16.3
  4. 综上所述,本文使用:kubernetes:1.22.8和client-go:v0.22.8的组合

方案设计

  • 正式编码前先说清楚要开发的内容,整体架构如下:
    在这里插入图片描述

  • 下面的具体的步骤:

  1. 开发一个web服务,名为client-go-unit-tutorials,基于gin框架
  2. 提供一个接口query_pods_by_label_app,作用是根据namespace和label的值,查询出符合条件的所有pod的名称
  3. 上述接口的具体实现用到了client-go库,使用库中的api去kubernetes的api-server查找pod,将结果的name作为接口的返回值,返回给请求方
  4. client-go库要想成功访问kubernetes,必须要有kubernetes环境的.kube/config文件,这里为了省事儿,直接将web服务部署到kubernetes环境的机器上,这样就能直接访问.kube/config文件了
  5. 编写单元测试代码,在没有kubernetes环境的情况下,也能成功执行那段操作kubernetes的代码
  • 再次提醒:client-go-unit-tutorials可以在一个独立的机器上运行,也能直接运行在kubernetes机器上,还能做成镜像运行在kubernetes环境
  • 接下来开始编码吧

编码:准备工程

  • 执行命令名为go mod init client-go-unit-tutorials,新建module
  • 确保您的goproxy是正常的
  • 执行命令go get -u github.com/gin-gonic/gin,下载gin
  • 执行命令go get k8s.io/client-go@v0.22.8,下载client-go的指定版本
  • 现在工程已经准备好了,接着就是具体的编码,我们先从最核心的开始:操作kubernetes

编码:操作kubernetes

  • 新建文件夹kubernetes_service,在里面新增文件kube.go,这是集中了kubernetes操作的代码,内容如下,这里面有几处要注意的地方,稍后会提到
package kubernetesservice

import (
	"context"
	"flag"
	"log"
	"path/filepath"
	"sync"

	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/selection"
)

var CLIENT_SET kubernetes.Interface
var ONCE sync.Once

// DoInit Indexer相关的初始化操作,这里确保只执行一次
func DoInit() {
	ONCE.Do(initInKubernetesEnv)
}

// GetClient 调用此方法返回clientSet对象
func GetClient() kubernetes.Interface {
	return CLIENT_SET
}

// SetClient 可以通过initInKubernetesEnv在kubernetes初始化,如果有准备好的clientSet,也可以调用SetClient直接设置,而无需初始化
func SetClient(clientSet kubernetes.Interface) {
	CLIENT_SET = clientSet
}

// initInKubernetesEnv 这里是真正的初始化逻辑
func initInKubernetesEnv() {
	log.Println("开始初始化Indexer")

	var kubeconfig *string

	// 试图取到当前账号的家目录
	if home := homedir.HomeDir(); home != "" {
		// 如果能取到,就把家目录下的.kube/config作为默认配置文件
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		// 如果取不到,就没有默认配置文件,必须通过kubeconfig参数来指定
		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}

	// 加载配置文件
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		panic(err.Error())
	}

	// 用clientset类来执行后续的查询操作
	CLIENT_SET, err = kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	log.Println("kubernetes服务初始化成功")
}

// QueryPodNameByLabelApp 根据指定的namespace和label值搜索
func QueryPodNameByLabelApp(context context.Context, namespace, app string) ([]string, error) {
	log.Printf("QueryPodNameByLabelApp, namespace [%s], app [%s]", namespace, app)

	equalRequirement, err := labels.NewRequirement("app", selection.Equals, []string{app})

	if err != nil {
		return nil, err
	}

	selector := labels.NewSelector().Add(*equalRequirement)

	// 查询pod列表
	pods, err := CLIENT_SET.
		CoreV1().
		Pods(namespace).
		List(context, metav1.ListOptions{
			// 传入的selector在这里用到
			LabelSelector: selector.String(),
		})

	if err != nil {
		return nil, err
	}

	names := make([]string, 0)

	for _, v := range pods.Items {
		names = append(names, v.GetName())
	}

	return names, nil
}

// CreateNamespace 单元测试的辅助工具,用于创建namespace
func CreateNamespace(context context.Context, client kubernetes.Interface, name string) error {
	namespaceObj := &v1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}

	_, err := client.CoreV1().Namespaces().Create(context, namespaceObj, metav1.CreateOptions{})
	return err
}

// DeleteeNamespace 单元测试的辅助工具,用于创建namespace
func DeleteNamespace(context context.Context, client kubernetes.Interface, name string) error {
	err := client.CoreV1().Namespaces().Delete(context, name, metav1.DeleteOptions{})
	return err
}

/*
// CreateDeployment 单元测试的辅助工具,用于创建namespace
func CreateDeployment(context context.Context, client kubernetes.Interface, namespace, name, image, app string, replicas int32) error {
	_, err := client.AppsV1().Deployments(namespace).Create(context, &apps.Deployment{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Deployment",
			APIVersion: "apps/v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
			Labels: map[string]string{
				"app": app,
			},
		},
		Spec: apps.DeploymentSpec{
			Replicas: &replicas,
			Template: v1.PodTemplateSpec{
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Image: image,
						},
					},
				},
			},
		},
	}, metav1.CreateOptions{})

	return err

}
*/

  • 上述代码要有以下几处需要注意
  1. 操作kubernetes需要使用Clientset对象,该对象的创建过程集中在initInKubernetesEnv方法中,先加载配置文件,再根据配置文件创建Clientset
  2. CreateNamespace方法的作用是创建namespace,这里面有常规client-go库的使用方法,即Clientset的api的一些操作,以及一些资源对象的初始化
  3. QueryPodNameByLabelApp方法实现了核心业务功能,稍微有点复杂,在查找pod对象的时候,使用label做了一次过滤
  • 接下来是响应web请求的服务类basic_curd.go,代码如下,可见非常简单,就是取请求参数,再调用上面写到的api去kubernetes查询,再返回即可
package handler

import (
	"log"
	"net/http"

	"github.com/gin-gonic/gin"

	kubernetesservice "client-go-unit-tutorials/kubernetes_service"
)

const (
	PARAM_NAMESPACE = "namespace"
	PARAM_APP       = "label_app"
)

func QueryPodsByLabelApp(context *gin.Context) {
	rlt := make(map[string]interface{})
	namespace := context.DefaultQuery(PARAM_NAMESPACE, "")
	app := context.DefaultQuery(PARAM_APP, "")

	log.Printf("query param, namespace [%s], app [%s]", namespace, app)
	names, err := kubernetesservice.QueryPodNameByLabelApp(context, namespace, app)

	if err != nil {
		rlt["message"] = err.Error()
		context.JSON(http.StatusInternalServerError, rlt)
		return
	}

	rlt["message"] = "success"
	rlt["names"] = names
	context.JSON(http.StatusOK, rlt)
}
  • 为了将gin初始化逻辑封装起来好给外部调用,这里创建了/initor/customize_initor.go,内容也很简单,就是gin的路由初始化
package initor

import (
	"github.com/gin-gonic/gin"

	"client-go-unit-tutorials/handler"
)

const (
	PATH_QUERY_PODS_BY_LABEL_APP = "/query_pods_by_label_app"
)

func InitRouter() *gin.Engine {
	r := gin.Default()

	// 绑定path的handler
	r.GET(PATH_QUERY_PODS_BY_LABEL_APP, handler.QueryPodsByLabelApp)

	return r
}

  • 最后是main.go,这里面很简单,主动调用kubernetes和gin的初始化方法
package main

import (
	"client-go-unit-tutorials/initor"
	kubernetesservice "client-go-unit-tutorials/kubernetes_service"
)

func main() {
	// 初始化kubernetes相关配置
	kubernetesservice.DoInit()

	router := initor.InitRouter()
	_ = router.Run(":18080")
}
  • 以上就是完整的代码了,接下来咱们把代码运行起来看看效果

运行代码前的准备工作

  • 首先要在kubernetes环境把deployment部署好,如此调用查询接口才有数据返回
  • 先创建namespace
kubectl create namespace client-go-tutorials
  • 创建名为nginx-deployment-service.yaml的文件,内容如下
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: client-go-tutorials
  name: nginx-deployment
  labels:
    app: nginx-app
    type: front-end
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-app
      type: front-end
  template:
    metadata:
      labels:
        app: nginx-app
        type: front-end
        # 这是第一个业务自定义label,指定了mysql的语言类型是c语言
        language: c
        # 这是第二个业务自定义label,指定了这个pod属于哪一类服务,nginx属于web类
        business-service-type: web
    spec:
      containers:
        - name: nginx-container
          image: nginx:latest
          resources:
            limits:
              cpu: "0.5"
              memory: 128Mi
            requests:
              cpu: "0.1"
              memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
  namespace: client-go-tutorials
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx-app
    type: front-end
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30011
  • 执行以下脚本完成部署
kubectl apply -f nginx-deployment-service.yaml
  • 稍后会创建三个nginx的pod,接下来咱们就要用代码来查询这些pod了
kubectl get pods -n client-go-tutorials
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-78f6b696d9-j98xj   1/1     Running   0          19h
nginx-deployment-78f6b696d9-wp4qf   1/1     Running   0          7d17h
nginx-deployment-78f6b696d9-wpnt7   1/1     Running   0          20h

运行代码

  • 正常情况下,应该是执行go build编译项目,得到名为client-go-unit-tutorials的可执行文件,部署在可以访问kubernetes的机器上运行
  • 我这边开发机上就部署着kubernetes,因此,只要在vscode上运行项目就行了,运行应用的配置文件launch.json,如下
{
    "version": "0.2.0",
    "configurations": [
        
        {
            "name": "Launch Package",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "${workspaceFolder}"
        }
    ]
}
  • 调用请求的方法很多,postman、curl命令都可以,我这里用的是vscode的REST Client插件,可以把请求以脚本的方式保存下来,脚本如下
### 变量
@namespace=client-go-tutorials
@label_app=nginx-app

### 测试用例,指定namespace和label查询所有的pod名称
GET http://192.168.50.76:18080/query_pods_by_label_app?namespace={{namespace}}&label_app={{label_app}}
  • 点击下图红色箭头所指的Send Request就会发送请求
    在这里插入图片描述
  • 收到响应如下,可见所有符合要求的pod的name都在响应body中了
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Sun, 02 Jul 2023 04:58:03 GMT
Content-Length: 139
Connection: close

{
  "message": "success",
  "names": [
    "nginx-deployment-78f6b696d9-j98xj",
    "nginx-deployment-78f6b696d9-wp4qf",
    "nginx-deployment-78f6b696d9-wpnt7"
  ]
}
  • 至此,整篇内容已经完成了二分之一,接下里要看的就是如何编写单元测试代码了,要在一个没有kubernetes的环境下成功运行操作kubernetes的代码

关键知识点:使用client-go库的代码如何写单元测试

  • 如果您只想了解client-go有关的单元测试的关键知识点,对其他内容不感兴趣,下面黄色箭头所指这行代码足够了,在单元测试中使用fake.NewSimpleClientset()创建的clientset,只要运行单元测试时应用代码用到的是这个clientset,就可以和实际kubernetes环境使用clientset一样了,创建的资源也能被查出来
    在这里插入图片描述
  • 打开上图中的NewSimpleClientset方法,看看它创建的clientset是何方神圣,如下图,这个fake包下面的Clientset,已经把kubernetes.Interface接口完整实现了,在单元测试中可以用来取代正式环境中调用kubernetes.NewForConfig创建的clentset对象
    在这里插入图片描述
  • 以上解答了单元测试时如何脱离kubernetes环境使用client-go库的问题,这只是一个技术点而已,接下来咱们把完整的单元测试代码写出来

编码:单元测试

  • 首先是辅助工具,这里面有多个方法,都是辅助单元测试的,例如SingleTest方法可以用来发送请求并将响应返回,Check方法可以检查返回内容等
package unittesthelper

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/suite"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

const (
	TEST_NAMESPACE       = "client-go-tutorials"
	TEST_POD_NAME_PREFIX = "nginx-pod-"
	TEST_IMAGE           = "nginx:latest"
	TEST_LABEL_APP       = "nginx-app"
	TEST_POD_NUM         = 3
)

// 数据结构,用于保存web响应的body
type ResponseNames struct {
	Message string   `json:"message"`
	Names   []string `json:"names"`
}

// SingleTest 辅助方法,发请求,返回响应
func SingleTest(router *gin.Engine, url string) (int, string, error) {
	log.Printf("start SingleTest, request url : %s", url)
	w := httptest.NewRecorder()
	req, _ := http.NewRequest(http.MethodGet, url, nil)
	router.ServeHTTP(w, req)
	return w.Code, w.Body.String(), nil
}

// 9. 辅助方法,解析web响应,检查结果是否符合预期
func Check(suite *suite.Suite, body string, expectNum int) {
	suite.NotNil(body)
	response := &ResponseNames{}

	err := json.Unmarshal([]byte(body), response)

	if err != nil {
		log.Fatalf("unmarshal response error, %s", err.Error())
	}

	suite.EqualValues(expectNum, len(response.Names))
}

// CreatePodObj 辅助方法,用于创建pod对象
func CreatePodObj(namespace, name, app, image string) *v1.Pod {
	return &v1.Pod{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Deployment",
			APIVersion: "apps/v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
			Labels: map[string]string{
				"app": app,
			},
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Image: image,
				},
			},
		},
	}
}

// CreateDeployment 单元测试的辅助工具,用于创建namespace
func CreatePods(context context.Context, client kubernetes.Interface, namespace, name, image, app string) error {
	_, err := client.CoreV1().Pods(namespace).Create(context, CreatePodObj(namespace, name, app, image), metav1.CreateOptions{})
	return err
}

// CreatePod 辅助方法,用于创建多个pod
func CreatePod(context context.Context, client kubernetes.Interface, num int) {
	for i := 0; i < num; i++ {
		if err := CreatePods(context,
			client,
			TEST_NAMESPACE,
			fmt.Sprintf("%s%d", TEST_POD_NAME_PREFIX, i),
			TEST_IMAGE,
			TEST_LABEL_APP); err != nil {
			log.Fatalf("create pod [%d] error, %s", i, err.Error())
		}
	}
}
  • 接下来就是完整的单元测试代码,这里面是一个常规的单元测试集的开发,注释中用数字标明了每一步的执行顺序,按部就班完成就好,要注意的是SetupTest方法,里面用mock出来的clientset创建了三个pod,这些pod在查询的时候是可以被查出来的,有了这个mock版的clientset的帮助,就算没有kubernetes环境,咱们的代码照样能正常运行
package handler_test

import (
	"client-go-unit-tutorials/handler"
	"client-go-unit-tutorials/initor"
	kubernetesservice "client-go-unit-tutorials/kubernetes_service"
	"client-go-unit-tutorials/unittesthelper"
	"context"
	"fmt"
	"log"
	"net/http"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/suite"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/fake"
)

// 1. 定义suite数据结构
type MySuite struct {
	suite.Suite
	ctx       context.Context
	cancel    context.CancelFunc
	clientSet kubernetes.Interface
	router    *gin.Engine
}

// 2. 单元测试的初始化操作
func (mySuite *MySuite) SetupTest() {
	client := fake.NewSimpleClientset()
	kubernetesservice.SetClient(client)

	mySuite.ctx, mySuite.cancel = context.WithCancel(context.Background())
	mySuite.clientSet = client
	mySuite.router = initor.InitRouter()

	// 初始化数据,创建namespace
	if err := kubernetesservice.CreateNamespace(mySuite.ctx, client, unittesthelper.TEST_NAMESPACE); err != nil {
		log.Fatalf("create namespace error, %s", err.Error())
	}

	// 初始化数据,创建pod
	unittesthelper.CreatePod(mySuite.ctx, client, 3)
}

// 3. 定义测试完成后的收尾工作,例如清理一些资源
func (mySuite *MySuite) TearDownTest() {

	// 删除namespace
	if err := kubernetesservice.DeleteNamespace(mySuite.ctx, kubernetesservice.GetClient(), unittesthelper.TEST_NAMESPACE); err != nil {
		log.Fatalf("delete namespace error, %s", err.Error())
	}

	mySuite.cancel()
}

// 4. 启动测试集
func TestBasicCrud(t *testing.T) {
	suite.Run(t, new(MySuite))
}

// 5. 定义测试集
func (mySuite *MySuite) TestBasicCrud() {
	// 5.1 若有需要,执行monkey.Patch
	// 5.2 若执行了monkey.Patch,需要执行defer monkey.UnpatchAll()

	// 5.3 执行单个测试
	// 参考 client-go/examples/fake-client/main_test.go/main_test.go
	mySuite.Run("常规查询", func() {
		url := fmt.Sprintf("%s?%s=%s&%s=%s",
			initor.PATH_QUERY_PODS_BY_LABEL_APP,
			handler.PARAM_NAMESPACE,
			unittesthelper.TEST_NAMESPACE,
			handler.PARAM_APP,
			unittesthelper.TEST_LABEL_APP)

		code, body, error := unittesthelper.SingleTest(mySuite.router, url)

		if error != nil {
			mySuite.Fail("SingleTest error, %v", error)
			return
		}

		// 检查返回码
		mySuite.EqualValues(http.StatusOK, code)

		// 检查结果
		unittesthelper.Check(&mySuite.Suite, body, unittesthelper.TEST_POD_NUM)
	})
}

  • 点击下图黄色箭头所指按钮,即可开始单元测试
    在这里插入图片描述
  • 得到结果如下,在没有kubernetes环境的情况下,单元测试通过,所有操作kubernetes的代码均能正常运行
=== RUN   TestBasicCrud
=== RUN   TestBasicCrud/TestBasicCrud
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /query_pods_by_label_app  --> client-go-unit-tutorials/handler.QueryPodsByLabelApp (3 handlers)
=== RUN   TestBasicCrud/TestBasicCrud/常规查询
2023/07/02 05:17:27 start SingleTest, request url : /query_pods_by_label_app?namespace=client-go-tutorials&label_app=nginx-app
2023/07/02 05:17:27 query param, namespace [client-go-tutorials], app [nginx-app]
2023/07/02 05:17:27 QueryPodNameByLabelApp, namespace [client-go-tutorials], app [nginx-app]
[GIN] 2023/07/02 - 05:17:27 | 200 |     205.281µs |                 | GET      "/query_pods_by_label_app?namespace=client-go-tutorials&label_app=nginx-app"
--- PASS: TestBasicCrud/TestBasicCrud/常规查询 (0.00s)
--- PASS: TestBasicCrud/TestBasicCrud (0.00s)
--- PASS: TestBasicCrud (0.00s)
PASS
ok      client-go-unit-tutorials/handler        0.034s


> Test run finished at 7/2/2023, 1:17:26 PM <
  • 至此,client-go初级篇已经完成,希望能对刚刚涉及kubernetes开发的读者有所帮助

源码下载

  • 如果您不想编写代码,也可以从GitHub上直接下载,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):
名称链接备注
项目主页https://github.com/zq2599/blog_demos该项目在GitHub上的主页
git仓库地址(https)https://github.com/zq2599/blog_demos.git该项目源码的仓库地址,https协议
git仓库地址(ssh)git@github.com:zq2599/blog_demos.git该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在tutorials/client-go-unit-tutorials文件夹下,如下图红框所示:
    在这里插入图片描述

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

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

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

相关文章

分层架构简介

MVC是架构模式&#xff08;设计模式中的结构性模式&#xff09;&#xff0c;不是系统架构&#xff0c;更不是我们常说的三层架构 MVC的缺陷如下&#xff1a; 1.导致控制器冗余&#xff08;有大量的业务逻辑&#xff0c;可能开始没有&#xff0c;但是后来越来越多&#xff09;…

QT学习笔记5--槽函数重载解决办法

connect函数 connect(sender, signal, receiver, slot); 槽函数示例 void student:: treat(QString foodname) void student:: treat(int index) 由上可见&#xff0c;有两个名字相同&#xff0c;但形参不同的槽函数。 可以通过函数指针的方式 &#xff0c;用指针指向具体…

linux环境安装mysql8.0.32

linux环境安装mysql8.0.32 一、下载安装包二、安装前准备2.1 卸载旧版本mysql2.2 检查是否安装了 mariadb 数据库2.3 安装依赖包创建 mysql 用户 三、安装3.1 上传并解压安装包&#xff08;上传路径没有要求&#xff0c;一般在/usr/local&#xff09;3.2 初始化数据库3.3 注册数…

Java面试题6月

redis有哪些缓存淘汰策略 https://blog.51cto.com/u_11720620/5198874 生产环境内存溢出&#xff08;OOM&#xff09;问题处理方案 https://note.youdao.com/ynoteshare/index.html?id5cc182642eb02bc64197788c7722baae&typenote&_time1688287588653 jstack找出占用…

C++之GNU C的__attribute__((constructor))和((destructor))静态构造函数实现(一百四十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Elasticsearch实战(二十三)---ES数据建模与Mysql对比 一对多模型

Elasticsearch实战—ES数据建模与Mysql对比实现 一对多模型 文章目录 Elasticsearch实战---ES数据建模与Mysql对比实现 一对多模型1.一对多 模型1.1 Mysql建模 2.一对多 Index ES 数据模型2.1 类似Mysql, 依旧创建两个Index索引库2.2 采用ES架构 嵌套数组模型2.3采用ES架构 冗余…

【JUC-2】Synchronized关键字相关知识

Synchronized synchronized是Java中的关键字&#xff0c;是一种同步锁。它修饰的对象有以下几种&#xff1a; 修饰一个代码块&#xff0c;被修饰的代码块称为同步语句块&#xff0c;其作用的范围是大括号{}括起来的代码&#xff0c;作用的对象是调用这个代码块的对象&#xf…

【C++2】进程 信号 dbus

文章目录 1.进程&#xff1a;fork()&#xff0c;ps -ef (同-aux) | more2.信号&#xff1a;signal&#xff08;, EXIT&#xff09;&#xff0c;jps2.1 捕捉信号&#xff1a;ctrlc&#xff1a;22.2 捕捉信号&#xff1a;kill -9&#xff1a;92.3 捕捉信号&#xff1a;kill&#…

欧几里得算法

0x00 前言 改补的内容是一点都不会少。本章来看欧几里得算法 0x01 概述 欧几里得算法又称为辗转相除法&#xff0c;指用于计算两个非负整数a和b的最大公约数。 两个整数的最大公约数是能够同时整除他们的最大的正整数。 基本原理&#xff1a;两个整数的最大公约数等于其中…

【动态规划】子数组系列(上)

子数组问题 文章目录 【动态规划】子数组系列&#xff08;上&#xff09;1. 最大子数组和1.1 题目解析1.2 算法原理1.2.1 状态表示1.2.2 状态转移方程1.2.3 初始化1.2.4 填表顺序1.2.5 返回值 1.3 代码实现 2. 环形子数组的最大和2.1 题目解析2.2 算法原理2.2.1 状态表示2.2.2 …

C++2(表达式和关系运算)

目录 1.表达式基础 1.表达式基础 运算符重载&#xff0c;就是自己定义 - * / 之类的运算符怎么运算 C中的左值和右值 C语言左值在左侧&#xff0c;右值在右侧 在cpp中要复杂的多 能取到地址的表达式是左值 不能取到地址的表达式是右值 常量对象为代表的左值不能作为赋值语句的左…

【Linux】网络相关概念概述以及原理简单分析介绍

文章目录 [toc] Linux 网络概述网络发展独立模式网络互联局域网LAN 和 广域网WAN 认识 "协议"协议的分层网络协议栈OSI七层模型TCP/IP五层(四层)模型TCP/IP网络协议栈 与 操作系统 的关系 **重新以计算机的视角看待 网络协议栈 局域网内部通信原理简单介绍不同局域网…

mybatis web使用02

处理 transfer 请求的 servlet package com.wsd.web;import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRe…

GAMES101 笔记 Lecture08 Shading 2(Shading, Pipeline and Texture Mapping)

目录 Specular Term(高光项)Ambient Term(环境光照项)Blinn-Phong Reflection ModelShading Frequencies(着色频率)Shade each triangle(flat shading)在每个三角形上进行着色Shade each vertex (Gouraud shading)(顶点着色)Shade each pixel (Phong shading)Defining Per-Vert…

【C++详解】——哈希

目录 unordered系列关联式容器 unordered_map unordered_map的接口说明 1.unordered_map的构造 2.unordered_map的容量 3.迭代器相关 4.unordered_map的元素访问 5. unordered_map的查询 6.unordered_map的修改操作 unordered_set 性能测试 底层结构——Hash 哈希…

copula简介

二元正态copula最为重要

MySQL - 自连接查询

1. 测试数据 创建 category 表 : CREATE TABLE category(categoryid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主题id,pid INT(10) NOT NULL COMMENT 父id,categoryName VARCHAR(50) NOT NULL COMMENT 主题名字,PRIMARY KEY(categoryid) ) ENGINEINNODB AUTO_INCREM…

cmd的学习

目录 常用的cmd命令 使用cmd的例子 常用的cmd命令 指令作用盘符名称:盘符切换dir查看当前路径下的内容tree以树形结构输出当前路径下的内容cd进入单级目录cd ..回退到上一级目录cd 目录1\目录2\...进入多级目录cd \回退到盘符目录cls清屏exit退出窗口 &#xff08;值得注意的…

Android AlertDialog setView,kotlin

Android AlertDialog setView&#xff0c;kotlin <?xml version"1.0" encoding"utf-8"?> <com.google.android.material.textfield.TextInputLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width…

MySQL数据库——主从复制和读写分离

MySQL数据库——主从复制和读写分离 一、主从复制和读写分离的相关知识1.什么是读写分离&#xff1f;2.为什么要读写分离呢&#xff1f;3.什么时候要读写分离&#xff1f;4.主从复制与读写分离5.mysql支持的复制类型6.主从复制的工作过程7.MySQL 读写分离原理8.目前较为常见的 …