go语言 | grpc原理介绍(二)

gRPC

gRPC 是一个高性能、通用的开源 RPC 框架,其由 Google 2015 年主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf 序列化协议开发,且支持众多开发语言。

由于是开源框架,通信的双方可以进行二次开发,所以客户端和服务器端之间的通信会更加专注于业务层面的内容,减少了对由 gRPC 框架实现的底层通信的关注。

如下图,DATA 部分即业务层面内容,下面所有的信息都由 gRPC 进行封装。

在这里插入图片描述

gRPC 的特点

  • 跨语言使用,支持 C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP 等编程语言;
  • 基于 IDL 文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub;
  • 通信协议基于标准的 HTTP/2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量;
  • 序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架,基于 HTTP/2 + PB, 保障了 RPC 调用的高性能;
  • 安装简单,扩展方便(用该框架每秒可达到百万个RPC)。

gRPC调用模型

在这里插入图片描述
1、客户端(gRPC Stub)调用 A 方法,发起 RPC 调用。

2、对请求信息使用 Protobuf 进行对象序列化压缩(IDL)。

3、服务端(gRPC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回。

4、对响应结果使用 Protobuf 进行对象序列化压缩(IDL)。

5、客户端接受到服务端响应,解码请求体。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果。


客户端与服务端是如何交互的

在这里插入图片描述

  1. 在建立连接之前,客户端/服务端都会发送连接前言(Magic+SETTINGS),确立协议和配置项。
  2. 在传输数据时,是会涉及滑动窗口(WINDOW_UPDATE)等流控策略的。
  3. 传播 gRPC 附加信息时,是基于 HEADERS 帧进行传播和设置;而具体的请求/响应数据是存储的 DATA 帧中的。
  4. 请求/响应结果会分为 HTTP 和 gRPC 状态响应两种类型。
  5. 客户端发起 PING,服务端就会回应 PONG,反之亦可。

浅谈使用理解

RPC 流

在 RPC 架构下,服务端(Server)定义了一组对外暴露的远程方法(Remote Procedures)。客户端(Client)通过生成对应的存根(Stub)来实现这些远程方法的本地代理。存根内包含与服务端远程方法相对应的函数签名,从而使客户端能够通过本地调用来触发远程操作。操作流程如下:

  1. 客户端通过存根(Stub)调用 getProduct 方法。
  2. 客户端存根对传入的消息(Message)执行序列化操作,并构建一个 HTTP POST 请求。在 gRPC 框架中,这一请求遵application/grpc 的 content-type规范,并将目标远程方法(/ProductInfo/getProduct)标记在 HTTP Header(Path)中。
  3. 经构建的 HTTP POST 请求随后通过网络传输到服务端。
  4. 服务端接收到消息后,从 HTTP Header 解析出待调用的远程方法,并将消息交由服务端存根(Server Stub)处理。
  5. 服务端存根对收到的消息执行反序列化,恢复为原始的数据结构。
  6. 服务端本地执行 getProduct 方法,其中反序列化后的消息作为输入参数。
  7. 方法执行后,服务端对返回结果进行序列化,并以 HTTP 响应的形式返回给客户端。该响应的构建过程与客户端的请求构建过程相对应。
  8. 客户端收到服务端的响应消息后,进行反序列化操作,并将解码后的数据提供给等待的客户端进程。

在这里插入图片描述

这些步骤与大多数 RPC 系统(如 CORBA、Java RMI 等)非常相似。gRPC 与它们之间的主要区别在于它对 message 进行编码的方式,它使用 protocol buffer 进行编码。

Protocol Buffers

Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

Protocol Buffers 是 Google 的一种语言中立、平台中立和可扩展的结构化数据序列化机制,可以视为是更小、更快、更简单的 XML。您只需一次性定义数据结构,然后可以使用特殊生成的源代码轻松地将结构化数据写入和读取出各种数据流,并且支持多种编程语言。

你可以理解 ProtoBuf 是一种更加灵活、高效的数据格式,与 XML、JSON 类似,在一些高性能且对响应速度有要求的数据传输场景非常适用。

ProtoBuf 在 gRPC 的框架中主要有三个作用:定义数据结构、定义服务接口,通过序列化和反序列化方式提升传输效率。

为什么 ProtoBuf 会提高传输效率呢?

我们知道使用 XML、JSON 进行数据编译时,数据文本格式更容易阅读,但进行数据交换时,设备就需要耗费大量的 CPU 在 I/O 动作上,自然会影响整个传输速率。Protocol Buffers 不像前者,它会将字符串进行序列化后再进行传输,即二进制数据。

在这里插入图片描述

接下来我们看下如何使用 protocol buffer 对消息进行编码。

使用 protocol buffer 定义服务包括定义服务中的远程方法和定义我们希望通过网络发送的消息。

仍以 ProductInfo 服务中的 getProduct 函数为例。该 getProduct 函数接受一个 ProductID 消息作为输入参数并返回一个 Product 消息。protocol buffer 定义如下:

syntax = "proto3";package ecommerce;service ProductInfo {rpc getProduct(ProductID) returns (Product);
}message Product {string id = 1;string name = 2;string description = 3;float price = 4;
}message ProductID {string value = 1;
}

假设我们需要获取产品 ID 为 15 的产品详细信息,我们创建一个值为 15 的 ProductID 消息,并将其传递给 getProduct 函数。

product, err := c.GetProduct(ctx, &pb.ProductID{Value:15})

在下面的 ProductID 消息结构中,有一个 value 字段,其索引为 1。当我们创建一个 value 等于 15 的消息实例时,生成的字节内容为该字段的字段标识符(field identifier)+value 具体的编码值。字段标识符有时也称为标签(tag):

message ProductID {string value = 1;
}

字节内容结构如下图所示,其中,每个消息字段由一个标签值(Tag)和其编码值(Value)组成。
在这里插入图片描述

在 Protocol Buffers(通常简称为 Protobuf)中,标签值(Tag)具有非常重要的作用,它用于标识序列化数据流中的各个字段。

  1. 字段索引: 在 .proto 文件中,每个字段都会有一个唯一的数字标识,通常称为字段索引(Field Index)。这个数字是用于在序列化和反序列化过程中快速识别字段的。
  2. 类型编号: 类型编号(Type Identifier)表明了该字段的数据类型(例如:int32, string等)。这有助于解析器理解如何解码或解析这个字段。

标签值组合了这两部分信息(字段索引和类型编号)来生成一个唯一的标识符,用于在序列化数据流中定位和解析字段。

在给定的

 message ProductID { string value = 1; } 

示例中,字段 value 的字段索引是 1。字符串类型(string)在 Protocol Buffers 中对应的类型标识符(Wire Type)是 2。为了生成标签值,首先需要将字段索引左移 3 位,然后将类型标识符添加到其中。
下表显示了字段类型如何映射到类型编号的:
在这里插入图片描述
计算标签值:

标签值 = (字段索引 << 3) | 类型标识符= (1 << 3) | 2= 8 | 2= 10

为什么使用左移 3 位(<< 3):这个设计选择允许 3 位用于类型标识符(Wire Type)。左移 3 位是为了在标签值中留出空间来存储类型标识符。类型标识符是一个 0 到 7 的值,正好可以用 3 位二进制数来表示。这样,通过字段索引左移 3 位后,最低的 3 位就可以用来存储类型标识符。这种设计方式实现了一个紧凑、高效的编码方案,同时也提供了足够的信息来解析序列化后的数据。
在Protocol Buffers中,变长整数(Varints)采用一种特殊的编码方式,使得较小的数值可以用较少的字节表示。具体来说,每个字节的最低7位用于存储整数的实际数值,而最高位(第8位)用于标记是否还有更多字节。如果最高位是1,那么表示该整数还有后续字节;如果最高位是0,那么表示这是该整数的最后一个字节。

字符串通常使用一种称为"Length-delimited"的编码方式。这种方法也适用于字节串(byte arrays)和嵌套消息。

  1. 前缀长度:在实际的字符串内容之前,会有一个变长整数(Varint)来表示接下来的字符串(或字节串或嵌套消息)的长度。这个变长整数是使用Protocol Buffers的Varint编码方式进行编码的。
  2. 字符串内容:紧接着前缀长度的是字符串的实际内容,它是以原始字节形式存在的。这些字节的数量与前缀长度中指定的数值相匹配。
  3. 解码:在解码阶段,首先会读取前缀长度(它本身是一个Varint,可以用一个或多个字节表示)。解码器然后会知道接下来要读取多少字节来获取完整的字符串或字节串。
当然,让我给出一个简单的例子以说明如何使用Length-delimited编码方式来编码字符串。假设我们有一个字符串"hello"。计算长度: 字符串"hello"包含5个字符,因此其长度是5。编码长度: 使用Protocol Buffers的Varint编码方式,我们将这个长度数字(即5)编码为一个变长整数。在这个特定的情况下,5是一个小的数值,所以它可以用单个字节来表示:0b00000101。字符串内容: "hello"的ASCII编码分别是:0x68 0x65 0x6C 0x6C 0x6F组合: 最后,我们将编码后的长度和实际的字符串内容组合起来:0b00000101 0x68 0x65 0x6C 0x6C 0x6F所以,按照Length-delimited编码方式,字符串"hello"将被编码为:05 68 65 6C 6C 6F(以十六进制表示)。

从上面的介绍,我们得出在编码方面 Protocol Buffers 对比 JSON、XML 的优点:

  • 标准的 IDL 和 IDL 编译器,这使得其对工程师非常友好;
  • 序列化数据非常简洁,紧凑,与 XML 相比,其序列化之后的数据量约为 1/3 到 1/10;
  • 解析速度非常快,比对应的 XML 快约 20-100 倍;
  • 提供了非常友好的动态库,使用非常简单,反序列化只需要一行代码。

Protobuf 也有其局限性:

  • 二进制格式导致可读性差
  • 不具备自描述能力

Protobuf 适用场景:

  • Protobuf 具有广泛的用户基础,空间开销小以及高解析性能是其亮点,非常适合于公司内部的对性能要求高的 RPC 调用;
  • 由于 Protobuf 提供了标准的 IDL 以及对应的编译器,其 IDL 文件是参与各方的非常强的业务约束;
  • Protobuf 与传输层无关,采用 HTTP 具有良好的跨***的访问属性,所以 Protobuf 也适用于公司间对性能要求比较高的场景;
  • 由于其解析性能高,序列化后数据量相对少,非常适合应用层对象的持久化场景;

长度前缀消息帧

在 gRPC 通信协议中,采用了长度前缀消息帧(Length-Prefixed Message Framing)机制来进行数据打包和传输。具体来说,每个编码后的消息体前会附加一个 4 字节的字段,用于标识接下来的消息体的字节长度。

这种机制提供了一种有效的方式来界定消息的边界,从而允许接收方进行准确和高效的消息解析。由于长度字段由 4 字节表示,因此理论上最大可支持的消息体尺寸为 2 32 − 1 2^{32} - 1 2321 字节,即近 4 GB。

这样的设计旨在实现高效的流解析和处理,同时也支持消息体的动态长度,进一步提升了通信协议的灵活性和可扩展性。

假设我们有一个简单的 gRPC 服务,其中有一个名为 GetUserInfo 的远程调用,用于获取用户信息。当客户端要获取用户 ID 为 123 的用户信息时,该调用的消息体(已序列化和编码)可能是一个二进制字符串,例如 0x12 0x07 0x55 0x73 0x65 0x72 0x31 0x32 0x33(这只是一个假设的二进制表示)。在这种情况下,消息体的长度为 9 字节。
长度前缀添加:在这个二进制消息体前,gRPC 会添加一个 4 字节的长度字段。假设长度用大端字节序表示,则该字段可能是 0x00 0x00 0x00 0x09。
完整的消息帧:添加了长度前缀后,完整的消息帧将变为 0x00 0x00 0x00 0x09 0x12 0x07 0x55 0x73 0x65 0x72 0x31 0x32 0x33。
发送到服务器:这个长度为 13 字节的消息帧将通过网络发送到 gRPC 服务器。
服务器解析:服务器首先读取前 4 字节(0x00 0x00 0x00 0x09),解析出消息体的长度为 9 字节。然后,服务器会读取接下来的 9 字节,作为完整的消息体进行解码和处理。

这样,通过使用长度前缀消息帧,gRPC 能够准确地界定每个消息体的开始和结束,从而实现高效和准确的消息解析。

在这里插入图片描述

除了消息大小之外,消息帧还会预留一个 1 字节的无符号整数来指示数据是否被压缩。Compressed-Flag 值为 1 表示二进制数据使用 Message-Encoding 标头中声明的压缩方式进行压缩,值 0 表示没有对消息字节进行压缩。

现在消息已装帧并准备好通过网络发送给对应的处理方。对于 client 端请求消息,接收者是 server。对于响应消息,接收者是 client 端。在接收端,一旦收到一条消息,首先需要读取第一个字节,检查消息是否被压缩。然后,读取接下来的四个字节以获取二进制消息的大小。一旦知道大小,就可以从消息流中读取具体的消息了。对于简单消息,我们只需处理一条带长度前缀的消息,而对于流式消息,我们需要处理多条带长度前缀的消息。

在gRPC和Protocol Buffers的上下文中,长度前缀消息帧(Length-Prefixed Message Framing)与字段的三部分(字段编号(Tag)、类型、编码值)是两个在不同层次上应用的编码机制,它们不会互相矛盾。长度前缀消息帧:这是一种更为底层的打包方式,通常用在gRPC的传输层。这里的4字节长度前缀用于标识接下来整个消息体(即一个完整的Protocol Buffers消息)的长度。这不仅包括字段的编码值,还包括每个字段的标签和类型信息。这样,收到消息的一方知道要读取多少字节来获取完整的消息。字段的三部分编码:这是Protocol Buffers内部的编码机制。在这里,每个字段都被编码为标签、类型和值。这三者都包含在上面提到的长度前缀消息帧内。由于这两个机制作用在不同的层次,因此不会有矛盾。具体来说,长度前缀消息帧为整个Protocol Buffers消息提供一个“外壳”,而字段的三部分编码则是这个“外壳”内部的内容。关于顺序性的问题,实际上,字段的三部分(标签、类型、编码值)总是作为一个整体连续存储和传输的。也就是说,一个字段的标签、类型和值会连续出现在数据流中,然后才是下一个字段的标签、类型和值。这种连续性由Protocol Buffers的编码和解码算法来保证。综上所述,长度前缀消息帧和字段的三部分编码各自在不同的层面起作用,两者可以和谐共存,不会出现顺序或界定的问题。长度前缀是针对整个消息体,而字段的标签、类型和编码值是针对消息体内部的各个字段。这样设计确保了数据传输的高效和准确。

思考下长度前缀消息帧,是否会失序?

在gRPC和大多数基于TCP的通信协议中,数据的顺序性是由底层的TCP协议保证的。TCP是一个面向连接的、可靠的、字节流协议,它确保所有的字节都会按照发送的顺序到达接收端。

长度前缀消息帧在这种上下文中是作为一个整体发送的,即4字节的长度前缀和跟随的消息体(由长度前缀指定的长度)会作为一个整体在网络上传输。由于TCP保证了数据的顺序性和完整性,因此不会出现长度前缀和消息体之间、或不同消息之间的失序问题。

这样的设计确保了在从底层TCP流中读取数据并解析成gRPC消息时,能够正确地识别每个消息的边界,并按照正确的顺序处理它们。

简言之,长度前缀消息帧在gRPC通信中不会失序,这是由底层的TCP协议保证的。这也是为什么gRPC可以作为一个高性能、可靠的RPC框架被广泛使用的原因之一。


基于 HTTP/2 的 gRPC

除了 Protocol Buffers 之外,从交互图中和分层框架可以看到, gRPC 还有另外一个优势——它是基于 HTTP 2.0 协议的。由于 gRPC 基于 HTTP 2.0 标准设计,带来了更多强大功能,如多路复用、二进制帧、头部压缩、推送机制。
这些功能给设备带来重大益处,如节省带宽、降低 TCP 连接次数、节省 CPU 使用等,gRPC 既能够在客户端应用,也能够在服务器端应用,从而以透明的方式实现两端的通信和简化通信系统的构建。
HTTP 1.X 定义了四种与服务器交互的方式,分别为 GET、POST、PUT、DELETE,这些在 HTTP 2.0 中均保留,我们看看 HTTP 2.0 的新特性:双向流、多路复用、二进制帧、头部压缩

跟其他的相比:

  • XML序列化(Xstream)无论在性能和简洁性上比较差。
  • Thrift 与 Protobuf 相比在时空开销方面都有一定的劣势。
  • Protobuf 和 Avro 在两方面表现都非常优越。

如下图所示,gRPC Channel 表示 client 端与 server 端之间的连接,即 HTTP/2 连接。当 client 端创建 gRPC Channel 时,它会在后台创建与 server 端的 HTTP/2 连接。创建好 Channel 后,我们可以重用它来向 server 端发送多个远程调用,这些远程调用会映射到 HTTP/2 的流中。在远程调用中发送的消息以 HTTP/2 帧的形式发送。一个帧可能携带一个 gRPC 长度前缀消息,如果一个 gRPC 消息非常大,它可能跨越多个数据帧

在这里插入图片描述

在gRPC中,Channel是一个抽象概念,它代表了客户端(client)与服务端(server)之间的一个连接。具体来说,它封装了底层的HTTP/2连接以及其他与连接相关的状态信息。Channel负责管理底层连接的建立、维护以及关闭,并提供了一种简单的方式来进行远程过程调用(RPC)。

在上一节中,我们讨论了如何将我们的消息帧转为以长度为前缀的消息。当我们通过网络将它们作为请求或响应消息发送时,我们需要随消息一起发送额外的 HTTP 头。下面,我们看看如何构造请求 / 响应消息,以及需要为每条消息传递哪些标头。

请求消息

请求消息是发起远程调用的消息。在 gRPC 中,请求消息总是由 client 端应用程序触发,它由三个主要部分组成:请求头、长度前缀消息和流结束标志,如下图所示。client 端首先发送请求头,之后是长度前缀消息,最后是 EOS,标识消息发送完毕。
在这里插入图片描述

下面仍以 ProductInfo 服务中的 getProduct 函数为例,来解释请求消息是如何在 HTTP/2 帧中发送的。
当我们调用该 getProduct 函数时,client 端会发送以下请求头:

HEADERS (flags = END_HEADERS):method = POST 
:scheme = http 
:path = /ProductInfo/getProduct 
:authority = abc.com 
te = trailers 
grpc-timeout = 1S 
content-type = application/grpc 
grpc-encoding = gzip 
authorization = Bearer xxxxxx 

:method:设置 HTTP 方法。对于 gRPC,:method 标头始终为 POST.

:scheme:设置 HTTP 协议。如果启用了 TLS,则协议设置为 “https”,否则为 “http”。

:path:设置终端路径。对于 gRPC,此值构造为 “/{服务名称}/{方法名称}”。

:authority:设置目标 URI 的虚拟主机名。

te:设置不兼容代理的检测。对于 gRPC,该值必须是 “trailers”。

grpc-timeout:设置调用超时时常。如果未指定,server 端应假定无限超时。

content-type:设置内容类型。对于 gRPC,内容类型应以 application/grpc. 如果没有,gRPC server 会响应 HTTP 状态 415(不支持的媒体类型)。

grpc-encoding:设置消息压缩方式。可能的值为 identity、gzip、deflate、snappy 及自定义压缩方式。

authorization:这是可选的请求头,用于访问又安全限制的终端服务。

一旦 client 端发起与 server 端的调用,client 端就会以 HTTP/2 数据帧的形式发送带有长度前缀的消息。如果一个数据帧无法放下长度前缀消息,它可以跨越多个数据帧。通过在最后一个数据帧上添加一个 END_STREAM 标志来标识请求消息的结束。当没有数据要发送但我们需要关闭请求流时,我们需要发送一个带有 END_STREAM 标志的空数据帧:

DATA (flags = END_STREAM)

响应消息

响应消息由 server 端响应 client 端的请求而生成。与请求消息类似,在大多数情况下,响应消息也由三个主要部分组成:响应标头、带长度前缀的消息和尾部。当响应中没有以长度为前缀的消息需要发送给 client 端时,响应消息仅包含响应标头和尾部。

在这里插入图片描述

继续回到上一个例子。当 server 端向 client 端发送响应时,它首先发送响应头,如下所示:

HEADERS (flags = END_HEADERS):status = 200 
grpc-encoding = gzip 
content-type = application/grpc 

:status:标识 HTTP 请求的状态。

grpc-encoding:设置消息压缩类型。可能的值包括 identity、gzip、deflate、snappy 和自定义类型。

content-type:设置内容类型。对于 gRPC,content-type 应该设置为 application/grpc。

一旦 server 端发送完响应标头,就会以 HTTP/2 数据帧的形式发送带有长度前缀的消息。与请求消息类似,如果一个数据帧无法放下长度前缀消息,它可以跨越多个数据帧:

DATA

与请求消息不同的是,END_STREAM 标志不随数据帧一起发送,它作为一个单独的响应头发送(被称作 Trailers),通知 client 端我们完成了响应消息的发送。Trailers 还会携带请求的状态码和状态消息:

HEADERS (flags = END_STREAM, END_HEADERS)
grpc-status = 0 # OK 
grpc-message = xxxxxx 

grpc-status: gRPC 状态代码。可以参考 gRPC 官方文档查找状态码的定义。
grpc-message:错误描述。这是可选的,仅在处理请求出现错误时设置。

在某些情况下,请求调用可能会立即失败。在这些情况下,server 端需要在没有数据帧的情况下发回响应。此时,server 端只会发送 Trailers 作为响应。

关于gRPC的通信方式,在下一个章节中会介绍。

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

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

相关文章

关于编程不得不说的事

这些年&#xff0c;互联网爆炸式的发展&#xff0c;促生了无数程序员&#xff0c;也促生了大量 IT培训机构。短短数年间&#xff0c;科班出生的程序员和培训机构出生的程序员呈指数增长。程序员的职业也不再是金饭碗。写了这么多代码&#xff0c;有些感触&#xff0c;所以写下来…

Python--快速入门二

Python--快速入门二 1.Python数据类型 1.可以通过索引获取字符串中特定位置的字符&#xff1a; a "Hello" print(a[3]) 2.len函数获取字符串的长度&#xff1a; a "Hello" print(a) print(len(a)) 3.空值类型表示完全没有值&#xff1a; 若不确定当…

用户自定义消息及层次划分

有些人对术语 WM_USER 表示消息范围基的名称有不同的意见&#xff0c;因为 WM_USER 是由窗口类的实现者来定义的。他们抱怨的是&#xff0c;用户不能使用它们&#xff0c;因为它们属于窗口类定义的一部分。 但是&#xff0c;问题是&#xff0c;”这里的用户是谁&#xff1f;”…

IOS渲染流程之提交图层数据至RenderThread进程

大致链路 UIView/CALayer---->CoreAnimation./Core Graphics/Core Image---->GPU Drive-->GPU 图层树/视图树 一个UIView&#xff08;视图&#xff09;对应一个CALayer&#xff08;图层&#xff09;&#xff0c;CALayer对应显示的数据其有个content代表Bitamp&#…

循环队列练习

循环队列练习 相关内容&#xff1a; 1.队列顺序存储的不足 2.循环队列&#xff08;队列头尾相接的顺序存储结构&#xff09; //队列的初始化、入队、出队、取对头、计算队长度 #include<stdio.h> #define MAXSIZE 10 typedef int Status; #define OK 1 #define ERROR 0…

Java用log4j写日志

日志可以方便追踪和调试问题&#xff0c;以前用log4net写日志&#xff0c;换Java了改用log4j写日志&#xff0c;用法和log4net差不多。 到apache包下载下载log4j的包&#xff0c;解压后把下图两个jar包引入工程 先到网站根下加一个log4j2.xml的配置文件来配置日志的格式和参…

幂等性(防重复提交)

文章目录 1. 实现原理2.使用示例3. Idempotent注解4. debug过程 主要用途&#xff1a;防止用户快速双击某个按钮&#xff0c;而前端没有禁用&#xff0c;导致发送两次重复请求。 1. 实现原理 幂等性要求参数相同的方法在一定时间内&#xff0c;只能执行一次。本质上是基于red…

微信小程序获取剪切板的内容到输入框中

xml代码 <navigation-bar title"Weixin" back"{{false}}" color"black" background"#FFF"></navigation-bar> <view><input placeholder"请输入内容" name"content" type"text" …

爆料!马斯克 xAI 打造的ChatGPT竞品被曝光!

夕小瑶科技说 原创 作者 | 王二狗 马斯克旗下公司 xAI 的第一款AI模型曝光&#xff01; 名为&#xff1a;Grōk &#xff0c;有望成为ChatGPT最强竞品&#xff01; Grōk是什么意思呢&#xff1f;二狗我问了一下GPT-4&#xff1a; Grōk AI 曝光12项功能 这次Grōk模型都曝…

腾讯云CVM S5服务器优惠价格表,买一年送3个月

腾讯云服务器CVM标准型S5有活动&#xff0c;CVM 2核2G S5优惠价280.8元一年自带1M带宽&#xff0c;15个月313.2元、2核4G配置748.2元15个月、4核8G配置1437.24元15个月、8核16G优惠价3048.48元15个月&#xff0c;公网带宽可选1M、3M、5M或10M&#xff0c;腾讯云服务器网txyfwq.…

视频剪辑技巧:批量合并视频,高效省时,添加背景音乐提升品质

随着社交媒体的兴起&#xff0c;视频制作越来越受到人们的关注。掌握一些视频剪辑技巧&#xff0c;可以让我们轻松地制作出令人惊艳的视频。本文将介绍一种高效、省时的视频剪辑技巧&#xff0c;帮助您批量合并视频、添加背景音乐&#xff0c;并提升视频品质。现在一起来看看云…

TimeGPT-1——第一个时间序列数据领域的大模型他来了

一直有一个问题:时间序列的基础模型能像自然语言处理那样存在吗?一个预先训练了大量时间序列数据的大型模型&#xff0c;是否有可能在未见过的数据上产生准确的预测?最近刚刚发表的一篇论文&#xff0c;Azul Garza和Max Mergenthaler-Canseco提出的TimeGPT-1&#xff0c;将ll…

Web自动化测试 —— PageObject设计模式!

一、page object 模式简介 1.1、传统 UI 自动化的问题 无法适应 UI 频繁变化无法清晰表达业务用例场景大量的样板代码 driver/find/click 二、page object 设计原则 2.1、POM 模式的优势 降低 UI 变化导致的测试用例脆弱性问题让用例清晰明朗&#xff0c;与具体实现无关 2.…

【Vue3+Vite+bwip-js库】 生成DataMatrix码

前提条件 已存在的vue3vite架构前端项目对二维码分类有一定的了解 生成的码的样式如下&#xff08;DataMatrix&#xff09; 该二维码容量如下 详情见&#xff1a;DataMatrix介绍 Vue3Vite 导入 bwip-js生成DataMatrix 1. 安装 npm install bwip-js --save2. 引入使用 <…

Angular异步数据流编程

1 目前常见的异步编程的几种方法 首先给出一个异步请求的实例&#xff1a; import {Injectable} from angular/core;Injectable({providedIn: root }) export class RequestServiceService {constructor() {}getData() {setTimeout(() > {let res zhaoshuai-lcreturn res…

creating server tcp listening socket 127.0.0.1:6379: bind No error

window下启动redis服务报错&#xff1a; creating server tcp listening socket 127.0.0.1:6379: bind No error 解决方案如下按顺序输入如下命令即可连接成功 redis-cli.exeshutdownexit运行&#xff1a;redis-server.exe redis.windows.conf shutdown出现以下错误&#xff…

如何将PDF文件转换成翻页电子书?这个网站告诉你

​随着电子书的普及&#xff0c;越来越多的人开始将PDF文件转换成翻页电子书。翻页电子书不仅方便阅读&#xff0c;而且还可以在手机上轻松翻页。那么如何将PDF文件转换成翻页电子书呢&#xff1f;今天就为大家介绍一个网站&#xff0c;可以帮助你轻松完成这个任务。 1.首先&am…

storm安装手册及笔记

图解Storm相关概念 图解storm的并发机制 安装Storm的步骤 1、安装一个zookeeper集群 2、上传storm的安装包&#xff0c;解压 3、修改配置文件storm.yaml #所使用的zookeeper集群主机 storm.zookeeper.servers: - "weekend05" - "weekend06"…

Linux网络编程03

select的缺陷 &#xff08;1&#xff09;fd,set的本质是一个位图&#xff0c;容量是固定的1024&#xff0c;因此最大只能监听1024个连接 &#xff08;可以扩容&#xff09; &#xff08;2&#xff09;监听和就绪用的是同一个数据结构&#xff0c;使用困难 &#xff08;3&#x…

Zinx框架-游戏服务器开发003:架构搭建-需求分析及TCP通信方式的实现

文章目录 1 项目总体架构2 项目需求2.1 服务器职责2.2 消息的格式和定义 3 基于Tcp连接的通信方式3.1 通道层实现GameChannel类3.1.1 TcpChannel类3.1.2 Tcp工厂类3.1.3 创建主函数&#xff0c;添加Tcp的监听套接字3.1.4 代码测试 3.2 消息类的结构设计和实现3.2.1 消息的定义3…