【Elacticsearch】 集群发现机制 ,分片副本机制,负载机制,容错机制,扩容机制, 分片路由原理

news2024/10/7 14:28:33

集群发现机制

  Elasticsearch采用了master-slave模式, ES会在集群中选取一个节点成为主节点,只有Master节点有资格维护全局的集群状态,在有节点加入或者退出集群的时候,它会重新分配分片,并将集群最新状态发送给集群中其它节点,主节点会以周期性ping的方式以验证其它节点是否存活。

Elasticsearch的选举算法7.x之前基于Bully选举算法,7.x之后的ES,采用-种新的选主算法Raft ;

选举时机

  • 集群初始化

  • 集群的Master崩溃的时候

  • 任何一个节点发现当前集群中的Master节点没有得到n/2 + 1节点认可的时候,触发选举;

选举的基本原则

ES针对当前集群中所有的Master Eligible Node进行选举得到master节点,为了避免出现Split-brain现象,ES选择了分布式系统常见的quorum(多数派)思想,也就是只有获得了超过半数选票的节点才能成为master。在ES中使用 discovery.zen.minimum_master_nodes 属性设置quorum,这个属性一般设置为 eligibleNodesNum / 2 + 1

选举的流程说明如下

  1. 节点node向所有比自己大的节点发送选举消息(选举为election消息)
  2. 如果节点node得不到任何回复(回复为alive消息),那么节点node成为master,并向所有的其它节点宣布自己是master(宣布为Victory消息)
  3. 如果node得到了任何回复,node节点就一定不是master,同时等待Victory消息,如果等待Victory超时那么重新发起选举;

Bully算法

      Leader选举的基本算法之一。

在bully算法中,每个节点都有一个编号,只有编号最大的存活节点才能成为master节点。

Discovery模块:负责发现集群中的节点,以及选择主节点。ES支持多种不同Discovery类型选择,内置的实现称为Zen Discovery。

Zen Discovery封装了节点发现(Ping)、选主等实现过程。

算法假定所有节点都有一个惟一的ID,该ID对节点进行排序。 任何时候的当前Leader都是参与集群的最高id节点。 该算法的优点是易于实现,但是,当拥有最大 id 的节点处于不稳定状态的场景下会有问题,例如 Master 负载过重而假死,集群拥有第二大id 的节点被选为 新主,这时原来的 Master 恢复,再次被选为新主,然后又假死…

elasticsearch 通过推迟选举直到当前的 Master 失效来解决上述问题;但是容易产生脑裂,再通过 法定得票人数过半 解决脑裂;

     在 es 中,发送投票就是发送加入集群请求.在 handleJoinRequest 过程统计投票,收到的连接被存储到 pendingJoinRequests.
在 checkPendingJoinsAndElectIfNeeded 中检查投票是否足够,其中会过滤掉没有 Master 资格节点的投票;

代码实现逻辑:

1. 筛选activeMasters列表

Ping所有节点并获取PingResponse;

  1. 过滤有成为 Master 资格的节点
  2. 创建了三个列表;

    其中,joinedOnceActiveNodes.size <= activeNodes.size,差别在于是否含有 localnode, 其他的内容都一样,都是来自ping 的结果

Es的master就是从activeMasters列表或者masterCandidates列表选举出来,所以选举之前es首先需要得到这两个列表。Elasticsearch节点成员首先向集群中的所有成员发送Ping请求,elasticsearch默认等待discovery.zen.ping_timeout时间,然后elasticsearch针对获取的全部response进行过滤,筛选出其中activeMasters列表,activeMaster列表是其它节点认为的当前集群的Master节点

 2.筛选masterCandidates列表

masterCandidates列表是当前集群有资格成为Master的节点,如果我们在elasticsearch.yml中配置了如下参数,那么这个节点就没有资格成为Master节点,也就不会被筛选进入masterCandidates列表;

Elasticsearch的任意一个节点都可以设置node.master和node.data属性

  配置某个节点没有成为master资格 node.master:false;

3. 从activeMasters列表选举Master节点

activeMaster列表是其它节点认为的当前集群的Master节点列表,如果activeMasters列表不为空,elasticsearch会优先从activeMasters列表中选举,也就是对应着流程图中的蓝色框,选举的算法是Bully算法,笔者在前文中详细介绍了Bully算法,Bully算法会涉及到优先级比较, 在activeMasters列表优先级比较的时候,如果节点有成为master的资格,那么优先级比较高,如果activeMaster列表有多个节点具有master资格,那么选择id最小的节点

代码如下

private static int compareNodes(DiscoveryNode o1, DiscoveryNode o2) {
    if (o1.isMasterNode() && !o2.isMasterNode()) {
        return -1;
    }
    if (!o1.isMasterNode() && o2.isMasterNode()) {
        return 1;
    }
    return o1.getId().compareTo(o2.getId());
}

public DiscoveryNode tieBreakActiveMasters(Collection<DiscoveryNode> activeMasters) {
    return activeMasters.stream().min(ElectMasterService::compareNodes).get(); 
}

4. 从masterCandidates列表选举Master节点

这一节对应的是红色流程图中红色部分,如果activeMaster列表为空,那么会在masterCandidates中选举,masterCandidates选举也会涉及到优先级比较,masterCandidates选举的优先级比较和masterCandidates选举的优先级比较不同。它首先会判断masterCandidates列表成员数目是否达到了最小数目discovery.zen.minimum_master_nodes。如果达到的情况下比较优先级,优先级比较的时候首先比较节点拥有的集群状态版本编号,然后再比较id,这一流程的目的是让拥有最新集群状态的节点成为master

public static int compare(MasterCandidate c1, MasterCandidate c2) {
    int ret = Long.compare(c2.clusterStateVersion, c1.clusterStateVersion);
    if (ret == 0) {
        ret = compareNodes(c1.getNode(), c2.getNode());
    }
    return ret;
}

5. 本地节点是master

经过上述选举之后,会选举出一个准master节点, 准master节点会等待其它节点的投票,如果有discovery.zen.minimum_master_nodes-1个节点投票认为当前节点是master,那么选举就成功,准master会等待discovery.zen.master_election.wait_for_joins_timeout时间,如果超时,那么就失败。在代码实现上准master通过注册一个回调来实现,同时借助了AtomicReference和CountDownLatch等并发构建实现

if (clusterService.localNode().equals(masterNode)) {
    final int requiredJoins = Math.max(0, electMaster.minimumMasterNodes() - 1); 
    nodeJoinController.waitToBeElectedAsMaster(requiredJoins, masterElectionWaitForJoinsTimeout,
            new NodeJoinController.ElectionCallback() {
                @Override
                public void onElectedAsMaster(ClusterState state) {
                    joinThreadControl.markThreadAsDone(currentThread);
                    nodesFD.updateNodesAndPing(state); // start the nodes FD
                }
                @Override
                public void onFailure(Throwable t) {
                    logger.trace("failed while waiting for nodes to join, rejoining", t);
                    joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
                }
            }
    );

本地节点是Master的时候,Master节点会开启错误检测(NodeFaultDetection机制),它节点会定期扫描集群所有的成员,将失活的成员移除集群,同时将最新的集群状态发布到集群中,集群成员收到最新的集群状态后会进行相应的调整,比如重新选择主分片,进行数据复制等操作

6. 本地节点不是master

当前节点判定在集群当前状态下如果自己不可能是master节点,首先会禁止其他节点加入自己,然后投票选举出准Master节点。同时监听master发布的集群状态(MasterFaultDetection机制),如果集群状态显示的master节点和当前节点认为的master节点不是同一个节点,那么当前节点就重新发起选举。

非Master节点也会监听Master节点进行错误检测,如果成员节点发现master连接不上,重新加入新的Master节点,如果发现当前集群中有很多节点都连不上master节点,那么会重新发起选举。

Raft算法选主流程

Raft作为一种分布式一致性协议,其本身不止描述了选举过程,还提供了日志同步安全性的相关行为的描述;

其设计原则如下:

  • 容易理解
  • 减少状态的数量,尽可能消除不确定性

在Raft中,节点可能的状态有三种,其转换关系如下:

正常情况下,集群中只有一个Leader,其他节点全是Follower。Follower 都是被动接收请求,从不主动发送任何请求。Candidate - 候选人,候补者;应试者 是从Follower到Leader的中间状态。

Raft中引入任期(term) 的概念,每个term内最多只有一个Leader。term 在Raft算法中充当逻辑时钟的作用。服务器之间通信的时候会携带这个term,如果节点发现消息中的term小于自己的term,则拒绝这个消息;如果大于本节点的term,则更新自己的term。如果一个Candidate或者Leader发现自己的任期过期了,它会立即回到Follower状态。

Raft选举流程为:

  • 增加当前节点本地的current term,切换到Candidate状态;
  • 当前节点投自己一票,并且并行给其他节点发送RequestVote RPC (让大家投他) ;

然后等待其他节点的响应,会有如下三种结果:

  • 如果接收到大多数服务器的选票,那么就变成Leader。成为Leader后,向其他节点发送心跳消息来确定自己的地位并阻止新的选举。
  • 如果收到了别人的投票请求,且别人的term比自己的大,那么候选者退化为Follower;
  • 如果选举过程超时,再次发起一轮选举;

ES实现Raft算法选主流程

ES实现中,候选人不先投自己,而是直接并行发起RequestVote,这相当于候选人有投票给其他候选人的机会。这样的好处是可以在一定程度上避免3个节点同时成为候选人时,都投自己,无法成功选主的情况。

ES不限制每个节点在某个term上只能投一票, 节点可以投多票,这样会产生选出多个主的情况:

  • Node2被选为主,收到的投票为:Node2、 Node3;
  • Node3被选为主,收到的投票为:Node3、 Node1;

对于这种情况,ES的处理是让最后当选的Leader成功,作为Leader。如果收到RequestVote请求,他会无条件退出Leader状态。在本例中,Node2先被选为主,随后他收到Node3的RequestVote请求,那么他退出Leader状态,切换为CANDIDATE,并同意向发起RequestVote候选人投票。因此最终Node3成功当选为Leader。

动态维护参选节点列表

在此之前,我们讨论的前提是在集群节点数量不变的情况下,现在考虑下集群扩容、缩容、节点临时或永久离线时是如何处理的。在7.x之前的版本中,用户需要手工配置minimum_master_nodes, 来明确告诉集群过半节点数应该是多少,并在集群扩缩容时调整他。现在,集群可以自行维护。

在取消了discovery.zen.minimum_master_nodes 配置后,现在的做法不再记录“quorum”法定数量的具体数值,取而代之的是记录一个节点列表,这个列表中保存所有具备master资格的节点(有些情况下不是这样,例如集群原本只有1个节点,当增加到2个的时候,这个列表维持不变,因为如果变成2,当集群任意节点离线,都会导致无法选主。这时如果再增加一个节点,集群变成3个,这个列表中就会更新为3个节点),称为VotingConfiguration,他会持久化到集群状态中。

在节点加入或离开集群之后,Elasticsearch 会自动对VotingConfiguration 做出相应的更改,以确保集群具有尽可能高的弹性。在从集群中删除更多节点之前,等待这个调整完成是很重要的。你不能一次性停止半数或更多的节点。(感觉大面积缩容时候这个操作就比较感人了,一部分一部分缩)。默认情况下,ES自动维护VotingConfiguration。有新节点加入的时候比较好办,但是当有节点离开的时候,他可能是暂时的重启,也可能是永久下线。你也可以人工维护VotingConfiguration,配置项为:cluster.auto_shrink_voting_configuration,当你选择人工维护时,有节点永久下线,需要通过voting exclusions API将节点排除出去。如果使用默认的自动维护VotingConfiguration,也可以使用voting exclusions API来排除节点,例如一次性下线半数以上的节点。

如果在维护VotingConfiguration时发现节点数量为偶数,ES会将其中一个排除在外,保证VotingConfiguration是奇数。因为当是偶数的情况下,网络分区将集群划分为大小相等的两部分,那么两个子集群都无法达到“多数”的条件。

分片&副本机制

分片:Elasticsearch集群允许系统存储的数据量超过单机容量,这是通过shard实现的。在一个索引index中,数据(document)被分片处理(sharding)到多个分片上。也就是说:每个分片都保存了全部数据中的一部分。

一个分片是一个 Lucene 的实例,它本身就是一个完整的搜索引擎。文档被存储到分片内,但应用程序直接与索引而不是与分片进行交互。

副本:为了解决访问压力过大时单机无法处理所有请求的问题,Elasticsearch集群引入了副本策略replica。副本策略对index中的每个分片创建冗余的副本。

副本的作用如下:

1. 提高系统容错性

当分片所在的机器宕机时,Elasticsearch可以使用其副本进行恢复,从而避免数据丢失。

2. 提高ES查询效率

处理查询时,ES会把副本分片和主分片公平对待,将查询请求负载均衡到副本分片和主分片。

副本分片并不是越多越好,原因有以下两点:

(1)多个 replica 可以提升搜索操作的吞吐量和性能,但是如果只是在相同节点数目的集群上增加更多的副本分片并不能提高性能,因为每个分片从节点上获得的资源会变少,这个时候你就需要增加更多的硬件资源来提升吞吐量。

(2)更多的副本分片数提高了数据冗余量,保证了数据的完整性,但是根据上边主副分片之间的交互原理可知,分片间的数据同步会占用一定的网络带宽,影响效率,所以索引的分片数和副本数也不是越多越好。

分片和副本机制

  1、index包含多个shard;
  2、每个shard都是一个最小工作单元,承担部分数据;每个shard都是一个lucene示例,有完整的建立索引和处理请求的能力;
  3、增减节点时,shard会自动在nodes中负载均衡;
  4、primary shard和replica shard,每个document只存在于某个primary shard以及其对应的replica shard中,不可能存在于多个primary shard;
  5、replica shard是primary shard的副本,负责容错,以及承担读请求负载;
  6、primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改。
  7、primary shard的默认数量是5,replica默认是1,(总共默认有10个shard,5个primary shard,5个replica shard);
  8、primary shard不能和自己的replica shard放在同一个 节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用,但是可以和其它primary shard的replica shard放在同一个节点上);

负载机制

容错机制

扩容机制

垂直扩容
买更牛逼的服务器,价钱没上限!!!而且瓶颈还会存在。比如你现在10T数据,磁盘满了,放不下了。现在业务数据总量能到达100T,那你再买个100T的磁盘?那你真有钱,100T满了咋办?

2、水平扩容
业界经常采用的方案,采购越来越多的10T服务器,性能比较一般,但是很多10T服务器组织在一起,就能构成强大的存储能力。(推荐。划算,还不会瓶颈)
 

分片路由原理

Elasticsearch 索引一条数据(一个文档)时,中如何知道一条应该存放到哪个分片中呢?

采用这个公式决定的:

shard = hash(routing) % number_of_primary_shards

Routing 是一个可变值,默认是文档的_id,也可以设置成一个自定义的值。Routing 通过 Hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到余数。这个余数在 0 到 number_of_primary_shards-1 之间,它就是我们所寻求的文档所在分片的位置。

这就是为什么创建好索引后就无法改变分片数量。因为如果数量变化了,所有之前路由的值都会无效,文档也再也找不到了。

在 ES 集群中,每个节点通过上面的计算公式都知道集群中的文档的存放位置,所以每个节点都有处理读写请求的能力。在一个写请求被发送到某个节点后,该节点即为协调节点,协调节点会根据路由公式计算出需要写到哪个分片上,再将请求转发到该分片的主分片节点上。

ES 为了提高写入的能力这个过程是并发写的,同时为了解决并发写的过程中数据冲突的问题,ES 通过乐观锁的方式控制,每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。一旦所有的副本分片都报告写成功才会向协调节点报告成功,协调节点向客户端报告成功。

写数据示例


如上图所示,每个节点有5个分片,每个分片都在另一个节点上有一个副本。 

1.客户端向节点1(协调节点)发送写请求,通过路由计算公式得到值为 1,则当前数据应被写到2.主分片(1(主)) 上。
2.节点1将请求转发到主分片(1(主))所在的节点(节点2),节点2接受请求并写入到磁盘。
3.并发将数据复制到副本分片(1(副))上,其中通过乐观并发控制数据的冲突。
4.一旦所有的副本分片都报告成功,则节点2将向协调节点(节点1)报告成功
5.协调节点(节点1)向客户端报告成功。
 

更新中。。。

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

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

相关文章

构造函数和class类挂载属性和方法的相互转化

class是es6新出的关键词&#xff0c;方便我们快速建立类和继承的关系&#xff0c;es6以前我们都是使用function构造函数模拟类 本文讲述了function构造函数和类中定义属性方法的对应关系&#xff0c;主要有以下四点 1.实例属性 2.实例方法 3.原型方法 4.静态方法 类中定义 cl…

学生管理系统(SpringBoot+MybatisPlus+Vue+ElementUI)

做了一个很简单的学生管理系统&#xff0c;就两张表login&#xff0c;student。主要是用它熟悉一下Vue和ElementUI。下面看下一些页面 登录页面 主页 添加、修改页面 删除 注销 我这里分享Jar包和dist&#xff1a;包含.sql文件 链接&#xff1a;https://pan.baidu.com/s/1ZDxv…

比较全面的DHCP配置

DHCP基本原理和配置 1.基本原理 2.配置 2.1.基于接口的DHCP配置 2.2.基于全局的DHCP配置 2.3.DHCP中继配置 1、基本原理 DHCP (dynamic host configration protocol) 动态主机配置协议&#xff0c;用来给主机自动分配 IP 地址&#xff0c;基于UDP封装&…

重新了解的git以及git的工作场景

git的四大状态 untrack&#xff0c;modified&#xff0c;committed和staged untrack 没有记录的文件&#xff0c;就是新创建的文件 modified 修改过的文件&#xff0c;和版本库里的文件不一致 staged 暂存&#xff0c;把改动记录下来。执行完git add之后&#xff0c;得到的状态…

CSS面经

1、CSS的BFC 一、何为BFC BFC&#xff08;Block Formatting Context&#xff09;格式化上下文&#xff0c;是Web页面中盒模型布局的CSS渲染模式&#xff0c;指一个独立的渲染区域或者说是一个隔离的独立容器。 二、形成BFC的条件 1、浮动元素&#xff0c;float 除 none 以外的值…

SpringMVC 源码分析之 DispatcherServlet

SpringMVC 源码分析之 DispatcherServlet FrameworkServletserviceprocessRequestLocaleContext 和 RequestAttributesLocaleContextRequestAttributes 事件发布 DispatcherServletdoService代码分析 doDispatch参数含义具体的处理逻辑&#xff1a; processDispatchResult 引用…

CH2023、Adobe Character Animator 2023(动画角色制作软件)下载教程、安装教程

最后附下载地址 Adobe CH简介&#xff1a; Adobe Character Animator是一款基于动画制作的软件&#xff0c;它可以将手绘的角色通过摄像头或麦克风捕捉到的实时动作转化为动画效果。该软件结合了人工智能和动画技术&#xff0c;可以快速创建高质量的角色动画&#xff0c;并且…

构建基于前后端分离的医学影像学学习平台:Java技术实现与深度解析

在医学领域,影像学学习平台是一种重要的工具,用于帮助医学学生和专业人士学习和研究医学影像。本文将介绍如何使用Java构建一个基于前后端分离的医学影像学学习平台,通过结合前沿的Web开发技术和医学影像处理算法,为用户提供强大且高效的学习工具。 技术架构设计: 在构…

华为OD机试之阿里巴巴找黄金宝箱(II)(Java源码)

阿里巴巴找黄金宝箱(II) 题目描述 一贫如洗的樵夫阿里巴巴在去砍柴的路上&#xff0c;无意中发现了强盗集团的藏宝地&#xff0c;藏宝地有编号从0~n 的箱子&#xff0c;每个箱子上面贴有箱子中藏有金币的数量。 从金币数量中选出一个数字集合&#xff0c;并销毁贴有这些数字的…

Visual ChatGPT原理解读——大模型论文阅读笔记四

论文&#xff1a;https://arxiv.org/abs/2303.04671 代码&#xff1a;https://github.com/microsoft/TaskMatrix 一. 整体框架 如图所示&#xff0c;用户上传一张黄花的图像并输入一个复杂的语言指令“请根据该图像的预测深度生成一朵红花&#xff0c;然后逐步使其像卡通一样”…

go-zero学习 第一章 基础

go-zero学习 第一章 基础 重要提示1 相关命令2 参考文档3 架构图4 go-zero环境搭建4.1 注意事项4.2 go-zero 需要安装的组件4.3 自动安装4.4 手动安装 5 单体服务5.1 简单入门5.2 api语法5.3 api 文件格式化 6 微服务6.1 简单入门6.2 proto 语法 7 目录结构介绍 重要提示 因官…

spring boot绮梦餐饮系统-计算机毕设 附源码88672

spring boot绮梦餐饮系统 目录 毕业设计 摘 要 Abstract 第1章 前 言 1.1 研究背景 1.2 研究现状 1.3 系统开发目标 第2章 系统开发环境 2.1 J2EE技术 2.2 Spring boot框架 2.3 MySQL数据库 2.4 B/S结构 2.5 JavaScript 运行模式 1.1是一种解释性脚本语言&…

Segment Anything Model(SAM)如何促进医学图像分割

文章目录 How Segment Anything Model (SAM) Boost Medical Image Segmentation?摘要segment anything modelHow SAM performs on Medical Image Segmentation?Pathology Image SegmentationLiver Tumor Segmentation from CECTPolyps Segmentation from Colonoscopy ImagesB…

Win10 hyper-v与vmware不兼容解决方案

Win10 hyper-v与vmware不兼容怎么办 一、异常1.1 异常描述 - V M w a r e W o r k s t a t i o n 与 H y p e r − V 不兼容 \color{red}{VMware Workstation 与 Hyper-V 不兼容} VMwareWorkstation与Hyper−V不兼容1.2 异常原因 二、解决办法2.1 关闭Hyper-V启动2.2 关闭内核…

【面试题】6月 vue核心面试题汇总

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 一、MVVM原理 在Vue2官方文档中没有找到Vue是MVVM的直接证据&#xff0c;但文档有提到&am…

判断数组中的每个元素是否为NaN numpy.isnan()

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 判断数组中的每个元素是否为NaN numpy.isnan() [太阳]选择题 请问关于以下代码的最后输出正确的是&#xff1f; import numpy as np a np.array([1, np.nan, np.inf, float(nan)]) print(&…

PSD矢量处理:Graphics Mill 11.1.18 Crack

光栅和矢量处理 .NET SDK 图形铣床 11 图PSD矢量处理:Graphics Mill 11.1.18可供 .NET 和 ASP.NET 开发人员使用的最可靠的映像工具集。它允许用户轻松地向 .NET 应用添加复杂的光栅和矢量图像处理功能。 光栅图形 加载并保存 JPEG、PNG 和另外 8 种图像格式 调整大小&#xff…

SignalR服务主动发送消息给客户端

文章目录 背景尝试方案使用IHubContext上下文进行处理第一步 创建一个类用于处理上下文&#xff08;WarningBroadcast&#xff09;第二步&#xff1a;如何实例化这个对象呢下面是我实现的方式 第三步&#xff1a;使用扩展类 调用通知关于MessageBus的使用demo发布消息订阅消息 …

java 心理教育网站系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 java 心理教育网站系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

使用 Flask 的g对象和 MySQL 实现用户登录和注销功能

在Flask中&#xff0c;g对象是一个特殊类型的对象&#xff0c;被称为"上下文全局变量"。它在一次请求的生命周期内存储信息&#xff0c;并且可以在整个请求过程中访问和共享。 g对象对于存储用户登录信息非常有用。例如&#xff0c;当用户通过验证登录时&#xff0c…