GoLong的学习之路,进阶,微服务之序列化协议,Protocol Buffers V3

这章是接上一章,使用RPC包,序列化中没有详细去讲,因为这一块需要看的和学习的地方很多。并且这一块是RPC中可以说是最重要的一块,也是性能的重要影响因子。今天这篇主要会讲其使用方式。

文章目录

  • Protocol Buffers V3 背景以及概念
  • 如何使用?
    • 分配字段编号
      • 指定字段规则
    • 数据类型
      • `标量类型`
      • `复合类型`
        • 枚举
        • 其他消息类型
        • 嵌套类型
        • 更新消息类型
        • 未知字段
        • Any
        • oneof
          • oneof 特性
          • 向后兼容性问题
        • Maps
  • JSON 映射

Protocol Buffers V3 背景以及概念

序列化是系统通信的基础组件,在大数据AI框架云原生分布式系统中广泛使用。

当对象需要跨进程跨语言跨节点传输持久化状态读写复制时,都需要进行序列化,其性能易用性影响运行效率开发效率

但是对于序列化框架而言,业内将其分为两类:静态序列化框架,动态序列化框架。

其中各有优缺点:

  • 静态序列化框架
    • 不支持对象引用和多态、需要提前生成代码等原因,无法作为领域对象直接面向应用进行跨语言开发
    • 常见的有:protobufflatbufferthrift
  • 动态序列化框架
    • 提供了易用性和动态性,但不支持跨语言,且性能存在显著不足,并不能满足高吞吐、低延迟和大规模数据传输场景需求
    • 常见的有:JDK序列化KryoFstHessianPickle

但是前几天阿里推出了Fury,号称比JDK快了170倍,并且兼具静态序列化和动态序列化的优点。这个下一章会讲。目前的gRPC框架是用protobuf搭建的。所以咱们先让自己的项目成功运行起来再说。所以这个后面再说。

Protocol Buffersgoogle开源的一种结构数据序列化机制,可跨语言、跨平台。

相比XMLJSONThrift等其他序列化格式,Protocol Buffers的序列化和反序列化性能是很高的,且Protocol Buffers序列化后是二进制流,因此数据大小和传输速度是很好的。

所以它非常适合在数据存储RPC 数据交换的场景下使用。

以下使用手法是翻译自官网

如何使用?

定义一个 .proto 文件

定义一个搜索请求消息格式,其中每个搜索请求都包含:

  • 一个查询词字符串
  • 你感兴趣的查询结果所在的特定页码数
  • 每一页应展示的结果数
syntax = "proto3";message SearchRequest {string query = 1;int32 page_number = 2;int32 result_per_page = 3;
}
  • 文件的第一行指定使用 proto3 语法。

如果不这样写,protocol buffer编译器将假定你使用 proto2。这个声明必须是文件的第一个非空非注释行。

  • SearchRequest 消息定义指定了三个字段(名称/值对) ,每个字段表示希望包含在此类消息中的每一段数据。每个字段都有一个名称和一个类型

指定字段类型:

  • 两个整数page_number result_per_page和一个字符串query(上面的例子)
  • 但是也可以为字段指定组合类型,包括枚举和其他消息类型

proto3语法主要包括:

  1. 消息(message)定义
  2. 服务(service)定义
  3. 其他部分语法

一个message内的字段一般包含:

  • 数据类型
  • 字段名
  • 字段编号tag

添加更多消息类型:

可以在一个.proto 文件中定义多个消息类型。定义与 SearchRequest 消息类型对应的应答消息格式SearchResponse,就可以将其添加到同一个.proto文件中。

message SearchRequest {string query = 1;int32 page_number = 2;int32 result_per_page = 3;
}message SearchResponse {...
}

添加注释:

要给你的.proto文件添加注释,需要使用C/C++风格的///* ... */语法。

保留字段:

  • 如果通过完全删除字段或将其注释掉来更新消息类型,那么未来的用户在对该类型进行自己的更新时可以重用字段号。
  • 如果其他人以后加载旧版本的相同.proto文件,这可能会导致严重的问题,包括数据损坏,隐私漏洞等等。

分配字段编号

消息定义中的每个字段都有一个 唯一的编号

这些字段编号用来在消息二进制格式中标识字段,在消息类型使用后就不能再更改

注意

  • 范围115中的字段编号需要一个字节进行编码,包括字段编号和字段类型
  • 范围162047的字段编号采用两个字节

应该为经常使用的消息元素保留数字1到15的编号。切记为将来可能添加的经常使用的元素留出一些编号。

可以指定的最小字段数是 1 ,最大的字段数是 2 29 − 1 可以指定的最小字段数是1,最大的字段数是 2^{29}−1 可以指定的最小字段数是1,最大的字段数是2291
即536,870,911。

也不能使用1900019999,它们是预留给Protocol Buffers协议实现的。

如果你在你的.proto文件中使用了预留的编号Protocol Buffers编译器就会报错。

同样,你也不能使用任何之前保留的字段编号。

指定字段规则

消息字段可以是下列字段之一:

  • singular: 格式正确的消息可以有这个字段的零个或一个(但不能多于一个)。这是 proto3语法的默认字段规则。
  • repeated: 该字段可以在格式正确的消息中重复任意次数(包括零次)。重复值的顺序将被保留。

确保这种情况不会发生的一种方法是指定已删除字段的字段编号(和/名称,这也可能导致 JSON 序列化问题)是保留的reserved

如果将来有任何用户尝试使用这些字段标识符,protocol buffer编译器将发出提示。

message Foo {reserved 2, 15, 9 to 11;reserved "foo", "bar";
}

注意:不能在同一个reserved语句中混合字段名字段编号

当你使用 protocol buffer 编译器来运行.proto文件时,编译器用你选择的语言生成你需要使用文件中描述的消息类型,包括获取和设置字段值,将消息序列化为输出流,以及从输入流解析消息的代码。

  • C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
  • 对于 Java,编译器生成一个.java 文件,每种消息类型都有一个类,还有一个特殊的 Builder 类用于创建消息类实例。
  • 对于 Kotlin,除了 Java 生成的代码之外,编译器还生成一个每种消息类型的 .kt 文件,包含一个 DSL,可用于简化消息实例的创建。
  • Python 稍有不同ー Python 编译器为.proto文件中的每个消息类型生成一个带静态描述符的模块,然后与 metaclass 一起使用,在运行时创建必要的 Python 数据访问类。
  • 对于 Go,编译器为文件中的每种消息类型生成一个类型(type)到一个.pb.go 文件。
  • 对于 Ruby,编译器生成一个.rb 文件,其中包含一个包含消息类型的 Ruby 模块。
  • 对于 Objective-C,编译器从每个.proto文件生成一个 pbobjc.h 和 pbobjc.m 文件,.proto文件中描述的每种消息类型都有一个类。
  • 对于 C# ,编译器生从每个.proto文件生成一个.cs 文件。.proto文件中描述的每种消息类型都有一个类。
  • 对于 Dart,编译器为文件中的每种消息类型生成一个.pb.dart 文件。

数据类型

数据类型:标量类型复合类型

标量类型

在这里插入图片描述
当解析消息时,如果编码消息不包含特定的 singular 元素,则解析对象中的相应字段将设置为该字段的默认值。

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于布尔值,默认值为 false。
  • 对于数值类型,默认值为零。
  • 对于枚举,默认值是第一个定义的枚举值,该值必须为0。
  • 对于消息字段,未设置该字段。其确切值与语言有关。
  • repeated 字段的默认值为空(通常是适当语言中的空列表)。

请注意,对于标量消息字段,一旦消息被解析,就无法判断字段是显式设置为默认值(例如,是否一个布尔值是被设置为 false)还是根本没有设置: 在定义消息类型时应该牢记这一点。例如,如果你不希望某个行为在默认情况下也发生,那么就不要设置一个布尔值,该布尔值在设置为 false 时会开启某些行为。还要注意,如果将标量消息字段设置为默认值,则该值将不会在传输过程中序列化。

复合类型

复合类型包括:枚举、嵌套其他message、Any(Map,Oneof)等

枚举

在定义消息类型时,你可能希望其中一个字段只能是预定义的值列表中的一个值。

可以通过在消息定义中添加一个枚举,为每个可能的值添加一个常量来非常简单地完成这项工作。

message SearchRequest {string query = 1;int32 page_number = 2;int32 result_per_page = 3;enum Corpus {UNIVERSAL = 0;WEB = 1;IMAGES = 2;LOCAL = 3;NEWS = 4;PRODUCTS = 5;VIDEO = 6;}Corpus corpus = 4;
}

Corpus enum 的第一个常量映射为零: 每个 enum 定义必须包含一个常量,该常量映射为零作为它的第一个元素。

  1. 必须有一个零值,这样我们就可以使用0作为数值默认值。
  2. 零值必须是第一个元素,以便与 proto2语义兼容,其中第一个枚举值总是默认值。

你可以通过将相同的值分配给不同的枚举常量来定义别名。为此,你需要将 allow _ alias 选项设置为 true,否则,当发现别名时,protocol 编译器将生成错误消息。

内部定义

message MyMessage1 {enum EnumAllowingAlias {option allow_alias = true;UNKNOWN = 0;STARTED = 1;RUNNING = 1;}
}
message MyMessage2 {enum EnumNotAllowingAlias {UNKNOWN = 0;STARTED = 1;// RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.}
}

注意:枚举的常数必须在32位整数的范围内。

由于枚举值在传输时使用变长编码,因此负值效率低,因此不推荐使用。

可以在消息定义中定义枚举,如上面的例子所示,也可以在外面定义——这样就可以在.proto文件中的消息定义中重用这些枚举。

外部定义

enum Ezarten {option allow_alias = true; //开启枚举值重复开关ZARTEN1 = 0;ZARTEN2 = 1;ZARTEN3 = 2;ZARTEN4 = 2; //开启option allow_alias = true后枚举值可以重复}//定义一个message类型
message ZartenOne {string name = 1;int32 age = 2;int32 height = 3;Ezarten ezarten = 4;
}

可以使用_MessageType_._EnumType_ 语法,使用在一个消息中声明的enum类型作为不同消息中的字段类型。

使用消息内的枚举
若枚举定义在内部,其他message要使用这个枚举,可以使用 “message名.枚举名”的形式:

//定义一个message类型
message ZartenOne {string name = 1;int32 age = 2;int32 height = 3;enum Ezarten {ZARTEN1 = 0;ZARTEN2 = 1;ZARTEN3 = 2;}Ezarten ezarten = 4;
}//定义一个message类型
message ZartenTwo {string name = 1;int32 age = 2;int32 height = 3;ZartenOne.Ezarten ezarten = 4;
}

当对一个使用了枚举的.proto文件运行 protocol buffer 编译器的时候,对于 Java Kotlin,或 C++ 生成的代码中将有一个对应的enum,或者对于 Python 会生成一个特殊的EnumDescriptor类,它被用于在运行时生成的类中创建一组带有整数值的符号常量。

生成的代码可能会受到特定于语言的枚举数限制(单种语言的数量低于千)

反序列化过程中,不可识别的枚举值将保留在消息中,尽管当消息被反序列化时,这种值的表示方式依赖于语言。

  • 开放枚举:在支持值超出指定符号范围(如 C++ 和 Go)开放枚举类型的语言中,未知枚举值仅存储为其底层的整数表示形式。
  • 闭合枚举类型:在具有闭合枚举类型 (如 Java)的语言中,枚举中的一个类型将用于表示一个无法识别的值,并且可以使用特殊的访问器访问底层的整数。

在这两种情况下,如果消息被序列化,那么不可识别的值仍然会与消息一起被序列化。

其他消息类型

你可以使用其他消息类型作为字段类型:

message SearchResponse {repeated Result results = 1;
}message Result {string url = 1;string title = 2;repeated string snippets = 3;
}

如果你希望用作字段类型的消息类型已经在另一个.proto文件中定义了,该怎么办?

你可以通过 import 来使用来自其他.proto文件的定义。要导入另一个.proto 的定义,你需要在文件顶部添加一个import语句

import "myproject/other_protos.proto";

默认情况下,只能从直接导入的.proto文件中使用定义。但是,有时你可能需要将 .proto 文件移动到新的位置。你可以在旧目录放一个占位的.proto文件使用import public 概念将所有导入转发到新位置,而不必直接移动.proto文件并修改所有的地方。

import public依赖项可以被任何导入包含import public语句的proto的代码传递依赖。
语法:

  • import
  • import public
  1. 相同的是,在文件A中两者可以直接引用它们上一级proto文件B的内容。
  2. 不同的是,若文件B内使用了import引用文件C,则文件A不能使用文件C的内容;

若文件B内使用了import public引用文件C,则文件A可以使用文件C的内容。

类似于编程语言中的类是否可以继承的含义。

文件:new.proto

所有的定义都被移到了这里

文件:old.proto
这是所有客户端都要导入的原型

import public "new.proto";
import "other.proto";

文件:client.proto
你可以使用old.protonew.proto,但是不能使用 other.proto

protocol 编译器使用命令行-I/--proto_path参数指定的一组目录中搜索导入的文件。如果没有给该命令行参数,则查看调用编译器的目录。

一般来说,你应该将--proto_path参数设置为项目的根目录并为所有导入使用正确的名称。

嵌套类型

方式1:

message SearchResponse {message Result {string url = 1;string title = 2;repeated string snippets = 3;}repeated Result results = 1;
}

方式2:
要在其父消息类型之外重用此消息类型,通过_Parent_._Type_使用。

message SomeOtherMessage {SearchResponse.Result result = 1;
}

方式3:一层又一层嵌入其中

message Outer {                  // Level 0message MiddleAA {  // Level 1message Inner {   // Level 2int64 ival = 1;bool  booly = 2;}}message MiddleBB {  // Level 1message Inner {   // Level 2int32 ival = 1;bool  booly = 2;}}
}
更新消息类型

在不破坏任何现有代码的情况下更新消息类型非常简单:只需记住以下规则:

  • 不要更改任何现有字段的字段编号
  • 如果添加新字段
    • 那么任何使用“旧”消息格式通过代码序列化的消息仍然可以通过新生成的代码进行解析。
      • 你应该记住这些元素的默认值,以便新代码能够正确地与旧代码生成的消息交互。
      • 类似地,新代码创建的消息可以通过旧代码解析: 旧的二进制文件在解析时直接忽略新字段。
  • 字段可以被删除,只要字段编号不再用于你更新的消息类型。
    • 你可能希望改为重命名字段,或者为其添加"OBSOLETE_“前缀,或者声明字段编号为reserved,以便.proto的未来用户不可能不小心重复使用这个编号。
  • int32uint32int64uint64bool都是兼容的——这意味着你可以在不破坏向前或向后兼容性的情况下将一个字段从这些类型中的一个更改为另一个。
  • 如果一个数字被解析到一个并不适当的类型中,你会得到与在 C++ 中将数字转换为该类型相同的效果
    • 例如,如果一个64位的数字被读作 int32,它将被截断为32位
  • sint32sint64相互兼容,但与其他整数类型不兼容
  • stringbytes是兼容的,只要字节是有效的 UTF-8
  • 如果字节包含消息的编码版本,则嵌入的消息与bytes兼容
  • fixed32sfixed32兼容 fixed64sfixed64兼容。
  • 对于stringbytes消息字段optional字段与repeated字段兼容。
    • 给定重复字段的序列化数据作为输入
      • 如果该字段是基本类型字段,期望该字段为可选字段的客户端将接受最后一个输入值
      • 如果该字段是消息类型字段,则合并所有输入元素
    • 注意,这对于数字类型,包括 boolsenums通常是不安全的。
      • 重复的数值类型字段可以按packed的格式序列化,如果是optional字段,则无法正确解析这些字段
  • Enum 在格式方面与 int32uint32int64uint64兼容(请注意,如果不适合,值将被截断)。
    • 但是要注意,当消息被反序列化时,客户端代码可能会区别对待它们:
      • 例如,未被识别的 proto3 enum将保留在消息中,但是当消息被反序列化时,这种类型的表示方式依赖于语言。Int字段总是保留它们的值。
  • 将单个值更改为新的oneof成员是安全的,并且二进制兼容。
    • 如果确保没有代码一次设置多个字段,那么将多个字段移动到新的oneof字段中可能是安全的。
    • 将任何字段移动到现有的字段中都是不安全的。
未知字段

未知字段是格式良好的协议缓冲区序列化数据,表示解析器不识别的字段。
旧二进制解析由新二进制发送的带有新字段的数据时,这些新字段将成为旧二进制中的未知字段

3.5版本中,我们重新引入了未知字段的保存来匹配 proto2行为。在3.5及以后的版本中,解析期间保留未知字段,并将其包含在序列化输出中

Any

Any 消息类型允许你将消息作为嵌入类型使用,而不需要其.proto定义。Any包含一个任意序列化的字节消息,以及一个解析为该消息的类型作为消息的全局唯一标识符的URL

要使用Any类型,需要导入google/protobuf/any.proto

给定消息类型的默认类型 URLtype.googleapis.com/_packagename_._messagename_

import "google/protobuf/any.proto";message ErrorStatus {string message = 1;repeated google.protobuf.Any details = 2;
}

不同的语言实现将支持运行库助手以类型安全的方式打包和解包 Any值:

  • 在java中,Any类型会有特殊的pack()和unpack()访问器,

  • 在C++中,Any类型会有特殊的PackFrom()和UnpackTo()方法。

oneof

如果你有一条包含多个字段的消息,并且最多同时设置其中一个字段,那么你可以通过使用oneof来实现并节省内存,优化

oneof字段类似于常规字段,只不过oneof中的所有字段共享内存,而且最多可以同时设置一个字段。

设置其中的任何成员都会自动清除所有其他成员。

根据所选择的语言,可以使用特殊 case()WhichOneof() 方法检查 oneof 中的哪个值被设置

在生成的代码中,其中一个字段具有与常规字段相同的 getter setter

你还可以获得一个特殊的方法来检查其中一个设置了哪个值

message SampleMessage {oneof test_oneof {string name = 4;SubMessage sub_message = 9;}
}

然后将其中一个字段添加到该字段的定义中。

你可以添加任何类型的字段,除了map字段repeated字段

oneof有很多特性,具体的个人建议去看文档:

oneof 特性
  • 设置一个字段将自动清除该字段的所有其他成员。
    • 因此,如果你设置了多个 oneof字段,那么只有最后设置的字段仍然具有值。
  • 如果解析器在连接中遇到同一个成员的多个成员,则只有最后看到的成员用于解析消息。
  • oneof 不支持repeated。
  • 反射 api 适用于 oneof 字段。
  • 如果将 oneof 字段设置为默认值(例如将 int32 oneof 字段设置为0) ,则将设置该字段的“ case”,并在连接上序列化该值。
  • 如果使用 C++ ,确保你的代码不会导致内存崩溃。
    • 因为通过调用 set_name()方法已经删除了 sub_message
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name");      // 删除name
sub_message->set_...            // 这里的崩溃
  • 在C++中,如果你使用Swap()两个 oneof 消息,每个消息,两个消息将拥有对方的值
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
向后兼容性问题

添加或删除一个字段时要小心。

  • 如果检查one of的值返回None/NOT_SET,这可能意味着 one of 没有被设置,或者它已经被设置为one of的不同版本中的一个字段。
  • 这没有办法区分,因为没有办法知道未知字段是否是 oneof 的成员。

标签重用问题:

  • 将字段移入移出 oneof:在序列化和解析消息之后,你可能会丢失一些信息(某些字段将被清除)。但是,你可以安全地将单个字段移动到新的 oneof 字段中,并且如果已知只设置了一个字段,则可以移动多个字段。
  • 删除一个oneof 字段再添加回来:这可能会在消息被序列化和解析后清除当前设置的 oneof 字段。
  • 拆分或合并oneof:这与移动常规字段有类似的问题。
Maps

如果你想创建一个关联映射作为你数据定义的一部分。

map<key_type, value_type> map_field = N;

  • 其中key_type可以是任何整型或字符串类型(除了浮点类型和字节以外的任何标量类型)
  • value_type可以是除另一个映射以外的任何类型。
map<string, Project> projects = 3;
  • 映射字段不能重复。
  • 映射值的有线格式排序和映射迭代排序是未定义的,因此不能依赖于映射项的特定排序。
  • 当为 .proto 生成文本格式时,映射按键排序。数字键按数字排序。
  • 当从连接解析或合并时,如果有重复的映射键,则使用最后看到的键。当从文本格式解析映射时,如果有重复的键,解析可能会失败
  • 如果为映射字段提供了键但没有值,则该字段序列化时的行为与语言相关。在 C++ Java KotlinPython 中,类型的默认值是序列化的,而在其他语言中,没有任何值是序列化的。

JSON 映射

proto3支持 JSON 的规范编码,使得系统之间更容易共享数据。下表按类型逐一描述了编码。

如果 json 编码的数据中缺少某个值,或者该值为 null,那么在解析为protocol buffer时,该值将被解释为适当的默认值。如果一个字段在 protocol buffer 中具有默认值,为了节省空间,默认情况下 json 编码的数据中将省略该字段。具体实现可以提供在JSON编码中可选的默认值。

在这里插入图片描述
在这里插入图片描述
一个proto3协议 JSON 实现可能提供以下选项:

  • 提供默认值的字段在proto3 JSON 输出中,值为默认值的字段被省略。可以提供一个选项,用默认值覆盖此行为和输出字段。
  • 忽略位置字段在缺省情况下,Proto3 JSON 解析器应该拒绝未知字段,但在解析过程中可能会提供一个忽略未知字段的选项。
  • 使用 proto 字段名而不是小驼峰名称默认情况下,proto3 JSON 打印机应该将字段名转换为 lowerCamelCase,并使用它作为 JSON 名称
    • 可以提供一个选项,用原型字段名作为 JSON 名。需要协议3 JSON 解析器同时接受转换后的 lowerCamelCase `名称和原始字段名称。
  • 以整数而不是字符串形式展示枚举值在 JSON 输出中,默认情况下使用枚举值的名称。可以提供一个选项来代替使用枚举值的数值。

剩下的可以看:官方文档

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

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

相关文章

RHEL7.5编译openssl1.1.1w源码包到rpm包

openssl1.1.1w下载地址 https://www.openssl.org/source/ 安装依赖包 yum -y install curl which make gcc perl perl-WWW-Curl rpm-build wget http://mirrors.aliyun.com/centos-vault/7.5.1804/os/x86_64/Packages/perl-WWW-Curl-4.15-13.el7.x86_64.rpm rpm -ivh pe…

JVM之堆学习

一、Java虚拟机内存结构图 二、堆的介绍 1. 前面学习的程序计数器&#xff0c;虚拟机栈和本地方法栈都是线程私有的&#xff0c;堆是线程共享的&#xff1b; 2. 通过 new 关键字&#xff0c;创建的对象都会使用堆内存&#xff0c;其特点是&#xff1a; 它是线程共享的&#x…

【每日一题】【面试经典150 | 动态规划】爬楼梯

Tag 【动态规划】【数组】 题目来源 70. 爬楼梯 题目解读 有过刷题「动态规划」刷题经验的读者都知道&#xff0c;爬楼梯问题是一种最典型也是最简单的动态规划问题了。 题目描述为&#xff1a;你每次可以爬 1 或者 2 个台阶&#xff0c;问爬上 n 阶有多少种方式。 解题思路…

智能优化算法应用:基于探路者算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于探路者算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于探路者算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.探路者算法4.实验参数设定5.算法结果6.参考文…

开源书籍—鸢尾花书:从加减乘除到机器学习系列 | 开源专题 No.50

Visualize-ML/Book1_Python-For-Beginners Stars: 2.4k License: NOASSERTION 《编程不难》是一本名为鸢尾花书的开源项目&#xff0c;它从基础的加减乘除开始&#xff0c;逐步引导读者进入机器学习领域。该项目提供了 PDF 草稿和 Jupyter 笔记&#xff0c;并经过至少两轮修改…

LED恒流调节器FP7125,应用LED街道照明、调光电源、汽车大灯、T5T8日光灯

目录 一、FP7125概述 二、FP7125功能 三、应用领域 近年来&#xff0c;随着人们环保意识的不断增强&#xff0c;LED照明产品逐渐成为照明行业的主流。而作为LED照明产品中的重要配件&#xff0c;LED恒流调节器FP7125的出现为LED照明带来了全新的发展机遇。 一、FP7125概述 FP…

OBS实时字幕(第三第四种方法)

我的视频地址 https://www.bilibili.com/video/BV1uN411V7uG基于wispper的obs实时字幕插件&#xff0c;中文支持不太好&#xff0c;需要加载模型 github.com/occ-ai/obs-localvocal推荐使用这个 下面参考B站 www.bilibili.com/video/BV11u411G7Vt Curses&#xff1a;为OBS、V…

【NSX-T】7. 搭建NSX-T环境 —— 部署和配置 Edge Cluster

目录 7. 部署和配置 Edge Cluster7.1 配置 Edge 节点&#xff08;1&#xff09;Name and Description&#xff08;2&#xff09;Credentials&#xff08;3&#xff09;Configure Deployment&#xff08;4&#xff09;Configure Node Settings&#xff08;5&#xff09;Configur…

压缩软件电脑版哪个好?

压缩软件是我们存储文件、清理电脑、向他人发送文件经常用到的工具&#xff0c;下面就从头到尾操作一遍各个软件压缩步骤&#xff0c;根据需求选择好啦。可以放心的是&#xff0c;这四款软件都经过了安全测试&#xff0c;能够保证文件的安全性&#xff0c;并且能够兼容多种操作…

qt-C++笔记之模拟实现一个linux终端窗口

qt-C笔记之模拟实现一个linux终端窗口 code review! 文章目录 qt-C笔记之模拟实现一个linux终端窗口一.运行二.main.cpp三.不足&#xff0c;待改进点 一.运行 二.main.cpp 代码 #include <QApplication> #include <QPlainTextEdit> #include <QLineEdit>…

微服务技术 RabbitMQ SpringAMQP P61-P76

B站学习视频https://www.bilibili.com/video/BV1LQ4y127n4?p61&vd_source8665d6da33d4e2277ca40f03210fe53a 文档资料: 链接&#xff1a;https://pan.baidu.com/s/1P_Ag1BYiPaF52EI19A0YRw?pwdd03r 提取码&#xff1a;d03r 一 初始MQ 1. 同步通讯 2. 异步通讯 3. MQ常…

arm-linux设备fsck命令移植

arm-linux设备fsck命令移植 文章目录 **arm-linux设备fsck命令移植**1、下载e2fsprogs-源码2、解压3、进入源码目录4、配置编译环境&#xff1a;使用以下命令配置交叉编译环境5、测试 1、下载e2fsprogs-源码 首先要确定自己的文件系统格式&#xff0c;IG2000的文件系统是ext4&…

Leetcode每日一题(分割回文串Ⅰ)

分割回文串Ⅰ import java.util.ArrayList; import java.util.List;class Solution {private List<List<String>> ans new ArrayList<>();boolean f[][] new boolean[1010][1010];//i到j的字符是否为回文串public static void main(String[] args) {Sys…

设计模式——策略模式(Strategy Pattern)

概述 策略模式又叫政策模式&#xff0c;是一种对象行为型模式。它是将定义的算法家族分别封装起来&#xff0c;让它们之间可以互相替换&#xff0c;从而让算法的变化不会影响到使用算法的用户。策略模式的主要目的是将算法的定义与使用分开&#xff0c;也就是将算法的行为和环…

Linux-----11、压缩打包

# 打包压缩 # 一、压缩工具 # 1、常见的压缩与解压缩工具 压缩工具说明解压缩工具zip兼容类unix与windows&#xff0c;可以压缩多个文件或目录unzipgzip压缩单个文件&#xff0c;压缩率相对低&#xff0c;cpu开销相对低gunzipbzip2压缩单个文件&#xff0c;压缩率相对高&…

Linux运维 网工应知必会 100 个知识点总结!

1&#xff09;什么是链接&#xff1f; 链接是指两个设备之间的连接。它包括用于一个设备能够与另一个设备通信的电缆类型和协议。 2&#xff09;OSI 参考模型的层次是什么&#xff1f; 有 7 个 OSI 层&#xff1a;物理层&#xff0c;数据链路层&#xff0c;网络层&#xff0…

Ubuntu Desktop 22.04 禁用自动更新

Ubuntu 的自动更新并不稳定&#xff0c;之前的一次推送更新了内核版本&#xff0c;稳定性影响比较大&#xff0c;禁用自动更新的方法如下&#xff1a; 1.在设置里禁用软件更新 2.关闭 unattended-upgrades 如果只禁用上面的更新&#xff0c;unattended-upgrades 还是会在后台…

大数据云计算——使用Prometheus-Operator进行K8s集群监控

大数据云计算——使用Prometheus-Operator进行K8s集群监控 一、 背景 在非operator配置的普罗中我们监控k8s集群都是通过配置configmap进行服务发现和指标拉取。切换到prometheus-operator难免会有些使用问题。不少用户已经习惯底层配置自动发现的方式。当过渡到servicemonit…

C# OpenCvSharp DNN 部署FastestDet

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN 部署FastestDet 效果 模型信息 Inputs ------------------------- name&#xff1a;input.1 tensor&#xff1a;Float[1, 3, 512, 512] --------------------------------------------------------------- Outpu…

【UE5.1 MetaHuman】使用mixamo_converter把Mixamo的动画重定向给MetaHuman使用

目录 前言 效果 步骤 一、下载mixamo_converter软件 二、Mixamo动画重定向 三、导入UE 四、动画重定向 五、使用重定向后的动画 前言 上一篇&#xff08;【UE5】初识MetaHuman 创建虚拟角色&#xff09;中我们已经制作了一个MetaHuman&#xff0c;本篇博文将介绍如何…