基于Vue3+Ts+Vite项目中grpc-Web的应用以及其中的坑

背景:

最近项目中有一个需求:在新项目中使用grpc进行前后端通信。我便基于此需求开始了新的研究。

首先我是想抄作业的,但是翻了很多相关grpc-web的文章,写的都不是很详细,再涉及到grpc-web服务的升级迭代,生成的代码有了变动,导致我根本没找到什么可以复用的有效资料。

没办法,只能自己搞了!文章会有点长,耐心看完应该会有些帮助。如果有大佬看到可以留下些意见,对grpc和go语言都是现学现卖的小白阶段。

其实原理还是比较简单的。如果还不了解grpc的同学可以自己去搜索一下相关知识,这个在度娘那能搜到很多。我在这里简要概括一下:

grpc释义

gRPC 是 Google 开发的一个高效的、开源的远程过程调用 (Remote Procedure Call, RPC) 框架,旨在跨网络分布式系统中实现不同服务之间的通信。它建立在 HTTP/2 协议之上,利用 Protocol Buffers 作为序列化协议,具有以下几个特点:

  1. 跨语言支持
    gRPC 支持多种编程语言,包括 Go、Java、Python、C++、Node.js 等。这使得开发者可以用不同的语言编写各自的服务,同时通过 gRPC 轻松进行通信。

  2. 高效的二进制传输
    gRPC 使用 Protocol Buffers (Protobuf) 作为其接口定义语言 (IDL) 和数据序列化格式。相比于传统的基于文本的序列化格式(如 JSON 或 XML),Protobuf 是一种高效的二进制格式,具有更小的消息体积和更快的序列化/反序列化速度。

  3. HTTP/2 支持
    gRPC 使用 HTTP/2 进行通信,具备双向流、多路复用、头部压缩等特性,使得它在需要高性能、低延迟的应用场景中具有显著优势。

  4. 多种通信模式
    gRPC 支持多种通信模式,不仅可以实现简单的一对一的请求-响应模型,还支持:

服务端流式 RPC:客户端发送一个请求,服务端可以返回多个响应。
客户端流式 RPC:客户端发送多个请求,服务端返回一个响应。
双向流式 RPC:客户端和服务端可以互相发送多个消息,形成流式通信。
5. 强类型定义
gRPC 中的服务及消息都需要通过 Protocol Buffers 来定义,提供了强类型的接口,减少了通信中的潜在错误,并且具有更好的可维护性和扩展性。

gRPC-Web 及其与 gRPC 的关系

虽然 gRPC 非常强大,但它原生是为后端服务之间的通信设计的,使用 HTTP/2 和 Protobuf 可能无法直接在浏览器环境中兼容。浏览器对 HTTP/2 和某些传输方式的支持较为有限,而且通常不能直接处理 Protobuf 编码的二进制数据。这时,gRPC-Web 出现了。

gRPC-Web 是 gRPC 的一个扩展,它允许前端应用(如在浏览器中运行的 JavaScript 应用)通过 gRPC 与后端服务通信。gRPC-Web 作为一种精简的 gRPC 实现,它通常配合一个代理(如 Envoy)使用,将浏览器的 HTTP/1.x 或 HTTP/2 请求转换为标准的 gRPC 格式,然后再发送到后端 gRPC 服务器。

gRPC-Web 的特点

兼容浏览器
gRPC-Web 让 Web 应用可以使用大部分 gRPC 的功能,虽然有些特性(如双向流)在 gRPC-Web 中受限,但它可以处理基本的请求-响应和单向流。

无需更改后端
后端仍然可以使用标准的 gRPC 服务,gRPC-Web 通过代理进行转换,无需对现有的 gRPC 服务进行重大的修改。

减少客户端复杂性
gRPC-Web 提供了简化的客户端 API,前端开发者可以像使用传统的 REST API 一样调用后端服务,但享受 gRPC 的高性能和高效的数据传输。

gRPC 和 gRPC-Web 的关系总结
gRPC 是一种后端服务之间高效通信的框架,支持多种语言和多种通信模式。
gRPC-Web 是 gRPC 的一个精简版本,专门为浏览器环境设计,允许前端通过 HTTP/1.x 或 HTTP/2 与 gRPC 后端服务通信。
它们共同构成了一个强大的生态系统,让前后端之间的通信更加高效和安全,同时保持了现代 Web 应用的开发灵活性。


而我们的需求主要是为了前后端统一用一套接口规范。即同一套proto文件,来生成前后端的接口。

概念解释清楚了,开始操作,大概可以分为以下几个部分:
  1. 定义服务和消息类型
    使用 Protocol Buffers (Protobuf) 定义 gRPC 服务及其请求和响应消息格式。这些定义描述了客户端和服务端如何通信,类似于 API 的契约。

  2. 生成客户端和服务端代码
    基于 Protobuf 文件,通过 protoc 编译器生成服务端和客户端的代码。对于前端 gRPC-Web 通信,客户端代码需要使用 gRPC-Web 的插件生成 TypeScript 或 JavaScript 文件。

  3. 实现后端服务
    在服务端实现 gRPC 服务逻辑。根据生成的代码,编写服务端的业务逻辑处理请求,并返回相应的响应。这个过程与标准 gRPC 服务实现相同。

  4. 设置代理(如 Envoy)
    由于浏览器无法直接与 gRPC 服务通信,通常需要配置一个代理(如 Envoy)。代理将前端发送的 gRPC-Web 请求转换为标准 gRPC 请求,并将响应转发回浏览器。代理还负责处理 gRPC-Web 请求的 HTTP/1.x 或 HTTP/2 兼容性问题。

  5. 前端集成 gRPC-Web
    在前端应用中使用 gRPC-Web 客户端库调用 gRPC 服务。通过生成的客户端代码,前端可以发起 gRPC-Web 请求,与后端进行通信。前端与使用传统 API 的方式相似,但底层使用 gRPC 协议。

  6. 通信和调试
    前端通过 gRPC-Web 发出请求,代理将请求转换并转发到 gRPC 服务。服务端处理请求并返回响应,代理将响应再传回前端。调试时,确保代理和服务端正确配置,且通信符合预期。

我以为我会很通顺的做完这几步,真正实践时候发现坑还挺多的。一步步来吧。

  • 定义服务和消息类型(.proto文件)
// 使用 proto3 语法版本
syntax = "proto3";// 定义包名为 calculation,这个包名会影响生成的代码的命名空间
package calculation;// 设置 Go 语言生成的包路径和包名
option go_package = "./calculation;proto";// 定义 CalculationService 服务,包含两个 RPC 方法:Add 和 Subtract
service CalculationService {// 定义 Add RPC 方法,接收 AddRequest 消息,返回 AddResponse 消息rpc Add(AddRequest) returns (AddResponse);// 定义 Subtract RPC 方法,接收 SubtractRequest 消息,返回 SubtractResponse 消息rpc Subtract(SubtractRequest) returns (SubtractResponse);
}// 定义 AddRequest 消息结构,包含两个字段 num1 和 num2,都是 double 类型
message AddRequest {double num1 = 1; // 第一个加数double num2 = 2; // 第二个加数
}// 定义 AddResponse 消息结构,包含一个字段 result,表示加法结果
message AddResponse {double result = 1; // 加法的结果
}// 定义 SubtractRequest 消息结构,包含两个字段 num1 和 num2,都是 double 类型
message SubtractRequest {double num1 = 1; // 被减数double num2 = 2; // 减数
}// 定义 SubtractResponse 消息结构,包含一个字段 result,表示减法结果
message SubtractResponse {double result = 1; // 减法的结果
}

这个proto文件中定义了两个方法,最简单的两个数字的加减,我在其中添加了注释。

接下来就是对proto文件的编译。

这里首先要确保已经安装了如下插件:
全局安装protoc:

      mac:brew install protobuflinux:sudo apt install -y protobuf-compiler

全局安装protoc-gen-grpc-web地址:protoc-gen-grpc-web
前端项目中安装grpc-web (npm)

然后将proto文件放到前端项目根路径下,并在同级目录新建一个文件夹generated存放生成的代码,执行命令:

protoc -I=. calculation.proto \--js_out=import_style=commonjs:./generated \--grpc-web_out=import_style=typescript,mode=grpcwebtext:./generated

正常会在generated目录下生成三个文件:calculation_pb.js, calculation_pb_d.ts, ApiServiceClientPb.ts

按流程接下来我们就可以使用它了。
在index.vue中:

// 从生成的 gRPC 客户端文件中导入 CalculationServiceClient 类
import { CalculationServiceClient } from '@generated/ApiServiceClientPb'// 从生成的消息类型文件中导入所有的消息类型,命名为 api
import * as api from '@generated/api_pb.js'// 创建一个 AddRequest 请求对象,用于存储加法操作的两个参数
const request = new api.AddRequest()
// 设置第一个加数 num1,值为 10
request.setNum1(10)
// 设置第二个加数 num2,值为 1
request.setNum2(1)// 创建一个 CalculationServiceClient 客户端实例,指向后端的 `/api` 端点
const client = new CalculationServiceClient('/api')// 异步函数,用于调用 gRPC 服务
async function grpcCall() {// 使用 client 调用 add 方法,传入请求对象和空的元数据对象 {}client.add(request, {}, (err, response) => {// 如果调用过程中发生错误,输出错误信息if (err) {console.error('Error:', err)} // 如果调用成功,输出响应结果else {// 从响应中获取结果 result 并输出console.log('Response-Result:', response.getResult())}})
}

到这里,抛开代理的事情不谈,我们应该是可以在控制台看到接口调用的。但是并没有(会有报错,详细报错信息可以试一下)。

原因是这里生成的代码(api.pb.js)中包含了语法:

var jspb = require('google-protobuf');
和
goog.object.extend(exports, proto.api);

这里使用了CommonJS 模块规范,但是我们项目使用了Vite,Vite 默认支持 ES Module(ESM)规范。这里我第一次尝试是使用@rollup/plugin-commonjs插件来解决,但是失败了,原因未知,反正我没成功,有兴趣可以尝试,大佬成功了可以告诉我一下哈!

对CommonJS 模块规范和ES Module规范不了解的转至这篇文章:搞清CommonJS、AMD、CMD、ES6的联系与区别

我这里用了笨办法:
var jspb = require('google-protobuf');改为import * as jspb from 'google-protobuf';,并将goog.object.extend(exports, proto.api);改为

const { ApiRequest, ApiResponse } = proto.api;
export { ApiRequest, ApiResponse };

这样就可以完美解决这个问题。并且不会影响另一个生成文件中的引用。(如下)

import * as grpcWeb from 'grpc-web';
import * as api_pb from './api_pb'; // proto import: "api.proto"

可是这会面临一个问题:如果proto文件中定义的接口很多很繁杂,并且每次更改之后都要重新来改。显然是不现实的。所以后面我封装了shell命令来帮我们完成这个操作,可以不用担心。

先把对proto文件生成和替换CommonJS 模块规范的代码贴出来吧!

  • 在package.json中添加script:
"protoc": "sh ./protoc/protoc.sh"

新建protoc文件夹,在其中新建protoc.sh文件。

# grpc_node_plugin的路径
PROTOC_GEN_GRPC_PATH="../node_modules/.bin/grpc_tools_node_protoc_plugin"# 写入生成代码的目标目录(.js和.d.ts文件)
OUT_DIR="./generated"
# 传入的 .proto 文件名,用户可以动态修改这个字段,例如修改为 user.proto
PROTO_FILE_NAME="api"  # 动态修改此变量为你需要的 proto 文件名# 自动提取包名
PROTO_FILE="${PROTO_FILE_NAME}.proto"
if [ -f "$PROTO_FILE" ]; then# 使用 grep 提取 package 行,并使用 awk 提取包名     动态修改此变量为需要的 proto 包名PROTO_PACKAGE_NAME=$(grep -m 1 '^package ' "$PROTO_FILE" | awk '{print $2}' | sed 's/;//') # 去掉分号echo "Detected package name: $PROTO_PACKAGE_NAME"
elseecho "Error: Proto file '$PROTO_FILE' not found."exit 1
fi# 移除生成目录中的旧文件
rm -rf ${OUT_DIR}/*
# 生成web所需的文件,动态使用传入的 .proto 文件
protoc \--js_out="import_style=commonjs:${OUT_DIR}" \--grpc-web_out="import_style=typescript,mode=grpcwebtext:${OUT_DIR}" \./${PROTO_FILE_NAME}.proto# 使用动态文件名拼接生成的 api_pb.js 文件路径
GENERATED_FILE="${OUT_DIR}/${PROTO_FILE_NAME}_pb.js"echo ${GENERATED_FILE}# 替换api_pb.js中的内容,生成的是commonjs格式,需要手动替换为es6语法。
node ./protoc/generateExports.js ${GENERATED_FILE} ${PROTO_PACKAGE_NAME}

其中唯一要修改的是文件名PROTO_FILE_NAME,包名会通过文件名自动检索。当然这一步也可以优化成让代码自动检索文件夹内所有proto文件并执行。

  • 在同级文件夹下新建文件generateExports.js,用来进行文件替换。
import fs from 'node:fs'
// import path from 'node:path'
import process from 'node:process'// 从命令行获取文件路径参数,如果没有传递则使用默认路径
// const filePath = process.argv[2]
const filePath = process.argv[2]
const packageName = process.argv[3]
try {// 读取指定的文件let fileContent = fs.readFileSync(filePath, 'utf-8')// 删除 goog.object.extend(exports, proto.api); api为包名,packageNameconst regex = new RegExp(`goog\\.object\\.extend\\(exports, proto\\.${packageName}\\);\\n?`, 'g')fileContent = fileContent.replace(regex, '')// 替换 var jspb = require('google-protobuf'); 为 import * as jspb from 'google-protobuf';fileContent = fileContent.replace(/var jspb = require\('google-protobuf'\);\n?/g, 'import * as jspb from \'google-protobuf\';\n')// 提取 goog.exportSymbol 语句,并生成 ES6 模块导出const exportSymbols = []const exportSymbolRegex = new RegExp(`goog\\.exportSymbol\\('proto\\.${packageName}\\.(\\w+)', null, global\\);\\n?`, 'g')fileContent = fileContent.replace(exportSymbolRegex, (match, p1) => {exportSymbols.push(p1)return match // 保留原有语句})// 添加 ES6 模块导出语句const exportStatements = exportSymbols.length > 0 ? `\n\nconst { ${exportSymbols.join(', ')} } = proto.${packageName};\nexport { ${exportSymbols.join(', ')} };` : ''const outputContent = fileContent + exportStatements// 写入生成的文件fs.writeFileSync(filePath, `${outputContent}\n`)// eslint-disable-next-line no-consoleconsole.log(`${filePath} has been generated successfully.`)
}
catch (error) {console.error(`Error processing file: ${filePath}`, error)
}

这样就完成了我们前面的基本准备。正常在执行index.vue的代码就能看到请求了。

接下来开始配置envoy代理部分:

这里由于我对envoy也没了解过。所以对它的一大串配置也是难受得很。只能慢慢试探。

  1. 下载地址:envoy
  2. 创建配置文件:
static_resources:listeners:- name: listener_0address:socket_address:address: 0.0.0.0port_value: 8080 # 代理前端gRPC请求的端口filter_chains:- filters:- name: envoy.filters.network.http_connection_managertyped_config:"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManagerstat_prefix: ingress_httpaccess_log:- name: envoy.access_loggers.stdouttyped_config:"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLoghttp_filters:- name: envoy.filters.http.grpc_webtyped_config:"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb- name: envoy.filters.http.routertyped_config:"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Routerroute_config:name: local_routevirtual_hosts:- name: local_servicedomains: ["*"]routes:- match:prefix: "/" # 匹配所有请求route:cluster: grpc_backend # 将请求转发到集群clusters:- name: grpc_backend # 集群名称type: LOGICAL_DNSconnect_timeout: 5sdns_lookup_family: V4_ONLYlb_policy: ROUND_ROBINhttp2_protocol_options: {} # 启用HTTP/2load_assignment:cluster_name: grpc_backendendpoints:- lb_endpoints:- endpoint:address:socket_address:address: 127.0.0.1port_value: 8088 # 后端 gRPC 服务的端口# transport_socket:#   name: envoy.transport_sockets.tls#   typed_config:#     "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext#     sni: 127.0.0.1 # 后端服务的 SNI

这里网上找的都没法用,改了很多次,其中有几个要注意的点:

  • http2_protocol_options: {} # 启用HTTP/2 这里一定要打开,因为grpc需要http2.
  • http_filters: - name: envoy.filters.http.grpc_web typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 这里要注意一定要加上envoy.filters.http.grpc_web这个,并加上对应地址。且envoy.filters.http.grpc_web一定要在最后面。

其他配置:

  • 顶部socket_address下面配置的地址是你要代理的前端接口地址。这里要在vue项目中单独配置反向代理给它。如下:
// vite.config.jsproxy: {'/proxy': {target: env.VITE_APP_API_BASEURL,changeOrigin: command === 'serve' && env.VITE_OPEN_PROXY === 'true',rewrite: path => path.replace(/^\/proxy/, ''),},'/api': {target: 'http://127.0.0.1:8080',changeOrigin: true,ws: true,rewrite: path => path.replace(/^\/api/, ''),},},

其中/api是我要代理出去的配置。这里端口8080和envoy相对应。所以我在index.vue中会这样写:

const client = new CalculationServiceClient('/api')
  • 底部的socket_address配置是我们要代理出去的目标地址。这里我将后端服务起在了本地的8088端口。

  • 启动envoy:

envoy -c /path/to/your/envoy.yaml --log-level debug

这样我们就完成了代码的闭环,如果你的后端没有问题的话,接口应该就可以获取到数据了!

到这里我的功能(接口调用,前后端交互)基本完成了,但是这么简单去调用很难维护。我试了一下grpc的库:@improbable-eng/grpc-web来完成这部分的调用操作。但是失败了,因为它需要的参数和我生成的文件对不上。不知道是不是因为它们的更新版本不对等。有知道的大佬可以告诉一下或者推荐文章给我,万分感谢!

所以这里无奈我只能自己手动封装了。封装逻辑在下一篇文章中vue3+Ts中grpc-web的代码封装思路。

我还分享了一篇简单的go语言实现grpc功能的最简单的例子代码: 用 Go 语言实现一个最简单的 gRPC 服务端,有兴趣可以关注一下。

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

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

相关文章

初学Qt之环境安装与 hello word

环境: Qt Creator 4.11.0 (Community) Qt 5.14.0 目录 1.Qt环境配置 1.1 下载Qt 5.14.0 1.2 注册Qt账号 1.3 安装Qt 1.4 配置环境变量 2.创建项目 2.1 创建一个项目 2.2 初始代码解析 2.3 可视化GUI ​编辑 2.4 hello word 2.4.1 可视化hello word …

线性回归-随机梯度法下降算法预测波士顿房价(附带数据集下载链接, 长期有效)

机器学习基本步骤: ​1.数据获取-导入外部数据集housing_price 数据获取: 听着高大上, 实际上就是把内部/外部数据集加载进来 2.数据基本处理-缺失值处理, 分割数据集, 数据基本处理:实际上就是对数据中缺失的数据和异常的数据进行处理, 然后进行数据集分割 3.特征工程-特…

Spring Boot知识管理系统:创新与实践

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统,它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等,非常适…

公开课学习:软件测试面试3大难题

1.验证码机制的处理:自动化遇到验证码怎么办?怎么测试? 流程:先识别元素,再对元素进行操作。实际上,验证码无法用自动化技术操作解决,都是由开发给万能码,或者屏蔽验证码去解决!那如果不能屏…

数据结构——优先级队列(堆)

概念: 在操作数据的时候,操作的数据具有优先级,需要返回最高级别的优先级数据或者添加新对象时就需要用到优先级队列。 jdk1.8中的PrioriytQueue底层实现了堆这种数据结构实际上,堆其实就是在完全二叉树进行调整而来。 堆&#x…

C++STL--------vector

文章目录 一、vector常用接口介绍1、initializer_list2、接口有很多类似3、typeid(类型).name()4、find() 函数5、内置类型构造 二、vector()常用接口模拟实现 截图来源网站:https://legacy.cplusplus.com/reference/vector/vector/ 一、vector常用接口介绍 是一个…

哪种护眼大路灯孩子用着最好?公认最好的护眼大路灯

哪种护眼大路灯孩子用着最好?最近也有不少家长关注到了孩子视力健康的这个情况,很着急开始寻找各种能够减少孩子因为不良光线影响视力健康的方法,其中大路灯以良好的表现成为家长们的首选,但快速发展的市场中,却涌入了…

Golang 逃逸分析(Escape Analysis)理解与实践篇

Golang 逃逸分析(Escape Analysis)理解与实践篇 文章目录 1.逃逸分析2.相关知识(栈、堆、GC分析)3.逃逸分析综合-实践 demo 逃逸分析(Escape Analysis)是编译器在编译期进行的一项优化技术,是Gl…

Qt QTableWidget多行表头、表头折行显示

表头折行显示 //方法一QVector<QString> chNames;chNames<<"表头1"<<"表头2长命名abcdefg";ui.tableWidget->setColumnCount(chNames.size()1);ui.tableWidget->setHorizontalHeaderItem(0, new QTableWidgetItem(QString::fromL…

【C】C语言常见概念~

C语言常见概念 转义字符 转义字符&#xff0c;顾名思义&#xff0c;转变原来意思的字符 比如 #include <stdio.h> int main() {printf("abcndef");return 0; }输出的结果为&#xff1a; 将代码修改一下&#xff1a; #include <stdio.h> int main(…

双目视觉搭配YOLO实现3D测量

一、简介 双目&#xff08;Stereo Vision&#xff09;技术是一种利用两个相机来模拟人眼视觉的技术。通过对两个相机获取到的图像进行分析和匹配&#xff0c;可以计算出物体的深度信息。双目技术可以实现物体的三维重建、距离测量、运动分析等应用。 双目技术的原理是通过两…

SpringBoot基础(五):集成JUnit5

SpringBoot基础系列文章 SpringBoot基础(一)&#xff1a;快速入门 SpringBoot基础(二)&#xff1a;配置文件详解 SpringBoot基础(三)&#xff1a;Logback日志 SpringBoot基础(四)&#xff1a;bean的多种加载方式 SpringBoot基础(五)&#xff1a;集成JUnit5 目录 一、JUnit…

敏捷开发 与 Scrum

‌敏捷开发的概念起源于20世纪90年代末期&#xff0c;旨在解决传统软件开发方法中存在的效率低下问题。‌ 传统软件开发方法因其繁琐的过程和对文档的严格要求&#xff0c;导致了“重型化危机”&#xff0c;这使得开发效率大幅下降。为了应对这些问题&#xff0c;敏捷方法应运…

微信小程序 - 01 - 一些补充和注意点(补充ing...)

目录 一、节流二、在一个发请求的函数中&#xff0c;只有发生下拉动作&#xff0c;才执行关闭下拉代码 最近在学微信小程序&#xff0c;把学习过程中的一些补充和注意点总结一下&#xff0c;内容会比较简单&#xff0c;因为只涉及基础知识&#xff0c;供个人参考 一、节流 情…

AIGC毕设项目分享:基于RAG的数字人对话系统及其应用

本研究的主要目标是设计并实现一个基于检索增强生成&#xff08;RAG&#xff09;技术的数字人对话系统&#xff0c;旨在提升数字人系统在多轮对话中的上下文管理、情境感知能力以及动态内容生成效果。系统结合了深度学习中的最新大语言模型技术&#xff0c;通过引入RAG框架来增…

K8S配置MySQL主从自动水平扩展

前提环境 操作系统Ubuntu 22.04 K8S 1.28.2集群&#xff08;1个master2个node&#xff09; MySQL 5.7.44部署在K8S的主从集群 metrics-server v0.6.4 概念简介 在K8s中扩缩容分为两种 ●Node层面&#xff1a;对K8s物理节点扩容和缩容&#xff0c;根据业务规模实现物理节点自动扩…

Python列表专题:list与in

Python是一种强大的编程语言,其中列表(list)是最常用的数据结构之一。列表允许我们存储多个元素,并且可以方便地进行各种操作。在Python中,in运算符被广泛用于检测元素是否存在于列表中。本文将深入探讨Python列表及其与in运算符的结合使用。 1. Python列表的基础 1.1 什…

爬虫案例——网易新闻数据的爬取

案例需求&#xff1a; 1.爬取该新闻网站——&#xff08;网易新闻&#xff09;的数据&#xff0c;包括标题和链接 2.爬取所有数据&#xff08;翻页参数&#xff09; 3.利用jsonpath解析数据 分析&#xff1a; 该网站属于异步加载网站——直接网页中拿不到&#xff0c;需要…

项目开发--基于docker实现模型容器化服务

背景 1、docker-compose build 和 docker-compose up -d分别是什么作用&#xff1f; 2、如何进入新构建的容器当中 3、模型保存的方法区别 4、如何让docker容器启动的时候能使用cuda进行模型推理加速 5、如何实现容器的迭代 解决方案 问题1 docker-compose build 和 docker…

WindowsAPI|每天了解几个winAPI接口之Iphlpapi.h网络配置相关文档详细分析三

上一篇&#xff1a;WindowsAPI|每天了解几个winAPI接口之Iphlpapi.h网络配置相关文档详细分析二 如果有错误欢迎指正批评&#xff0c;在此只作为科普和参考。 文章目录 GetTcpTable&GetExtendedTcpTable&#xff1a;获取TCP连接的信息GetOwnerModuleFromTcpEntry&#xff1…