Go-服务注册和发现,负载均衡,配置中心
文章目录
- 什么是服务注册和发现
- 技术选型
- Consul 的安装和配置
- 1. 安装
- 2. 访问
- 3. 访问dns
- Consul 的api接口
- go操作consul
- grpc下的健康检查
- grpc的健康检查规范
- 动态获取可用端口号
- 负载均衡策略
- 1. 什么是负载均衡
- 2. 负载均衡策略
- 1. 集中式load balance
- 2. 进程内load balance
- 3. 独立进程load balance
- 常见的负载均衡算法
- 1. 轮询(Round Robin)法
- 2. 随机法
- 3. 源地址哈希法
- 4. 加权轮询(Weight Round Robin)法
- 5. 加权随机(Weight Random)法
- 6. 最小连接数法
- grpc负载均衡策略
- 为什么需要分布式配置中心
- 分布式配置中心选型
- nacos的基本使用
- gin集成nacos
- 如何将nacos中的配置映射成go的struct
什么是服务注册和发现
假如这个产品已经在线上运行,有一天运营想搞一场促销活动,那么我们相对应的【用户服务】可能就要新开启三个微服务实例来支撑这场促销活动。而与此同时,作为苦逼程序员的你就只有手动去 API gateway 中添加新增的这三个微服务实例的 ip 与port ,一个真正在线的微服务系统可能有成百上千微服务,难道也要一个一个去手动添加吗?有没有让系统自动去实现这些操作的方法呢?答案当然是有的。
当我们新添加一个微服务实例的时候,微服务就会将自己的 ip 与 port 发送到注册中心,在注册中心里面记录起来。当 API gateway 需要访问某些微服务的时候,就会去注册中心取到相应的 ip 与 port。从而实现自动化操作。
技术选型
Consul 与其他常见服务发现框架对比

Consul 的安装和配置
1. 安装
docker run -d -p 8500:8500 -p 8300:8300 -p 8301:8301 -p 8302:8302 -p 8600:8600/ud
docker container update --restart=always 容器名字
2. 访问
浏览器访问 127.0.0.1:8500
3. 访问dns
consul提供dns功能,可以让我们通过, 可以通过dig命令行来测试,consul默认的dns端口是8600, 命令行:
linux下的dig命令安装:
yum install bind-utils
dig @192.168.1.103 -p 8600 consul.service.consul SRV
windows下载dig命令 :dig.zip
Consul 的api接口
- 添加服务
https://www.consul.io/api-docs/agent/service#register-service - 删除服务
https://www.consul.io/api-docs/agent/service#deregister-service - 设置健康检查
https://www.consul.io/api-docs/agent/check - 同一个服务注册多个实例
- 获取服务
https://www.consul.io/api-docs/agent/service#list-services
go操作consul
package mainimport ("fmt""github.com/hashicorp/consul/api")
func Register(address string, port int, name string, tags []string, id string) ercfg := api.DefaultConfig()cfg.Address = "192.168.1.103:8500"client, err := api.NewClient(cfg)if err != nil {panic(err)}//⽣成对应的检查对象check := &api.AgentServiceCheck{HTTP: "http://192.168.1.102:8021/health",Timeout: "5s",Interval: "5s",DeregisterCriticalServiceAfter: "10s",
}//⽣成注册对象registration := new(api.AgentServiceRegistration)registration.Name = nameregistration.ID = idregistration.Port = portregistration.Tags = tagsregistration.Address = addressregistration.Check = checkerr = client.Agent().ServiceRegister(registration)if err != nil {panic(err)}return nil}
func AllServices(){cfg := api.DefaultConfig()cfg.Address = "192.168.1.103:8500"client, err := api.NewClient(cfg)if err != nil {panic(err)}data, err := client.Agent().Services()if err != nil {panic(err)}for key, _ := range data{fmt.Println(key)}
}
func FilterSerivice(){cfg := api.DefaultConfig()cfg.Address = "192.168.1.103:8500"client, err := api.NewClient(cfg)if err != nil {panic(err)}data, err := client.Agent().ServicesWithFilter(`Service == "user-web"`)if err != nil {panic(err)}for key, _ := range data{fmt.Println(key)}
}
func main(){//_ = Register("192.168.1.102", 8021, "user-web", []string{"mxshop", "bobby"}//AllServices()FilterSerivice()
}
grpc下的健康检查
grpc的健康检查规范
官方文档
grpc健康检查重要点:
1. check = {
“GRPC”: f’{ip}:{port}’,
“GRPCUseTLS”: False,
“Timeout”: “5s”,
“Interval”: “5s”,
“DeregisterCriticalServiceAfter”: “5s”,
}
- 一定要确保网络是通的
- 一定要确保srv服务监听端口是对外可访问的
- GRPC一定要自己填写
动态获取可用端口号
package utilsimport ("net")func GetFreePort() (int, error) {addr, err := net.ResolveTCPAddr("tcp", "localhost:0")if err != nil {return 0, err}l, err := net.ListenTCP("tcp", addr)if err != nil {return 0, err}defer l.Close()return l.Addr().(*net.TCPAddr).Port, nil
}
负载均衡策略
1. 什么是负载均衡

2. 负载均衡策略
1. 集中式load balance
集中式LB方案,如下图。首先,服务的消费方和提供方不直接耦合,而是在服务消费者和服务提供者之间有一个独立的LB(LB通常是专门的硬件设备如F5,或者基于软件如LVS,HAproxy等实现)。

LB上有所有服务的地址映射表,通常由运维配置注册,当服务消费方调用某个目标服务时,它向LB发起请求,由LB以某种策略(比如Round-Robin)做负载均衡后将请求转发到目标服务。
服务消费方如何发现LB呢?通常的做法是通过DNS,运维人员为服务配置一个DNS域名,这个域名指向LB。
这种方案基本可以否决,因为它有致命的缺点:所有服务调用流量都经过load balance服务器,所以load balance服务器成了系统的单点,一旦LB发生故障对整个系统的影响是灾难性的。为了解决这个问题,必然需要对这个load balance部件做分布式处理(部署多个实例,冗余,然后解决一致性问题等全家桶解决方案),但这样做会徒增非常多的复杂度。
2. 进程内load balance
进程内load balance。将load balance的功能和算法以sdk的方式实现在客户端进程内。先看架构图:

可看到引入了第三方:服务注册中心。它做两件事:
- 维护服务提供方的节点列表,并检测这些节点的健康度。检测的方式是:每个节点部署成功,都通知服务注册中心;然后一直和注册中心保持心跳。
- 允许服务调用方注册感兴趣的事件,把服务提供方的变化情况推送到服务调用方。这种方案下,整个load balance的过程是这样的:
- 服务注册中心维护所有节点的情况。
- 任何一个节点想要订阅其他服务提供方的节点列表,向服务注册中心注册。
- 服务注册中心将服务提供方的列表(以长连接的方式)推送到消费方。
- 消费方接收到消息后,在本地维护一份这个列表,并自己做load balance。
可见,服务注册中心充当什么角色?它是唯一一个知道整个集群内部所有的节点情况的中心。所以对它的可用性要求会非常高,这个组件可以用Zookeeper实现。
这种方案的缺点是:每个语言都要研究一套sdk,如果公司内的服务使用的语言五花八门的话,这方案的成本会很高。第二点是:后续如果要对客户库进行升级,势必要求服务调用方修改代码并重新发布,所以该方案的升级推广有不小的阻力。
3. 独立进程load balance
该方案是针对第二种方案的不足而提出的一种折中方案,原理和第二种方案基本类似,不同之处是,他将LB和服务发现功能从进程内移出来,变成主机上的一个独立进程,主机上的一个或者多个服务要访问目标服务时,他们都通过同一主机上的独立LB进程做服务发现和负载均衡。如图

这个方案解决了上一种方案的问题,不需要为不同语言开发客户库,LB的升级不需要服务调用方改代码。
但新引入的问题是:这个组件本身的可用性谁来维护?还要再写一个watchdog去监控这个组件?另外,多了一个环节,就多了一个出错的可能,线上出问题了,也多了一个需要排查的环节。
常见的负载均衡算法
在分布式系统中,多台服务器同时提供一个服务,并统一到服务配置中心进行管理,消费者通过查询服务配置中心,获取到服务到地址列表,需要选取其中一台来发起RPC远程调用。如何选择,则取决于具体的负载均衡算法,对应于不同的场景,选择的负载均衡算法也不尽相同。负载均衡算法的种类有很多种,常见的负载均衡算法包括轮询法、随机法、源地址哈希法、加权轮询法、加权随机法、最小连接法等,应根据具体的使用场景选取对应的算法。
1. 轮询(Round Robin)法
轮询很容易实现,将请求按顺序轮流分配到后台服务器上,均衡的对待每一台服务器,而不关心服务器实际的连接数和当前的系统负载。
2. 随机法
通过系统随机函数,根据后台服务器列表的大小值来随机选取其中一台进行访问。由概率概率统计理论可以得知,随着调用量的增大,其实际效果越来越接近于平均分配流量到后台的每一台服务器,也就是轮询法的效果。
3. 源地址哈希法
源地址哈希法的思想是根据服务消费者请求客户端的IP地址,通过哈希函数计算得到一个哈希值,将此哈希值和服务器列表的大小进行取模运算,得到的结果便是要访问的服务器地址的序号。采用源地址哈希法进行负载均衡,相同的IP客户端,如果服务器列表不变,将映射到同一个后台服务器进行访问。
4. 加权轮询(Weight Round Robin)法
不同的后台服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不一样。跟配置高、负载低的机器分配更高的权重,使其能处理更多的请求,而配置低、负载高的机器,则给其分配较低的权重,降低其系统负载,加权轮询很好的处理了这一问题,并将请求按照顺序且根据权重分配给后端。
5. 加权随机(Weight Random)法
加权随机法跟加权轮询法类似,根据后台服务器不同的配置和负载情况,配置不同的权重。不同的是,它是按照权重来随机选取服务器的,而非顺序。
6. 最小连接数法
前面我们费尽心思来实现服务消费者请求次数分配的均衡,我们知道这样做是没错的,可以为后端的多台服务器平均分配工作量,最大程度地提高服务器的利用率,但是,实际上,请求次数的均衡并不代表负载的均衡。因此我们需要介绍最小连接数法,最小连接数法比较灵活和智能,由于后台服务器的配置不尽相同,对请求的处理有快有慢,它正是根据后端服务器当前的连接情况,动态的选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能的提高后台服务器利用率,将负载合理的分流到每一台服务器。
grpc负载均衡策略
-
grpc的负载均衡策略
文档 -
go使用grpc负载均衡
文档 -
关于serverconfig
官方文档 -
go的grpc测试
package mainimport ("OldPackageTest/grpclb_test/proto""context""fmt""log"_ "github.com/mbobakov/grpc-consul-resolver" // It's important"google.golang.org/grpc")
func main() {conn, err := grpc.Dial("consul://192.168.1.103:8500/user-srv?wait=14s&tag=srv",grpc.WithInsecure(),grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),)if err != nil {log.Fatal(err)}defer conn.Close()for i := 0; i<10; i++{userSrvClient := proto.NewUserClient(conn)rsp, err := userSrvClient.GetUserList(context.Background(), &proto.PageInPn: 1,PSize: 2,})if err != nil {panic(err)}for index, data := range rsp.Data{fmt.Println(index, data)}}
}
为什么需要分布式配置中心
我们现在有一个项目,使用gin进行开发的,配置文件的话我们知道是一个叫做config.yaml的文件。
我们也知道这个配置文件会在项目启动的时候被加载到内存中进行使用的。
考虑两种情况:
a. 添加配置项
ⅰ. 你现在的用户服务有10个部署实例,那么添加配置项你得去十个地方修改配置文件还得重新启动等。
ⅱ. 即使go的viper能完成修改配置文件自动生效,那么你得考虑其他语言是否也能做到这点,其他的服务是否也一定会使用viper?
b. 修改配置项
大量的服务可能会使用同一个配置,比如我要更好jwt的secrect,这么多实例怎么办?
c. 开发、测试、生产环境如何隔离:
前面虽然已经介绍了viper,但是依然一样的问题,这么多服务如何统一这种考虑因素?
分布式配置中心选型
目前最主流的分布式配置中心主要是有spring cloud config、 apollo和nacos,spring cloud属于java的spring体系,我们就考虑apollo和nacos。apollo与nacos 都为目前比较流行且维护活跃的2个配置中心。
apollo是协程开源,nacos是阿里开源
- apollo大而全,功能完善。nacos小而全,可以对比成django和flask的区别
- 部署nacos更加简单。
- nacos不止支持配置中心还支持服务注册和发现。
- 都支持各种语言,不过apollo是第三方支持的,nacos是官方支持各种语言
两者都很活跃,不过看得出来nacos想要构建的生态野心更大,不过收费意图明显。

nacos的基本使用
文档
- 命名空间
可以隔离配置集,将某些配置集放到某一个命名空间之下。
命名空间我们一般用来区分 微服务 - 组
抛出一个问题: 你现在确实能够隔离微服务,但是不同的微服务的开发、测试、生产环境如何区别,组可以用来区别区别开发、测试、生产环境 - dataid - 配置集
一个配置集就是一个配置文件, 实际上可以更灵活
gin集成nacos
nacos-sdk-go地址
- go操作nacos
package mainimport ("fmt""time""github.com/nacos-group/nacos-sdk-go/clients""github.com/nacos-group/nacos-sdk-go/common/constant""github.com/nacos-group/nacos-sdk-go/vo")
func main(){sc := []constant.ServerConfig{{IpAddr: "192.168.1.103",Port: 8848,},
}cc := constant.ClientConfig {NamespaceId: "c1872978-d51c-4188-a497-4e0cd20b97d5", // 如果需要⽀持TimeoutMs: 5000,NotLoadCacheAtStart: true,LogDir: "tmp/nacos/log",CacheDir: "tmp/nacos/cache",RotateTime: "1h",MaxAge: 3,LogLevel: "debug",
}
configClient, err := clients.CreateConfigClient(map[string]interface{}{"serverConfigs": sc,"clientConfig": cc,
})
if err != nil {panic(err)
}content, err := configClient.GetConfig(vo.ConfigParam{DataId: "user-web.yaml",Group: "dev"})if err != nil {panic(err)}fmt.Println(content)err = configClient.ListenConfig(vo.ConfigParam{DataId: "user-web.yaml",Group: "dev",OnChange: func(namespace, group, dataId, data string) {fmt.Println("配置⽂件变化")fmt.Println("group:" + group + ", dataId:" + dataId + ", data:" + dat},})time.Sleep(3000 * time.Second)
}
如何将nacos中的配置映射成go的struct
转换地址: https://www.json2yaml.com/convert-yaml-to-json
相关文章:
Go-服务注册和发现,负载均衡,配置中心
文章目录 什么是服务注册和发现技术选型 Consul 的安装和配置1. 安装2. 访问3. 访问dns Consul 的api接口go操作consulgrpc下的健康检查grpc的健康检查规范动态获取可用端口号 负载均衡策略1. 什么是负载均衡2. 负载均衡策略1. 集中式load balance2. 进程内load balance3. 独立…...
k8s-实验部署 1
1、k8s集群部署 更改所有主机名称和解析 开启四台实验主机,k8s1 仓库;k8s2 集群控制节点; k8s3 和k8s4集群工作节点; 集群环境初始化 使用k8s1作为仓库,将所有的镜像都保存在本地,不要将集群从外部走 仓库…...
Git的原理与使用(一)
目录 Git初始 Git安装 Git基本操作 创建git本地仓库 配置git 工作区,暂存区,版本库 添加文件,提交文件 查看.git文件 修改文件 版本回退 小结 Git初始 git是一个非常强大的版本控制工具.可以快速的将我们的文档和代码等进行版本管理. 下面这个实例看理解下为什么需…...
1204. 错误票据
题目: 1204. 错误票据 - AcWing题库 思路: 将输入的数据存入数组,从小到大排序后遍历,若 (a[i] a[i - 1])res1 a[i]--->重号;若(a[i] - a[i - 1] > 2)res2 a[i] - 1--->断号。 难点:题目只告诉我们输入…...
uniapp中在组件中使用被遮挡或层级显示问题
uniapp中在组件中使用或croll-view标签内使用uni-popup在真机环境下会被scroll-view兄弟元素遮挡,在开发环境下和安卓系统中可以正常显示,但在ios中出现了问题 看了许多文章都没有找到问题的原因,最后看到这一个文章http://t.csdnimg.cn/pvQ…...
windows下安装es及logstash、kibna
1、安装包下载 elasticsearch https://www.elastic.co/cn/downloads/past-releases#elasticsearch kibana安装包地址: https://www.elastic.co/cn/downloads/past-releases/kibana-8-10-4 logstash安装包地址: https://www.elastic.co/cn/downloads/past…...
华为ensp:rip宣告
ip全部配置好 R1 进入r1视图模式 rip network 192.168.1.0 network 1.0.0.0 R2 进入r2视图模式 rip network 192.168.2.0 network 1.0.0.0 这样就完成了宣告 display ip routing-table 查看路由表...
Django中简单的增删改查
用户列表展示 建立列表 views.py def userlist(request):return render(request,userlist.html) urls.py urlpatterns [path(admin/, admin.site.urls),path(userlist/, views.userlist), ]templates----userlist.html <!DOCTYPE html> <html lang"en">…...
HCIE-Rainbow迁移工具
Rainbow迁移工具 Rainbow迁移工具支持p2v(物理机到虚拟机的迁移) v2v(虚拟机到虚拟机的迁移) Rainbow迁移是整机迁移,不会单独迁移上层的业务,也不会单独迁移数据,只会迁移整个虚拟机或者磁盘。…...
AI 绘画 | Stable Diffusion 涂鸦功能与局部重绘
在 StableDiffusion图生图的面板里,除了图生图(img2img)选卡外,还有局部重绘(Inpaint),涂鸦(Sketch),涂鸦重绘(Inpaint Sketch),上传重绘蒙版(Inpaint Uplaod)、批量处理(…...
[LeetCode周赛复盘] 第 371 场周赛20231112
[LeetCode周赛复盘] 第 371 场周赛20231112 一、本周周赛总结100120. 找出强数对的最大异或值 I1. 题目描述2. 思路分析3. 代码实现 100128. 高访问员工1. 题目描述2. 思路分析3. 代码实现 100117. 最大化数组末位元素的最少操作次数1. 题目描述2. 思路分析3. 代码实现 100124…...
Google Guava Cache LoadingCache 基本使用
一. 添加依赖 <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>27.1-jre</version> </dependency>二. 创建CacheLoader LoadingCache<Long, String> cache CacheBuilder.newB…...
AWS云服务器EC2实例进行操作系统迁移
AWS云服务器EC2实例进行操作系统迁移 文章目录 AWS云服务器EC2实例进行操作系统迁移1. 亚马逊EC2云服务器简介1.2 亚马逊EC2云务器与弹性云服务器区别 2. 亚马逊EC2云服务器配置流程2.1 亚马逊EC2云服务器实例配置2.1.1 EC2实例购买教程2.1.1 EC2实例初始化配置2.1.2 远程登录E…...
《015.SpringBoot+vue之音乐网》【前后端分离】
《015.SpringBootvue之音乐网》【前后端分离】 项目简介 [1]本系统涉及到的技术主要如下: 推荐环境配置:DEA jdk1.8 Maven MySQL 前后端分离; 后台:SpringBootMybatisMySQL; 前台:Vue3.0 TypeScript Vue-Router Vuex Axios …...
网格算法和穷举法
介绍 网格算法和穷举法都是暴力搜索最优点的算法,在很多竞赛题中有应用,当重点讨论模型本身而轻视算法的时候,可以使用这种暴力方案,最好使用一些高级语言作为编程工具 当需要在多个离散的点(比如网格点)…...
【AI】自回归 (AR) 模型使预测和深度学习变得简单
自回归 (AR) 模型是统计和时间序列模型,用于根据数据点的先前值进行分析和预测。这些模型广泛应用于各个领域,包括经济、金融、信号处理和自然语言处理。 自回归模型假设给定时间变量的值与其过去的值线性相关,这使得它们可用于建模和预测时…...
安卓常见设计模式14------单例模式(Kotlin版)
1. W1 是什么,什么是单例模式? 单例模式属于创建型模式,旨在确保一个类只有一个实例,并提供一个全局访问点来获取该实例。单例模式的核心思想是限制类的实例化,使得系统中只有一个共享的实例。 2. W2 为什么&#…...
卡尔曼家族从零解剖-(06)一维卡尔曼滤波编程实践
讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解的 卡尔曼家族从零解剖 链接 :卡尔曼家族从零解剖-(00)目录最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/133846882 文末正下方中心提供了本人 联系…...
macOS使用conda初体会
最近在扫盲测序的一些知识 其中需要安装一些软件进行练习,如质控的fastqc,然后需要用conda来配置环境变量和安装软件。记录一下方便后续查阅学习 1.安装miniconda 由于我的电脑之前已经安装了brew,所以我就直接用brew安装了 brew install …...
GetPrivateProfileSection使用
基本语法 GetPrivateProfileSection 是一个 Windows API 函数,用于检索指定 INI 文件中特定节的所有键值对。它可以读取INI文件中指定节所有的键值对并将结果存储在指定的缓冲区中。 以下是 GetPrivateProfileSection 函数的基本语法: DWORD GetPriva…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
