Kubernetes operator(五)api 和 apimachinery 篇

news2024/11/14 8:59:02

云原生学习路线导航页(持续更新中)

  • 本文是 Kubernetes operator学习 系列第五篇,主要对 k8s.io/api 和 k8s.io/apimachinery 两个项目 进行学习
  • 基于 kubernetes v1.24.0 代码分析
  • Kubernetes operator学习系列 快捷链接
    • Kubernetes operator(一)client-go篇
    • Kubernetes operator(二)CRD篇
    • Kubernetes operator(三)code-generator 篇
    • Kubernetes operator(四)controller-tools 篇
    • Kubernetes operator(五)api 和 apimachinery 篇

1.k8s.io/api 项目

1.1.k8s.io/api 项目是什么

  • 一开始,kubernetes的 内建资源 还不太多,内建资源的 结构定义,都是放在项目里维护的。
  • 后来为了方便资源的管理和扩展,将 所有内建资源 的结构定义文件、scheme注册文件、deepcopy等文件,放入了 staging/src 目录下,作为一个单独的项目维护。该项目的名称就是 k8s.io/api
  • 因此,简单来说,k8s.io/api 项目,维护着 Kubernetes 所有内建资源 的 struct定义。

1.2.k8s.io/api 的源码分析

  • 下图中的每一个目录,都代表一个group
    在这里插入图片描述
  • 一个 Group 下,可能会存在多个 Version
    在这里插入图片描述
  • 每个version下,都会包含三个文件:doc.go、register.go、types.go
    • doc.go:声明了按照 package 维度,为所有 structs 提供生成的声明
    • types.go:编写资源的详细结构,一般包括:资源、资源List、资源Spec、资源Status 的详细定义
    • register.go:提供注册到 runtime.Scheme 的函数
      在这里插入图片描述
  • apps/v1/types.go 为例,查看其内容,发现包含 GroupVersion=apps/v1 下的所有Resource结构定义。
    • 下图只截出了一部分,没有截出全部。
      在这里插入图片描述
  • 因此,我们操作内建资源的时候,所有 GVK 内建资源的结构,都是由 k8s.io/api 这个项目提供的。

2.k8s.io/apimachinery 项目

2.1.k8s.io/apimachinery 项目是什么

  • k8s.io/apimachinery 项目是一个关于Kubernetes API资源的工具集,为 k8s.io/api 项目所有的资源,提供下列能力。
    • ObjectMeta与TypeMeta
    • Scheme
    • RESTMapper
    • 编码与解码
    • 版本转换
  • 有了 k8s.io/apimachinery,就可以很方便的操作 kubernetes API。

2.2.k8s.io/apimachinery 提供 TypeMeta 与 ObjectMeta

  • TypeMeta 与 ObjectMeta 是特别常用的两个数据结构。kubernetes 的每一个资源,都会包含一个 TypeMeta、一个ObjectMeta。
    • TypeMeta是内嵌的,转json的时候不会有嵌套结构
    • ObjectMeta,json标签就是 metadata
    type Pod struct {
    	metav1.TypeMeta `json:",inline"`
    	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
    	Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
    	Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
    }
    
  • TypeMeta:位于 apimachinery/pkg/runtime/types.go
    type TypeMeta struct {
    	// +optional
    	APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty" protobuf:"bytes,1,opt,name=apiVersion"`
    	// +optional
    	Kind string `json:"kind,omitempty" yaml:"kind,omitempty" protobuf:"bytes,2,opt,name=kind"`
    }
    
  • ObjectMeta:位于 apimachinery/pkg/apis/meta/v1/types.go
    type ObjectMeta struct {
    	Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
    	GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"`
    	Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"`
    	SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"`
    	UID types.UID `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"`
    	ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"`
    	Generation int64 `json:"generation,omitempty" protobuf:"varint,7,opt,name=generation"`
    	CreationTimestamp Time `json:"creationTimestamp,omitempty" protobuf:"bytes,8,opt,name=creationTimestamp"`
    	DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" protobuf:"bytes,9,opt,name=deletionTimestamp"`
    	DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" protobuf:"varint,10,opt,name=deletionGracePeriodSeconds"`
    	Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"`
    	Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"`
    	OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"`
    	Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"`
    	ZZZ_DeprecatedClusterName string `json:"clusterName,omitempty" protobuf:"bytes,15,opt,name=clusterName"`
    	ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"`
    }
    

2.3.k8s.io/apimachinery 的rumetime/schema包提供 GVRK 各种数据结构

  • 在kubernetes中,为了方便描述资源,或描述REST 的URL,提出了5个概念:
    • GV:GroupVersion
    • GR:GroupResource
    • GVR:GroupVersionResource
    • GK:GroupKind
    • GVK:GroupVersionKind
  • 其中,GR、GVR都是用来描述 RESTFUL API 的,GK、GVK都是用来描述资源类型的
  • 5种数据结构的 struct 定义,都是写在 k8s.io/apimachinery/pkg/runtime/schema/group_version.go 文件中
  • 该文件中还提供了这5种数据结构相互转换的方法
    • 其中,APIVersion Kind,就是我们平时写yaml看到的apiVersion:Group/Version Kind

    • 各结构的转换方法如下:
      在这里插入图片描述

    • 源码如下:在这里插入图片描述
      在这里插入图片描述

2.4.k8s.io/apimachinery 提供 scheme 数据结构

2.4.1.资源的internal版本、external版本

2.4.1.1.资源的internal版本、external版本是什么
  • kubernetes的资源,并非一下就确定好的,是有一个发展过程的,因此一个资源Kind,可能在多个 GroupVersion 下同时存在。比如 Deployment,在apps/v1下存在,在apps/v1beta1下也存在。
  • 那么,在kubernetes的开发者想要处理Deployment的时候,到底应该按照哪个版本写程序呢?
    • 按理说,每一种GVK都要有相应的处理方法。
    • 但是这样实在是太繁琐了,维护起来不方便,还会有大量重复代码
  • 因此,为每一种GK,维护了一个internal版本,作为中转节点
    • apps/v1/Deploymentapps/v1beta1/Deployment 的相互转换,均是先转成internal的Deployment,再转成 对外的版本
    • kubernetes的作者们,只需要对 internal版本 的资源编写逻辑,就可以处理所有version的资源
  • 这种设计方式,将GVK之间的 拓扑结构,变成了星型结构,非常巧妙。
2.4.1.2.internal版本 和 external版本 相互转换的源码位置
  • kubernetes/pkg/apis 中,每个目录都是一个group,每个group都有一个 internal 的 资源 types.go 文件
    在这里插入图片描述
  • external 资源 --> internal 资源 的方法
    在这里插入图片描述

2.4.2.scheme的作用

  • kubernetes的资源版本太多了,没有谁专门有时间去维护,还是让资源自己来注册比较方便。
  • scheme就是为资源注册信息设计的一个数据结构,每个GVK,将自己的信息封装成一个scheme对象,并将这个scheme对象交给APIServer统一管理,API Server就能够认识这种 GVK 了
  • 在k8s.io/api 项目中,每一个GV下都有一个register.go文件,里面就是将当前GV下的所有Kind,注册到 APIServer 的统一scheme中去。比如 staging/src/k8s.io/api/apps/v1/register.go 文件,
    package v1
    
    import (
    	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    	"k8s.io/apimachinery/pkg/runtime"
    	"k8s.io/apimachinery/pkg/runtime/schema"
    )
    
    // GroupName is the group name use in this package
    const GroupName = "apps"
    
    // SchemeGroupVersion is group version used to register these objects
    var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
    
    // Resource takes an unqualified resource and returns a Group qualified GroupResource
    func Resource(resource string) schema.GroupResource {
    	return SchemeGroupVersion.WithResource(resource).GroupResource()
    }
    
    var (
    	// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
    	// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
    	SchemeBuilder      = runtime.NewSchemeBuilder(addKnownTypes)
    	localSchemeBuilder = &SchemeBuilder
    	AddToScheme        = localSchemeBuilder.AddToScheme
    )
    
    // Adds the list of known types to the given scheme.
    func addKnownTypes(scheme *runtime.Scheme) error {
    	scheme.AddKnownTypes(SchemeGroupVersion,
    		&Deployment{},
    		&DeploymentList{},
    		&StatefulSet{},
    		&StatefulSetList{},
    		&DaemonSet{},
    		&DaemonSetList{},
    		&ReplicaSet{},
    		&ReplicaSetList{},
    		&ControllerRevision{},
    		&ControllerRevisionList{},
    	)
    	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
    	return nil
    }
    

2.4.3.k8s.io/apimachinery 提供 scheme 数据结构

  • 根据前面的描述,我们知道scheme是一个数据结构,它的struct其实就是 k8s.io/apimachinery 提供的
  • staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go 文件中,有 Scheme 结构
    type Scheme struct {
        // map,记录 gvk-->type。其中type是通过反射的方式记录的
      	gvkToType map[schema.GroupVersionKind]reflect.Type
      
      	// map,记录 type-->gvk
      	typeToGVK map[reflect.Type][]schema.GroupVersionKind
      
      	// map,记录 type-->gvk。像pod这种,只有一个version的,就记录在这里。
      	unversionedTypes map[reflect.Type]schema.GroupVersionKind
      
      	// map,记录 gvk-->type。像pod这种,只有一个version的,就记录在这里。
      	unversionedKinds map[string]reflect.Type
      
      	// Map from version and resource to the corresponding func to convert
      	// resource field labels in that version to internal version.
      	fieldLabelConversionFuncs map[schema.GroupVersionKind]FieldLabelConversionFunc
      
      	// map,记录默认方法。为某一个具体的type,设置默认值
      	defaulterFuncs map[reflect.Type]func(interface{})
      
      	// 转换器
      	converter *conversion.Converter
      
      	// 记录version的优先级。当没有选择version的时候,优先使用谁
      	versionPriority map[string][]string
      
      	// observedVersions keeps track of the order we've seen versions during type registration
      	observedVersions []schema.GroupVersion
      
      	// schemeName is the name of this scheme.  If you don't specify a name, the stack of the NewScheme caller will be used.
      	// This is useful for error reporting to indicate the origin of the scheme.
      	schemeName string
    }
    
  • Scheme结构中,所有的字段首字母都是小写的,即非导出的,外界无法访问。为此,staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go 文件中还提供了一个方法 NewScheme(),用于初始化一个空的Scheme对象
    func NewScheme() *Scheme {
    	s := &Scheme{
    		gvkToType:                 map[schema.GroupVersionKind]reflect.Type{},
    		typeToGVK:                 map[reflect.Type][]schema.GroupVersionKind{},
    		unversionedTypes:          map[reflect.Type]schema.GroupVersionKind{},
    		unversionedKinds:          map[string]reflect.Type{},
    		fieldLabelConversionFuncs: map[schema.GroupVersionKind]FieldLabelConversionFunc{},
    		defaulterFuncs:            map[reflect.Type]func(interface{}){},
    		versionPriority:           map[string][]string{},
    		schemeName:                naming.GetNameFromCallsite(internalPackages...),
    	}
    	s.converter = conversion.NewConverter(nil)
    
    	// Enable couple default conversions by default.
    	utilruntime.Must(RegisterEmbeddedConversions(s))
    	utilruntime.Must(RegisterStringConversions(s))
    	return s
    }
    
  • 此外,staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go 文件还提供了很多方法,用于将GVK注册到Scheme对象中。用的比较多的是AddKnownTypes()方法
    func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) {
    	s.addObservedVersion(gv)
    	for _, obj := range types {
    		t := reflect.TypeOf(obj)
    		if t.Kind() != reflect.Ptr {
    			panic("All types must be pointers to structs.")
    		}
    		t = t.Elem()
    		s.AddKnownTypeWithName(gv.WithKind(t.Name()), obj)
    	}
    }
    

2.5.3.Scheme结构提供的常用方法

2.5.3.1.AddKnownTypes
  • 方法签名:func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object)
  • 方法功能:向 scheme 中注册GVK,参数1 gv 表示 GroupVersion,参数2 types 是具体的 Kind 类型
  • 举例:staging/src/k8s.io/api/apps/v1/register.go 文件中,使用 AddKnownTypes 方法,将apps/v1下的所有Kind,都注册到scheme中去
    func addKnownTypes(scheme *runtime.Scheme) error {
    	scheme.AddKnownTypes(SchemeGroupVersion,
    		&Deployment{},
    		&DeploymentList{},
    		&StatefulSet{},
    		&StatefulSetList{},
    		&DaemonSet{},
    		&DaemonSetList{},
    		&ReplicaSet{},
    		&ReplicaSetList{},
    		&ControllerRevision{},
    		&ControllerRevisionList{},
    	)
    	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
    	return nil
    }
    
2.5.3.2.KnownTypes
  • 方法签名:func (s *Scheme) KnownTypes(gv schema.GroupVersion) map[string]reflect.Type
  • 方法功能:获取指定GV下所有Kind的Type类型
  • 举例
    types := Scheme.KnownTypes(schema.GroupVersion{
        Group:   "apps",
        Version: "v1",
    })
    
2.5.3.3.VersionsForGroupKind
  • 方法签名:func (s *Scheme) VersionsForGroupKind(gk schema.GroupKind) []schema.GroupVersion
  • 方法功能:获取指定GK的所有Versions,并以GV列表的形式返回
  • 举例
    groupVersions := Scheme.VersionsForGroupKind(
    	schema.GroupKind{
            Group: "apps",
            Kind:  "Deployment",
    })
    // 输出
    ["apps/v1" "apps/v1beta1" "apps/v1beta2"]
    
2.5.3.4.ObjectKinds
  • 方法签名:func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error)
  • 方法功能:获取指定object 的 所有可能的 group、version、kind 值,并以 GVK 列表的形式返回
  • 举例
    gvks, notVersioned, err := Scheme.ObjectKinds(&appsv1.Deployment{})
    // 输出
    ["apps/v1 Deployment"]
    
2.5.3.5.New
  • 方法签名:func (s *Scheme) New(kind schema.GroupVersionKind) (Object, error)
  • 方法功能:根据指定的GVK,创建该资源的一个对象
  • 举例:
    deployment, err := Scheme.New(schema.GroupVersionKind{
        Group:   "apps",
        Version: "v1",
        Kind:    "Deployment",
    })
    
2.5.3.6.AddConversionFunc
  • 方法源码
    func (s *Scheme) AddConversionFunc(a, b interface{}, fn conversion.ConversionFunc) error {
    	return s.converter.RegisterUntypedConversionFunc(a, b, fn)
    }
    
  • 该方法,用于向scheme中注册 不同资源 的自定义转换器。

2.5.k8s.io/apimachinery 提供 RESTMapper 结构

2.5.1.理解GVR和GVK的用途

  • 在 上面2.3 中提到,k8s.io/apimachinery 提供了 GR/GVR、GK/GVK 等数据结构。GR和GVR 负责对接 RESTful 风格的url路径,GK和GVK 负责确定一个具体的kubernetes资源
  • GVR举例
    • 用户想要获取 apps组下、v1版本的 deployments,如何编写url地址?–> GET /apis/apps/v1/deployments
    • 这个url中,就可以使用 GVR 描述,group为apps,version为v1,Resource为deployments
  • GVK举例
    • 当kubernetes的代码中,想要操作一个资源的时候,如何找到资源的struct 结构?通过GVK去找
    • 比如 apps/v1/Deployment,就可以确定 group为apps,version为v1,kind为Deployment,就可以找到这个资源的struct

2.5.2.RESTMapper是什么

  • 当用户使用 REST风格 的 url 访问资源时,kubernetes如何确定需要操作哪一个GVK呢?
  • REST风格 的 url,可以从中得到 GVR,只需要完成 GVR 到 GVK 的转换就可以了
  • 因此,apimachinery维护了一个数据结构 RESTMapper,记录 GVR 和 GVK 的映射关系
    type RESTMapping struct {
    	// Resource is the GroupVersionResource (location) for this endpoint
    	Resource schema.GroupVersionResource
    
    	// GroupVersionKind is the GroupVersionKind (data format) to submit to this endpoint
    	GroupVersionKind schema.GroupVersionKind
    
    	// Scope contains the information needed to deal with REST Resources that are in a resource hierarchy
    	Scope RESTScope
    }
    
  • 另外,apimachinery还提供了一个接口 RESTMapper,接口中提供了 将 GVR 转成 GVK 的方法
    • 其中,KindForKindsFor 就是将 GVR 转成 GVK 的方法
    type RESTMapper interface {
    	// KindFor takes a partial resource and returns the single match.  Returns an error if there are multiple matches
    	KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error)
    
    	// KindsFor takes a partial resource and returns the list of potential kinds in priority order
    	KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error)
    
    	// ResourceFor takes a partial resource and returns the single match.  Returns an error if there are multiple matches
    	ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error)
    
    	// ResourcesFor takes a partial resource and returns the list of potential resource in priority order
    	ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error)
    
    	// RESTMapping identifies a preferred resource mapping for the provided group kind.
    	RESTMapping(gk schema.GroupKind, versions ...string) (*RESTMapping, error)
    	// RESTMappings returns all resource mappings for the provided group kind if no
    	// version search is provided. Otherwise identifies a preferred resource mapping for
    	// the provided version(s).
    	RESTMappings(gk schema.GroupKind, versions ...string) ([]*RESTMapping, error)
    
    	ResourceSingularizer(resource string) (singular string, err error)
    }
    

2.5.3.DefaultRESTMapper

  • RESTMapper接口,有一个默认的实现 DefaultRESTMapper
    • 位于 staging/src/k8s.io/apimachinery/pkg/api/meta/restmapper.go
    type DefaultRESTMapper struct {
    	defaultGroupVersions []schema.GroupVersion
    
    	resourceToKind       map[schema.GroupVersionResource]schema.GroupVersionKind
    	kindToPluralResource map[schema.GroupVersionKind]schema.GroupVersionResource
    	kindToScope          map[schema.GroupVersionKind]RESTScope
    	singularToPlural     map[schema.GroupVersionResource]schema.GroupVersionResource
    	pluralToSingular     map[schema.GroupVersionResource]schema.GroupVersionResource
    }
    
  • staging/src/k8s.io/apimachinery/pkg/api/meta/restmapper.go 中还提供了一个 NewDefaultRESTMapper 方法,用于新建一个DefaultRESTMapper

2.6.k8s.io/apimachinery 提供序列化、编解码能力

2.6.1.k8s.io/apimachinery 的 runtime.serializer 包

  • k8s.io/apimachinery 中,关于 序列化 和 编解码 的代码,大都在 staging/src/k8s.io/apimachinery/pkg/runtime/serializer 包下
    在这里插入图片描述
  • json、protobuf、yaml包,分别提供了对应格式的序列化器,共3种序列化器

2.6.2.k8s.io/apimachinery 提供了序列化的通用接口

  • staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go 文件中,提供了序列化的通用接口 Serializer。Serializer接口提供了编解码能力。
    type Serializer interface {
    	Encoder
    	Decoder
    }
    
  • Encoder是编码器接口,还是在 staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go 文件中
    type Encoder interface {
    	Encode(obj Object, w io.Writer) error
    	Identifier() Identifier
    }
    
  • Decoder是解码器接口,还是在 staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go 文件中
    type Decoder interface {
    	Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error)
    }
    

2.6.3.json 序列化器

  • staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go 文件中,提供了json序列化器
    type Serializer struct {
    	meta    MetaFactory
    	options SerializerOptions
    	creater runtime.ObjectCreater
    	typer   runtime.ObjectTyper
    
    	identifier runtime.Identifier
    }
    
  • json序列化器 实现了 runtime.interface.go 中的Serializer接口,实现了 Encode、Decode方法
  • 创建一个json序列化器,有多个方法
    NewSerializerNewSerializerWithOptions

2.6.4.yaml 序列化器

  • staging/src/k8s.io/apimachinery/pkg/runtime/serializer/yaml/yaml.go 文件中,提供了yaml序列化器
    type yamlSerializer struct {
    	// the nested serializer
    	runtime.Serializer
    }
    
  • yaml序列化器 实现了 runtime.interface.go 中的Serializer接口

2.6.5.protobuf 序列化器

  • staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go 文件中,提供了protobuf序列化器
    type Serializer struct {
    	prefix  []byte
    	creater runtime.ObjectCreater
    	typer   runtime.ObjectTyper
    }
    
  • protobuf序列化器 实现了 runtime.interface.go 中的Serializer接口

2.7.k8s.io/apimachinery 提供 不同资源 相互转换能力

  • scheme提供了AddConversionFunc方法,用于向scheme中注册 不同资源 的自定义转换器。
  • 举例:创建了一个Scheme对象,名为scheme。我们就可以通过下面的方法,注册 appsv1.Deployment 与 appsv1beta1.Deployment 的相互转换方法
    scheme.AddConversionFunc(
        (*appsv1.Deployment)(nil),
        (*appsv1beta1.Deployment)(nil),
        func(a, b interface{}, scope conversion.Scope) error{
            v1deploy := a.(*appsv1.Deployment)
            v1beta1deploy := b.(*appsv1beta1.Deployment)
            // make conversion here
            return nil
    })
    

3.参考博客

  • Kubernetes学习笔记[第5章]API Machinery
  • runtime/Serializer源码分析

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

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

相关文章

数字巨轮航行大数据海洋:数据可视化引领时代潮流

在大数据时代的潮流中,数据可视化如同一艘畅行无阻的科技巨轮,引领我们穿越数字浩瀚的大海,使我们在信息的航程中游刃有余。下面我就从可视化从业者的角度,来简单说说数据可视化是如何帮助我们在大数据时代畅行无阻的。 数据可视化…

【Servlet】——Servlet API 详解

个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Servlet】 本专栏旨在分享学习Servlet的一点学习心得,欢迎大家在评论区交流讨论💌 目录 一、HttpServlet二、Htt…

【unity小技巧】unity3d环境带雾的昼夜系统变化

最终效果 文章目录 最终效果眩光素材眩光配置全局灯光配置天空盒配置天空盒资产配置天空盒,开启雾 代码控制天空盒 环境 雾 灯光昼夜交替变化参考完结 眩光素材 链接:https://pan.baidu.com/s/1qlFSJSju6ZjwCylwkh14eA?pwdveww 提取码:veww…

Oracle和Mysql数据库

数据库 Oracle 体系结构与基本概念体系结构基本概念表空间(users)和数据文件段、区、块Oracle数据库的基本元素 Oracle数据库启动和关闭Oracle数据库启动Oracle数据库关闭 Sqlplussqlplus 登录数据库管理系统使用sqlplus登录Oracle数据库远程登录解锁用户修改用户密码查看当前语…

Android学习之路(29) Gradle初探

前言: 大家回想一下自己第一次接触Gradle是什么时候? 相信大家也都是和我一样,在我们打开第一个AS项目的时候, 发现有很多带gradle字样的文件:setting.gradle, build.gradle,gradle.warpper,以及在gradle文件中各种配置&#xff…

来看看Tomcat和Web应用的目录结构

在前面两篇大致了解了Tomcat的架构和运行流程,以及Tomcat应用中的web.xml。 聊一聊Tomcat的架构和运行流程,尽量通俗易懂一点-CSDN博客 来吧,好好理解一下Tomcat下的web.xml-CSDN博客 那接下来,再看看Tomcat的目录,…

BeanDefinitionRegistry学习

Spring版本5.1.x 简介 在Spring框架中,BeanDefinitionRegistry是一个接口,它主要用于向注册表中注册BeanDefinition实例,完成注册的过程。该接口的主要方法是registerBeanDefinition,用于将一个BeanDefinition实例注册到注册表中…

分享一个WPF项目

最近在学习WPF开发方式,找到一些项目进行拆解学习;本位主要分享一个WPF项目,叫做WPFDevelopers,在git上大约有1.3K星,话不多说,先看看效果: 这个项目开发可以编译启动后直接查看样例、Xaml、Cha…

如何搭建私有云盘SeaFile并实现远程访问本地文件资料

🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​💫个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-hsDnDEybLME85dTx {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

让AI作为人类的“副驾驶”

让AI作为副驾驶,辅助人类完成工作,是人工智能技术在实际应用中的一个重要方向。这个设想在多个领域都有广泛应用的可能性: 驾驶场景:自动驾驶技术中,AI副驾驶可以实时监测路况、规划最优行驶路线、预警潜在危险&#x…

接口自动化测试详解

🍅 视频学习:文末有免费的配套视频可观看 🍅 关注公众号【互联网杂货铺】,回复 1 ,免费获取软件测试全套资料,资料在手,涨薪更快 引言 与UI相比,接口一旦研发完成,通常变…

Unity | Spine动画记录

https://blog.csdn.net/linshuhe1/article/details/79792432 https://blog.csdn.net/winds_tide/article/details/128925407 1.需要的三个文件 通常制作好的 Spine 动画导出时会有三个文件: .png 、.json 和 .atlas: skeleton-name.json 或 skeleton-…

【10秒开服】雾锁王国服务器全自动部署教程

你是火焰之子,一个濒死种族最后的希望火苗。苏醒吧,克服腐化一切的迷雾所裹挟的恐怖,重新夺回你的王国所失落的瑰丽。置身于广袤世界,战胜难以想象的强大Boss,修造宏伟厅堂,在这款至多16名玩家的合作类生存…

LF will be replaced by CRLF the next time Git touches it

参考这篇文章git提示“warning: LF will be replaced by CRLF”的解决办法-CSDN博客

问题:魁奈在税收政策方面的主张是() #学习方法#其他#经验分享

问题:魁奈在税收政策方面的主张是() A.征收农业税 B.征收工商业税 C.征收间接税 D.征收地租税 参考答案如图所示

挑战杯 opencv 图像识别 指纹识别 - python

0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于机器视觉的指纹识别系统 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:3分工作量:3分创新点:4分 该项目较为新颖,适…

奇瑞瑞虎8,是真不能随便碰

文 | AUTO芯球 作者 | 李虎 我是实在看不下去了啊 这奔驰车主砸车 现在开始反转了啊 但卡住我喉咙的是定损5200的奇瑞引擎盖啊 我是真买不起,也不敢买啊 我怕A柱断了,要修20万啊 但我一算,这也不对啊 顶配的报价也只有16.18万啊 如果…

TCP TIME_WAIT 过多怎么处理

文章目录 1.什么是 TCP TIME_WAIT?2.为什么要 TIME_WAIT?3.TIME_WAIT 过多的影响4.解决办法4.1 调整短连接为长连接4.2 调整系统内核参数 5.小结参考文献 1.什么是 TCP TIME_WAIT? TCP 断开连接四次挥手过程中,主动断开连接的一方&#xff…

计算机毕业设计SSM基于的流浪宠物救助系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: vue mybatis Maven mysql5.7或8.0等等组成,B…

嵌入式中VMware 三种网络模式的详解与配置方法

今天梳理下 VMware虚拟机的三种网络工作模式。 相信通过这两篇文章,能够帮助你全面搞懂虚拟机的网络工作模式和配置。 VMware的三种网络工作模式 vmware为我们提供了三种网络工作模式, 它们分别是: Bridged(桥接模式)、NAT(网…