Golang gRPC

为什么要使用 gRPC?

我们的示例是一个简单的路线映射应用程序,它允许客户端获取有关路线上的特征的信息,创建路线摘要,并与服务器和其他客户端交换路线信息,例如交通更新。

使用 gRPC,我们可以在 .proto 文件中定义一次服务,并在 gRPC 支持的任何语言中生成客户端和服务器,这些客户端和服务器反过来可以在从大型数据中心的服务器到您自己的平板电脑等各种环境中运行——不同语言和环境之间通信的所有复杂性都由 gRPC 为您处理。我们还获得了使用协议缓冲区的所有优势,包括高效的序列化、简单的 IDL 和简单的接口更新。

设置

您应该已经安装了生成客户端和服务器接口代码所需的工具——如果您还没有,请参阅快速入门中的先决条件部分以获取设置说明。

获取示例代码

示例代码是 grpc-go 仓库的一部分。

  1. 将仓库下载为 zip 文件 并解压缩,或者克隆仓库

    $ git clone -b v1.63.0 --depth 1 https://github.com/grpc/grpc-go
    
  2. 更改到示例目录

    $ cd grpc-go/examples/route_guide
    

定义服务

我们的第一步(正如您从gRPC 简介中了解到的那样)是使用协议缓冲区定义 gRPC 服务以及方法请求响应类型。有关完整的 .proto 文件,请参见routeguide/route_guide.proto。

要定义服务,您需要在 .proto 文件中指定一个名为 service 的服务

service RouteGuide {...
}

然后,您在服务定义中定义 rpc 方法,指定它们的请求和响应类型。gRPC 允许您定义四种服务方法,所有这些方法都在 RouteGuide 服务中使用

  • 简单 RPC,其中客户端使用存根将请求发送到服务器并等待响应返回,就像普通的函数调用一样。

    // Obtains the feature at a given position.
    rpc GetFeature(Point) returns (Feature) {}
    
  • 服务器端流式 RPC,其中客户端向服务器发送请求并获得一个流来读取返回的消息序列。客户端从返回的流中读取,直到没有更多消息为止。正如您在示例中看到的,您可以在响应类型之前放置 stream 关键字来指定服务器端流式方法。

    // Obtains the Features available within the given Rectangle.  Results are
    // streamed rather than returned at once (e.g. in a response message with a
    // repeated field), as the rectangle may cover a large area and contain a
    // huge number of features.
    rpc ListFeatures(Rectangle) returns (stream Feature) {}
    
  • 客户端流式 RPC,其中客户端写入一系列消息并使用提供的流将它们发送到服务器。客户端完成消息写入后,它将等待服务器读取所有消息并返回其响应。您可以在请求类型之前放置 stream 关键字来指定客户端流式方法。

    // Accepts a stream of Points on a route being traversed, returning a
    // RouteSummary when traversal is completed.
    rpc RecordRoute(stream Point) returns (RouteSummary) {}
    
  • 双向流式 RPC,其中双方使用读写流发送一系列消息。两个流独立运行,因此客户端和服务器可以按任何顺序进行读写:例如,服务器可以等待接收所有客户端消息后再写入其响应,或者它可以交替读取一条消息,然后写入一条消息,或者其他一些读写组合。每个流中的消息顺序得以保留。您可以在请求和响应之前都放置 stream 关键字来指定此类型的方法。

    // Accepts a stream of RouteNotes sent while a route is being traversed,
    // while receiving other RouteNotes (e.g. from other users).
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
    

我们的 .proto 文件还包含用于我们的服务方法中使用的所有请求和响应类型的协议缓冲区消息类型定义——例如,以下是 Point 消息类型

// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {int32 latitude = 1;int32 longitude = 2;
}

生成客户端和服务器代码

接下来,我们需要从我们的 .proto 服务定义中生成 gRPC 客户端和服务器接口。我们使用协议缓冲区编译器 protoc 和一个特殊的 gRPC Go 插件来完成此操作。这与我们在快速入门中所做的类似。

从 examples/route_guide 目录中,运行以下命令

$ protoc --go_out=. --go_opt=paths=source_relative \--go-grpc_out=. --go-grpc_opt=paths=source_relative \routeguide/route_guide.proto

运行此命令会在 routeguide 目录中生成以下文件

  • route_guide.pb.go,它包含所有协议缓冲区代码来填充、序列化和检索请求和响应消息类型。
  • route_guide_grpc.pb.go,它包含以下内容
    • 用于客户端调用的接口类型(或存根),该接口类型包含在 RouteGuide 服务中定义的方法。
    • 用于服务器实现的接口类型,也包含在 RouteGuide 服务中定义的方法。

创建服务器

首先让我们看看如何创建 RouteGuide 服务器。如果您只对创建 gRPC 客户端感兴趣,您可以跳过本节,直接前往创建客户端(尽管您可能会觉得它仍然很有趣!)。

使我们的 RouteGuide 服务发挥作用有两个部分

  • 实现从服务定义生成的接口:执行我们服务的实际“工作”。
  • 运行一个 gRPC 服务器来监听来自客户端的请求并将它们调度到正确的服务实现。

您可以在server/server.go中找到我们的示例 RouteGuide 服务器。让我们仔细看看它是如何工作的。

实现 RouteGuide

如您所见,我们的服务器具有一个 routeGuideServer 结构类型,它实现了生成的 RouteGuideServer 接口

type routeGuideServer struct {...
}
...func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {...
}
...func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {...
}
...func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {...
}
...func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {...
}
...
简单 RPC

routeGuideServer 实现我们所有的服务方法。让我们先看看最简单的类型 GetFeature,它只从客户端获取 Point 并从其数据库中返回相应的特征信息,即 Feature

func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {for _, feature := range s.savedFeatures {if proto.Equal(feature.Location, point) {return feature, nil}}// No feature was found, return an unnamed featurereturn &pb.Feature{Location: point}, nil
}

该方法为 RPC 传递了一个上下文对象和客户端的 Point 协议缓冲区请求。它返回一个包含响应信息的 Feature 协议缓冲区对象以及一个 error。在方法中,我们用适当的信息填充 Feature,然后 return 它以及 nil 错误,告诉 gRPC 我们已经完成了处理 RPC,并且可以将 Feature 返回给客户端。

服务器端流式 RPC

现在让我们看看我们的一个流式 RPC。ListFeatures 是一个服务器端流式 RPC,因此我们需要将多个 Feature 发送回我们的客户端。

func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {for _, feature := range s.savedFeatures {if inRange(feature.Location, rect) {if err := stream.Send(feature); err != nil {return err}}}return nil
}

如您所见,这次我们没有在方法参数中获得简单的请求和响应对象,而是获得了请求对象(客户端想要在其中查找 Feature 的 Rectangle)以及一个特殊的 RouteGuide_ListFeaturesServer 对象来写入我们的响应。

在方法中,我们填充了尽可能多的需要返回的 Feature 对象,并使用它的 Send() 方法将其写入 RouteGuide_ListFeaturesServer。最后,就像在我们的简单 RPC 中一样,我们返回 nil 错误,告诉 gRPC 我们已经完成了写入响应。如果在此调用中发生任何错误,我们返回一个非 nil 错误;gRPC 层将将其转换为适当的 RPC 状态,以在网络上传输。

客户端流式 RPC

现在让我们看看一些更复杂的内容:客户端流式方法 RecordRoute,其中我们从客户端获得 Point 的流并返回一个包含其行程信息的 RouteSummary。如您所见,这次方法根本没有请求参数。相反,它获得了 RouteGuide_RecordRouteServer 流,服务器可以使用该流来读取写入消息——它可以使用 Recv() 方法接收客户端消息,并使用 SendAndClose() 方法返回其单个响应。

func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {var pointCount, featureCount, distance int32var lastPoint *pb.PointstartTime := time.Now()for {point, err := stream.Recv()if err == io.EOF {endTime := time.Now()return stream.SendAndClose(&pb.RouteSummary{PointCount:   pointCount,FeatureCount: featureCount,Distance:     distance,ElapsedTime:  int32(endTime.Sub(startTime).Seconds()),})}if err != nil {return err}pointCount++for _, feature := range s.savedFeatures {if proto.Equal(feature.Location, point) {featureCount++}}if lastPoint != nil {distance += calcDistance(lastPoint, point)}lastPoint = point}
}

在方法体中,我们使用 RouteGuide_RecordRouteServer 的 Recv() 方法重复读取客户端的请求到一个请求对象(在本例中为 Point),直到没有更多消息:服务器需要在每次调用后检查 Recv() 返回的错误。 如果是 nil,则流仍然有效,可以继续读取;如果它是 io.EOF,则消息流已结束,服务器可以返回其 RouteSummary。 如果它有其他值,我们按原样返回错误,以便它被 gRPC 层转换为 RPC 状态。

双向流式 RPC

最后,让我们看看我们的双向流式 RPC RouteChat()

func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {for {in, err := stream.Recv()if err == io.EOF {return nil}if err != nil {return err}key := serialize(in.Location)... // look for notes to be sent to clientfor _, note := range s.routeNotes[key] {if err := stream.Send(note); err != nil {return err}}}
}

这次我们得到一个 RouteGuide_RouteChatServer 流,它与我们的客户端流式示例一样,可以用来读取和写入消息。但是,这次我们在客户端仍然向其消息流写入消息时,通过方法的流返回值。

这里的读写语法与我们的客户端流式方法非常相似,除了服务器使用流的 Send() 方法而不是 SendAndClose(),因为它正在写入多个响应。虽然双方总是按照写入顺序获得对方的邮件,但客户端和服务器都可以按任意顺序读写——流是完全独立运行的。

启动服务器

实现完所有方法后,我们还需要启动一个 gRPC 服务器,以便客户端可以使用我们的服务。以下代码段展示了我们如何为我们的 RouteGuide 服务做到这一点

lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
if err != nil {log.Fatalf("failed to listen: %v", err)
}
var opts []grpc.ServerOption
...
grpcServer := grpc.NewServer(opts...)
pb.RegisterRouteGuideServer(grpcServer, newServer())
grpcServer.Serve(lis)

要构建并启动服务器,我们需要

  1. 使用以下方法指定我们要用来监听客户端请求的端口
    lis, err := net.Listen(...).
  2. 使用 grpc.NewServer(...) 创建一个 gRPC 服务器实例。
  3. 将我们的服务实现注册到 gRPC 服务器。
  4. 在服务器上调用 Serve() 并提供我们的端口信息,执行阻塞等待,直到进程被杀死或调用 Stop()

创建客户端

在本节中,我们将介绍如何为我们的 RouteGuide 服务创建一个 Go 客户端。您可以在 grpc-go/examples/route_guide/client/client.go 中查看我们完整的示例客户端代码。

创建存根

要调用服务方法,我们首先需要创建一个 gRPC 通道 来与服务器通信。我们通过将服务器地址和端口号传递给 grpc.Dial() 来创建它,如下所示

var opts []grpc.DialOption
...
conn, err := grpc.Dial(*serverAddr, opts...)
if err != nil {...
}
defer conn.Close()

当服务需要身份验证凭据时,您可以在 grpc.Dial 中使用 DialOptions 来设置身份验证凭据(例如,TLS、GCE 凭据或 JWT 凭据)。RouteGuide 服务不需要任何凭据。

设置好 gRPC 通道 后,我们需要一个客户端 存根 来执行 RPC。我们可以使用从示例 .proto 文件生成的 pb 包提供的 NewRouteGuideClient 方法获得它。

client := pb.NewRouteGuideClient(conn)
调用服务方法

现在让我们看看如何调用服务方法。请注意,在 gRPC-Go 中,RPC 以阻塞/同步模式运行,这意味着 RPC 调用将等待服务器响应,并将返回响应或错误。

简单 RPC

调用简单的 RPC GetFeature 几乎与调用本地方法一样简单。

feature, err := client.GetFeature(context.Background(), &pb.Point{409146138, -746188906})
if err != nil {...
}

如您所见,我们在前面获得的存根上调用了该方法。在我们的方法参数中,我们创建并填充了一个请求协议缓冲区对象(在本例中为 Point)。我们还传递了一个 context.Context 对象,它允许我们在必要时更改 RPC 的行为,例如超时/取消正在进行的 RPC。如果调用没有返回错误,那么我们可以从第一个返回值中读取来自服务器的响应信息。

log.Println(feature)
服务器端流式 RPC

以下是我们调用服务器端流式方法 ListFeatures 的地方,该方法返回地理 Feature 的流。如果您已经阅读了 创建服务器,其中一些内容可能看起来很熟悉 - 流式 RPC 在两端以类似的方式实现。

rect := &pb.Rectangle{ ... }  // initialize a pb.Rectangle
stream, err := client.ListFeatures(context.Background(), rect)
if err != nil {...
}
for {feature, err := stream.Recv()if err == io.EOF {break}if err != nil {log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)}log.Println(feature)
}

与简单 RPC 一样,我们将上下文和请求传递给该方法。但是,我们没有得到响应对象,而是得到了 RouteGuide_ListFeaturesClient 的实例。客户端可以使用 RouteGuide_ListFeaturesClient 流来读取服务器的响应。

我们使用 RouteGuide_ListFeaturesClient 的 Recv() 方法重复读取服务器的响应到响应协议缓冲区对象(在本例中为 Feature),直到没有更多消息:客户端需要在每次调用后检查 Recv() 返回的错误 err。如果为 nil,则流仍然有效,可以继续读取;如果它是 io.EOF,则消息流已结束;否则必须存在 RPC 错误,该错误将通过 err 传递。

客户端流式 RPC

客户端流式方法 RecordRoute 与服务器端方法类似,除了我们只将上下文传递给该方法并返回一个 RouteGuide_RecordRouteClient 流,我们可以使用它来写入和读取消息。

// Create a random number of random points
r := rand.New(rand.NewSource(time.Now().UnixNano()))
pointCount := int(r.Int31n(100)) + 2 // Traverse at least two points
var points []*pb.Point
for i := 0; i < pointCount; i++ {points = append(points, randomPoint(r))
}
log.Printf("Traversing %d points.", len(points))
stream, err := client.RecordRoute(context.Background())
if err != nil {log.Fatalf("%v.RecordRoute(_) = _, %v", client, err)
}
for _, point := range points {if err := stream.Send(point); err != nil {log.Fatalf("%v.Send(%v) = %v", stream, point, err)}
}
reply, err := stream.CloseAndRecv()
if err != nil {log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
}
log.Printf("Route summary: %v", reply)

RouteGuide_RecordRouteClient 具有一个 Send() 方法,我们可以用它向服务器发送请求。完成使用 Send() 将客户端的请求写入流后,我们需要在流上调用 CloseAndRecv() 来让 gRPC 知道我们已完成写入并希望接收响应。我们从 CloseAndRecv() 返回的 err 中获取 RPC 状态。如果状态为 nil,则 CloseAndRecv() 的第一个返回值将是一个有效的服务器响应。

双向流式 RPC

最后,让我们看看我们的双向流式 RPC RouteChat()。与 RecordRoute 一样,我们只将上下文对象传递给该方法,并返回一个可以用来写入和读取消息的流。但是,这次我们在服务器仍然向其消息流写入消息时,通过方法的流返回值。

stream, err := client.RouteChat(context.Background())
waitc := make(chan struct{})
go func() {for {in, err := stream.Recv()if err == io.EOF {// read done.close(waitc)return}if err != nil {log.Fatalf("Failed to receive a note : %v", err)}log.Printf("Got message %s at point(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude)}
}()
for _, note := range notes {if err := stream.Send(note); err != nil {log.Fatalf("Failed to send a note: %v", err)}
}
stream.CloseSend()
<-waitc

这里的读写语法与我们的客户端流式方法非常相似,除了我们在完成调用后使用流的 CloseSend() 方法。虽然双方总是按照写入顺序获得对方的邮件,但客户端和服务器都可以按任意顺序读写——流是完全独立运行的。

试一试!

从 examples/route_guide 目录执行以下命令

  1. 运行服务器

    $ go run server/server.go
    
  2. 从另一个终端运行客户端

    $ go run client/client.go
    

您将看到类似以下的输出

Getting feature for point (409146138, -746188906)
name:"Berkshire Valley Management Area Trail, Jefferson, NJ, USA" location:<latitude:409146138 longitude:-746188906 >
Getting feature for point (0, 0)
location:<>
Looking for features within lo:<latitude:400000000 longitude:-750000000 > hi:<latitude:420000000 longitude:-730000000 >
name:"Patriots Path, Mendham, NJ 07945, USA" location:<latitude:407838351 longitude:-746143763 >
...
name:"3 Hasta Way, Newton, NJ 07860, USA" location:<latitude:410248224 longitude:-747127767 >
Traversing 56 points.
Route summary: point_count:56 distance:497013163
Got message First message at point(0, 1)
Got message Second message at point(0, 2)
Got message Third message at point(0, 3)
Got message First message at point(0, 1)
Got message Fourth message at point(0, 1)
Got message Second message at point(0, 2)
Got message Fifth message at point(0, 2)
Got message Third message at point(0, 3)
Got message Sixth message at point(0, 3)

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

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

相关文章

【Linux】进程间通信(匿/命名管道、共享内存、消息队列、信号量)

文章目录 1. 进程通信的目的2. 管道2.1 原理2.2 匿名管道2.3 管道通信场景&#xff1a;进程池2.4 命名管道 3. System V共享内存3.1 操作共享内存3.2 使用共享内存通信 4. System V 消息队列&#xff08;了解&#xff09;5. System V 信号量&#xff08;了解&#xff09;5.1 信…

VirtualBox 解决虚拟机Cable Unplugged 无法上网问题

问题描述 VirtualBox 中的虚拟机无法上网&#xff0c;在虚拟机中查看网络设置显示 Cable Unplugged。 解决方案 选择VirtualBox 上方任务栏的控制->设置->网络&#xff0c;勾选接入网线即可解决。

大学适合学C语言还是Python?

在大学学习编程时&#xff0c;选择C语言还是Python&#xff0c;这主要取决于你的学习目标、专业需求以及个人兴趣。以下是对两种语言的详细比较&#xff0c;帮助你做出更明智的选择&#xff1a; C语言 优点&#xff1a; 底层编程&#xff1a;C语言是一种底层编程语言&#x…

【深入浅出】深入浅出Bert(附面试题)

本文的目的是为了帮助大家面试Bert&#xff0c;会结合我的面试经历以及看法去讲解Bert&#xff0c;并非完整的技术细致讲解&#xff0c;介意请移步。 深入浅出】深入浅出Bert&#xff08;附面试题&#xff09; 网络结构Pre-TrainingFine-Tuning 输入编码词向量编码句子编码位置…

thrift rpc 四种类型的服务端的实现详细介绍

thrift rpc 四种类型的服务端的实现详细介绍 这里主要是使用 thrift 开发的时候服务器端的实现&#xff0c;以及 thrift 提供给我们多钟的服务的实现&#xff0c;以及每个实现的服务器的特点和 API 介绍&#xff0c;TServer 主要包含以下几种实现 TSimpleServer 阻塞的但线程…

Python | Leetcode Python题解之第530题二叉搜索树的最小绝对差

题目&#xff1a; 题解&#xff1a; # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, x): # self.val x # self.left None # self.right Noneclass Solution(object):def isValidBST(self, root):"…

[Prometheus学习笔记]从架构到案例,一站式教程

文章目录 Prometheus 优势Prometheus 的组件、架构Prometheus Server 直接从监控目标中或者间接通过推送网关来拉取监控指标&#xff0c;它在本地存储所有抓取到的样本数据&#xff0c;并对此数据执行一系列规则&#xff0c;以汇总和记录现有数据的新时间序列或生成告警。可以通…

抓住亚马逊、shein新品扶持期,利用测评提升搜索排名与销量

亚马逊的卖家们应该意识到&#xff0c;新发布的产品在上线后的2到4周内&#xff0c;通常会获得平台的流量支持。这一阶段被称为“新品流量黄金期”&#xff0c;在此期间&#xff0c;产品的搜索排名和曝光率通常会比平时更高。因此&#xff0c;如何有效利用这一阶段&#xff0c;…

轻松入门WordPress:在Ubuntu上搭建本地网站并配置公网访问地址

文章目录 前言1. 安装WordPress2. 创建WordPress数据库3. 安装相对URL插件4. 安装内网穿透发布网站4.1 命令行方式&#xff1a;4.2. 配置wordpress公网地址 5. 配置WordPress固定公网地址 前言 本文主要介绍如何在Linux Ubuntu系统上使用WordPress搭建一个本地网站&#xff0c…

华为云计算知识总结——及案例分享

目录 一、华为云计算基础知识二、华为云计算相关案例实战案例一&#xff1a;搭建弹性云服务器&#xff08;ECS&#xff09;并部署Web应用案例二&#xff1a;构建基于OBS的图片存储和分发系统案例三&#xff1a;基于RDS的高可用数据库应用案例四&#xff1a;使用华为云DDoS防护保…

银行金融知识竞赛活动策划方案

根据《中国人民银行**市中心支行“创新金融服务&#xff0c;支持经济发展”业务竟赛活动实施方案》安排&#xff0c;中支决定于9月28日举办**市人民银行系统“创新金融服务&#xff0c;支持经济发展”现场业务竞赛&#xff0c;为确保业务竞赛组织工作顺利开展&#xff0c;特制定…

动态规划 01背包(算法)

现有四个物品&#xff0c;小偷的背包容量为8&#xff0c;怎么可以偷得价值较多的物品 如: 物品编号&#xff1a; 1 2 3 4 物品容量&#xff1a; 2 3 4 5 物品价值&#xff1a; 3 4 5 8 记f(k,w) ,当背包容量为w,可以偷k件物品…

引领数字时代:万码优才如何变革IT人才招聘新体验(这里有更精准的推荐)

目录 引领数字时代&#xff1a;万码优才如何变革IT人才招聘新体验引领未来科技&#xff0c;精准链接IT精英精准匹配&#xff0c;高效对接海量资源&#xff0c;覆盖广泛优化体验&#xff0c;简化流程 全面升级&#xff1a;AI赋能数字人才职业成长AI模拟面试职场千问智能简历评估…

Rocky Linux 9安装后无法远程ssh密码登录解决

在Rocky Linux 9版本中&#xff0c;为了增加安全性&#xff0c;默认情况下禁用SSH root密码登录。这是系统默认设定的规则&#xff0c;我们同样也可以更改它。   允许Rocky Linux 9 root用户通过ssh登录方法&#xff1a; 1.编辑SSH配置文件 2.找到以下内容 PermitRootLogin …

1.2 图像处理基本操作

在本实战中&#xff0c;我们将学习如何使用OpenCV进行基本的图像处理操作。首先&#xff0c;我们将通过cv2.imread()函数读取图像&#xff0c;并使用cv2.imshow()在窗口中显示它。接着&#xff0c;我们将探索如何通过cv2.imwrite()保存图像&#xff0c;并设置不同的参数以控制图…

【C++】哈希表模拟:开散列技术与哈希冲突处理

C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理模板初阶String使用String模拟实现Vector使用及其模拟实现List使用及其模拟实现容器适配器Stack与QueuePriori…

「Mac畅玩鸿蒙与硬件18」鸿蒙UI组件篇8 - 高级动画效果与缓动控制

高级动画可以显著提升用户体验&#xff0c;为应用界面带来更流畅的视觉效果。本篇将深入介绍鸿蒙框架的高级动画&#xff0c;包括弹性动画、透明度渐变和旋转缩放组合动画等示例。 关键词 高级动画弹性缓动自动动画缓动曲线 一、Animation 组件的高级缓动曲线 缓动曲线&#…

SpringBoot源码解析(二):启动流程之引导上下文DefaultBootstrapContext

SpringBoot源码系列文章 SpringBoot源码解析(一)&#xff1a;启动流程之SpringApplication构造方法 SpringBoot源码解析(二)&#xff1a;启动流程之引导上下文DefaultBootstrapContext 目录 前言一、入口二、DefaultBootstrapContext1、BootstrapRegistry接口2、BootstrapCon…

ELK之路第三步——日志收集筛选logstash和filebeat

logstash和filebeat&#xff08;偷懒版&#xff09; 前言logstash1.下载2.修改配置文件3.测试启动4.文件启动 filebeat1.下载2.配置3.启动 前言 上一篇&#xff0c;我们说到了可视化界面Kibana的安装&#xff0c;这一篇&#xff0c;会简单介绍logstash和filebeat的安装和配置。…

Python毕业设计选题:基于Hadoop的租房数据分析系统的设计与实现

开发语言&#xff1a;Python框架&#xff1a;flaskPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 系统首页 房屋信息详情 个人中心 管理员登录界面 管理员功能界面 用户管理界面 房屋信…