k8s编程operator实战之云编码平台——②controller初步实现

news2024/11/17 7:18:07

文章目录

    • 1、工作空间镜像制作
    • 2、controller实现
      • 2.1 使用kubebuilder创建工程
      • 2.2 代码实现
        • 2.2.1 引入grpc
        • 2.2.2 实现CloudIdeService
          • StatusInformer的实现
          • CloudSpaceService定义
          • 方法CreateSpaceAndWaitForRunning
          • 方法GetPodSpaceInfo
          • 方法DeleteSpace
          • 方法GetPodSpaceStatus
        • 2.2.3 controller实现
          • SetupWithManager
          • Reconcile
        • 2.2.4 启动grpc server以及优雅退出服务器
          • StartGrpcServer
          • SetupSignal
        • 2.2.5 目录结构
    • 3、测试
      • 3.1 创建工作空间并且等待运行
      • 3.2 获取Pod状态
      • 3.3 获取pod信息
      • 3.4 删除pod
    • 总结

k8s编程operator系列:
k8s编程operator——(1) client-go基础部分
k8s编程operator——(2) client-go中的informer
k8s编程operator——(3) 自定义资源CRD
k8s编程operator——(4) kubebuilder & controller-runtime
k8s编程operator实战之云编码平台——①架构设计
k8s编程operator实战之云编码平台——②controller初步实现
 
在上一章中介绍了整个云编码平台的架构设计,本章中将实现架构中的controller。需要使用的技术: k8s二次开发kubebuildergrpc

controller用来创建、删除、获取Pod信息以及状态的维护。

 

1、工作空间镜像制作

        首先我们要制作code-server的docker镜像,先来制作一个含有go运行环境的工作空间镜像,工具包括:go sdk、make、git

首先创建一个文件夹用来存放我们制作镜像需要的文件:

mkdir go_template

 

1、下载code-server

下载code-server v4.9.0版本:

下载的网址为:https://github.com/coder/code-server/releases

wget https://github.com/coder/code-server/releases/download/v4.9.0/code-server-4.9.0-linux-amd64.tar.gz

2、下载go sdk,版本为v1.19.4

下载地址:https://golang.google.cn/dl/

wget https://golang.google.cn/dl/go1.19.4.linux-amd64.tar.gz

3、编写Dockerfile

Dockerfile如下:

  • 基础镜像使用ubuntu:20.04
  • 14、15行为安装必要的软件
  • 16行安装git
  • 在启动code-server时不使用密码验证,默认打开/root/workspace文件
FROM ubuntu:20.04

WORKDIR /.workspace

COPY code-server-4.9.0-linux-amd64.tar.gz .
COPY go1.19.4.linux-amd64.tar.gz .

RUN tar zxvf code-server-4.9.0-linux-amd64.tar.gz && \
    rm -f code-server-4.9.0-linux-amd64.tar.gz    && \
    tar zxvf go1.19.4.linux-amd64.tar.gz -C /usr/local     && \
    rm -f go1.19.4.linux-amd64.tar.gz             && \
    mkdir -p /root/workspace /go/{src,pkg,bin}    && \
    apt-get -y update                             && \
    apt-get -qq update                            && \
    apt-get -qq install -y --no-install-recommends ca-certificates curl && \
    apt install git

ENV GO111MODULE on
ENV GOPROXY https://goproxy.cn,direct
ENV GOROOT /usr/local/go
ENV PATH /usr/local/go/bin:$PATH
ENV GOPATH /go
ENV PATH $GOPATH/bin:$PATH

WORKDIR /.workspace/code-server-4.9.0-linux-amd64

EXPOSE 9999

CMD ["./bin/code-server", "--port", "9999", "--host", "0.0.0.0", "--auth", "none", "--disable-update-check", "--open", "/root/workspace/"]

4、构建镜像

在go_template中构建镜像,需要将code-server、go sdk和Docker都放在该文件夹下,也不要放多余的其它文件

docker build -t code-server-go1.19:v0.1 .

构建查看镜像(我的镜像名为mangohow/code-server-go1.19):

在这里插入图片描述

5、push到docker hub

在构建完成后,最好将镜像推送到dockerhub或者其它镜像仓库中

首先要给镜像打一个tag,yourUsername为你的镜像仓库的用户名

docker tag code-server-go1.19:v0.1 yourUsername/code-server-go1.19:v0.1

推送到镜像仓库

# 先登录
docker login
docker push yourUsername/code-server-go1.19:v0.1

 

2、controller实现

controller的实现直接使用kubebuilder,它会为我们生成一套模板代码

注意:最好直接在linux上编码,在windows上有很多问题

 

2.1 使用kubebuilder创建工程

1、创建文件夹

mkdir cloud-ide-k8s-controller
cd cloud-ide-k8s-controller/

2、创建工程

# 初始化
go mod init cloud-ide-k8s-controller
kubebuilder init

在这里插入图片描述

# 创建api
kubebuilder create api --group cloud-ide  --version v1 --kind Pod

由于我们没有创建自定义资源,所以在Create Resource选择中选择n

在这里插入图片描述

到此,工程已经创建完毕,开始编码

 

2.2 代码实现

2.2.1 引入grpc

Pod的创建、删除以及信息查询等工作我们都在controller中实现,然后通过grpc的方式提供给web后端来使用。

使用grpc需要安装protobuf和proto-gen-go,关于这部分的安装在此就不再介绍,网上教程很多。

1、grpc的proto文件定义

首先先实现四个grpc service:

createSpaceAndWaitForRunning:创建Pod并且等待Pod的状态变为Running,当我们在创建Pod后,它需要一段时间来启动,在这段时间之间是无法访问的,因此我们需要等待它启动完成后再返回
deleteSpace:删除Pod
getPodSpaceStatus:获取Pod运行的状态
getPodSpaceInfo:获取Pod的信息

在工程中创建pb/proto文件夹,然后创建proto文件:

service.proto如下:

syntax = "proto3";

package pb;

option go_package = "./;pb";

// 限制用户的工作空间的资源使用,有CPU、内存和存储的大小限制
message ResourceLimit {
  string cpu = 1;
  string Memory = 2;
  string Storage = 3;
}

// 在创建Pod时,需要webserver提供要创建的Pod的信息
// 其中有:Pod的名称、Pod的命名空间、使用的镜像、以及要使用的端口和资源限制
message PodInfo {
  string name = 1;
  string namespace = 2;
  string image = 3;
  uint32 port = 4;
  ResourceLimit resourceLimit = 5;
}

message Response {
  int32 status = 1;
  string message = 2;
}

message QueryOption {
  string name = 1;
  string namespace = 2;
}

message PodStatus {
  int32 status = 1;
  string message = 2;
}

message PodSpaceInfo {
  string nodeName = 1;
  string ip = 2;
  int32 port = 3;
}

service CloudIdeService {
  // 创建云IDE空间并等待Pod状态变为Running
  rpc createSpaceAndWaitForRunning(PodInfo) returns (PodSpaceInfo);
  // 删除云IDE空间
  rpc deleteSpace(QueryOption) returns (Response);
  // 获取Pod运行状态
  rpc getPodSpaceStatus(QueryOption) returns (PodStatus);
  // 获取云IDE空间Pod的信息
  rpc getPodSpaceInfo(QueryOption) returns (PodSpaceInfo);
}

生成代码:

protoc --go_out=plugins=grpc:./pb ./pb/proto/*.proto

可以将这个命令加到makefile中,以后在使用就更方便了,可以直接使用make proto

在这里插入图片描述

 

2.2.2 实现CloudIdeService

创建cloudspaceservice.go实现CloudIdeService接口

要实现的方法总共有四个:

type CloudIdeServiceClient interface {
	// 创建云IDE空间并等待Pod状态变为Running
	CreateSpaceAndWaitForRunning(ctx context.Context, in *PodInfo, opts ...grpc.CallOption) (*PodSpaceInfo, error)
	// 删除云IDE空间
	DeleteSpace(ctx context.Context, in *QueryOption, opts ...grpc.CallOption) (*Response, error)
	// 获取Pod运行状态
	GetPodSpaceStatus(ctx context.Context, in *QueryOption, opts ...grpc.CallOption) (*PodStatus, error)
	// 获取云IDE空间Pod的信息
	GetPodSpaceInfo(ctx context.Context, in *QueryOption, opts ...grpc.CallOption) (*PodSpaceInfo, error)
}

CreateSpaceAndWaitForRunning:给ApiServer发送请求创建Pod,并且等待Pod处于运行状态后在返回。

DeleteSpace:删除指定的Pod

GetPodSpaceStatus:获取Pod的运行状态,Pod是否存在

GetPodSpaceInfo:获取Pod的ip和port信息

等待Pod处于运行状态

Pod在刚创建时处于Pending状态,当Pod启动完毕后,会处于Running状态,因为我们要监视Pod的状态,当它的状态变为Runnging时,就通知我们的CreateSpaceAndWaitForRunning方法Pod已经就绪。

要监视Pod的状态可以通过client go的informer来实现,使用kubebuilder生成的工程中已经有现成的模板代码,也就是我们只需要在controller中实现Reconcile方法即可。

我们可以实现一个通知器,其中含有map[string]chan struct{},当我们在CreateSpaceAndWaitForRunning方法中请求ApiServer创建Pod后,就向map中添加一个键值对 key:podname val: chan,然后从chan中读取数据,由于Pod还未准备就绪,因此读取空的chan将会导致阻塞。在Reconcile中当监视到Pod的状态变为Running后,就向chan中发送消息另一端就可以收到Pod已经就绪的通知

StatusInformer的实现
package statussync

import (
	"errors"
	"sync"
)

var (
	ErrNotFound = errors.New("not Found")
)

// StatusInformer 状态同步通知器,当Pod状态处于Running时,通知对端
type StatusInformer struct {
	sync.Mutex
	m map[string]chan struct{}
}

func NewManager() *StatusInformer {
	return &StatusInformer{
		m: make(map[string]chan struct{}),
	}
}

// 向map中添加一个chan
func (m *StatusInformer) Add(name string) <-chan struct{} {
	m.Lock()
	defer m.Unlock()
	ch := make(chan struct{}, 1)
	m.m[name] = ch

	return ch
}

func (m *StatusInformer) Delete(name string) {
	m.Lock()
	defer m.Unlock()
	delete(m.m, name)
}

// 同步消息
func (m *StatusInformer) Sync(name string) error {
	m.Lock()
	defer m.Unlock()
	ch, ok := m.m[name]
	if !ok {
		return ErrNotFound
	}
	ch <- struct{}{}

	return nil
}

 

CloudIdeService接口实现:

CloudSpaceService定义

在CloudSpaceService的字段有三个,分别是:client、logger和statusInformer

client可以对Pod进行CRUD,在查询时会查询本地的缓存

package service

import (
	"context"
	"fmt"
	"github.com/go-logr/logr"
	"github.com/mangohow/cloud-ide-k8s-controller/pb"
	"github.com/mangohow/cloud-ide-k8s-controller/tools/statussync"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
	ResponseSuccess = &pb.Response{Status: 200, Message: "success"}
	ResponseFailed  = &pb.Response{Status: 400, Message: "failed"}
)

const (
	PodNotExist int32 = iota
	PodExist
)

type CloudSpaceService struct {
	client         client.Client            // client可用于pod的CRUD
	logger         *logr.Logger
	statusInformer *statussync.StatusInformer
}

func NewCloudSpaceService(client client.Client, logger *logr.Logger, manager *statussync.StatusInformer) *CloudSpaceService {
	return &CloudSpaceService{
		client:         client,
		logger:         logger,
		statusInformer: manager,
	}
}

var podTpl = &v1.Pod{
	TypeMeta: metav1.TypeMeta{
		Kind:       "Pod",
		APIVersion: "v1",
	},
	ObjectMeta: metav1.ObjectMeta{
		Labels: map[string]string{
			"kind": "cloud-ide",
		},
	},
}

方法CreateSpaceAndWaitForRunning

在Pod的定义中一定要将镜像的拉取策略改为IfNotPresent,之前这个字段没有填。每次在创建工作空间的时候,都要等30~40s,后来我才发现,默认的策略是Always,也就是每次创建工作空间的时候都会从dockerhub拉取镜像,所以才导致这么慢。修改后,启动工作空间就只需要不到5s。

在Resources中,我将代码注释了,因为我的虚拟机配置太低,如果不注释,那么在创建Pod时就可能会因为资源不足而导致Pod创建失败。

// CreateSpaceAndWaitForRunning 创建一个云IDE空间, 并等待Pod的状态变为Running
func (s *CloudSpaceService) CreateSpaceAndWaitForRunning(ctx context.Context, info *pb.PodInfo) (*pb.PodSpaceInfo, error) {
	// 1、获取一个Pod的深拷贝
    pod := podTpl.DeepCopy()
    // 2、填充参数
	s.fillPod(info, pod)
    // 3、创建Pod
	err := s.client.Create(context.Background(), pod)
	if err != nil {
		fmt.Printf("create pod:%s, info:%v\n", err.Error(), info)
		return nil, err
	}
	// 4、向informer中添加chan,当Pod准备就绪时就会收到通知
	ch := s.statusInformer.Add(pod.Name)
	// 从informer中删除
	defer s.statusInformer.Delete(pod.Name)
	// 等待pod状态处于Running
	<-ch
	
    // 返回Pod的信息
	return s.GetPodSpaceInfo(context.Background(), &pb.QueryOption{
		Name:      info.Name,
		Namespace: info.Namespace,
	})
}

func (s *CloudSpaceService) fillPod(info *pb.PodInfo, pod *v1.Pod) {
	pod.Name = info.Name           // 指定Pod名称
	pod.Namespace = info.Namespace    // 指定Pod的命名空间
	pod.Spec.Containers = []v1.Container{
		{
			Name:            info.Name,     // 容器名称和Pod名称相同
			Image:           info.Image,    // 容器的镜像
			ImagePullPolicy: v1.PullIfNotPresent,      // 镜像拉取策略
			Ports: []v1.ContainerPort{
				{
					ContainerPort: int32(info.Port),
				},
			},
			Resources: v1.ResourceRequirements{
				//Limits: map[v1.ResourceName]resource.Quantity{
				//	v1.ResourceCPU:    resource.MustParse(info.ResourceLimit.Cpu),
				//	v1.ResourceMemory: resource.MustParse(info.ResourceLimit.Memory),
				//},
				// 最小需求CPU2核、内存4Gi == 4 * 2^10
				//Requests: map[v1.ResourceName]resource.Quantity{
				//	v1.ResourceCPU:    resource.MustParse("2"),
				//	v1.ResourceMemory: resource.MustParse("4Gi"),
				//},
			},
		},
	}

}

方法GetPodSpaceInfo

根据Pod名称以及命名空间查询Pod的Ip和port

func (s *CloudSpaceService) GetPodSpaceInfo(ctx context.Context, option *pb.QueryOption) (*pb.PodSpaceInfo, error) {
	pod := v1.Pod{}
	err := s.client.Get(context.Background(), client.ObjectKey{Name: option.Name, Namespace: option.Namespace}, &pod)
	if err != nil {
		s.logger.Error(err, "get pod space info")
		return &pb.PodSpaceInfo{}, err
	}

	return &pb.PodSpaceInfo{NodeName: pod.Spec.NodeName,
		Ip:   pod.Status.PodIP,
		Port: pod.Spec.Containers[0].Ports[0].ContainerPort}, nil
}
方法DeleteSpace

根据Pod的名称和命名空间删除Pod

// DeleteSpace 删除一个云空间
func (s *CloudSpaceService) DeleteSpace(ctx context.Context, option *pb.QueryOption) (*pb.Response, error) {
	pod := v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      option.Name,
			Namespace: option.Namespace,
		},
	}
	err := s.client.Delete(context.Background(), &pod)
	if err != nil {
		s.logger.Error(err, "delete space")
		return ResponseFailed, err
	}

	return ResponseSuccess, nil
}
方法GetPodSpaceStatus

根据Pod的名称和命名空间获取Pod的运行状态

func (s *CloudSpaceService) GetPodSpaceStatus(ctx context.Context, option *pb.QueryOption) (*pb.PodStatus, error) {
	pod := v1.Pod{}
	err := s.client.Get(context.Background(), client.ObjectKey{Name: option.Name, Namespace: option.Namespace}, &pod)
	if err != nil {
		s.logger.Error(err, "get pod space status")
		return &pb.PodStatus{Status: PodNotExist, Message: "NotExist"}, err
	}

	return &pb.PodStatus{Status: PodExist, Message: string(pod.Status.Phase)}, nil
}

 

2.2.3 controller实现

在pod_controller文件中,需要实现的方法主要有两个:ReconcileSetupWithManager

type PodReconciler struct {
	client.Client
	Scheme         *runtime.Scheme
	statusInformer *statussync.StatusInformer   // 添加statusInformer
}
SetupWithManager

在SetupWithManager方法中需要指定我们要监视的资源的类型,也就是Pod:

func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		// Uncomment the following line adding a pointer to an instance of the controlled resource as an argument
		For(&v1.Pod{}).
		Complete(r)
}
Reconcile

在Reconciler中监视Pod的状态,当Pod处于running状态时,通知对端

func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := log.FromContext(ctx)

	// TODO(user): your logic here
	pod := &v1.Pod{}
    // 获取Pod
	err := r.Client.Get(context.Background(), req.NamespacedName, pod)
	if err != nil {
		if !errors.IsNotFound(err) {
			logger.Error(err, "get pod")
			return ctrl.Result{Requeue: true}, err
		}
	}
	fmt.Printf("name:%s, status:%s\n", pod.Name, pod.Status.Phase)
	// 如果Pod的状态处于Running,通知对端Pod已经处于Running状态
	if pod.Status.Phase == v1.PodRunning {
		r.statusInformer.Sync(pod.Name)
	}

	return ctrl.Result{}, nil
}

 

2.2.4 启动grpc server以及优雅退出服务器

我们需要在main函数中启动我们的grpc服务器并注册服务。处理信号,当接收到退出的信号时退出grpc server和controller

StartGrpcServer
func StartGrpcServer(client client.Client, logger *logr.Logger, manager *statussync.StatusInformer) *grpc.Server {
	// 创建listener
    listener, err := net.Listen("tcp", ":6387")
	if err != nil {
		panic(fmt.Errorf("create grpc service: %v", err))
	}
    // 创建grpc server
	server := grpc.NewServer()
    // 注册我们的服务
	pb.RegisterCloudIdeServiceServer(server, service.NewCloudSpaceService(client, logger, manager))
	
    // 启动grpc server
	go func() {
		err := server.Serve(listener)
		if err != nil && err == grpc.ErrServerStopped {
			fmt.Printf("server stopped")
		} else if err != nil {
			panic(fmt.Errorf("start grpc server: %v", err))
		}
	}()

	return server
}

SetupSignal

signal.go:

package signal

import (
	"context"
	"os"
	"os/signal"
	"syscall"
)

var onlyOneSignalHandler = make(chan struct{})

func SetupSignal(fns ...func()) context.Context {
	// 当函数被调用两次,就会panic
	close(onlyOneSignalHandler)
	sigCh := make(chan os.Signal, 2)
   
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		<-sigCh
		for _, fn := range fns {
			fn()
		}
		cancel()
        // 第二次接收到信号,直接退出
		<-sigCh
		os.Exit(1)
	}()

	return ctx
}

main函数如下:

const WatchedNamespace = "cloud-ide"

func main() {
	var metricsAddr string
	var enableLeaderElection bool
	var probeAddr string
	flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
	flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
	flag.BoolVar(&enableLeaderElection, "leader-elect", false,
		"Enable leader election for controller manager. "+
			"Enabling this will ensure there is only one active controller manager.")
	opts := zap.Options{
		Development: true,
	}
	opts.BindFlags(flag.CommandLine)
	flag.Parse()

	ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))

	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
		Scheme:                 scheme,
		MetricsBindAddress:     metricsAddr,
		Port:                   9443,
		HealthProbeBindAddress: probeAddr,
		LeaderElection:         enableLeaderElection,
		LeaderElectionID:       "81275557.my.domain",
		// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
		// when the StatusInformer ends. This requires the binary to immediately end when the
		// StatusInformer is stopped, otherwise, this setting is unsafe. Setting this significantly
		// speeds up voluntary leader transitions as the new leader don't have to wait
		// LeaseDuration time first.
		//
		// In the default scaffold provided, the program ends immediately after
		// the manager stops, so would be fine to enable this option. However,
		// if you are doing or is intended to do any operation such as perform cleanups
		// after the manager stops then its usage might be unsafe.
		// LeaderElectionReleaseOnCancel: true,
		Namespace: WatchedNamespace,  // 指定要监视的Pod的namespace,我们的工作空间都创建在同一个命名空间下,只需监视这个命名空间下的Pod即可
	}) 
	if err != nil {
		setupLog.Error(err, "unable to start manager")
		os.Exit(1)
	}
	
    // 创建statusInfomer
	manager := statussync.NewManager()
	if err = controllers.NewPodReconciler(mgr.GetClient(), mgr.GetScheme(), manager).SetupWithManager(mgr); err != nil {
		setupLog.Error(err, "unable to create controller", "controller", "Pod")
		os.Exit(1)
	}
	//+kubebuilder:scaffold:builder

	if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
		setupLog.Error(err, "unable to set up health check")
		os.Exit(1)
	}
	if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
		setupLog.Error(err, "unable to set up ready check")
		os.Exit(1)
	}

	// 启动grpc服务
	grpcServer := StartGrpcServer(mgr.GetClient(), &ctrl.Log, manager)
	// 安装信号处理
	ctx := signal.SetupSignal(func() {
		ctrl.Log.Info("receive signal, is going to shutdown")
		grpcServer.GracefulStop()
	})

	setupLog.Info("starting manager")

	if err := mgr.Start(ctx); err != nil {
		setupLog.Error(err, "problem running manager")
		os.Exit(1)
	}
}

 

2.2.5 目录结构

目录结构如下:

在这里插入图片描述

 

3、测试

接下来测试各个功能,由于我们的服务是通过grpc的方式提供的。可以使用ApiPost7来发送请求,ApiPost7支持grpc接口的调用,免费的。

官网:https://www.apipost.cn/

3.1 创建工作空间并且等待运行

启动我们的controller,可以使用make来启动:

make run
# 或者编译后再启动,编译后的文件为bin/manager
make build

使用ApiPost创建一个grpc,将service.proto导入

调用CreateSpaceAndWaitForRunning接口创建pod

在这里插入图片描述

controller的输出如下:

在这里插入图片描述

查看pod:

在这里插入图片描述

 

3.2 获取Pod状态

在这里插入图片描述

 

3.3 获取pod信息

在这里插入图片描述

 

3.4 删除pod

在这里插入图片描述

在这里插入图片描述

 

总结

至此,controller已经初步实现了功能,可以实现Pod的创建、删除、Pod状态获取、Pod信息获取

github地址:https://github.com/mangohow/cloud-ide-k8s-controller

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

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

相关文章

人才盘点的工具与方法有哪些?怎样做好人才盘点?

人才盘点是对组织和人才进行系统管理的一种流程。在此过程中&#xff0c;对组织架构、人员配比、人才绩效、关键岗位的继任计划、关键人才发展、关键岗位的招聘及关键人才的晋升和激励进行深入讨论&#xff0c;并制定详细的组织行动计划&#xff0c;确保组织以更加优化的结构和…

非零基础自学计算机操作系统 第1章 操作系统概述 1.5 操作系统的硬件环境 1.5.1 定时装置 1.5.2 堆与栈 1.5.3 寄存器

非零基础自学计算机操作系统 文章目录非零基础自学计算机操作系统第1章 操作系统概述1.5 操作系统的硬件环境1.5.1 定时装置1.5.2 堆与栈1.5.3 寄存器第1章 操作系统概述 1.5 操作系统的硬件环境 构建一个高效、可靠的操作系统&#xff0c;硬件需要提供哪些支持&#xff1f; 1…

MySQL数据库基本使用(一)-------登录及查看基本信息

1.MySQL登录命令 格式如下&#xff1a; mysql -h 主机名 -P 端口号 -u 用户名 -p密码例如&#xff1a; mysql -h localhost -P 3306 -u root -pabc123 # 这里我设置的root用户的密码是abc123注意&#xff1a; -p与密码之间不能有空格&#xff0c;其他参数名与参数值之间可以…

Spring Boot启动原理源码

Spring Boot启动原理源码 注意:这个springboot启动源码和springboot自动配置原理的源码是十分重要的,面试的时候要是问springboot,一般都会问这两个。 源码&#xff1a; SpringBoot 事假监听器发布顺序&#xff1a; 1.ApplicationStartingEvent 2.ApplicationEnvironmentPrepa…

springboot+mybatis配置多数据源实战

1.背景说明 2.配置多数据源步骤 2.1 项目结构变更 2.2 添加配置类 2.3 修改配置文件数据连接配置信息 1.背景说明一般一个项目中只会连接一个数据库.但是随着需求变更,会要求同一个项目中连接多个数据库,本文就讲一下如何在一个项目中对多…

usaco training刷怪旅 第一层第二题 Greedy Gift Givers

usaco training 关注我持续创作training题解 翻译有点奇葩&#xff0c;我就上原题目了&#xff0c;各位自己翻译吧QwQ A group of NP (2 ≤ NP ≤ 10) uniquely named friends has decided to exchange gifts of money. Each of these friends might or might not give some m…

一种基于PCI总线的反射内存卡设计

一种基于PCI总线的反射内存卡设计 摘要&#xff1a; 对实时传输&#xff0c; 传统的以太网络由于传输协议开销的不确定性&#xff0c; 很难满足实时网络的要求&#xff0c; 实时网络是一种应用于高实时性要求的专用网络通信技术&#xff0c; 一般采用基于高速网络的共享存储器…

Python爬虫实战,requests+openpyxl模块,爬取小说数据并保存txt文档(附源码)

前言 今天给大家介绍的是Python爬取小说数据并保存txt文档&#xff0c;在这里给需要的小伙伴们代码&#xff0c;并且给出一点小心得。 首先是爬取之前应该尽可能伪装成浏览器而不被识别出来是爬虫&#xff0c;基本的是加请求头&#xff0c;但是这样的纯文本数据爬取的人会很多…

web网页设计与开发:基于HTML+CSS+JavaScript简单的个人博客网页制作期末作业

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

怎么让文字转换成语音?一步一步让你学会

在日常的生活中&#xff0c;我们经常会需要将文字转换成语音的情况&#xff0c;例如广告词、给文本配音等等&#xff0c;当然我们就简单的方法就是自己手动进行配音&#xff0c;但是如果没有专业的设备和配音环境&#xff0c;是很难配出很好的效果的&#xff0c;这该怎么办呢&a…

飞链云智能机器人-基于ChatGPT的有趣问答

最近ChatGPT火起来了&#xff1b; 可玩性很高&#xff0c;不亚于之前AI绘画的视觉冲击&#xff1b;这次ChatGPT带来的是逻辑冲击&#xff1b;上下文逻辑远超现有市面上其他所有的AI对话机器人&#xff1b; 有人用技巧训练ChatGPT&#xff0c;ChatGPT机器人宣言要毁灭人类&…

备战2023蓝桥国赛-传纸条

题目描述&#xff1a; 解析&#xff1a; 这道题想了我好久&#xff0c;一开始我是想假如只走一条路线&#xff0c;从(1,1)走到&#xff08;m,n&#xff09;&#xff0c;这种问题该怎么解决呢&#xff1f;针对这种问题我是设了dp[k][i][j]表示走了k步到达(i,j)的好心程度之和的…

[附源码]JAVA毕业设计迎宾酒店管理系统录屏(系统+LW)

[附源码]JAVA毕业设计迎宾酒店管理系统录屏&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目…

R语言中使用多重聚合预测算法(MAPA)进行时间序列分析

最近我们被客户要求撰写关于时间序列分析的研究报告&#xff0c;包括一些图形和统计输出。这是一个简短的演示&#xff0c;可以使用该代码进行操作。使用MAPA生成预测。 > mapasimple(admissions)t1 t2 t3 t4 t5 t6 t7 t8 t9 t…

ElasticsearchRestTemplate 和ElasticsearchRepository 的使用

操作ElasticSearch的数据&#xff0c;有两种方式一种是 ElasticsearchRepository 接口&#xff0c;另一种是ElasticsearchTemplate接口 SpringData对ES的封装ElasticsearchRestTemplate类&#xff0c;可直接使用&#xff0c;此类在ElasticsearchRestTemplate基础上进行性一定程…

Kibana:使用 Maps 来显示分布式的团队

在我之前的文章 “Kibana&#xff1a;如何在 Maps 应用中显示图片提示” 里&#xff0c;我展示了如何在 Kibana 中使用图片来展示一个图片的提示。这个在很多情况下是非常有用的&#xff0c;比如在疫情发生期间&#xff0c;我可以通过点击地图上的点来查看发生疫情人员的详细情…

ADI Blackfin DSP处理器-BF533的开发详解40:图像处理专题-GrayStretch 图像的灰度拉伸(含源码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 功能介绍 代码实现了图像的灰度拉伸&#xff0c;代码运行时&#xff0c;会通过文件系统打开工程文件根目下" …/ImageView"路径中的 t…

回归预测 | MATLAB实现WOA-GRU鲸鱼算法优化门控循环单元多输入单输出回归预测

回归预测 | MATLAB实现WOA-GRU鲸鱼算法优化门控循环单元多输入单输出回归预测 目录回归预测 | MATLAB实现WOA-GRU鲸鱼算法优化门控循环单元多输入单输出回归预测写在前面效果一览基本描述模型描述程序设计参考资料写在前面 程序获取 | 机器学习/深度学习程序获取方式&#xff0…

Django学习第一天记录

1.安装Django(Windows环境) 首先需要确定系统中存在python环境&#xff0c;当前&#xff0c;本机的python环境为python 3.6.6&#xff0c;可以使用命令python --version进行查看。 在python环境成功搭建的基础上&#xff0c;我们使用命令pip install django即可进行django环境…

虚拟现实解决方案,实现 VR 数智机房

如今&#xff0c;虚拟现实技术作为连接虚拟世界和现实世界的桥梁&#xff0c;正加速各领域应用形成新场景、新模式、新业态。 图扑软件基于自研可视化引擎 HT for Web 搭建的 VR 数据中心机房&#xff0c;是将数据中心的运营搬到 VR 虚拟场景。以数据中心实际场景为基础&#…