【深入浅出Nacos原理及调优】「原理分析专题」配置中心加载原理和配置实时更新原理分析

news2024/11/28 12:54:11

官方资源

官方资源

带着问题去思考

  • 客户端长轮询的响应时间会受什么影响
  • 为什么更改了配置信息后客户端会立即得到响应
  • 客户端的超时时间为什么要设置为30s
  • 带着以上这些问题我们从服务端的代码中去探寻结论。

配置中心 (Configuration Center)

系统开发过程中通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独立的配置文件的形式存在。目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中,由系统管理员或者运维人员完成这个步骤。配置变更是调整系统运行时的行为的有效手段之一。

配置管理 (Configuration Management)

在数据中心中,系统中所有配置的编辑、存储、分发、变更管理、历史版本管理、变更审计等所有与配置相关的活动统称为配置管理。

名字服务 (Naming Service)

分布式系统中所有对象(Object)、实体(Entity)的“名字”到关联的元数据之间的映射管理服务,例如 ServiceName -> Endpoints Info, Distributed Lock Name -> Lock Owner/Status Info, DNS Domain Name -> IP List, 服务发现和 DNS 就是名字服务的2大场景。

配置服务 (Configuration Service)

在服务或者应用运行过程中,提供动态配置或者元数据以及配置管理的服务提供者。

官方资源

https://nacos.io/zh-cn/docs/quick-start.html

Nacos之配置中心

  • 动态配置管理是 Nacos的三大功能之一,通过动态配置服务,可以在所有环境中以集中和动态的方式管理所有应用程序或服务的配置信息。
  • 动态配置中心可以实现配置更新时无需重新部署应用程序和服务即可使相应的配置信息生效,这极大了增加了系统的运维能力。

动态配置

Nacos的动态配置的能力,看看 Nacos是如何以简单、优雅、高效的方式管理配置,实现配置的动态变更的,接下来来了解下 Nacos 的动态配置的功能。

环境准备

首先我们要准备一个 Nacos 的服务端,现在有两种方式获取 Nacos 的服务端:

  • 通过源码编译
  • 下载Release包

注意:自己去编译可以了解一些过程也有可能会碰到一些问题,这些都是很重要的经验,好了那我们直接源码编译吧。

  1. clone 一份 nacos 的代码到自己的 github 库,然后把代码 clone 到本地。

    git clone https://github.com/alibaba/nacos.git
    
  2. 在项目的根目录下执行以下命令(假设我们已经配置好了 java 和 maven 环境):

    cd nacos/
    mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U  
    
  3. 执行成功之后你将会看到如下图所示的结果:

  4. 在项目的 distribution 目录下我们就可以找到可执行程序了,包括两个压缩包,这两个压缩包就是nacos 的 github 官网上发布的 Release 包。

ls -al distribution/target/
// change the $version to your actual path
cd distribution/target/nacos-server-$version/nacos/bin

  1. 把编译好的两个压缩包拷贝出来,解压出来直接使用,这样就相当于我们下载了 Release 包了。

  2. 解压后文件结构和nacos-server一样,我们直接执行 startup.sh 即可启动一个单机的 Nacos 服务端了。

启动服务端

Linux/Unix/Mac

启动命令(standalone代表着单机模式运行,非集群模式):

sh startup.sh -m standalone

如果您使用的是ubuntu系统,或者运行脚本报错提示[[符号找不到,可尝试如下运行:

bash startup.sh -m standalone

Windows

启动命令(standalone代表着单机模式运行,非集群模式):

startup.cmd -m standalone

适用场景

动态配置管理的效果之后,我们知道了大概的原理了,Nacos 服务端保存了配置信息,客户端连接到服务端之后,根据 dataID,group可以获取到具体的配置信息,当服务端的配置发生变更时,客户端会收到通知。当客户端拿到变更后的最新配置信息后,就可以做自己的处理了,这非常有用,所有需要使用配置的场景都可以通过 Nacos 来进行管理。可以说 Nacos 有很多的适用场景,包括但不限于以下这些情况:

  • 数据库连接信息
  • 限流规则和降级开关
  • 流量的动态调度

推还是拉

了解了 Nacos 的配置管理的功能了,但是有一个问题我们需要弄明白,那就是 Nacos 客户端是怎么实时获取到 Nacos 服务端的最新数据的。其实客户端和服务端之间的数据交互,无外乎两种情况:

  1. 服务端推数据给客户端
  2. 客户端从服务端拉数据

创建 ConfigService

首先是创建了一个 ConfigService。而 ConfigService 是通过 ConfigFactory 类创建的,如下图所示:

这里并没有通过单例或者缓存技术,也就是说每次调用都会重新创建一个 ConfigService的实例。

实例化 ConfigService

来看下 NacosConfigService 的构造方法,看看 ConfigService 是怎么实例化的,如下图所示:

实例化时主要是初始化了两个对象,他们分别是:HttpAgent和ClientWorker

HttpAgent

agent 是通过装饰着模式实现的,ServerHttpAgent 是实际工作的类,MetricsHttpAgent 在内部也是调用了 ServerHttpAgent 的方法,另外加上了一些统计操作,所以我们只需要关心 ServerHttpAgent 的功能就可以了。

ClientWorker

agent 实际是在 ClientWorker 中发挥能力的,下面我们来看下 ClientWorker 类,以下是 ClientWorker 的构造方法,如下图所示:

可以看到 ClientWorker 除了将 HttpAgent 维持在自己内部,还创建了两个线程池:

  • 第一个线程池是只拥有一个线程用来执行定时任务的 executor,executor 每隔 10ms 就会执行一次 checkConfigInfo() 方法,从方法名上可以知道是每 10 ms 检查一次配置信息。

  • 第二个线程池是一个普通的线程池,从 ThreadFactory 的名称可以看到这个线程池是做长轮询的。

现在让我们来看下 executor 每 10ms 执行的方法到底是干什么的,如下图所示:

  • checkConfigInfo方法是取出了一批任务,提交给 executorService 线程池去执行,执行的任务就是 LongPollingRunnable,每个任务都有一个 taskId。

  • LongPollingRunnable做了什么,主要分为两部分,第一部分是检查本地的配置信息,第二部分是获取服务端的配置信息然后更新到本地。

本地检查

首先取出与该 taskId 相关的 CacheData,然后对 CacheData 进行检查,包括本地配置检查和监听器的 md5 检查,本地检查主要是做一个故障容错,当服务端挂掉后,Nacos 客户端可以从本地的文件系统中获取相关的配置信息,如下图所示:

通过跟踪 checkLocalConfig 方法,可以看到 Nacos 将配置信息保存在了

~/nacos/config/fixed-{address}_8848_nacos/snapshot/DEFAULT_GROUP/{dataId}
服务端检查
  1. 通过 checkUpdateDataIds() 方法从服务端获取那些值发生了变化的 dataId 列表,

  2. 通过 getServerConfig 方法,根据 dataId 到服务端获取最新的配置信息,接着将最新的配置信息保存到 CacheData 中。

  3. 调用 CacheData 的 checkListenerMd5 方法。

可以看到,在该任务的最后,也就是在 finally 中又重新通过 executorService 提交了本任务。

ConfigListener

ConfigService 来添加一个 Listener 了,最终是调用了 ClientWorker 的 addTenantListeners 方法,如下图所示:

该方法分为两个部分,首先根据 dataId,group 和当前的场景获取一个 CacheData 对象,然后将当前要添加的 listener 对象添加到 CacheData 中去。

也就是说 listener 最终是被这里的 CacheData 所持有了,那 listener 的回调方法 receiveConfigInfo 就应该是在 CacheData 中触发的。

我们发现 CacheData 是出现频率非常高的一个类,在 LongPollingRunnable 的任务中,几乎所有的方法都围绕着 CacheData 类,现在添加 Listener 的时候,实际上该 Listener 也被委托给了 CacheData,那我们要重点关注下 CacheData 类了。

CacheData

首先让我们来看一下 CacheData 中的成员变量,如下图所示:

可以看到除了 dataId,group,content,taskId 这些跟配置相关的属性,还有两个比较重要的属性:listeners、md5。

listeners 是该 CacheData 所关联的所有 listener,不过不是保存的原始的 Listener 对象,而是包装后的 ManagerListenerWrap 对象,该对象除了持有 Listener 对象,还持有了一个 lastCallMd5 属性。

另外一个属性 md5 就是根据当前对象的 content 计算出来的 md5 值。

触发回调

ConfigService 的 Listener 是在什么时候触发回调方法 receiveConfigInfo 的。

在 ClientWorker 中的定时任务中,启动了一个长轮询的任务:LongPollingRunnable,该任务多次执行了 cacheData.checkListenerMd5() 方法,那现在就让我们来看下这个方法到底做了些什么,如下图所示:

该方法会检查 CacheData 当前的 md5 与 CacheData 持有的所有 Listener 中保存的 md5 的值是否一致,如果不一致,就执行一个安全的监听器的通知方法:safeNotifyListener,通知 Listener 的使用者,该 Listener 所关注的配置信息已经发生改变了。

safeNotifyListener 方法

Md5何时变更

那 CacheData 的 md5 值是何时发生改变的呢?我们可以回想一下,在上面的 LongPollingRunnable 所执行的任务中,在获取服务端发生变更的配置信息时,将最新的 content 数据写入了 CacheData 中,我们可以看下该方法如下:

可以看到是在长轮询的任务中,当服务端配置信息发生变更时,客户端将最新的数据获取下来之后,保存在了 CacheData 中,同时更新了该 CacheData 的 md5 值,所以当下次执行 checkListenerMd5 方法时,就会发现当前 listener 所持有的 md5 值已经和 CacheData 的 md5 值不一样了,也就意味着服务端的配置信息发生改变了,这时就需要将最新的数据通知给 Listener 的持有者。

至此配置中心的完整流程已经分析完毕了,可以发现,Nacos 并不是通过推的方式将服务端最新的配置信息发送给客户端的,而是客户端维护了一个长轮询的任务,定时去拉取发生变更的配置信息,然后将最新的数据推送给 Listener 的持有者。

客户端是通过一个定时任务来检查自己监听的配置项的数据的,一旦服务端的数据发生变化时,客户端将会获取到最新的数据,并将最新的数据保存在一个 CacheData 对象中,然后会重新计算 CacheData 的 md5 属性的值,此时就会对该 CacheData 所绑定的 Listener 触发 receiveConfigInfo 回调。

拉的优势

客户端拉取服务端的数据与服务端推送数据给客户端相比,优势在哪呢,为什么 Nacos 不设计成主动推送数据,而是要客户端去拉取呢?如果用推的方式,服务端需要维持与客户端的长连接,这样的话需要耗费大量的资源,并且还需要考虑连接的有效性,例如需要通过心跳来维持两者之间的连接。而用拉的方式,客户端只需要通过一个无状态的 http 请求即可获取到服务端的数据。

此外考虑到服务端故障的问题,客户端将最新数据获取后会保存在本地的 snapshot 文件中,以后会优先从文件中获取配置信息的值。

动态配置

  • 动态配置管理是 Nacos的三大功能之一,通过动态配置服务,可以在所有环境中以集中和动态的方式管理所有应用程序或服务的配置信息。
  • 动态配置中心可以实现配置更新时无需重新部署应用程序和服务即可使相应的配置信息生效,这极大了增加了系统的运维能力。

Nacos的动态配置的能力,看看 Nacos是如何以简单、优雅、高效的方式管理配置,实现配置的动态变更的,接下来来了解下 Nacos 的动态配置的功能。

客户端动态化配置机制

Nacos 的客户端维护了一个长轮询的任务,去检查服务端的配置信息是否发生变更,如果发生了变更,那么客户端会拿到变更的 groupKey 再根据 groupKey 去获取配置项的最新值即可。

客户端去发请求,询问服务端我所关注的配置项有没有发生变更,如果间隔时间设置的太长的话有可能无法及时获取服务端的变更,如果间隔时间设置的太短的话,那么频繁的请求对于服务端来说无疑也是一种负担。

如果客户端每隔一段长度适中的时间去服务端请求,而在这期间如果配置发生变更,服务端能够主动将变更后的结果推送给客户端,这样既能保证客户端能够实时感知到配置的变化,也降低了服务端的压力。

客户端长轮询

客户端长轮询的部分,也就是LongPollingRunnable中的checkUpdateDataIds 方法,该方法就是用来访问服务端的配置是否发生变更的,该方法最终会调用如下图所示的方法:

http请求操作

客户端是通过一个http的post 请求去获取服务端的结果的,并且设置了一个超时时间:30s。一般来讲:客户端足足等了29.5+s,才请求到服务端的结果,然后客户端得到服务端的结果之后,再做一些后续的操作,全部都执行完毕之后,在 finally 中又重新调用了自身,也就是说这个过程是一直循环下去的。

长轮询执行逻辑

客户端向服务端发起一次请求,最少要29.5s才能得到结果,当然啦,这是在配置没有发生变化的情况下。如果客户端在长轮询时配置发生变更的话,该请求需要多长时间才会返回呢,在客户端长轮询时修改配置。

未获得到修改数据的操作触发返回

获得到了修改数据操作立刻触发返回

服务端controller

上面说到了客户端发送的 http 请求中可以知道,请求的是服务端的 /v1/cs/configs/listener 这个接口,com.alibaba.nacos.config.server.controller.ConfigController.java,在 ConfigController 类中,如下图所示:

服务端是通过springMVC对外提供的 http 服务,对 HttpServletRequest 中的参数进行转换后,然后交给一个叫 inner 的对象去执行。inner 对象是 ConfigServletInner 类的实例,com.alibaba.nacos.config.server.controller.ConfigServletInner.java

该方法是一个轮询的接口,除了支持长轮询外还支持短轮询的逻辑。再次进入 longPollingService 的 addLongPollingClient 方法,如下图所示:

com.alibaba.nacos.config.server.service.LongPollingService.java

该方法主要是将客户端的长轮询请求添加到某个东西中去:服务端将客户端的长轮询请求封装成一个叫 ClientLongPolling 的任务,交给 scheduler 去执行。

服务端拿到客户端提交的超时时间后,又减去了 500ms 也就是说服务端在这里使用了一个比客户端提交的时间少 500ms 的超时时间,也就是 29.5s,看到这个 29.5s 我们应该有点兴奋了。

PS:这里的 timeout 不一定一直是 29.5,当 isFixedPolling() 方法为 true 时,timeout 将会是一个固定的间隔时间,这里为了描述简单就直接用 29.5 来进行说明。

接下来我们来看服务端封装的 ClientLongPolling 的任务到底执行的什么操作,如下图所示:

com.alibaba.nacos.config.server.service.LongPollingService.ClientLongPolling.java

ClientLongPolling 被提交给 scheduler 执行之后,实际执行的内容可以拆分成以下四个步骤:

  1. 创建一个调度的任务,调度的延时时间为 29.5s。
  2. 将该 ClientLongPolling 自身的实例添加到一个 allSubs 中去。
  3. 延时时间到了之后,首先将该 ClientLongPolling 自身的实例从 allSubs 中移除。
  4. 获取服务端中保存的对应客户端请求的 groupKeys 是否发生变更,将结果写入 response 返回给客户端。

allSubs 对象,该对象是一个 ConcurrentLinkedQueue 队列,ClientLongPolling 将自身添加到队列中。

调度任务

服务端对客户端提交上来的 groupKey 进行检查,如果发现某一个 groupKey 的 md5 值还不是最新的,则说明客户端的配置项还没发生变更,所以将该 groupKey 放到一个 changedGroupKeys 列表中,最后将该 changedGroupKeys 返回给客户端。对于客户端来说,只要拿到 changedGroupKeys 即可。

服务端数据变更

服务端直到调度任务的延时时间到了之前,ClientLongPolling 都不会有其他的任务可做,所以在这段时间内,该 allSubs 队列肯定有事情需要进行处理。

在客户端长轮询期间,更改了配置之后,客户端能够立即得到响应。

服务端数据变更接口

调用的请求,可以很容易的找到该请求对应的 url为:/v1/cs/configs 并且是一个 POST 请求,具体的方法是 ConfigController 中的 publishConfig 方法,如下图所示:

修改配置后,服务端首先将配置的值进行了持久化层的更新,然后触发了一个 ConfigDataChangeEvent 的事件,fireEvent 的方法:

com.alibaba.nacos.config.server.utils.event.EventDispatcher.java

fireEvent 方法实际上是触发的 AbstractEventListener 的 onEvent 方法,而所有的 listener 是保存在一个叫 listeners 对象中的。

被触发的 AbstractEventListener 对象则是通过 addEventListener 方法添加到 listeners 中的,找到 addEventListener 方法在何处被调用的,就知道有哪些 AbstractEventListener 需要被触发 onEvent 回调方法了。

可以找到是在 AbstractEventListener 类的构造方法中,将自身注册进去了,如下图所示:

com.alibaba.nacos.config.server.utils.event.EventDispatcher.AbstractEventListener.java

可以看到 AbstractEventListener 所有的子类中LongPollingService。当我们从 dashboard 中更新了配置项之后,实际会调用到 LongPollingService 的 onEvent 方法。

回到 LongPollingService 中,查看一下 onEvent 方法,如下图所示:

com.alibaba.nacos.config.server.service.LongPollingService.DataChangeTask.java

发现当触发了 LongPollingService 的 onEvent 方法时,实际是执行了一个叫 DataChangeTask 的任务,应该是通过该任务来通知客户端服务端的数据已经发生了变更,我们进入 DataChangeTask 中看下具体的代码,如下图所示:

遍历 allSubs 的队列

遍历 allSubs 的队列,该队列中维持的是所有客户端的请求任务,需要找到与当前发生变更的配置项的 groupKey 相等的 ClientLongPolling 任务

往客户端写响应数据

丢i与ClientLongPolling 任务后,只需要将发生变更的 groupKey 通过该 ClientLongPolling 写入到响应对象中,就完成了一次数据变更的 “推送” 操作了

如果 DataChangeTask 任务完成了数据的 “推送” 之后,需要将原来等待执行的调度任务取消掉了,这样就防止了推送操作写完响应数据之后,调度任务又去写响应数据。

可以从 sendResponse 方法中看到,确实是这样做的:

http请求本来就是无状态的,所以没必要也不能将超时时间设置的太长,这样是对资源的一种浪费。

与此同时服务端也将该请求封装成一个调度任务去执行,等待调度的期间就是等待 DataChangeTask 主动触发的,如果延迟时间到了 DataChangeTask 还未触发的话,则调度任务开始执行数据变更的检查,然后将检查的结果写入响应对象,如下图所示:

总结结论:

  1. Nacos 客户端会循环请求服务端变更的数据,并且超时时间设置为30s,当配置发生变化时,请求的响应会立即返回,否则会一直等到 29.5s+ 之后再返回响应
  2. Nacos 客户端能够实时感知到服务端配置发生了变化。
  3. 实时感知是建立在客户端拉和服务端“推”的基础上,但是这里的服务端“推”需要打上引号,因为服务端和客户端直接本质上还是通过 http 进行数据通讯的,之所以有“推”的感觉,是因为服务端主动将变更后的数据通过 http 的 response 对象提前写入了。

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

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

相关文章

用Python调用OpenAI API做有趣的事

获取 API KEY 首先需要 可以开全局的梯子,选择日本或韩国节点,可以通过 ipinfo 检查当前 IP 地址是否为日本或韩国地区,然后访问 OpenAI 网站注册账号并完成认证。 如果自己完成账号认证的成本太高,可以在某宝直接购买一个已经通…

存储也能“一键美颜”?看 XOCP 如何助力互联科技图片业务转型

近日,XSKY星辰天合联合“互联科技”推出了“图片云在线处理”解决方案,提供海量图片高效存储访问和在线图片处理服务,深入业务场景简化操作流程,提升照片流转速度,为客户打造高效敏捷的拍照体验。互联科技(…

DAS Over FC 技术允许 ATTO 分解存储并完成 vSAN 认证套件

一、介绍 最近,ATTO Technology, Inc. 以前所未有的方式 完成了 VMware vSAN ReadyNode 认证套件。 测试台本身是公式化的,以三台 Dell R640 服务器作为主机。 除了用于引导的 SD 卡和用于日志记录的单个 SAS SSD 之外,不存在任何内部存储…

EXCEL基础:IFNA、VLOOKUP、SUMIFS函数的使用

注意:本操作数据来源于excel home,特此声明。 查看原始数据,如下所示为【商品】的相关信息: 查看原始数据,如下所示为【供应商】的相关信息: 主要目的:把以上相关信息搞在一张表上。 把下表…

基于jsp+mysql+ssm台球俱乐部管理系统-计算机毕业设计

项目介绍 台球俱乐部系统设计主要是管理员登录后对整个系统相关操作进行处理,可进行管理员的添加和删除,会员信息管理、付费信息管理、球桌信息管理、订桌信息管理等操作管理。采用目前最流行的ssm框架和eclipse编辑器、mysql数据库设计并实现的 本系统…

[附源码]计算机毕业设计冬奥会网上商城Springboot程序

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: Springboot mybatis MavenVue等等组成,B/S模式…

什么是BPM系统?BPM流程管理系统介绍

一、什么是BPM系统? BPM系统(英文全称:Business Process Management,翻译后简称BPM)即业务流程管理系统,是指对端到端业务流程进行建模、分析和优化,以实现战略业务目标,其特点是注…

Elasticsearch:如何在 CentOS 上创建多节点的 Elasticsearch 集群 - 8.x

在我之前的文章 “Elasticsearch:使用 RPM 安装包来安装 Elastic Stack 8.x” 里,我详细地介绍了如何使用 RPM 安装包来安装 Elastic Stack 8.x。在今天的文章中,我来详细描述如何从零开始来创建一个含有三个节点的 Elasticsearch 集群。我们…

python文本处理尝试

Python文本处理尝试 最近打算看CSAPP,GitHub上看到有英语字幕ass源文件,想把字幕提取出来提高学习效率,先把ass文件转成txt文件,发现是这样👇 都在Dialogue的后面,打算尝试提取一下 不太熟练,下…

解决 Eclipse 中不见 Maven 问题

在 Windows -> Preferences 不见 Maven 和 File -> New -> Project 均不见 Maven,但 Help -> Eclipse Marketplace -> installed 却可见已经安装,网上的各种方法都不能解决。 解决步骤: 关闭 Eclipse;清空目录 C:…

肿瘤网络平台

肿瘤网络平台是为广大肿瘤医生量身定做的学习,交流功能健全的网络平台,平台汇聚了会议、病例、专家讲堂等内容,广大的肿瘤医生可以随时分享、讨论专业知识。 一、平台价值 让医生 了解肿瘤前沿进展 提高业务水平 丰富临床经验 二、平台架构…

如何挖掘外贸客户需求

是客户的衣食父母。 俗话说“知己知彼,百战不殆”,外贸业务员都想把自己的产品推销给客户。除了知道自己产品的特点,还应该知道客户的需求是什么。因此,前期的客户调研工作非常重要。 不是所有的客户都是意向或潜在客户。只有当…

小程序canvas 缩放/拖动/还原/封装和实例--开箱即用

小程序canvas 缩放/拖动/还原/封装和实例一、预览二、使用2.1 创建和配置方法三、源码3.1 实例组件3.2 核心类一、预览 之前写过web端的canvas 缩放/拖动/还原/封装和实例。最近小程序也需要用到,但凡是涉及小程序canvas还是比较多坑的,而且难用多了&am…

[附源码]Node.js计算机毕业设计订餐系统Express

项目运行 环境配置: Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术: Express框架 Node.js Vue 等等组成,B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境:最好是Nodejs最新版,我…

Linux中如何查看ntp是否同步?

Linux中如何查看ntp是否同步?在Linux中,查看ntp是否同步的方法主要有三种,分别是:ntpd命令、ntpstat命令、timedatectl命令,接下来是详细的内容介绍。  NTP用于将计算机客户或服务器的时间同步到另一服务器或参考时钟源。它使用…

Java基于微信小程序的计算机等级考试考练 毕业设计

网络的广泛应用给生活带来了十分的便利。所以把计算机等级考试考练与现在网络相结合,利用java技术建设计算机等级考试考练APP,实现计算机等级考试考练的信息化。则对于进一步提高计算机等级考试考练发展,丰富计算机等级考试考练经验能起到不少…

[附源码]Python计算机毕业设计电商后台系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置: Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术: django python Vue 等等组成,B/S模式 pychram管理等…

web前端期末大作业——HTML+CSS+JavaScript仿王者荣耀游戏网站设计与制作

🎉精彩专栏推荐👇🏻👇🏻👇🏻 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 💂 作者主页: 【主页——🚀获取更多优质源码】 🎓 web前端期末大作业…

基于kmeans算法的数据聚类matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 聚类算法也许是机器学习中“新算法”出现最多、最快的领域,一个重要的原因是聚类不存在客观标准,给定数据集总能从某个角度找到以往算法未覆盖的某种标准从而设计出新算法…

VR工厂:助力工厂数字化升级

近年来,疫情全球常态化和数字化进程加速,给传统制造业工厂在生产制作、业务销售上带来巨大考验。随着物联网、云计算、大数据和5G等信息技术的发展,制造工厂面临着第四次工业革命。2021年,国家发布“十四五规划”,着重…