Linux进程管理:(六)SMP负载均衡

文章说明:

  • Linux内核版本:5.0

  • 架构:ARM64

  • 参考资料及图片来源:《奔跑吧Linux内核》

  • Linux 5.0内核源码注释仓库地址:

    zhangzihengya/LinuxSourceCode_v5.0_study (github.com)

1. 前置知识

1.1 CPU管理位图

内核对CPU的管理是通过位图(bitmap)变量来管理的,并且定义了possible、present、online和active这4种状态:

/* 位图类型变量 */
// 表示系统中有多少个可以运行(现在运行或者将来某个时间点运行)的 CPU 内核
#define cpu_possible_mask ((const struct cpumask *)&__cpu_possible_mask)
// 表示系统中有多少个正处于运行(online)状态的 CPU 内核
#define cpu_online_mask   ((const struct cpumask *)&__cpu_online_mask)
// 表示系统中有多少个可处于运行状态的CPU内核,它们不一定都处于运行状态,有的CPU内核可能被热插拔了
#define cpu_present_mask  ((const struct cpumask *)&__cpu_present_mask)
// 表示系统中有多少个活跃的 CPU 内核
#define cpu_active_mask   ((const struct cpumask *)&__cpu_active_mask)
  • #define DECLARE_BITMAP(name,bits) \unsigned long name[BITS_TO_LONGS(bits)]typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;
    
    • name[]:位图类型变量使用一个长整数类型数组name[],每位代表一个CPU。对于64位处理器来说,一个长整数类型数组成员只能表示64个CPU内核。内核配置中有一个宏CONFIG_NR_CPUS为8,那么只需要一个长整数类型数组成员可。
    • struct cpumask:cpumask数据结构本质上是位图,内核通常使用cpumask的相关接口函数来管理CPU内核数量。

CPU位图的初始化流程如下图所示:

在这里插入图片描述

  • cpu_possible_mask通过查询系统DTS表或者ACPI表获取系统CPU内核数量
  • cpu_present_mask等同于cpu_possible_mask
  • cpu_active_mask是经过使能后的CPU内核数量

1.2 CPU调度域

根据系统的内存和高速缓存的布局,CPU域可分为如下几类:

在这里插入图片描述

Linux内核通过数据结构sched_domain_topology_level来描述CPU的层次关系,简称为SDTL:

// 用于描述 CPU 的层级关系
struct sched_domain_topology_level {sched_domain_mask_f mask;			// 函数指针,用于指定某个 SDTL 的 cpumask 位图// sd_flags 函数指针里指定了该调度层级的标志位sched_domain_flags_f sd_flags;		// 函数指针,用于指定某个 SDTL 的标志位int		    flags;int		    numa_level;struct sd_data      data;
#ifdef CONFIG_SCHED_DEBUGchar                *name;
#endif
};

另外,内核默认定义了一个数组default_topology[]来概括CPU物理域的层次结构

// CPU 拓扑关系,从下往上
static struct sched_domain_topology_level default_topology[] = {
#ifdef CONFIG_SCHED_SMT		// 超线程// cpu_smt_mask() 函数描述了 SMT 层级的 CPU 位图组成方式{ cpu_smt_mask, cpu_smt_flags, SD_INIT_NAME(SMT) },
#endif
#ifdef CONFIG_SCHED_MC		// 多核// cpu_coregroup_mask() 函数描述了 MC 层级的 CPU 位图组成方式{ cpu_coregroup_mask, cpu_core_flags, SD_INIT_NAME(MC) },
#endif						// 处理器// cpu_cpu_mask() 函数描述了 DIE 层级的 CPU 位图组成方式{ cpu_cpu_mask, SD_INIT_NAME(DIE) },{ NULL, },
};

default_topology[]数组可知,系统默认支持DIE层级、SMT层级以及MC层级。另外,在调度域里划分调度组,使用sched_group来描述调度组,调度组是负载均衡调度的最小单位,在最低层级的调度域中,通常一个调度组描述一个CPU。

在一个支持NUMA架构的处理器中,假设它支持SMT技术,那么整个系统的调度域和调度组的关系如下图所示(它在默认调度层级中新增一个NUMA层级的调度域):

在这里插入图片描述

  • DIE表示处理器封装,它是CPU拓扑结构中的一个层级,在多核处理器中,通常有多个物理处理器核心,组成一个封装

在Linux内核中使用sched_domain数据结构来描述调度层级。在超大系统中系统会频繁访问调度域数据结构,为了提升系统的性能和可扩展性,调度域sched_domain数据结构采用Per-CPU变量来构建:

// 描述调度层级
struct sched_domain {// 父调度域指针,顶层调度域必须为 null 终止struct sched_domain *parent;// 子调度域指针,底层调度域必须为 null 终止struct sched_domain *child;// groups 指向该调度域的平衡组链表struct sched_group *groups;// 最小平衡间隔(毫秒)unsigned long min_interval;// 最大平衡间隔(毫秒)unsigned long max_interval;...
};

下面举例来说明CPU调度域的拓扑关系,如下图所示,假设在一个4核处理器中,每个CPU拥有独立L1高速缓存且不支持超线程技术,4个物理CPU被分成两个簇Cluster0和Cluster1,每个簇包含两个物理CPU,簇中的CPU共享L2高速缓存

在这里插入图片描述

因为每个CPU内核只有一个运行线程,所以4核处理器没有SMT层级。簇由两个CPU组成,这两个CPU处于MC层级且是兄弟关系。整个处理器可以看作处于DIE层级,因此该处理器只有两个层级,即MC和DIE。根据上述原则,4核处理器的调度域和调度组的拓扑关系如下图所示:

在这里插入图片描述

在每个SDTL都为每个CPU分配了对应的调度域和调度组,以CPU0为例:

  • 对于DIE层级,CPU0对应的调度域是domain_die_0,该调度域管辖着4个CPU并包含两个调度组,分别为group_die_0和group_die_1
    • 调度组group_die_0管辖CPU0和CPU1
    • 调度组group_die_1管辖CPU2和CPU3
  • 对于MC层级,CPU0对应的调度域是domain_mc_0,该调度域中管辖着CPU0和CPU1并包含两个调度组,分别为group_mc_0和group_mc_1
    • 调度组group_mc_0管辖CPU0
    • 调度组group_mc_1管辖CPU1

除此以外,4核处理器的调度域和调度组还有两层关系,如下图所示:

在这里插入图片描述

综上所述,为了提升系统的性能和可扩展性,sched_domain数据结构采用Per-CPU变量来构建,这样可以减少CPU之间的访问竞争。而sched_group数据结构则是调度域内共享的。

2. LLC调度域

在负载均衡算法中,我们常常需要快速找到系统中最高层级的并且具有高速缓存共享属性的调度域。通常在—个系统,我们把最后一级高速缓存(Last Level Cache,LLC)称为LLC。在调度域标志位中,SD_SHARE_PKG_RESOURCE标志位用于描述高速缓存的共享属性。因此,在调度域层级中,包含LLC的最高一级调度域称为LLC调度域。在Linux内核中实现一组特殊用途的指针,用来指向LLC调度域,这些指针是Per-CPU变量。查找和设置LLC调度域是在update_top_cache_domain()函数中实现的。在select_idle_cpu()等函数中会使用到LLC 调度域。

// 指向 LLC 调度域
DEFINE_PER_CPU(struct sched_domain *, sd_llc);
// LLC 调度域包含多少个 CPU
DEFINE_PER_CPU(int, sd_llc_size);
// LLC 调度域第一个 CPU 的编号
DEFINE_PER_CPU(int, sd_llc_id);
DEFINE_PER_CPU(struct sched_domain_shared *, sd_llc_shared);
DEFINE_PER_CPU(struct sched_domain *, sd_numa);
DEFINE_PER_CPU(struct sched_domain *, sd_asym_packing);
// 指向第一个包含不同 CPU 架构的调度域,主要用于大/小核架构
DEFINE_PER_CPU(struct sched_domain *, sd_asym_cpucapacity);
DEFINE_STATIC_KEY_FALSE(sched_asym_cpucapacity);// 查找和设置 LLC 调度域
static void update_top_cache_domain(int cpu)
{...
}

3. 负载均衡

负载均衡机制从注册软中断开始,每次系统处理调度节拍时会检查当前是否需要处理负载均衡。负载均衡的流程如图所示:

在这里插入图片描述

为了使读者有更真切的理解,下文将根据流程图围绕源代码进行讲解这个过程:

load_balance()

// this_cpu 指当前正在运行的 CPU
// this_rq 表示当前就绪队列
// sd 表示当前正在做负载均衡的调度域
// idle 表示当前 CPU 是否处于 IDLE 状态
// continue_balancing 表示当前调度域是否还需要继续做负载均衡
static int load_balance(int this_cpu, struct rq *this_rq,struct sched_domain *sd, enum cpu_idle_type idle,int *continue_balancing)
{...struct lb_env env = {// 当前调度域.sd		= sd,// 当前的 CPU,后面可能要把一些繁忙的进程迁移到该 CPU 上.dst_cpu	= this_cpu,// 当前 CPU 对应的就绪队列.dst_rq		= this_rq,// 当前调度域里的第一个调度组的 CPU 位图.dst_grpmask    = sched_group_span(sd->groups),.idle		= idle,// 本次最多迁移 32 个进程(sched_nr_migrate_break全局变量的默认值为 32).loop_break	= sched_nr_migrate_break,// load_balance_mask 位图.cpus		= cpus,.fbq_type	= all,.tasks		= LIST_HEAD_INIT(env.tasks),};// 把 sd 调度域管辖的 CPU 位图复制到 load_balance_mask 位图里cpumask_and(cpus, sched_domain_span(sd), cpu_active_mask);schedstat_inc(sd->lb_count[idle]);redo:// should_we_balance() 函数判断当前 CPU 是否需要做负载均衡if (!should_we_balance(&env)) {*continue_balancing = 0;goto out_balanced;}// 查找该调度域中最繁忙的调度组group = find_busiest_group(&env);if (!group) {schedstat_inc(sd->lb_nobusyg[idle]);goto out_balanced;}// 在刚才找到的最繁忙的调度组中查找最繁忙的就绪队列busiest = find_busiest_queue(&env, group);if (!busiest) {schedstat_inc(sd->lb_nobusyq[idle]);goto out_balanced;}...// env.src_cpu 指向最繁忙的调度组中最繁忙的就绪队列中的 CPUenv.src_cpu = busiest->cpu;// env.src_rq 指向最繁忙的就绪队列env.src_rq = busiest;ld_moved = 0;if (busiest->nr_running > 1) {...// detach_tasks() 函数遍历最繁忙的就绪队列中所有的进程,找出适合被迁移的进程,然后让这些// 进程退出就绪队列。cur_ld_moved 变量表示已经迁出了多少个进程cur_ld_moved = detach_tasks(&env);...if (cur_ld_moved) {// attach_tasks() 函数把刚才从最繁忙就绪队列中迁出的进程都迁入当前 CPU 的就绪队列中attach_tasks(&env);ld_moved += cur_ld_moved;}...// 由于进程设置了 CPU 亲和性,不能迁移进程到 dst_cpu,因此还有负载没迁移完成,需要换一个新的// dst_cpu 继续做负载迁移if ((env.flags & LBF_DST_PINNED) && env.imbalance > 0) {...goto more_balance;}...}...
}

load_balance()->should_we_balance()

// 判断当前 CPU 是否需要做负载均衡
static int should_we_balance(struct lb_env *env)
{// sg 指向调度域中的第一个调度组struct sched_group *sg = env->sd->groups;...// 查找当前调度组是否有空闲 CPU,如果有空闲 CPU,那么使用变量 balance_cpu 记录该 CPUfor_each_cpu_and(cpu, group_balance_mask(sg), env->cpus) {if (!idle_cpu(cpu))continue;balance_cpu = cpu;break;}// 查找该调度组有哪些 CPU 适合做负载均衡// 	若调度组里有空闲 CPU,则优先选择空闲 CPU// 	调度组里没有空闲 CPU,则选择调度组的第一个 CPUif (balance_cpu == -1)balance_cpu = group_balance_cpu(sg);// 然后判断适合做负载均衡的 CPU 是否为当前 CPU,若为当前 CPU,则 should_we_balance()函数返回true,表示可以做负载均衡return balance_cpu == env->dst_cpu;
}

load_balance()->find_busiest_group()

// 查找该调度域中最繁忙的调度组
static struct sched_group *find_busiest_group(struct lb_env *env)
{...// 初始化 sd_lb_stats 结构体init_sd_lb_stats(&sds);// 更新该调度域中负载的相关统计信息update_sd_lb_stats(env, &sds);...// 如果没有找到最繁忙的调度组或者最繁忙的组中没有正在运行的进程,那么跳过该调度域if (!sds.busiest || busiest->sum_nr_running == 0)goto out_balanced;// 计算该调度域的平均负载sds.avg_load = (SCHED_CAPACITY_SCALE * sds.total_load)/ sds.total_capacity;// 如果最繁忙的调度组的组类型是 group_imbalanced,那么跳转到 force_balance 标签处if (busiest->group_type == group_imbalanced)goto force_balance;...// 若本地调度组的平均负载比刚才计算出来的最繁忙调度组的平均负载还要大,那么不需要做负载均衡if (local->avg_load >= busiest->avg_load)goto out_balanced;// 若本地调度组的平均负载比整个调度域的平均负载还要大,那么不需要做负载均衡if (local->avg_load >= sds.avg_load)goto out_balanced;// 如果当前 CPU 处于空闲状态,最繁忙的调度组里的空闲 CPU 数量大于本地调度组里的空闲 CPU 数量,说明不需要做负载均衡if (env->idle == CPU_IDLE) {if ((busiest->group_type != group_overloaded) &&(local->idle_cpus <= (busiest->idle_cpus + 1)))goto out_balanced;// 如果当前 CPU 不处于空闲状态,那么比较本地调度组的平均量化负载和最繁忙调度组的平均量化负载,这里使用了 imbalance_pct// 系数,它在 sd_init() 函数中初始化,默认值为125。若本地调度组的平均量化负载大于最繁忙组的平均量化负载,说明该调度组不// 忙,不需要做负载均衡。} else {if (100 * busiest->avg_load <=env->sd->imbalance_pct * local->avg_load)goto out_balanced;}force_balance:env->src_grp_type = busiest->group_type;// 根据最繁忙调度组的平均量化负载、调度域的平均量化负载和本地调度组的平均量化负载来计算该调度域需要迁移的负载不均衡值calculate_imbalance(env, &sds);// 返回最忙的调度组return env->imbalance ? sds.busiest : NULL;out_balanced:env->imbalance = 0;return NULL;
}

load_balance()->find_busiest_queue()

  1. for_each_cpu_and()遍历该调度组里所有的CPU
  2. 通过就绪队列的量化负载(runnable_load_avg)与CFS额定算力(cpu_capacity)的乘积来进行比较。使用weighted_cpuload()来获取cfs_rq->avg.runnable_load_avg的值,通过capacity_of()来获取cpu_capacity的值。这里要考虑不同处理器架构计算能力的不同对处理器负载的影响。

注意,数据结构rq中的cpu_capacity_orig成员指的是CPU的额定算力,而cpu_capacity成员指的是CFS就绪队列的额定算力,通常CFS额定算力最大能达到CPU额定算力的80%。

load_balance()->detach_tasks()

// detach_tasks() 函数遍历最繁忙的就绪队列中所有的进程,找出适合被迁移的进程,
// 然后让这些进程退出就绪队列。返回值表示已经迁出了多少个进程
static int detach_tasks(struct lb_env *env)
{...// env->imbalance 表示还有多少不均衡的负载需要迁移if (env->imbalance <= 0)return 0;// while 循环遍历最繁忙的就绪队列中所有的进程while (!list_empty(tasks)) {...// 在can_migrate_task()函数中判断哪些进程可以迁移,哪些进程不能迁移。// 不适合迁移的原因://	一是进程允许运行的CPU位图的限制(cpus_allowed)//	二是当前进程正在运行//	三是热高速缓存(cache-hot)的问题//	四如果进程负载的一半大于要迁移负载总量(env->imbalance),则该进程也不适合迁移if (!can_migrate_task(p, env))goto next;load = task_h_load(p);if (sched_feat(LB_MIN) && load < 16 && !env->sd->nr_balance_failed)goto next;if ((load / 2) > env->imbalance)goto next;// detach_task()函数让进程退出就绪队列,然后设置进程的运行CPU为迁移目的地CPU,并设置p->on_rq为// TASK_ON_RQ_MIGRARINGdetach_task(p, env);// 把要迁移的进程添加到env->tasks链表中list_add(&p->se.group_node, &env->tasks);detached++;// 递减不均衡负载总量 env->imbalanceenv->imbalance -= load;...}return detached;
}

load_balance()->attach_tasks()

// 把detach_tasks()分离出来的进程,重新添加到目标就绪队列中
static void attach_tasks(struct lb_env *env)
{...// 遍历env->tasks链表while (!list_empty(tasks)) {...// 把进程添加到目标CPU的就绪队列中attach_task(env->dst_rq, p);}...
}

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

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

相关文章

深度强化学习01

Random variable Probability Density Function 期望 Random Sampling 学习视频 这绝对是我看过最好的深度强化学习&#xff01;从入门到实战&#xff0c;7小时内干货不断&#xff01;_哔哩哔哩_bilibili

智慧城市新篇章:数字孪生的力量与未来

随着信息技术的迅猛发展和数字化浪潮的推进&#xff0c;智慧城市作为现代城市发展的新模式&#xff0c;正在逐步改变我们的生活方式和社会结构。在智慧城市的构建中&#xff0c;数字孪生技术以其独特的优势&#xff0c;为城市的规划、管理、服务等方面带来了革命性的变革。本文…

Mybatis-xml映射文件与动态SQL

xml映射文件 动态SQL <where><if test"name!null">name like concat(%,#{name},%)</if><if test"username!null">and username#{username}</if></where> <!-- collection&#xff1a;遍历的集合--> <!-- …

百科源码生活资讯百科门户类网站百科知识,生活常识

百科源码生活资讯百科门户类网站百科知识,生活常识 百科源码安装环境 支持php5.6&#xff0c;数据库mysql即可&#xff0c;需要有子目录权限&#xff0c;没有权限的话无法安装 百科源码可以创建百科内容&#xff0c;创建活动内容。 包含用户注册&#xff0c;词条创建&#xff…

Flask学习(四):路由转换器

默认的路由转换器&#xff1a; string &#xff08;缺省值&#xff09; 接受任何不包含斜杠的文本int接受正整数float接受正浮点数 path类似 string&#xff0c;但可以包含斜杠uuid接受 UUID 字符串 代码示例&#xff1a; app.route(/user/<username>) def show_u…

使用Python进行自然语言处理(NLP):NLTK与Spacy的比较【第133篇—NLTK与Spacy】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 使用Python进行自然语言处理&#xff08;NLP&#xff09;&#xff1a;NLTK与Spacy的比较 自…

用尾插的思想实现移除链表中的元素

目录 一、介绍尾插 1.链表为空 2.链表不为空 二、题目介绍 三、思路 四、代码 五、代码解析 1. 2. 3. 4. 5. 6. 六、注意点 1. 2. 一、介绍尾插 整体思路为 1.链表为空 void SLPushBack(SLTNode** pphead, SLTDataType x) {SLTNode* newnode BuyLTNode(x); …

蓝桥杯并查集|路径压缩|合并优化|按秩合并|合根植物(C++)

并查集 并查集是大量的树&#xff08;单个节点也算是树&#xff09;经过合并生成一系列家族森林的过程。 可以合并可以查询的集合的一种算法 可以查询哪个元素属于哪个集合 每个集合也就是每棵树都是由根节点确定&#xff0c;也可以理解为每个家族的族长就是根节点。 元素集合…

【开源鸿蒙】模拟运行OpenHarmony轻量系统QEMU RISC-V版

文章目录 一、准备工作1.1 编译输出目录简介 二、QEMU安装2.1 安装依赖2.2 获取源码2.3 编译安装2.4 问题解决 三、用QEMU运行OpenHarmony轻量系统3.1 qemu-run脚本简介3.2 qemu-run脚本参数3.3 qemu-run运行效果3.4 退出QEMU交互模式 四、问题解决五、参考链接 开源鸿蒙坚果派…

YOLOv8改进 | 图像去雾 | 门控可微分图像处理GDIP模块改善物体低照度检测检测(适用于图片不清晰等一切场景,全网独家首发)

一、本文介绍 本文给大家带来的改进机制是门控可微分图像处理GDIP模块&#xff0c;其可以理解为是一直图像增强领域的模块&#xff0c;其主要适用于雾天的一些去雾检测&#xff0c;当然了也适用于于一些图片模糊不清的场景&#xff0c;GDIP&#xff08;Gated Differentiable Im…

论文阅读——EarthPT

EarthPT: a time series foundation model for Earth Observation 一个Earth Observation (EO)预训练的Transformer。EarthPT是一个7亿参数解码Transformer基础模型&#xff0c;以自回归自监督方式进行训练&#xff0c;并专门针对EO用例进行开发。我们证明了EarthPT是一个有效的…

谷歌(edge)浏览器过滤,只查看后端发送的请求

打开F12 调试工具 选择Network 这是我们会发现 什么图片 文件 接口的请求很多很多&#xff0c;我们只需要查看我们后端发送的请求是否成功就好了 正常情况我们需要的都是只看接口 先点击这里这个 过滤 我们只需要点击 Fetch/XHR 即可过滤掉其他请求信息的展示 这样烦恼的问题就…

海豚调度系列之:单机部署

海豚调度系列之&#xff1a;单机部署 一、前置准备工作二、启动 DolphinScheduler Standalone Server三、登录 DolphinScheduler四、启停服务五、配置数据库 Standalone 仅适用于 DolphinScheduler 的快速体验. 如果你是新手&#xff0c;想要体验 DolphinScheduler 的功能&…

windows下修改mysql的max_allowed_packet的值

1)C:\Program Files\MySQL\MySQL Server 5.7 在MySQL 的安装目录下添加my.ini文件&#xff0c;同时添加空的data文件 2&#xff09;my.ini文件内容如下&#xff0c; [mysqld] port 3306 basedirC:\Program Files\MySQL\MySQL Server 5.7 datadirC:\Program Files\MySQL\MySQ…

【鸿蒙HarmonyOS开发笔记】自定义组件详解

自定义组件 除去系统预置的组件外&#xff0c;ArkTS 还支持自定义组件。使用自定义组件&#xff0c;可使代码的结构更加清晰&#xff0c;并且能提高代码的复用性。 我们开发的每个页面其实都可以视为自定义组件内置组件的结合 语法说明 自定义组件的语法如下图所示 各部分…

TCL管理Vivado工程

文章目录 TCL管理Vivado工程1. 项目目录2. 导出脚本文件3. 修改TCL脚本3.1 project.tcl3.2 bd.tcl 4. 工程恢复 TCL管理Vivado工程 工程结构 1. 项目目录 config: 配置文件、coe文件等。doc: 文档fpga: 最后恢复的fpga工程目录ip: ip文件mcs: bit流文件等,方便直接使用src: .…

npm包、全局数据共享、分包

使用 npm 包 小程序对 npm 的支持与限制 目前&#xff0c;小程序中已经支持使用 npm 安装第三方包&#xff0c;从而来提高小程序的开发效率。但是&#xff0c;在小程序中使用npm 包有如下 3 个限制&#xff1a; ① 不支持依赖于 Node.js 内置库的包 ② 不支持依赖于浏览器内置…

webgl canvas系列——快速加背景、抠图、加水印并下载图片

文章目录 ⭐前言⭐canvas绘制图片&#x1f496;绘制csdn图片&#x1f496;给png图片加背景&#x1f496;cavans下载图片&#x1f496;cavans上传图片并抠图&#x1f496;cavans添加文字水印&#x1f496;inscode 完整代码块 ⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#x…

建设IAM/IDM统一身份管理,实现系统之间的单点登录(SSO)

企业实施身份管理的现状&#xff1a; 1.身份存储分散&#xff0c;不能统一供应诸多应用系统&#xff0c;企业用户信息常常存在于多个系统&#xff0c;如HR系统有一套用户信息&#xff0c;OA系统也有一套用户信息&#xff0c;身份存储不集中&#xff0c;不能统一地为诸多应用系…

AJAX概念和axios使用、URL、请求方法和数据提交、HTTP协议、接口、form-serialize插件

AJAX概念和axios使用 AJAX概念 AJAX就是使用XMLHttpRequest对象与服务器通信&#xff0c;它可以使用JSON、XML、HTML和text文本等格式发送和接收数据&#xff0c;AJAX最吸引人的就是它的异步特性&#xff0c;也就是说它可以在不重新刷新页面的情况下与服务器通信&#xff0c;…