一文掌握gRPC

文章目录

    • 1. gRPC简介
    • 2. Http2.0协议
    • 3. 序列化-Protobuf
    • 4. gRPC开发实战环境搭建
    • 5. gRPC的四种通信方式(重点)
    • 6. gRPC的代理方式
    • 7. SprintBoot整合gRPC

1. gRPC简介

gRPC是由google开源的高性能的RPC框架。它是由google的Stubby这样一个内部的RPC框架演化出来,gRPC2015年开源,目前是在云原生时代的一个RPC的标准。

gRPC的核心设计思路:

  • 协议:使用Http2协议(传输数据使用二进制数据内容、支持双向流[双工]、连接的多路复用)
  • 序列化:基于二进制(protobuf- 谷歌开源的一种序列化方式)
  • 代理的创建 :让调用者像调本地方法一样去调用远端的方法

Thrift和gRPC的区别:首先Thrift和gRPC这两个RPC框架有一个共性,就是都支持异构语言的RPC

  • 网络通信层面:Thrift自己定义了自己的协议,直接基于TCP协议,而gRPC的协议是HTTP2的协议
  • 性能角度:ThriftRPC的效率是高于gRPC
  • gRPC是大厂背书(google),云原生时代gRPC与其它组件合作的更加好,所以gRPC应用的更广泛

gRPC的优点:

  • 能够高效的进行进程间通信(协议+序列化)
  • 支持多种语言,对主流的语言提供了原生的支持(C、GO、Java)
  • 支持多平台运行(Linux、Android、IOS、Mac1OS、Windows)
  • grpc序列化方式使用prorubuf、效率高
  • 使用Http2协议
  • google大厂背书

2. Http2.0协议

回顾HTTP1.x协议

  • Http1.0协议:基于请求响应的模式,并且是一个短连接(无状态的协议),传输文本格式的数据,并且是单工的(只有客户端找服务端,而无法实现服务端的主动推送)。

这里思考一个问题:Http底层使用TCP协议,TCP是一个长连接协议,为什么Http1.0是一个短连接协议?

因为HTTP1.0的短连接是自己的设计造成的,一次数据发送完毕就会主动断开,导致了Http1.0是一个短连接协议。这种设计是因为当时服务器性能较差,无法支持维持大量的长连接。

  • Http1.1协议:基于请求响应模式,但它支持有限的长连接(保持一段时间,一段时间Keepalived字段后自动断开连接),基于此出现了WebSocket技术。

总结:Http1.x协议它们传输数据都是文本格式(Http1.1请求体可以是二进制数据),可读性好但是效率低。Http1.x协议无法实现双工通信。Http1.x资源请求时,需要发送多个请求,建立多个连接(比如客户端从服务端拿一个网页,一次请求拿到了这个页面,但是这个页面如果里面有超链接、和CSS以及js资源,而这写资源又要发送新的请求,所以需要建立多个连接)

Http2.0协议

  • Http2.0协议是一个二进制协议,传输效率高于Http1.x协议,但是二进制可读性差
  • 可以实现双工通信
  • 一个请求可以请求多次和多个数据(多路复用)

为什么Http2.0可以实现多路复用?

Http2.0抽取了3个重要的概念,分别是数据流(Stream)、消息(Message)和帧(Frame),这三个概念有机的整合在一起就可以实现多路复用。

在这里插入图片描述

  1. Http2.0发送的一个数据时是以一个Stream为宏观单位的,例如一个连接上有3个数据流(Stream),每个数据流代表我们请求的一个功能。假如在Http1.x协议中,我们请求一个页面首先会请求到一个HTML页面,然后就会异步的发送两个请求来请求CSS资源和JS资源,所以需要三个连接。而在Http2.0中它会复用一个连接,创建3个Stream流,一个Stream流负责获取HTML页面,另一个Stream流负责获取CSS资源而最后一个Stream流负责获取JS资源,重要的是这三个Stream流会放在一个连接上(连接复用)。
  2. 而一个Stream中会有一个Message,这个Message里面会有两个frame,一个frame放置请求头,一个frame放置请求体
  3. 同时响应也可以有多个Stream

在这里插入图片描述

其它概念:

  1. 数据流的优先级:各个Stream有优先级,通过给不同的Stream设置权重,来限制不同流的传输顺序
  2. 可以做流控,如果客户端的流的发送速度大于服务端处理数据,导致服务端处理不过来,此时服务端可以通知客户端暂时停止发送流

3. 序列化-Protobuf

Protobuf是一种与编程语言无关(它自己定义的IDL语言),与平台无关(操作系统)。它定义了中间语言,可以方便在客户端和服务端中进行RPC传输。Protobuf有两种协议版本,一个是Protobuf2一个是Protobuf3,目前主流使用的是Protobuf3。在使用Protobuf的过程中我们需要按照Protobuf的编译器,这个编译器的作用就是可以把Protobuf的IDL的语言转换为具体某一种开发语言。

  • Protobuf编译器的安装

安装网址:https://github.com/protocolbuffers/protobuf/releases

在这里插入图片描述
在这里插入图片描述

idea同时安装Protobuf插件,方便开发。

注意2021.2版本后的idea是内置了Protobuf插件的,老版本可以选装ProtoBuf版本,但是二者是不能共存的

在这里插入图片描述

  • Protobuf基本使用(Protobuf3)

文件格式:Protobuf所有的内容都需要写在一个.proto文件中

版本设定:.proto文件的第一行,syntax="proto3";

注释:

  • 单行注释://
  • 多行注释:/**/

与java语言相关的语法:

//后续protobuf生产的java代码是一个源文件还是多个源文件
option java_multiple_files = true/false;
//指定protobuf生产的java源文件放置的位置
option java_package="com.jackiechai";
//指定protobuf生成的java类封装的大类的名称(因为protobuf生成的所有java类都会成为一个大类的内部类)
option java_outer_classname="UserService";

逻辑包:package xxx;用于protobuf对于文件内容的管理

导入:在实际的开发中我们可能有多个.proto,每个.proto管理自己的内容,现在可能出现一个问题,其中一个.proto依赖另一个.proto的内容,这里就需要使用导入语法:import "xxx/Userservice.proto"

在这里插入图片描述

  • ProtoBuf基本类型

官网可以查看ProtoBuf所支持的所有基本类型:https://protobuf.dev/programming-guides/proto3/

  • 枚举类型
enum SEASON{SPRING = 0;//0和1代表字段的编号SUMMER = 1;
}

注意枚举的编号是从0开始的

  • 消息Message
message LoginRequest{string username = 1;//1对应这个内容在消息中的编号string password = 2;int32 age = 3;
}

客户端就是通过message来进行信息交流。

编号:编号是1开始到 2 2 9 − 1 2^29-1 2291结束,注意其中19000-19999不能用,这个区间内的编号是protobuf自己保留的

关键字:消息中可以加入两个关键字,singular,这个是修饰消息字段的,可写也可以不写,表示这个字段的值只能是0个或者1个(默认关键字)。

message LoginRequest{singular string username = 1;//1对应这个内容在消息中的编号singular string password = 2;singular int32 age = 3;
}

repeated修饰的字段,表示这个字段的返回值是多个,等价于java中的list:

message LoginRequest{string content=1;repeated string stutas=2;
}

上面content是singular类型,stutas是repeated类型。

在protobuf中消息是可以嵌套的:

message searchResponse{message Result{string url=1;string title=2;}string xxx=1;int32 yyy=2;Result ppp=3;
}message AAA{string xxx=1;searchResponse.Result yyy=2;
}

oneof关键字,如下我们在使用test_oneof对象时,我们只能代表它的值之一,例如test_oneof可以代表name或者代表age。

message simpleMessage{oneof test_oneof{string name=1;int32 age=2;}test_oneof xxxx=1;
}
  • 服务的定义
service HelloService{//LoginRequest和HelloResponse就是前面的Messagerpc hello(LoginRequest) returns(HelloResponse){//hello就相当于一个服务接口,具体的服务逻辑是服务提供方来实现的} 
}

一个服务,例如上面HelloService,里面是可以定义多个服务方法的,而且根据我们的需要我们是可以定义多个服务的。对于gRPC来说它的服务方式是分成4种情况的,这里先不详细分析。

4. gRPC开发实战环境搭建

  • 项目结构
  1. xxxx-api模块:定义protobuf的idl语言,并且通过命令来创建具体的代码,后序服务端和客户端引入使用(客户端和服务端公共模块),包括定义message和service
  2. xxxx-server模块:服务提供方模块。实现API模块中定义的接口,需要和具体的业务相结合。然后发布整个gRPC服务(创建服务端程序)
  3. xxxx-client模块:创建服务端的代理,基于这个代理来进行RPC调用
  • API模块开发
  1. 创建API模块

在这里插入图片描述
2. 编写proto文件

syntax = "proto3";option java_multiple_files = false;
option java_package = "com.jackie";
option java_outer_classname = "HelloProto";/**发布RPC服务*/
//定义请求消息
message HelloRequest{string name = 1;
}//定义响应消息
message HelloResponse{string result = 1;
}/**开发service*/
service HelloService{rpc hello(HelloRequest) returns (HelloResponse){}
}

在这里插入图片描述
3. 将ProtoBuf的IDL内容转换为具体使用的语言的代码(protoc这个编译器完成的,它可以将这个IDL语言转换为我们使用的目标语言),所以我们要做的就是将这个proto文件进行编译:

protoc --java_out=/***/***  /***/***/***.proto
#--java_out=:表示生成java代码
# /***/***:生成的代码文件放的位置
#/***/***/***.proto:定义好的proto文件

上面是编译命令,但这种方式需要频繁的使用终端所以不适合开发。下面看看我们怎么在开发中怎么使用:

其实我们可以使用Maven插件来进行protobuf IDL文件的编译操作。首先我们需要引入相关的依赖:

<dependencies>
<!--       grpc-netty-shaded说明grpc底层通信使用的是netty框架--><dependency><groupId>io.grpc</groupId><artifactId>grpc-netty-shaded</artifactId><version>1.63.0</version><scope>runtime</scope></dependency>
<!--        protobuf使用所需的相关依赖--><dependency><groupId>io.grpc</groupId><artifactId>grpc-protobuf</artifactId><version>1.63.0</version></dependency><dependency><groupId>io.grpc</groupId><artifactId>grpc-stub</artifactId><version>1.63.0</version></dependency><dependency> <!-- necessary for Java 9+ --><groupId>org.apache.tomcat</groupId><artifactId>annotations-api</artifactId><version>6.0.53</version><scope>provided</scope></dependency></dependencies>

引入相关的maven插件:

<build><extensions><extension><groupId>kr.motd.maven</groupId><artifactId>os-maven-plugin</artifactId><version>1.7.1</version></extension></extensions><plugins><plugin><groupId>org.xolstice.maven.plugins</groupId><artifactId>protobuf-maven-plugin</artifactId><version>0.6.1</version><configuration><protocArtifact>com.google.protobuf:protoc:3.25.1:exe:${os.detected.classifier}</protocArtifact><pluginId>grpc-java</pluginId><pluginArtifact>io.grpc:protoc-gen-grpc-java:1.63.0:exe:${os.detected.classifier}</pluginArtifact><outputBaseDirectory>${basedir}/src/main/java</outputBaseDirectory><clearOutputDirectory>false</clearOutputDirectory></configuration><executions><execution><goals><goal>compile</goal><goal>compile-custom</goal></goals></execution></executions></plugin></plugins></build>

使用插件编译proto文件:
在这里插入图片描述
这样就生成java文件了:
在这里插入图片描述
我们仔细分析一下上面java文件的组成:

在这里插入图片描述
首先第一个类名是HelloProto这个类名就是我们在 proto文件中定义的外部类的名称:

option java_outer_classname = "HelloProto";

HelloRequest和HelloResponse都是在proto文件中定义过的:
在这里插入图片描述
它们都是HelloProto的内部类。HelloProto主要就是用来处理协议消息的。HelloServiceGrpc主要就是用来处理Grpc了。我们来看以下它的内部类,首先是HelloServiceImplBase它就代表我们的业务接口,后面服务方暴露业务接口给外界时就需要实现这个接口。以stub结尾的这些类对应的就是客户端的代理对象,这些stub都是客户端的代理,区别就是网络通信方式不同(阻塞、异步等)。

  • Server模块开发

主要包括两个过程,首先需要实现业务接口,添加具体的功能,然后创建服务端(Netty)。

  1. 实现业务接口并添加具体的功能

创建Service模块
在这里插入图片描述
创建ServiceImpl类

public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {/*** 1. 接收客户端提交的参数* 2. 业务处理* 3. 将处理结果返回给调用者** @param request          客户端请求的message* @param responseObserver 返回给客户端的结果*/public void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {//接收客户端的请求参数String name = request.getName();//业务处理System.out.println("name parameter" + name);//封装响应// 1.创建响应对象的构造者HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder();// 2.填充数据builder.setResult("hello method invoke");// 3.封装响应HelloProto.HelloResponse helloResponse = builder.build();responseObserver.onNext(helloResponse);responseObserver.onCompleted();}
}
  1. 创建服务端
public class GrpcServer1 {public static void main(String[] args) throws IOException, InterruptedException {//绑定端口ServerBuilder serverBuilder = ServerBuilder.forPort(9000);//有一个服务就调用一个serverBuilder.addService(new HelloServiceImpl());//创建服务对象Server server = serverBuilder.build();server.start();server.awaitTermination();}
}

服务端本质是用的netty

  • Client模块开发

客户端就是通过代理对象完成远端对象的调用。

  1. 创建客户端模型,并导入依赖

2
2. 创建客户端

public static void main(String[] args) {//创建通信的管道(netty)ManagedChannel managedChannel= ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();try{//获得代理对象(阻塞模式)HelloServiceGrpc.HelloServiceBlockingStub helloServiceBlockingStub= HelloServiceGrpc.newBlockingStub(managedChannel);//完成rpc调用//1. 准备参数HelloProto.HelloRequest.Builder builder=HelloProto.HelloRequest.newBuilder();builder.setName("jackie");HelloProto.HelloRequest build = builder.build();HelloProto.HelloResponse hello = helloServiceBlockingStub.hello(build);System.out.println(hello);}catch (Exception e){throw new RuntimeException(e);}finally {managedChannel.shutdown();}
}
  • 测试结果

在这里插入图片描述

在这里插入图片描述
我们回顾一下前面的服务端代码:
在这里插入图片描述
request对象里面就封装了客户端传过来的消息,responseObserver就是需要返回给客户端的消息会封装到这个对象中。调用responseObserveronNext方法就可以把处理后的响应消息通过网络回传给客户端,onCompleted这个方法的作用就是用于通知客户端响应已经结束了。

5. gRPC的四种通信方式(重点)

  • gRPC的四种通信方式如下:
  1. 简单rpc(我们上面的应用程序就是简单rpc),或者是一元rpc(Unary RPC)
  2. 服务端流式rpc(Server Stream RPC)
  3. 客户端流式rpc(Client Stream RPC)
  4. 双向流RPC(Bi-directional Stream RPC)
  • 简单rpc

一元rpc的特点如下:

  1. 当客户端发起调用后,会提交数据,并且等待服务端的响应
  2. 开发过程中主要采用的就是一元rpc这种通信方式
    在这里插入图片描述
    一元rpc的语法就是:
service HelloService{rpc hello(HelloRequest) returns (HelloResponse){}
}
  • 服务端流式rpc:客户端一个请求对象,服务端可以回传多个

服务端流式rpc的特点如下:

在这里插入图片描述
服务端流式rpc的使用场景有:

例如股票系统中,客户端发送一个股票的编号到服务端(一个数据),服务端就会返回一系列数据,这些数据就是这个股票在某一时刻的行情。

服务端流式rpc的语法变动如下:

service HelloService{rpc hello(HelloRequest) returns (stream HelloResponse){}
}

在返回值前面加stream关键字。然后客户端接收到的消息类型是Iterator类型,是一个迭代器,其它内容没有什么变动。

上面的代码中客户端是阻塞的,所以在接收消息时一直会处于阻塞状态,现在我们重写客户端代码,让其采用异步的方式来实现流式rpc的开发。

   public static void main(String[] args) {//创建通信的管道(netty)ManagedChannel managedChannel= ManagedChannelBuilder.forAddress("localhost",9000).usePlaintext().build();try{//获得代理对象(异步模式)HelloServiceGrpc.HelloServiceStub helloServiceBlockingStub= HelloServiceGrpc.newStub(managedChannel);//完成rpc调用//1. 准备参数HelloProto.HelloRequest.Builder builder=HelloProto.HelloRequest.newBuilder();builder.setName("jackie");HelloProto.HelloRequest build = builder.build();HelloProto.HelloResponse hello = helloServiceBlockingStub.hello(build, new StreamObserver<HelloProto.HelloResponse>() {//监听服务端的onNext方法(这个方法处理数据)public void onNext(HelloProto.HelloResponse helloResponse) {}//这个方法处理异常public void onError(Throwable throwable) {}//这个处理服务端响应结束public void onCompleted() {}});//一定要加这句代码,因为上面代码是没有阻塞的,所以客户端的回调方法还没来得及调,客户端就结束了managedChannel.awaitTermination(12,TimeUnit.SECONDS);}catch (Exception e){throw new RuntimeException(e);}finally {managedChannel.shutdown();}}
  • 客户端流式rpc

所谓的客户端流式rpc,指的是客户端发送多个请求对象,服务端只返回一个结果。
在这里插入图片描述
客户端流式rpc的语法变动如下:

service HelloService{rpc hello(stream HelloRequest) returns (HelloResponse){}
}
  • 双向流式rpc

意思就是客户端可以发送多个请求消息,服务端同时也可以发送多条响应消息(例如聊天室)。
在这里插入图片描述
双向流式rpc的语法变动如下:

service HelloService{rpc hello(stream HelloRequest) returns (stream HelloResponse){}
}

6. gRPC的代理方式

gRPC的代理方式有下面几种:

  • BlockingStub:阻塞通信方式
  • Stub:异步通过监听处理
  • FutureStub:异步方式(类似于netty中的future),同步异步都支持

前两种都分析了,现在来分析FutureStub,这种代理方式只能用于一元RPC

public static void main(String[] args){ManagedChannel managedChannel=ManagedChannelBuilder.forAddress("localhost",9000).userPlaintext().build();
try{TestServiceGrpc.TestServiceFuture testServiceFutureStub=TestServiceGrpc.newFutureStub(managedChannel);ListenableFuture<TestProto.TestResponse> responseListenableFuture=testServiceFutureStub.testSuns(HelloProto.TestRequest.newBuilder().setName("x").build());//获取响应内容(同步)TestProto.TestResponse testResponse=responseListenableFuture.get();System.out.println(restResponse.getResult());//异步方式/**Futuress.addCallback(responseListenableFuture,new FutureCallback<TestProto.TestResponse>(){@Overridepublic void onSuccess(TestProto.TestResponse result){}@Overridepublic void onFaulure(Throwable t){}
},Executors.newCachedThreadPool());**/managedChannel.awaitTermination(12,TimeUnit.SECONDS);
}catch(Exception e){e.printStackTrace();
}finally{managedChannel.shutdown();
}
}

7. SprintBoot整合gRPC

  • SpringBoot和gRPC整合的思想

gRPC与SpringBoot整合主要是两种思想:首先是服务端如何整合,另外就是客户端如何整合。

  • SpringBoot与gRPC整合过程中对于服务端做了什么封装

原生的gprc在创建服务端的时候就做了两件事,首先是实现服务接口,然后创建服务端发布gRPC功能。

public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {}

上面的HelloServiceImplBase就是通过protoBuf的IDL文件生成的一个服务接口,而SpringBoot无法提供实现服务接口这个工作进行封装的(这部分代码是不固定的)。而服务端的启动代码如下:

   public static void main(String[] args) throws IOException, InterruptedException {//绑定端口ServerBuilder serverBuilder = ServerBuilder.forPort(9000);//有一个服务就调用一个serverBuilder.addService(new HelloServiceImpl());//创建服务对象Server server = serverBuilder.build();server.start();server.awaitTermination();}

上面代码就是构建服务端的过程,这个过程SpringBoot是可以封装的(相对固定,变动的地方是服务端端口和服务发布的过程)。

  • 下面开始创建服务端:
  1. 搭建开发环境
  • 搭建SpringBoot的开发环境
  • 引入与Grpc相关的内容

创建服务端模块:
在这里插入图片描述
创建api模块:

syntax = "proto3";option java_multiple_files = false;
option java_package = "com.jackie";
option java_outer_classname = "HelloProto";/**发布RPC服务*/
//定义请求消息
message HelloRequest{string name = 1;
}//定义响应消息
message HelloResponse{string result = 1;
}/**开发service*/
service HelloService{rpc hello(HelloRequest) returns (HelloResponse){}rpc c2ss(HelloRequest) returns(stream HelloResponse){}rpc cs2s(stream HelloRequest) returns(HelloResponse){}rpc cs2ss(stream HelloRequest) returns(stream HelloResponse){}
}

在这里插入图片描述
在服务端模块中引入api模块的依赖:

 <dependency><groupId>org.example</groupId><artifactId>rpc-grpc-myapi</artifactId><version>1.0-SNAPSHOT</version></dependency>

在server模块中引入gRPC依赖:

 <dependency><groupId>net.devh</groupId><artifactId>grpc-server-spring-boot-starter</artifactId><version>2.14.0.RELEASE</version></dependency>

这个依赖里面就帮我们做了gRPC服务端的封装(不是官方的支持)。

  • 创建gRPC服务端
@GrpcService
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {@Overridepublic void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {//拿到请求参数String name=request.getName();System.out.println("name is "+name);responseObserver.onNext(HelloProto.HelloResponse.newBuilder().setResult("this is result").build());responseObserver.onCompleted();}@Overridepublic void c2ss(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) {super.c2ss(request, responseObserver);}@Overridepublic StreamObserver<HelloProto.HelloRequest> cs2s(StreamObserver<HelloProto.HelloResponse> responseObserver) {return super.cs2s(responseObserver);}@Overridepublic StreamObserver<HelloProto.HelloRequest> cs2ss(StreamObserver<HelloProto.HelloResponse> responseObserver) {return super.cs2ss(responseObserver);}
}

在这里插入图片描述

  • 配置gRPC服务的端口号

在这里插入图片描述

  • 启动服务类

我们看控制台的数据,我们可以发现一个问题,就是启动过程中Tomcat也启动了,实际上我们服务端是用的grpc服务端所以我们不需要Tomacat,所以在pom中排除Tomcat。

在这里插入图片描述

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency>

在这里插入图片描述
到此服务端开发就完毕了。

  • 下面开始创建客户端

传统的gPPC的客户端的开发流程为:首先通过管道设置服务端的ip和端口,然后获取stub(远端功能的代理)。

  1. 环境搭建

流程和server创建的过程一样。
在这里插入图片描述
然后对一些重要参数进行配置:

spring:application:name: grpc-clientgrpc:client:grpc-server:address: 'static://127.0.0.1:9000'negotiation-type: plaintext
  1. grpc客户端开发

创建controller

@RestController
public class TestController {//注入代理对象@GrpcClient("grpc-server")private HelloServiceGrpc.HelloServiceBlockingStub stub;@RequestMapping("/test")public String test1(String name) {System.out.println("name=" + name);HelloProto.HelloResponse helloResponse = stub.hello(HelloProto.HelloRequest.newBuilder().setName(name).build());return helloResponse.getResult();}
}

这里就完了了客户端的创建,上面其实就是SpringBoot+gRPC实现了一元RPC。

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

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

相关文章

Java日志总结

开发中&#xff0c;日志记录是不可或缺的一部分&#xff0c;应用日志的记录主要用于&#xff1a;记录操作轨迹数据、监控系统运行情况、系统故障定位问题&#xff0c;日志的重要性不言而喻&#xff0c;想要快速定位问题&#xff0c;日志分析是个重要的手段&#xff0c;Java也提…

JAVA 集合(单列集合)

集合框架 1.集合的特点 a.只能存储引用数据类型的数据 b.长度可变 c.集合中有大量的方法,方便我们操作 2.分类: a.单列集合:一个元素就一个组成部分: list.add(“张三”) b.双列集合:一个元素有两部分构成: key 和 value map.put(“涛哥”,“金莲”) -> key,value叫做键值…

锁和MVCC如何实现mysql的隔离级别

概述 MVCC解决读的隔离性&#xff0c;加锁解决写的隔离性。 读未提交 读未提交&#xff0c;更新数据大概率使用的是独享锁吧。 读已提交 在 Read Committed&#xff08;读已提交&#xff09;隔离级别下&#xff0c;每次执行读操作时都会生成一个新的 read view。这是因为在读…

AI 图像生成-环境配置

一、python环境安装 Windows安装Python&#xff08;图解&#xff09; 二、CUDA安装 CUDA安装教程&#xff08;超详细&#xff09;-CSDN博客 三、Git安装 git安装教程&#xff08;详细版本&#xff09;-CSDN博客 四、启动器安装 这里安装的是秋叶aaaki的安装包 【AI绘画…

【GlobalMapper精品教程】081:WGS84/CGCS2000转Lambert投影

参考阅读:ArcGIS实验教程——实验十:矢量数据投影变换 文章目录 一、加载实验数据二、设置输出坐标系三、数据导出一、加载实验数据 打开配套案例数据包中的data081.rar中的矢量数据,如下所示: 查看源坐标系:双击图层的,图层投影选项卡,数据的已有坐标系为WGS84地理坐标…

【3dmax笔记】021:对齐工具(快速对齐、法线对齐、对齐摄影机)

文章目录 一、对齐二、快速对齐三、法线对齐四、对齐摄影机五、注意事项3dmax提供了对齐、快速对齐、法线对齐和对齐摄像机等对齐工具: 对齐工具选项: 下面进行一一讲解。 一、对齐 快捷键为Alt+A,将当前选择对象与目标对象进行对齐。 最大对最大:

【小笔记】neo4j用load csv指令导入数据

【小笔记】neo4j用load csv指令导入数据 背景 很久没有用load CSV的方式导入过数据了因为它每次导入有数量限制&#xff08;印象中是1K还是1W&#xff09;&#xff0c;在企业中构建的图谱往往都是大规模的&#xff0c;此时通常采用的是Neo4j-admin import方式。最近遇到了一些…

振弦式表面应变计怎么安装

振弦式表面应变计是一种用于测量结构表面应变的高精度传感器&#xff0c;广泛应用于工程和科研领域。正确安装振弦式表面应变计对于确保测量结果的准确性至关重要。以下是安装振弦式表面应变计的步骤和注意事项&#xff1a; 1. 准备工作 在开始安装前&#xff0c;需要准备以下工…

whisper之初步使用记录

文章目录 前言 一、whisper是什么&#xff1f; 二、使用步骤 1.安装 2.python调用 3.识别效果评估 4.一点封装 5.参考链接 总结 前言 随着AI大模型的不断发展&#xff0c;语音识别等周边内容也再次引发关注&#xff0c;通过语音转文字再与大模型交互&#xff0c;从而…

【Gitlab远程访问本地仓库】Gitlab如何安装配置并结合内网穿透实现远程访问本地仓库进行管理

文章目录 前言1. 下载Gitlab2. 安装Gitlab3. 启动Gitlab4. 安装cpolar5. 创建隧道配置访问地址6. 固定GitLab访问地址6.1 保留二级子域名6.2 配置二级子域名 7. 测试访问二级子域名 前言 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xf…

为什么质量工程师必学六西格玛?突破职业发展的瓶颈?

在质量管理领域工作多年&#xff0c;你是否曾感受到事业发展的停滞不前&#xff1f;3年、5年的职业生涯&#xff0c;薪水依旧停留在每月5000-7000&#xff0c;而同行业的其他人却能月入2-3万&#xff0c;这种差距让人不禁陷入深思。 问题究竟出在哪里&#xff1f;为什么我们的…

揭秘图形编程 动静接口如何助力 AGV 集成

在公司软件开发团队的办公室里&#xff0c;阳光透过窗户洒在排列整齐的办公桌上。卧龙坐在办公桌前&#xff0c;面前摊开一份内测报告&#xff0c;他的手指时不时地敲击着桌面&#xff0c;流露出内心的烦躁。他抬起头&#xff0c;眼神中透露出一丝困惑&#xff0c;看向正在文件…

【网络安全】【Frida实践案例】某图xx付费功能逆向分析(一)

文章目录 一、目标应用二、环境三、步骤1、查看布局id2、用到的Log日志类信息3、尝试hook VIP判断方法 四、总结五、相关源码 1、文章仅供学习参考&#xff0c;严禁非法使用&#xff01;如非法使用&#xff0c;而导致的一切后果&#xff0c;由使用者自负&#xff01; 2、禁止将…

【C++】string类常用函数用法总结

目录 常用函数一览 默认成员函数 与容量有关的函数 part 1 part 2 part 3 与访问和遍历有关的函数 与修改有关的函数 npos 与string相关的其它常用函数 常用非成员函数 getline和cin的区别 常用函数一览 //默认成员函数 string();string(const char* s);string(si…

docker八大架构之应用服务集群架构

应用服务集群架构 在之前&#xff0c;一个应用层要负责所有的用户操作&#xff0c;但是有时用户增加后就会导致供不应求的现象&#xff08;单个应用不足以支持海量的并发请求&#xff0c;高并发的时候站点响应变慢&#xff09;&#xff0c;这时就需要增加应用层服务器&#xf…

【STM32 |GPIO】GPIO结构、GPIO输出

目录 GPIO简介 GPIO的基本结构 GPIO位结构&#xff08;每一位的具体电路结构&#xff09; 输入 上拉和下拉电阻 斯密特触发器 ​编辑 输出 GPIO模式 ​编辑 浮空输入、上拉输入、下拉输入 模拟输入 开漏输出和推挽输出 复用开漏输出和复用推挽输出 LED和蜂鸣器…

【资源分享】完胜谷歌翻译的Deepl翻译

::: block-1 “时问桫椤”是一个致力于为本科生到研究生教育阶段提供帮助的不太正式的公众号。我们旨在在大家感到困惑、痛苦或面临困难时伸出援手。通过总结广大研究生的经验&#xff0c;帮助大家尽早适应研究生生活&#xff0c;尽快了解科研的本质。祝一切顺利&#xff01;—…

【数据结构与算法】力扣 111. 二叉树的最小深度

题目描述 给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明&#xff1a; 叶子节点是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a; root [3,9,20,null,null,15,7] 输出&#xff1a; 2示例 2&#…

长事务的理解和预防

我们常常听说数据库发生了“长事务”而导致很严重的后果。那么何为长事务&#xff1f;长事务是如何产生的&#xff1f;长事务对数据库有什么影响&#xff1f;如何防止长事务的产生&#xff1f;以下对这几方面进行阐述和说明&#xff0c;以加深对SinoDB长事务的理解。 1&#x…

Python-VBA函数之旅-super函数

目录 一、super函数的常见应用场景 二、super函数使用注意事项 三、如何用好super函数&#xff1f; 1、super函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a; https://myelsa1024.blog.csdn.net/ 一、su…