【ETCD】【源码阅读】ETCD启动流程源码解读

启动流程的图如下:
在这里插入图片描述

1、主函数入口

ETCD 启动的入口在 etcd/server/main.go 文件中。

package mainimport ("os""go.etcd.io/etcd/server/v3/etcdmain"
)func main() {etcdmain.Main(os.Args)
}

这里调用了 etcdmain.Main(),这是 ETCD 的实际启动逻辑。

2、etcdmain.Main(os.Args)详解

代码源文件:etcd/server/v3/etcdmain.main.go

func Main(args []string) {// 1. 检查系统架构支持checkSupportArch()if len(args) > 1 { // // 2. 检查命令行参数cmd := args[1]switch cmd {case "gateway", "grpc-proxy": // // 3. 判断是否运行网关或 gRPC 代理模式if err := rootCmd.Execute(); err != nil {fmt.Fprint(os.Stderr, err)os.Exit(1)}return}}startEtcdOrProxyV2(args) // // 4. 启动普通 ETCD 节点或代理节点
}
2.1.checkSupportArch()
  • 功能:检查当前运行的系统架构是否被 ETCD 支持。
  • 目的:确保程序在不支持的架构上不会运行,例如特定 ARM 版本可能不被支持。
2.2. 检查命令行参数:
  • args 是启动时传递的命令行参数,args[0] 通常是可执行文件名,args[1] 是实际命令。
2.3. 启动不同模式:
  • 如果命令为 "gateway""grpc-proxy",表示要运行 ETCD 的网关或 gRPC 代理模式。
  • 执行 rootCmd.Execute(),启动对应的命令逻辑。
  • rootCmd 是 Cobra 框架定义的根命令,包含所有子命令和配置。
2.4. 启动普通节点或代理节点:
  • 如果没有匹配到特殊命令,调用 startEtcdOrProxyV2(args)

  • 功能:根据配置和参数,决定是启动标准 ETCD 节点还是运行代理模式。

    ​ 接下来可以深入分析 startEtcdOrProxyV2 函数,该函数负责实际启动 ETCD 实例或代理模式。它会加载配置、初始化组件,并启动 Raft、MVCC 等核心模块。

3、startEtcdOrProxyV2(args)详解

func startEtcdOrProxyV2(args []string) {// 禁用 gRPC 跟踪,优化性能grpc.EnableTracing = false// 创建一个新的配置对象cfg := newConfig()// 保存初始集群配置,方便后续处理defaultInitialCluster := cfg.ec.InitialCluster// 解析传入的命令行参数,将配置赋值给 cfgerr := cfg.parse(args[1:])// 初始化日志记录器,确保所有输出都写入日志。lg := cfg.ec.GetLogger()// If we failed to parse the whole configuration, print the error using// preferably the resolved logger from the config,// but if does not exists, create a new temporary logger.if lg == nil {var zapError error// use this loggerlg, zapError = logutil.CreateDefaultZapLogger(zap.InfoLevel)if zapError != nil {fmt.Printf("error creating zap logger %v", zapError)os.Exit(1)}}lg.Info("Running: ", zap.Strings("args", args))if err != nil {lg.Warn("failed to verify flags", zap.Error(err))switch {case errorspkg.Is(err, embed.ErrUnsetAdvertiseClientURLsFlag):lg.Warn("advertise client URLs are not set", zap.Error(err))}os.Exit(1)}cfg.ec.SetupGlobalLoggers()defer func() {logger := cfg.ec.GetLogger()if logger != nil {logger.Sync()}}()// 解析默认集群地址defaultHost, dhErr := (&cfg.ec).UpdateDefaultClusterFromName(defaultInitialCluster)if defaultHost != "" {lg.Info("detected default host for advertise",zap.String("host", defaultHost),)}if dhErr != nil {lg.Info("failed to detect default host", zap.Error(dhErr))}// 设置数据目录 如果未指定数据目录,使用默认目录if cfg.ec.Dir == "" {cfg.ec.Dir = fmt.Sprintf("%v.etcd", cfg.ec.Name)lg.Warn("'data-dir' was empty; using default",zap.String("data-dir", cfg.ec.Dir),)}var stopped <-chan struct{}var errc <-chan error// 判断数据目录类型并启动which := identifyDataDirOrDie(cfg.ec.GetLogger(), cfg.ec.Dir)if which != dirEmpty {lg.Info("server has already been initialized",zap.String("data-dir", cfg.ec.Dir),zap.String("dir-type", string(which)),)switch which {// 启动etcdcase dirMember:stopped, errc, err = startEtcd(&cfg.ec) // 启动 ETCD 服务case dirProxy:lg.Panic("v2 http proxy has already been deprecated in 3.6", zap.String("dir-type", string(which)))default:lg.Panic("unknown directory type",zap.String("dir-type", string(which)),)}} else {lg.Info("Initialize and start etcd server",zap.String("data-dir", cfg.ec.Dir),zap.String("dir-type", string(which)),)stopped, errc, err = startEtcd(&cfg.ec)}// 处理启动错误if err != nil {// ... 省略部分代码lg.Fatal("discovery failed", zap.Error(err))}// 信号处理与退出osutil.HandleInterrupts(lg)// At this point, the initialization of etcd is done.// The listeners are listening on the TCP ports and ready// for accepting connections. The etcd instance should be// joined with the cluster and ready to serve incoming// connections.notifySystemd(lg)select {case lerr := <-errc://2 fatal out on listener errorslg.Fatal("listener failed", zap.Error(lerr))case <-stopped:}osutil.Exit(0)
}
3.1. 关键调用

startEtcd(&cfg.ec):这是实际启动 ETCD 服务的函数,负责初始化和启动所有必要组件。

identifyDataDirOrDie():检查数据目录类型,决定是启动服务还是退出。

4、startEtcd(&cfg.ec)详解

// startEtcd runs StartEtcd in addition to hooks needed for standalone etcd.
func startEtcd(cfg *embed.Config) (<-chan struct{}, <-chan error, error) {// 是启动 ETCD 服务的核心函数。它负责初始化 ETCD 实例、注册中断信号处理,// 并等待服务成功启动或异常停止。// 最后,返回相关的停止和错误通道e, err := embed.StartEtcd(cfg)// 启动失败,立即返回错误if err != nil {return nil, nil, err}// 注册中断信号处理osutil.RegisterInterruptHandler(e.Close)// 等待服务状态select {case <-e.Server.ReadyNotify(): // wait for e.Server to join the clustercase <-e.Server.StopNotify(): // publish aborted from 'ErrStopped'}return e.Server.StopNotify(), e.Err(), nil
}
4.1 embed.StartEtcd(cfg)

根据传入的配置 cfg,启动一个嵌入式 ETCD 实例。

返回值 eembed.Etcd 实例,封装了 ETCD 服务的所有组件,包括 Raft 节点、存储引擎、gRPC 和 HTTP 接口。

错误处理:如果启动失败,立即返回错误。

4.2 RegisterInterruptHandler
  • 作用:监听系统中断信号(如 SIGINTSIGTERM),确保在接收到中断信号时,调用 e.Close() 优雅地关闭 ETCD 服务。
  • e.Close:关闭所有资源(网络连接、存储、Raft 等)。

5、embed.StartEtcd(cfg)详解

// StartEtcd launches the etcd server and HTTP handlers for client/server communication.
// The returned Etcd.Server is not guaranteed to have joined the cluster. Wait
// on the Etcd.Server.ReadyNotify() channel to know when it completes and is ready for use.
func StartEtcd(inCfg *Config) (e *Etcd, err error) {// 1.配置验证if err = inCfg.Validate(); err != nil {return nil, err}serving := false// 初始化 ETCD 实例e = &Etcd{cfg: *inCfg, stopc: make(chan struct{})}cfg := &e.cfg// 如果服务未成功启动,则关闭所有资源和监听器defer func() {if e == nil || err == nil {return}if !serving {// errored before starting gRPC server for serveCtx.serversCfor _, sctx := range e.sctxs {close(sctx.serversC)}}e.Close()e = nil}()if !cfg.SocketOpts.Empty() {cfg.logger.Info("configuring socket options",zap.Bool("reuse-address", cfg.SocketOpts.ReuseAddress),zap.Bool("reuse-port", cfg.SocketOpts.ReusePort),)}e.cfg.logger.Info("configuring peer listeners",zap.Strings("listen-peer-urls", e.cfg.getListenPeerURLs()),)// 配置 Peer(节点间通信)监听器if e.Peers, err = configurePeerListeners(cfg); err != nil {return e, err}e.cfg.logger.Info("configuring client listeners",zap.Strings("listen-client-urls", e.cfg.getListenClientURLs()),)// 配置 Client(客户端通信)监听器if e.sctxs, err = configureClientListeners(cfg); err != nil {return e, err}for _, sctx := range e.sctxs {e.Clients = append(e.Clients, sctx.l)}var (urlsmap types.URLsMaptoken   string)// 初始化集群信息memberInitialized := trueif !isMemberInitialized(cfg) {memberInitialized = falseurlsmap, token, err = cfg.PeerURLsMapAndToken("etcd")if err != nil {return e, fmt.Errorf("error setting up initial cluster: %w", err)}}// AutoCompactionRetention defaults to "0" if not set.if len(cfg.AutoCompactionRetention) == 0 {cfg.AutoCompactionRetention = "0"}// 设置存储和压缩选项autoCompactionRetention, err := parseCompactionRetention(cfg.AutoCompactionMode, cfg.AutoCompactionRetention)if err != nil {return e, err}backendFreelistType := parseBackendFreelistType(cfg.BackendFreelistType)srvcfg := config.ServerConfig{Name:                                     cfg.Name,ClientURLs:                               cfg.AdvertiseClientUrls,PeerURLs:                                 cfg.AdvertisePeerUrls,DataDir:                                  cfg.Dir,DedicatedWALDir:                          cfg.WalDir,SnapshotCount:                            cfg.SnapshotCount,SnapshotCatchUpEntries:                   cfg.SnapshotCatchUpEntries,MaxSnapFiles:                             cfg.MaxSnapFiles,MaxWALFiles:                              cfg.MaxWalFiles,InitialPeerURLsMap:                       urlsmap,InitialClusterToken:                      token,DiscoveryURL:                             cfg.Durl,DiscoveryProxy:                           cfg.Dproxy,DiscoveryCfg:                             cfg.DiscoveryCfg,NewCluster:                               cfg.IsNewCluster(),PeerTLSInfo:                              cfg.PeerTLSInfo,TickMs:                                   cfg.TickMs,ElectionTicks:                            cfg.ElectionTicks(),InitialElectionTickAdvance:               cfg.InitialElectionTickAdvance,AutoCompactionRetention:                  autoCompactionRetention,AutoCompactionMode:                       cfg.AutoCompactionMode,QuotaBackendBytes:                        cfg.QuotaBackendBytes,BackendBatchLimit:                        cfg.BackendBatchLimit,BackendFreelistType:                      backendFreelistType,BackendBatchInterval:                     cfg.BackendBatchInterval,MaxTxnOps:                                cfg.MaxTxnOps,MaxRequestBytes:                          cfg.MaxRequestBytes,MaxConcurrentStreams:                     cfg.MaxConcurrentStreams,SocketOpts:                               cfg.SocketOpts,StrictReconfigCheck:                      cfg.StrictReconfigCheck,ClientCertAuthEnabled:                    cfg.ClientTLSInfo.ClientCertAuth,AuthToken:                                cfg.AuthToken,BcryptCost:                               cfg.BcryptCost,TokenTTL:                                 cfg.AuthTokenTTL,CORS:                                     cfg.CORS,HostWhitelist:                            cfg.HostWhitelist,CorruptCheckTime:                         cfg.ExperimentalCorruptCheckTime,CompactHashCheckEnabled:                  cfg.ExperimentalCompactHashCheckEnabled,CompactHashCheckTime:                     cfg.ExperimentalCompactHashCheckTime,PreVote:                                  cfg.PreVote,Logger:                                   cfg.logger,ForceNewCluster:                          cfg.ForceNewCluster,EnableGRPCGateway:                        cfg.EnableGRPCGateway,ExperimentalEnableDistributedTracing:     cfg.ExperimentalEnableDistributedTracing,UnsafeNoFsync:                            cfg.UnsafeNoFsync,EnableLeaseCheckpoint:                    cfg.ExperimentalEnableLeaseCheckpoint,LeaseCheckpointPersist:                   cfg.ExperimentalEnableLeaseCheckpointPersist,CompactionBatchLimit:                     cfg.ExperimentalCompactionBatchLimit,CompactionSleepInterval:                  cfg.ExperimentalCompactionSleepInterval,WatchProgressNotifyInterval:              cfg.ExperimentalWatchProgressNotifyInterval,DowngradeCheckTime:                       cfg.ExperimentalDowngradeCheckTime,WarningApplyDuration:                     cfg.ExperimentalWarningApplyDuration,WarningUnaryRequestDuration:              cfg.WarningUnaryRequestDuration,ExperimentalMemoryMlock:                  cfg.ExperimentalMemoryMlock,ExperimentalTxnModeWriteWithSharedBuffer: cfg.ExperimentalTxnModeWriteWithSharedBuffer,ExperimentalBootstrapDefragThresholdMegabytes: cfg.ExperimentalBootstrapDefragThresholdMegabytes,ExperimentalMaxLearners:                       cfg.ExperimentalMaxLearners,V2Deprecation:                                 cfg.V2DeprecationEffective(),ExperimentalLocalAddress:                      cfg.InferLocalAddr(),ServerFeatureGate:                             cfg.ServerFeatureGate,}if srvcfg.ExperimentalEnableDistributedTracing {tctx := context.Background()tracingExporter, terr := newTracingExporter(tctx, cfg)if terr != nil {return e, terr}e.tracingExporterShutdown = func() {tracingExporter.Close(tctx)}srvcfg.ExperimentalTracerOptions = tracingExporter.optse.cfg.logger.Info("distributed tracing setup enabled",)}srvcfg.PeerTLSInfo.LocalAddr = srvcfg.ExperimentalLocalAddressprint(e.cfg.logger, *cfg, srvcfg, memberInitialized)// 创建服务实例if e.Server, err = etcdserver.NewServer(srvcfg); err != nil {return e, err}// buffer channel so goroutines on closed connections won't wait forevere.errc = make(chan error, len(e.Peers)+len(e.Clients)+2*len(e.sctxs))// newly started member ("memberInitialized==false")// does not need corruption checkif memberInitialized && srvcfg.ServerFeatureGate.Enabled(features.InitialCorruptCheck) {if err = e.Server.CorruptionChecker().InitialCheck(); err != nil {// set "EtcdServer" to nil, so that it does not block on "EtcdServer.Close()"// (nothing to close since rafthttp transports have not been started)e.cfg.logger.Error("checkInitialHashKV failed", zap.Error(err))e.Server.Cleanup()e.Server = nilreturn e, err}}// 启动服务e.Server.Start()e.servePeers()e.serveClients()// 用于暴露 ETCD 的 监控指标if err = e.serveMetrics(); err != nil {return e, err}e.cfg.logger.Info("now serving peer/client/metrics",zap.String("local-member-id", e.Server.MemberID().String()),zap.Strings("initial-advertise-peer-urls", e.cfg.getAdvertisePeerURLs()),zap.Strings("listen-peer-urls", e.cfg.getListenPeerURLs()),zap.Strings("advertise-client-urls", e.cfg.getAdvertiseClientURLs()),zap.Strings("listen-client-urls", e.cfg.getListenClientURLs()),zap.Strings("listen-metrics-urls", e.cfg.getMetricsURLs()),)serving = truereturn e, nil
}
5.1. 配置验证

验证配置是否合法。例如,检查必填项是否缺失

5.2. 初始化 ETCD 实例

创建一个新的 ETCD 实例,并初始化用于控制服务停止的通道 stopc

5.3.延迟清理(defer 块)

在函数退出时,确保资源被正确释放。如果服务未成功启动,则关闭所有资源和监听器。

5.4. 配置网络监听器
配置 Peer(节点间通信)监听器

为节点间通信设置监听器,方便集群内部通过 Raft 协议进行通信。

配置 Client(客户端通信)监听器:

为客户端请求设置监听器,处理外部对 ETCD 的访问。

5.5.初始化集群信息

如果当前节点是新成员,则生成初始集群信息,包括节点 URL 和集群令牌。

5.6.设置存储和压缩选项:

设置数据压缩和存储的保留策略,以控制存储空间占用。

5.7.创建服务实例

基于配置创建 Raft 节点和存储服务。

5.8.启动服务
e.Server.Start()
e.servePeers()
e.serveClients()

启动 Raft 节点,监听节点间和客户端的请求。

5.9 记录启动完成日志并返回结果
  • 记录服务启动成功的信息,包括节点 ID 和监听的 URL。
  • 返回已启动的 ETCD 实例

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

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

相关文章

计算机网络-应用层/运输层

应用层 在上一篇已经提到过, 计算机网络, 最核心的功能就是个产生信息, 发送信息.而并不关注其中的接受方究竟是人, 机器. 而协议, 就是双方约定的 可以表达一定含义的 消息内容. 符合协议的, 就能够被机器解读, 并进行下一步操作, 可能还会返回一定的响应内容. 而应用层, 有…

AD20 原理图库更新到原理图

一 点击工具&#xff0c;从库更新。快捷键TL 二 点击完成 三 执行变更&#xff0c;最后点击关闭

位运算符I^~

&运算&#xff1a;上下相等才是1&#xff0c;有一个不同就是0 |运算&#xff1a;只要有1返回的就是1 ^(亦或)运算&#xff1a;上下不同是1&#xff0c;相同是0 ~运算&#xff1a;非运算&#xff0c;与数据全相反 cpu核心运算原理&#xff0c;四种cpu底层小电路 例&#xf…

Python毕业设计选题:基于django的民族服饰数据分析系统的设计与实现_hadoop+spider

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 用户管理 民族服饰管理 看板展示 系统首页 民族服饰 服饰…

mac port 安装redis 并设置为系统服务 自定义配置方法

mac系统中&#xff0c;port 包管理工具比brew的速度快N倍&#xff0c;今天就给大家分享一下在macos系统中如何使用 port安装 redis数据库并配置为服务自动启动和自定义redis.conf配置的方法。 1. 安装redis sudo port install redis 2. 启动redis服务 sudo port load redis …

MySQL导入.sql文件后数据库乱码问题

问题分析&#xff1a; 当导入.sql文件后&#xff0c;发现数据库中的备注出现乱码&#xff0c;通常是由于一下原因导致&#xff1a; 字符集不匹配&#xff1a;.sql文件、MySQL服务器、客户端连接使用的字符集不一致。备注内容编码问题&#xff1a;备注内容本身的编码格式与数据…

RabbitMQ 架构介绍:深入理解与应用

RabbitMQ 是一个开源的消息代理&#xff08;Message Broker&#xff09;软件&#xff0c;它实现了高级消息队列协议&#xff08;AMQP&#xff09;&#xff0c;并提供了可靠的消息传递机制。RabbitMQ 广泛应用于分布式系统中&#xff0c;用于解耦系统组件、异步处理任务和实现消…

【西门子PLC.博途】——在S71200里写时间设置和读取功能块

之前我们在这篇文章中介绍过如何读取PLC的系统时间。我们来看看在西门子1200里面有什么区别。同时也欢迎关注gzh。 我们在S71200的帮助文档中搜索时间后找到这个数据类型 在博途中他是一个结构体&#xff0c;具体为 然后我们再看看它带的读取和写入时间块 读取时间&#xff1…

vue聊天对话语音消息播放动态特效

vue2写法&#xff0c;vue3也能用&#xff0c;粘之即走&#xff1a; 示例&#xff1a; <template><div class"voice-hidden"><divclass"voice-play-chat":class"[className, { animate-stop: !isPlaying }]"><div class&q…

Python 绘图工具详解:使用 Matplotlib、Seaborn 和 Pyecharts 绘制散点图

目录 数据可视化1.使用 matplotlib 库matplotlib 库 2 .使用 seaborn 库seaborn 库 3 .使用 pyecharts库pyecharts库 注意1. 确保安装了所有必要的库2. 检查Jupyter Notebook的版本3. 使用render()方法保存为HTML文件4. 使用IFrame在Notebook中显示HTML文件5. 检查是否有其他输…

【链表】力扣 141. 环形链表

一、题目 二、思路 龟兔进行赛跑 龟的速度是 1&#xff0c;兔的速度是 2龟兔从同一起点出发&#xff0c;若 龟追上兔 则说明 有环 存在&#xff1b;若追不上&#xff0c;则说明无环。 三、代码 /*** Definition for singly-linked list.* class ListNode {* int val;* …

Spring中使用Async进行异步功能开发实战-以大文件上传为例

目录 前言 一、场景再现 1、Event的同步机制 二、性能优化 1、异步支持配置 2、自定义处理线程池扩展 3、将线程池配置类绑定到异步方法 三、总结 前言 在之前的博客中&#xff0c;曾将讲了在SpringBoot中如何使用Event来进行大文件上传的解耦&#xff0c;原文地址&am…

PyTorch 深度学习框架简介:灵活、高效的 AI 开发工具

PyTorch 深度学习框架简介&#xff1a;灵活、高效的 AI 开发工具 PyTorch 作为一个深度学习框架&#xff0c;以其灵活性、可扩展性和高效性广受欢迎。无论是在研究领域进行创新实验&#xff0c;还是在工业界构建生产级的深度学习模型&#xff0c;PyTorch 都能提供所需的工具和…

Qt 2D绘图之五:图形视图框架的结构、坐标系统和框架间的事件处理与传播

参考文章链接: Qt 2D绘图之五:图形视图框架的结构和坐标系统 Qt 2D绘图之六:图形视图框架的事件处理与传播 图形视图框架的结构 在前面讲的基本绘图中,我们可以自己绘制各种图形,并且控制它们。但是,如果需要同时绘制很多个相同或不同的图形,并且要控制它们的移动、…

Qt开发技巧(二十四)滚动部件的滑动问题,Qt设置时区问题,自定义窗体样式不生效问题,编码格式问题,给按钮左边加个图,最小化后的卡死假象

继续记录一些Qt开发中的技巧操作&#xff1a; 1.滚动部件的滑动问题 再Linux嵌入式设备上&#xff0c;有时候一个页面的子部件太多&#xff0c;一屏放不下是需要做页面滑动&#xff0c;可以使用“QScrollArea”控件&#xff0c;拖来一个“QScrollArea”控件&#xff0c;将子部件…

基于yolov4深度学习网络的排队人数统计系统matlab仿真,带GUI界面

目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下&#xff08;完整代码运行后无水印&#xff09;&#xff1a; 仿真操作步骤可参考程序配套的操作视频。 2.算法涉及理论知识概要 在现代社会…

计算机毕业设计项目基于SSM闪光点映像摄影工作室预约系统

源代码数据库LW文档&#xff08;1万字以上&#xff09;开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统&#xff1a;Window操作系统 2、开发工具&#xff1a;IntelliJ IDEA或者Eclipse 3、数据库存储&#xff1a…

人工智能大模型培训讲师叶梓:Llama Factory 微调模型实战分享提纲

LLaMA-Factory ——一个高效、易用的大模型训练与微调平台。它支持多种预训练模型&#xff0c;并且提供了丰富的训练算法&#xff0c;包括增量预训练、多模态指令监督微调、奖励模型训练等。 LLaMA-Factory的优势在于其简单易用的界面和强大的功能。用户可以在不编写任何代码的…

Creating Server TCP listening socket *:6379: bind: No error

启动redis报错&#xff1a;Creating Server TCP listening socket *:6379: bind: No error 解决方案&#xff1a; 1、直接在命令行中输入 redis-cli.exe 2、输入shutdown&#xff0c;关闭 3、输exit&#xff0c;退出 4、重新输入 redis-server.exe redis.windows.conf&…

qt QSettings详解

1、概述 QSettings是Qt框架中用于应用程序配置和持久化数据的一个类。它提供了一种便捷的方式来存储和读取应用程序的设置&#xff0c;如窗口大小、位置、用户偏好等。QSettings支持多种存储格式&#xff0c;包括INI文件、Windows注册表&#xff08;仅限Windows平台&#xff0…