rpc grpc

RPC

Remote Procedure Call,远程过程调用,是用来屏蔽分布式计算中的各种调用细节,使得调用远端的方法就像调用本地的一样。

客户端与服务端沟通的过程

  1. 客户端发送数据(以字节流的方式);编码
  2. 服务端接受并解析,根据约定知道要执行什么,然后把结果返回给客户。解码

RPC

  1. RPC就是将上述过程封装下,使其操作更加优化;
  2. 使用一些大家都认可的协议,使其规范化;
  3. 做成一些框架,直接或间接产生利益。

RPC 和 HTTP 区别

RPC 和 HTTP 不是对等的概念。

  • RPC 是一个完整的远程调用方案,它包括了:接口规范 + 序列化反序列化规范 + 通信协议等。
  • HTTP 只是一个通信协议,工作在 OSI 的第七层,不是一个完整的远程调用方案。

RPC是能够基于 HTTP 实现,也可以不基于,基于更下一层的 TCP/ UDP协议。

RPC 原理

  • 调用方为 client,被调用方为 server
    在这里插入图片描述
    ① 服务调用方(client)以本地调用方式调用服务;

② client stub 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;

③ client stub 找到服务地址,并将消息发送到服务端;

④ server 端接收到消息;

⑤ server stub 收到消息后进行解码;

⑥ server stub 根据解码结果调用本地的服务;

⑦ 本地服务执行并将结果返回给 server stub;

⑧ server stub 将返回结果打包成能够进行网络传输的消息体;

⑨ 按地址将消息发送至调用方;

⑩ client 端接收到消息;

⑪ client stub 收到消息并进行解码;

⑫ 调用方得到最终结果。

RPC 接口和传统的 http 接口的区别

传输协议

  • RPC:可以基于TCP协议,也可以基于HTTP协议。
  • HTTP:基于HTTP协议。

传输效率

  • RPC:使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以很好的减少报文的体积,提高传输效率。
  • HTTP:如果是基于HTTP1.1的协议,请求中会包含很多无用的内容,如果是基于HTTP2.0,那么简单的封装以下是可以作为一个RPC来使用的,这时标准RPC框架更多的是服务治理。

服务治理

  • RPC:能做到自动通知,不影响上游。
  • HTTP:需要事先通知,修改Nginx/HAProxy配置。

GRPC

gRPC 是一个高性能的、开源的、通用的 RPC 框架。

  • grpc 只是一个框架,是对rpc的封装,类似的框架还有thrift等。
  • 目标是只需要关心第1步和最后1步,中间的其他步骤统统封装起来,让使用者无需关心。

grpc 解决的 rpc 三大问题

协议约定

gRPC 的协议是 Protocol Buffers,是一种压缩率极高的序列化协议,Google 在 2008 年开源了 Protocol Buffers,支持多种编程语言,所以 gRPC 支持客户端与服务端可以用不同语言实现

传输协议

gRPC 的数据传输用的是 Netty Channel, Netty 是一个高效的基于异步 IO 的网络传输架构。Netty Channel 中,每个 gRPC 请求封装成 HTTP 2.0 的 Stream。

服务发现

gRPC 本身没有提供服务发现的机制,需要通过其他组件。

Protocol Buffs

Protocol Buffss 是谷歌开源的一套成熟的数据结构序列化机制。
在这里插入图片描述

  • 序列化:将数据结构或对象转换成二进制串的过程。
  • 反序列化:将在序列化过程中所产生的二进制串转换成数据结构或者对象的过程。

protobuf 是谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为profobuf是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。

优势

  1. 序列化后体积相比Json和XML很小,适合网络传输
  2. 支持跨平台多语畜
  3. 消息格式升级和兼容性还不错
  4. 序列化反序列化速度很快

grpc-study

在这里插入图片描述

proto文件编写

hello.proto:

// 这是说明使用的是proto3语法
syntax = "proto3";// 这部分的内容是关于最后生成的go文件是处在哪个目录哪个包中
// . 代表在当前目录生成
// service 代表了生成的go文件的包名是service
option go_package = ".;service";// 然后需要定义一个服务,在这个服务中需要有一个方法,这个方法可以接受客户端的参数,再返回服务端的响应
// 定义了一个service,称为SayHello,这个服务中有一个rpc方法,名为SayHello
// 这个方法会发送一个HelloRequest,然后返回一个HelloResponse
service SayHello {rpc SayHello(HelloRequest) returns (HelloResponse) {}
}// message关键字,可以理解为Golang中的结构体
// 这里比较特别的是变量后面的“赋值”。注意,这里并不是赋值,而是在定义这个变量在这个message中的位置。
message HelloRequest {string requestName = 1;
//  int64 age = 2;
}message HelloResponse {string responseMsg = 1;
}

执行命令

protoc --go_out=. hello.proto
protoc --go-grpc_out=. hello.proto

在这里插入图片描述

生成 hello_grpc.pb.go 文件和 hello.pb.go 文件。

proto文件介绍

message

  • protobuf 中定义一个消息类型式是通过关键字 message 字段指定的。
  • 消息就是需要传输的数据格式的定义。
  • message 关键字类似于 C++ 中的 class,JAVA 中的 class,go 中的 struct。
  • 在消息中承载的数据分别对应于每一个字段,其中每个字段都有一个名字和一种类型。
  • 一个 proto 文件中可以定义多个消息类型。

hello.proto

// message关键字,可以理解为Golang中的结构体
// 这里比较特别的是变量后面的“赋值”。
// 注意,这里并不是赋值,而是在定义这个变量在这个message中的位置。
message HelloRequest {string requestName = 1;
//  int64 age = 2;
}message HelloResponse {string responseMsg = 1;
}

字段规则

  • required:消息体中必填字段,不设置会导致编码异常。在 protobuf2 中使用,在 protobuf3 中被删去。
  • optional:消息体中可选字段。protobuf3 没有了required,optional 等说明关键字,都默认为optional 。
  • repeate:消息体中可重复字段,重复的值的顺序会被保留在go中重复的会被定义为切片。

hello.proto

message HelloRequest {string requestName = 1;int64 age = 2;repeated string name = 3;
}

执行 protoc --go_out=. hello.proto
hello.pb.go

type HelloRequest struct {state         protoimpl.MessageState `protogen:"open.v1"`RequestName   string                 `protobuf:"bytes,1,opt,name=requestName,proto3" json:"requestName,omitempty"`Age           int64                  `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`Name          []string               `protobuf:"bytes,3,rep,name=name,proto3" json:"name,omitempty"`unknownFields protoimpl.UnknownFieldssizeCache     protoimpl.SizeCache
}

消息号

在消息体的定义中,每个字段都必须要有一个唯一的标识号,标识号是 [1,2^29-1] 范围内的一个整数

嵌套消息

可以在其他消息类型中定义、使用消息类型。

// Person 消息定义在 PersonInfo 消息内
message PersonInfo {message Person{string name = 1;int32 height = 2;repeated int32 weight = 3;
}
repeated Person info = 1;

如果要在它的父消息类型的外部重用这个消息类型,需要Personlnfo.Person的形式使用它。

message PersonMessage {message Person{PersonInfo.Person info = 1;
}

服务定义

如果想要将消息类型用在 RPC 系统中,可以在 .proto 文件中定义一个 RPC 服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。

service searchservicef # rpc 服务函数名 (参数) 返回 (返回参数)rpc search(searchRequest) returns (searchResponse)
}

定义了一个RPC服务,该方法接受 SearchRequest 返回 SearchResponse 。

服务端编写

  • 创建 gRPC Server 对象,可以理解为它是 Server 端的抽象对象。
  • 将 server (其包含需要被调用的服务端接口) 注册到 gRPC Server 的内部注册中心。
    这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理。
  • 创建 Listen,监听 TCP 端口。
  • gRPC Server开始 lis.Accept,直到 Stop

hello-server\main.go

package mainimport ("context""fmt""google.golang.org/grpc"pb "grpc-study/hello-server/proto""net"
)// hello server
type server struct {pb.UnimplementedSayHelloServer
}func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {return &pb.HelloResponse{ResponseMsg: "hello " + req.RequestName}, nil
}func main() {// 开启端口listen, _ := net.Listen("tcp", ":9090")// 创建grpc服务grpcServer := grpc.NewServer()// 在grpc服务端中去注册我们自己编写的服务pb.RegisterSayHelloServer(grpcServer, &server{})// 启动服务err := grpcServer.Serve(listen)if err != nil {fmt.Printf("failed to serve: %v", err)}
}

客户端编写

  • 创建与给定目标 (服务端) 的连接交互。
  • 创建 server的客户端对象。
  • 发送 RPC 请求,等待同步响应,得到回调后返回响应结果。
  • 输出响应结果。

hello-clinent\main.go

package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"pb "grpc-study/hello-server/proto""log"
)func main() {// 连接到server端,此处禁用安全传输,没有加密和验证conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {log.Fatalf("did not connect: %v", err)}defer conn.Close()// 建立连接clinent := pb.NewSayHelloClient(conn)// 执行rpc调用(这个方法在服务器端来实现并返回结果)resp, _ := clinent.SayHello(context.Background(), &pb.HelloRequest{RequestName: "Shigy"})fmt.Println(resp.GetResponseMsg())
}

运行结果

先运行服务端再运行客户端:

在这里插入图片描述

认证-安全传输

gRPC 是一个典型的 C/S 模型,需要开发客户端和服务端,客户端与服务端需要达成协议,使用某一个确认的传输协议来传输数据,gRPC 通常默认是使用 protobuf 来作为传输协议,当然也是可以使用其他自定义的。
在这里插入图片描述

客户端与服务端要通信之前,客户端如何知道自己的数据是发给哪一个明确的服务端呢?反过来,服务端是不是也需要有一种方式来弄个清楚自己的数据要返回给谁呢?
那么就不得不提 gRPC 的认证。
此处说到的认证,不是用户的身份认证,而是指多个 server 和多个 client 之间,如何识别对方是谁,并且可以安全的进行数据传输

  • SSL/TLS 认证方式 (采用 http2 协议)
  • 基于 Token 的认证方式 (基于安全连接)
  • 不采用任何措施的连接,这是不安全的连接 (默认采用 http1)
  • 自定义的身份认证

客户端和服务端之间调用,可以通过加入证书的方式,实现调用的安全性。

SSL/TLS认证方式

TLS (Transport Layer Security,安全传输层),TLS 是建立在传输层 TCP 协议之上的协议,服务于应用层,它的前身是 SSL (Secure Socket Layer 安全套接字层),它实现了将应用层的报文进行加密后再交由 TCP 进行传输的功能。

TLS 协议主要解决如下三个网络安全问题。

  1. 保密 (message privacy),保密通过加密 encryption 实现,所有信息都加密传输,第三方无法嗅探;
  2. 完整性 (message integrity),通过 MAC 校验机制,一旦被篡改,通信双方会立刻发现;
  3. 认证(mutual authentication),双方认证,双方都可以配备证书,防止身份被冒充;

生产环境可以购买证书(eg. 阿里云、腾讯)或者使用一些平台发放的免费证书(eg. 宝塔)

  • key:服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密。
  • csr:证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名。
  • crt:由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息。
  • pem:是基于 Base64 编码的证书格式,扩展名包括 PEM、CRT 和 CER 。

首先通过 openssl 生成证书和私钥。

  1. 下载便捷安装包 https://slproweb.com/products/Win32OpenSSL.html 一直下一步即可
  2. 配置环境变量(路径到bin)
  3. 命令行测试 openssl

生成证书

  • 新建目录 key,在该目录下执行以下命令:
    在这里插入图片描述
    在这里插入图片描述
  • 更改 openssl.cnf
  1. openssl.cfg 复制到 key 目录下
    在这里插入图片描述在这里插入图片描述

  2. 找到 [CA_default],打开 copy_extensions = copy (就是把前面的#去掉)
    在这里插入图片描述

  3. 找到 [ reg ],打开 reg_extensions = v3_req ...
    在这里插入图片描述

  4. 找到 [v3_reg],添加 subjectAltName =@alt_names
    在这里插入图片描述

  5. 添加新的标签 [ alt_names ],和标签字段 DNS.1 = *.kuangstudy.com
    在这里插入图片描述

  • key 目录下执行以下命令:
  1. 生成证书私钥 test.key
openssl genpkey -algorithm RSA -out test.key
  1. 通过私钥 test.key 生成证书请求文件 test.csr (注意cfg和cnf)
openssl req -new -nodes -key test.key -out test.csr -days 3650 -subj "/C=cn/OU=myorg/O=mycomp/CN=myname" -config ./openssl.cfg -extensions v3_req

test.csr 是上面生成的证书请求文件。ca.crt/server,key 是 CA证书文件和 key,用来对 test.csr 进行签名认证。这两个文件在第一部分生成。

  1. 生成 SAN证书 pem
openssl x509 -req -days 365 -in test.csr -out test.pem -CA server.crt -CAkey  server.key -CAcreateserial -extfile ./openssl.cfg -extensions v3_req

在这里插入图片描述

服务端

hello-server\main.go

package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials"pb "grpc-study/hello-server/proto""net"
)// hello server
type server struct {pb.UnimplementedSayHelloServer
}func (s *server) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {return &pb.HelloResponse{ResponseMsg: "hello " + req.RequestName}, nil
}func main() {// TSL认证// 两个参数分别足 cretFile ,keyFile// 自答名证书文件和私钥文件creds, _ := credentials.NewServerTLSFromFile("C:\\workspace4Goland\\grpc-study\\key\\test.pem", "C:\\workspace4Goland\\grpc-study\\key\\test.key")// 开启端口listen, _ := net.Listen("tcp", ":9090")// 创建grpc服务grpcServer := grpc.NewServer(grpc.Creds(creds))// 在grpc服务端中去注册我们自己编写的服务pb.RegisterSayHelloServer(grpcServer, &server{})// 启动服务err := grpcServer.Serve(listen)if err != nil {fmt.Printf("failed to serve: %v", err)}
}

客户端

hello-clinent\main.go

package mainimport ("context""fmt""google.golang.org/grpc""google.golang.org/grpc/credentials"pb "grpc-study/hello-server/proto""log"
)func main() {creds, _ := credentials.NewClientTLSFromFile("C:\\workspace4Goland\\grpc-study\\key\\test.pem", "*.kuangstudy.com")// 连接到server端,此处禁用安全传输,没有加密和验证conn, err := grpc.Dial("127.0.0.1:9090", grpc.WithTransportCredentials(creds))if err != nil {log.Fatalf("did not connect: %v", err)}defer conn.Close()// 建立连接clinent := pb.NewSayHelloClient(conn)// 执行rpc调用(这个方法在服务器端来实现并返回结果)resp, _ := clinent.SayHello(context.Background(), &pb.HelloRequest{RequestName: "Shigy"})fmt.Println(resp.GetResponseMsg())
}

Token认证

先看一个 gRPC 提供的一个接口,这个接口中有两个方法,接口位于 credentials 包下,这个接口需要客户端来实现

type PerRPCCredentials interface {GetRequestMetadata(ctx context.Context, uri ...string)(map[string]string, error)RequireTransportSecurity bool
}
  • 第一个方法作用是获取元数据信息,也就是客户端提供的key,value对,context 用于控制超时和取消,uri 是请求入口处的 uri
  • 第二个方法的作用是否需要基于 TLS 认证进行安全传输,如果返回值是 true,则必须加上 TLS 验证,返回值是 false 则不用

gRPC 将各种认证方式浓缩统一到一个凭证(credentials)上,可以单独使用一种凭证,比如只使用 TLS 凭证或者只使用自定义凭证,也可以多种凭证组合,gRPC 提供统一的 API 验证机制,使研发人员使用方便,这也是 gRPC 设计的巧妙之处。

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

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

相关文章

GStreamer —— 2.15、Windows下Qt加载GStreamer库后运行 - “播放教程 1:Playbin 使用“(附:完整源码)

运行效果 介绍 我们已经使用了这个元素,它能够构建一个完整的播放管道,而无需做太多工作。 本教程介绍如何进一步自定义,以防其默认值不适合我们的特定需求。将学习: • 如何确定文件包含多少个流,以及如何切换 其中。…

30、Vuex 为啥可以进行缓存处理

Vuex 状态管理基础与缓存的关联 Vuex 的核心概念: Vuex 主要由五个部分组成:state、mutations、actions、getters和modules。其中,state是存储数据的地方,类似于一个全局的数据仓库。在这个菜谱 APP 的例子中,缓存的数…

25届数字IC验证秋招总结

一、个人概况 双非本9硕,2024年初开始通过白皮书蓝皮书自学验证,半年实习经验,有竞赛无专利论文,在秋招期间投递企业130余家,绝大部分投递岗位为数字验证,面试20家,收到5个offer。因为背景和相关…

【商城实战(37)】Spring Boot配置优化:解锁高效商城开发密码

【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配&#xf…

网页制作12-html,css,javascript初认识のJavascipt脚本基础

一、JavaScript的三种基本使用方法:body|head|外部 网页效果: 运行代码: .html <!doctype html> <html> <head> <meta charset="utf-8"> <title>无标题文档</title><script> function n1(){document.getElementById(…

全面对比分析:HDMI、DP、DVI、VGA、Type-C、SDI视频接口特点详解

在当今的多媒体时代&#xff0c;视频接口的选择对于设备连接和显示效果至关重要。不同的视频接口在传输质量、兼容性、带宽等方面各有优劣。本文将全面对比分析常用的视频接口HDMI、DP、DVI、VGA、Type-C、SDI&#xff0c;帮助读者更好地理解它们的特点和适用场景。 一、HDMI&…

麒麟服务器操作系统PostgreSQL环境部署手册

软件简介 PostgreSQL 是一个免费的对象-关系数据库服务器(ORDBMS),在灵活的BSD许可证下发行。 ORDBMS(对象关系数据库系统)是面向对象技术与传统的关系数据库相结合的产物,查询处理是 ORDBMS 的重要组成部分,它的性能优劣将直接影响到DBMS 的性能。 软件环境 操作系统…

【蓝桥杯速成】| 4.递归

递归 题目一&#xff1a;最大公约数 问题描述 1979. 找出数组的最大公约数 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums &#xff0c;返回数组中最大数和最小数的 最大公约数 。 两个数的 最大公约数 是能够被两个数整除的最大正整数。 解题步骤 需要…

当大模型训练遇上“双向飙车”:DeepSeek开源周 DualPipe解析指南

前言 在大模型训练中&#xff0c;传统流水线并行因单向数据流和通信延迟的限制&#xff0c;导致GPU利用率不足60%&#xff0c;成为算力瓶颈。DeepSeek团队提出的DualPipe双向流水线架构&#xff0c;通过双向计算流与计算-通信重叠的创新设计&#xff0c;将前向与反向传播拆解为…

蓝桥杯好题推荐---前缀和

&#x1f308;个人主页&#xff1a; 羽晨同学-CSDN博客 &#x1f4ab;个人格言:“成为自己未来的主人~” 题目链接 【模板】前缀和https://ac.nowcoder.com/acm/problem/226282 解题思路 这种题目是要求我们找到一个数组中从l到r的元素的和&#xff0c;查询Q次&#xff0c;…

Nginx快速上手

什么是nginx Nginx 是一款开源的高性能 HTTP 和反向代理服务器&#xff0c;同时也提供了 IMAP/POP3/SMTP 代理功能。它由俄罗斯程序员 Igor Sysoev 于2004年首次发布&#xff0c;最初设计目的是为了解决 C10k 问题&#xff0c;即如何让单台服务器同时处理1万个并发连接的问题。…

【C++】:STL详解 —— 布隆过滤器

目录 布隆过滤器的概念 布隆过滤器的优点 布隆过滤器的缺点 布隆过滤器使用场景 布隆过滤器的实现 布隆过滤器的概念 布隆过滤器&#xff08;Bloom Filter&#xff09; 是一种空间效率极高的概率型数据结构&#xff0c;用于快速判断一个元素是否属于某个集合。其核心特点…

从Instagram到画廊:社交平台如何改变艺术家的展示方式

从Instagram到画廊&#xff1a;社交平台如何改变艺术家的展示方式 在数字时代&#xff0c;艺术家的展示方式正在经历一场革命。社交平台&#xff0c;尤其是Instagram&#xff0c;已经成为艺术家展示作品、与观众互动和建立品牌的重要渠道。本文将探讨社交平台如何改变艺术家的…

MySQL(事物上)

目录 示例&#xff1a; 一 引入事物 1. 概念 2. 事物的4大特性 3. 为什么要有事物&#xff1f; 二 事物操作 1. 查看存储引擎支持的事物 2. 事物的提交方式 2.1 查看事物的默认提交方式 2.2 设置事物的默认提交方式 2.3 查看事物的全局隔离级别 2.4 验证事物的回滚…

Spring Boot 实现多数据源配置

一、配置流程 在 Spring Boot 中实现多数据源配置通常用于需要连接多个数据库的场景。主要有以下几个步骤&#xff1a; 配置多个数据源的连接信息。定义多个数据源的 Bean。为每个数据源配置MyBatis的SqlSessionFactory和事务管理器。为每个数据源定义Mapper接口和Mapper XML…

p5.js:绘制各种内置的几何体,能旋转

向 豆包 提问&#xff1a;请编写 p5.js 示例&#xff0c; 绘制各种内置的几何体&#xff0c;能让这些几何体缓慢旋转。 cd p5-demo copy .\node_modules\p5\lib\p5.min.js . 此代码创建了一个包含多个内置几何体的 3D 场景&#xff0c;每个几何体都有不同的颜色和位置。运行代…

结构体定义与应用

引言 到今天为止,c语言的基础操作和基础数据类型,就都已经结束了,大家都知道,如果要实现复杂的功能,大家都可以通过函数封装调用,那么如果要实现基础数据类型的封装,该怎么办呢?答案就是结构体。 在C语言编程中,结构体(struct)是非常重要的一个概念,它为程序员提供…

MindGYM:一个用于增强视觉-语言模型推理能力的合成数据集框架,通过生成自挑战问题来提升模型的多跳推理能力。

2025-03-13&#xff0c;由中山大学和阿里巴巴集团的研究团队提出了MindGYM框架&#xff0c;通过合成自挑战问题来增强视觉-语言模型&#xff08;VLMs&#xff09;的推理能力。MindGYM框架通过生成多跳推理问题和结构化课程训练&#xff0c;显著提升了模型在推理深度和广度上的表…

R语言零基础系列教程-01-R语言初识与学习路线

代码、讲义、软件回复【R语言01】获取。 R语言初识 R是一个开放的统计编程环境&#xff0c;是一门用于统计计算和作图的语言。“一切皆是对象”&#xff0c;数据、函数、运算符、环境等等都是对象。易学&#xff0c;代码像伪代码一样简洁&#xff0c;可读性高强大的统计和可视…

PythonWeb开发框架—Flask-APScheduler超详细使用讲解

1.定时任务的两种实现方式 1.1 用scheduler.task装饰任务 安装插件&#xff1a; pip install Flask-APScheduler pip install apscheduler 脚本实现&#xff1a; ###app.py##导入依赖库 from flask import Flask import datetime import config from flask_apscheduler i…