【Nacos无压力源码领读】(三) Nacos 配置中心与热更新原理详解超详细解读

news2024/12/28 18:01:21

本文将从 Nacos 配置中心的基本使用入手, 详细介绍 Nacos 客户端发布配置, 拉取配置, 订阅配置的过程以及服务器对应的处理过程;

配置订阅以及热更新原理相关的部分, 我看了主流的博客网站, 绝对没有比这更详细的讲解;

如果在阅读过程中对文中提到的 SpringBoot 启动过程以及扩展机制不太了解, 或者出现了没见过的词, 在这篇文章 SpringBoot启动流程与配置类处理机制详解, 附源码与思维导图 中都能找到答案, 强烈建议学习后再来读本文;

对SpringBoot 属性加载机制不了解的可以看这里 SpringBoot 属性加载机制

Nacos配置中心

基本使用

一个大型项目, 服务数量会非常多, 每个服务都需要进行配置, 这会导致: 公共的一些配置需要修改时, 需要修改大量服务的配置文件; 并且需要重新启动涉及的所有服务;

Nacos就提供了作为配置中心的功能; 将配置(不仅是公共的, 服务独有的配置也可以保存到Nacos)保存在Nacos服务器中, 实现以中心化 (都在Nacos里)、外部化 (配置独立于服务之外保存) 和动态化 (动态化即可以实时刷新配置) 的方式管理配置。

基本原理

将配置以键值对的形式保存到Nacos中;

服务启动时, 从 Nacos 读取配置, 生成 PropertySource 对象, 保存到 Spring 容器中, 可以使用 @Value注解 等方式进行注入;

配置模型

为了更好地管理配置, 使得一个Nacos配置中心能支持多个应用, 多个服务, Nacos划分了几个不同的维度来组织保存的配置:

命名空间 Namespace

  • 用于用户粒度的配置隔离, 每一个命名空间都有一个唯一的ID与之对应, 默认情况下使用 public 命名空间;
  • 创建一个命名空间时, Nacos 会自动分配一个唯一的ID, 不需要用户指定;

分组 Group

  • 每个分组都有一个唯一的组名, 默认使用DEFAULT-GROUP分组;
  • 创建分组时, 由用户指定分组名称;

配置集 Data ID

  • 配置集是一组配置项的集合, 类比一个服务的一个配置文件;
  • 每个配置集都有一个唯一的 Data ID, 需要用户在创建配置集时手动指定;
  • SpringBoot项目启动时, 会自动生成一个配置集的DataID, 并从Nacos下载对应的配置;
  • 生成的DataID由三部分构成
${prefix}-${spring.profiles.active}.${file-extension}
  • 第一个字段 默认取spring.application.name的值, 也可以通过配置项 spring.cloud.nacos.config.prefix 来配置。

  • 第二个字段, 取spring.profiles.active的值, 并且在最前面拼接一个短横杠, 如果该属性没有配置, 这个字段为空, 也没有短横杠;

    spring.profiles.active用于指定激活的 Spring 配置文件。application.yml文件始终有效, 可以选择是否激活其它的配置文件; 可以根据不同的环境(例如开发、测试、生产等)来加载不同的配置,从而实现环境隔离和配置管理。

  • 第三个字段通过spring.cloud.nacos.config.file-extension属性配置,可以是 propertiesyaml;

  • 例如一个名称为 student-service 的服务, 激活了dev 配置文件, 配置的后缀是 yaml, 那么生成的DataID是

    student-service-dev.yaml;

配置项 Key - Value

  • 一个配置项就对应一个配置, 将配置的参数名与取值以键值对的形式保存;

使用Nacos配置中心

导包

<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

在单个服务中, 添加配置文件bootstrap.yml, 将和 Nacos-Config 相关的配置放到bootstrap.yml中;

  • bootstrap.yml 文件中的配置在应用启动的早期阶段 (引导阶段) 就会被加载;
  • application.yml 文件在 PropertySourceList 中的位置在 bootstrap.yml 之前,优先级更高, 因此可以覆盖 bootstrap.yml 中的配置;
  • Nacos 配置中心的相关配置需要在应用启动的早期阶段加载,以便应用能够尽早连接到 Nacos 服务器获取配置。
spring:
  application:
    # 生成DataID需要
    name: facade-service
  profiles:
    # 生成DataID需要
    active: dev
  cloud:
    nacos:
      config:
        # 服务器地址
        server-addr: localhost:8848
        # 生成DataID需要
        file-extension: yaml
        # 指定group
        group: DEFAULT_GROUP
        # 指定namespace, 值应该取命名空间对应的ID, 而不是空间名
        namespace: eaf7318f-2016-4fb2-a37e-3d528087e550

对 Nacos 配置集中的配置, 可以直接注入并使用; 此时还无法实现配置的热更新; 修改Nacos中保存的配置后, 不会在服务中立即生效, 需要重启服务;

@Value("${nacos.test.config0}")
String nacosTestConfig0;

@GetMapping("/facade/nacosconfig")
public String nacosConfig() {
    return nacosTestConfig0;
}

在使用了配置的类上添加@RefreshScope注解开启热更新;

开启热更新后, 无需重启服务, 即可实现配置更新;

@RefreshScope
public class FacadeController {
    @Value("${nacos.test.config0}")
    String nacosTestConfig0;

    @GetMapping("/facade/nacosconfig")
    public String nacosConfig() {
        return nacosTestConfig0;
    }
}

持久化

Derby 是一个基于JAVA开发关系型数据库, 是可嵌入的。应用程序可以将 Derby 嵌入应用程序进程中,从而无需管理单独的数据库进程或服务。

我们在Nacos服务器上写入的配置,会被持久化保存到Nacos自带的derby中,因此当我们重启Nacos之后,仍然可以看到之前的配置信息。

Nacos还支持将配置信息写入Mysql中:

  1. 在Mysql中,创建名为nacos的数据库 ;

  2. 在nacos数据库中,执行 nacos 安装目录的 conf 目录下的 mysql-schema.sql脚本, 创建表结构;

  3. 修改 Nacos安装目录 conf/application.properties文件

    spring.datasource.platform=mysql
    db.num=1
    # 这里的url要改成你自己的mysql数据库地址,并在你的mysql中创建名为nacos的数据库
    db.url.0=jdbc:mysql://11.162.196.16:3306/nacos?
    characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
    # 这里要改成你自己登录mysql的用户名和密码
    db.user.0=nacos_devtest
    db.password.0=youdontkno
    

监听多个配置文件

假设 bootstrap.yaml 有如下配置, 那么 Nacos 客户端启动时, 不仅会监听order-service-dev.yaml, 还会自动监听order-serviceorder-service.yaml两个文件;

spring:
  application:
    name: order-service
  profiles:
    active: dev
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        file-extension: yaml

如果要监听更多配置文件, 例如公共的 MySQL 配置文件, 例如公共的 Redis 配置文件, 进行如下配置

server:
  port: 7770
spring:
  application:
    name: order-service
  profiles:
    active: dev
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
        file-extension: yaml
        shared-configs:
          - data-id: mysql.yaml
            refresh: true
          - data-id: redis.yaml
            refresh: true

在这里插入图片描述

发布配置原理2.0.4

客户端

Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, "localhost");
ConfigService configService = NacosFactory.createConfigService(properties);
configService.publishConfig("test-mysql.yaml", "DEFAULT_GROUP", "my: content");
  1. 客户端通过 NacosConfigService 向 Nacos 配置中心发出 ConfigPublishRequest请求;
  2. 这个请求里封装了命名空间, Group, dataId, 配置内容等信息;
  3. 2.X 版本, 默认是使用的 gRPC 的方式发送;

服务端

以下分析基于开启了 MySQL 外置存储的情况;

服务端由 ConfigPublishRequestHandler 来处理发布请求;

  1. 将命名空间, 分组, dataId, 内容等信息从请求中提取出来, 然后插入到数据库中;

  2. 然后发布一个ConfigDataChangeEvent, 这个事件被异步地处理, 将配置的发布同步给 Nacos 集群中的其它结点;

  3. 然后再异步地将配置作为一个文件持久化保存到服务器的文件系统中;

    在这里插入图片描述

  4. 然后根据命名空间, 组名和data-id去获取内存中, 当前发布的配置集对应的缓存CacheItem, 这个缓存保存了配置集的一些描述信息, 例如由配置项计算出的MD5值, 例如上次修改时间, 例如配置文件的类型(比如yaml)等, 如果没有, 就新创建一个缓存;

    磁盘文件存内容, 内存缓存存描述信息;

    MD5 值作为配置的一种版本标记; 当一个配置文件的 MD5 改变, 认为该文件发生了更新;

  5. 检查缓存的 MD5 值(如果是新建的, MD5 = null) 与最新的配置集内容计算出的MD5值是否相同, 如果不同, 发布一个LocalDataChangeEvent , 推送新发布的配置给订阅了这个配置的客户端;

拉取配置原理

基于2.0.4版本

客户端

  1. ApplicationContext 初始化之前, 会有一个独立于应用程序主上下文的 BootstrapContext 先被创建并初始化, 这个上下文读取的是 bootstrap.yaml 文件;

  2. 这个上下文用于引导配置属性源;

  3. 了解: 在主上下文的 prepareEnvironment 阶段, 会发布一个EnvironmentPrepared 事件; 这个事件会被BootstrapApplicationListener( 也是在 spring-cloud- context.spring.facories文件中声明的 ) 处理;
    它会创建并初始化一个 BootstrapContext

  4. BootstrapContext 初始化时, 会加载 spring.factories 文件中声明的 BootstrapConfiguration类型的组件, 进行自动配置;

  5. SpringCloud 官方团队在spring-cloud-context-3.1.4.jar\META-INF\spring.factories文件中提供了一个 BoostrapConfiguration, 叫PropertySourceBootstrapConfiguration; 同时, 它也是一个初始化器 ApplicationContextInitializer;

  6. 这个初始化器会自动注入 BootstrapContext 中的所有 PropertySourceLocator, 包括NacosPropertySourceLocator

  7. NacosPropertySourceLocator是由 Nacos提供的 NacosConfigBootstrapConfiguration 注册的, 这个配置类也是一个BootstrapConfiguration;

  8. 这个已经注入NacosPropertySourceLocator的初始化器PropertySourceBootstrapConfiguration 会放到SpringApplication对象中保存;

  9. 这样, 就通过 SpringApplication 对象在 BootstrapContext 和应用的主上下文之间传递了PropertySourceBootstrapConfiguration

  10. 应用的主上下文在 prepareContext 阶段, 调用 applyInitializers 时, 会调用所有初始化器的初始化方法, 这其中就包括PropertySourceBootstrapConfiguration的初始化方法; 这个初始化方法会遍历自己持有的PropertySourceLocator, 并调用他们的 locateCollection 方法

    为什么采用初始化器ApplicationContextInitializerBootstrapConfiguration的方式去发送拉取配置的请求? 因为要确保在ApplicationContext初始化之前就能拿到 Nacos 配置中心中的配置, 这样才能在refreshContext的时候去使用这些配置

  11. 所以, Nacos 自动配置类注入的 NacosPropertySourceLocator 就派上用场了;

  12. NacosPropertySourceLocator中, 从 Environment 中获取到 bootstrap.yaml 文件对应的 PropertySource, 进而拿到 Nacos 配置中心的地址, 和要拉取的配置文件的 DataId 等信息,

  13. 创建NacosConfigService, 调用其getConfig方法拉取配置;

  14. getConfig 方法中, 会首先尝试从本地的一个特定路径下读取配置文件, 这个文件目录中间有一级是data开头的;

  15. 但是, 客户端在生成本地缓存的时候, 是放到的snapshot开头的文件夹下; 所以除非你手动创建data开头的本地缓存, 这里永远读不到数据;

    根据 Github Issues 中 Nacos 作者的官方回答, 这是一种容灾机制, 防止连不上 Nacos 配置中心, 应用程序无法启动;

    连不上的时候, 你就手动创建data 开头的缓存目录, 然后把 snapshot 文件夹下自动保存的配置文件复制进去;

  16. 如果从本地缓存读到了, 直接返回, 但一般都是读不到;

  17. 如果读不到, 发送拉取配置的请求, 从 Nacos 服务器获取配置集;

  18. 将从服务器获取到的配置集保存到 snapshot 文件夹下;

  19. 将获取到的配置集封装成属性源, 保存到 Environment 中; 以后就可以使用了; 每一个 dataId, 都会对应一个属性源 PropertySource;

  20. 并且, 来自 Nacos 配置中心的属性源, 会在 Environment 内的 PropertySources 内的 List 中排最前;

  21. 另外, 这些来自Nacos 的属性源, 还会在这时被添加到一个 NacosPropertySourceRepository 中, 后面订阅的时候就是从这里取的要订阅的属性源, 获取Group和 DataId (为什么没有命名空间? 因为一个客户端获取的配置集的命名空间是固定的, 保存在一个固定的地方, 发请求的时候当然也会携带上命名空间);

服务端

  1. ConfigQueryRequestHandler中处理拉取请求;

  2. 默认情况下, 读取服务器本地的缓存文件, 而不是数据库; 读取后返回给客户端; 如果本地缓存没有读到, 也不会去数据库中读, 返回空;

    可以进行配置, 让服务器从数据库读取, 而不是本地缓存;

  3. 在服务器启动的时候, 会从数据库拉取配置信息, 保存到服务器文件系统中;

默认情况下, 手动修改数据库中的配置集内容, 只会影响到控制台, 不会影响到Nacos本地缓存, 也就不会改变客户端拉取配置的结果;

只有Nacos Server 重启的时候, 才会将数据库中的内容装载到服务器本地;

控制台

控制台显示的配置信息是直接从数据库中读取的;

这里强烈建议大家自己动手验证, 手动修改数据库中保存的配置, 然后去看控制台的显示和客户端拉到的配置是否一致;

这里是一个大坑, 有时候明明控制台看到的是 variable = a, 但项目中读到的可能是 variable = b;

订阅配置

客户端发起订阅

  1. 和拉取配置不同, 你得先准备好了 Web 容器, 才能去订阅配置, 所以应用启动时自动订阅配置, 是由 Nacos-Config 的普通自动配置类向 Spring 容器中注册了一个 ApplicationListener, 叫NacosContextRefresher;
  2. 由这个监听器, 监听 ApplicationReady 事件 ( 这个事件是在 callRunners 之后发布的 );
  3. 监听到之后, 就会遍历 NacosPropertySourceRepository, 获取每个 Nacos 属性源的组名和DataId, 然后注册与之对应的监听器:
String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
Listener listener = listenerMap.computeIfAbsent(key,
        // 回调函数                                        
		lst -> new AbstractSharedListener() {
			@Override
			public void innerReceive(String dataId, String group,
					String configInfo) {
				refreshCountIncrement();
				nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
				applicationContext.publishEvent(
						new RefreshEvent(this, null, "Refresh Nacos config"));
				if (log.isDebugEnabled()) {
					log.debug(String.format(
							"Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
									group, dataId, configInfo));
						}
					}
				});
try {
    // 向服务器注册监听器;
	configService.addListener(dataKey, groupKey, listener);
	log.info("[Nacos Config] Listening config: dataId={}, group={}", dataKey,
			groupKey);
}

服务器处理监听请求

  1. 抽取出客户端的 ConnectionId, 和要监听的文件的标识 groupKey, 虽然叫groupKey, 但实际是DataId-Group-namespace构成的;

在这里插入图片描述

  1. ConfigChangeListenContext中维护订阅关系;
public class ConfigChangeListenContext {
    
    /**
     * <GroupKey, Set<监听了这个GroupKey的ConnnectId>>
     */
    private ConcurrentHashMap<String, HashSet<String>> groupKeyContext = new ConcurrentHashMap<String, HashSet<String>>();
    
    /**
     * <客户端ID, Map<这个客户端监听的GroupKey, 配置的MD5>值
     */
    private ConcurrentHashMap<String, HashMap<String, String>> connectionIdContext = new ConcurrentHashMap<String, HashMap<String, String>>();   
}

服务器推送更新

在这里插入图片描述

  • 这个事件被RpcConfigChangeNotifier监听器处理, 从事件中提取出对应的配置集的 DataId, group, groupKey 等等信息;
  • 从维护监听关系的对象ConfigChangeListenContext中, 根据 GroupKey取出监听了这个配置集的所有客户端的ConnectionId;
  • 遍历所有 ConnectionId, 将新的配置, 封装成一个配置变化的通知请求, 通过 RPC 工具将请求 Push 给客户端;

客户端热更新

使用 Java API 监听某个来自 Nacos 的配置文件, 配置文件重新发布后, Environment 中的 PropertySource 确实更新了, 但是使用 @Value 注解注入了某个属性值的Bean, 他的属性值没有改变; 因为 @Value 通过反射将属性值注入后, 这个属性值就保存在 Bean 中了, 修改 Environment 不会影响到它;

Nacos 采取的策略是将配置了热更新 ( @RefreshScope ) 的 Bean 销毁, 重新创建;

客户端收到配置变化的通知请求后, 发布一个刷新事件

String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
Listener listener = listenerMap.computeIfAbsent(key,
        // 回调函数                                        
		lst -> new AbstractSharedListener() {
			@Override
			public void innerReceive(String dataId, String group,
					String configInfo) {
				refreshCountIncrement();
				nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
				applicationContext.publishEvent(
						new RefreshEvent(this, null, "Refresh Nacos config"));
				if (log.isDebugEnabled()) {
					log.debug(String.format(
							"Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
									group, dataId, configInfo));
						}
					}
				});
try {
    // 向服务器注册监听器;
	configService.addListener(dataKey, groupKey, listener);
	log.info("[Nacos Config] Listening config: dataId={}, group={}", dataKey,
			groupKey);
}

这个刷新事件最终来到ContextRefresher进行处理, 做了两个重要操作

一是更新 Environment 里面对应的 PropertySource, 更新完以后会发布一个EnvironmentChangeEvent, 是一个扩展点; 例如有一个监听器监听到这个事件后, 会更新 @ConfigurationProperties 注解修饰的参数类;

二是把有@RefreshScope注解修饰的 Bean 销毁重建; 完成后发布一个RefreshScopeRefreshedEvent, Gateway 就是通过对这个事件的监听, 完成路由配置的热更新;

重建bean
  1. @RefreshScope注解的本质是@Scope("refresh"); 一个 Bean 的 Scope 可以是 singleton, 可以是 prototype, 可以是 Session (每个 Session 创建一个新的 Bean); 在创建 bean的时候, 会根据 BeanDefinition 中的 Scope 进行不同的创建逻辑;
  2. 总得来说: 所有 Scope 为 refresh 的 Bean, 创建时都会被放到同一个缓存中;
  3. 更新 Environment 后, 发布一个环境更新事件, 由ConfigurationPropertiesRebinder处理这个事件;
  4. 销毁 refreshScope 缓存中的 bean, 然后重新初始化这些被销毁的 bean;
安全问题
  1. 销毁时加锁, 防止多个线程同时销毁;
  2. 销毁之前, 创建一个代理, 在新的 bean 没有准备好之前, 用这个代理;

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

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

相关文章

交叉编译nginx1.20.0

一、说明 简略写一下过程&#xff0c;仅用于参考&#xff0c;建议与其他交叉编译教程一起看&#xff0c;检查是否有遗漏的问题。 二、源码修改 1、auto/cc/name vi auto/cc/name 注释 21 行 exit 1。 2、auto/types/sizeof vi auto/types/sizeof 将 15 行处的” ngx_size”…

数字图像处理(理论篇)专栏介绍

专栏导读 数字图像处理是计算机视觉领域的基石&#xff0c;它涉及到图像的获取、表示、处理和分析等多个方面。本专栏将通过一系列精心挑选的实战案例&#xff0c;引导读者从基础概念到高级技术&#xff0c;逐步深入学习数字图像处理的各个方面。 专栏目录 数字图像处理 第一…

离线+树状数组,ABC253 F - Operations on a Matrix

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 F - Operations on a Matrix 二、解题报告 1、思路分析 我们通过差分树状数组&#xff0c;可以轻松解决操作1 操作3我们也可以通过树状数组来获取对应列的值 关键是操作2会对操作3造成影响 所以我们先对…

你了解堆和栈的由来吗?

人们提出内存中堆和栈这两个概念&#xff0c;肯定是要解决当时所遇到的问题&#xff0c;不会为了提出概念而提概念。堆和栈都是为了解决一些问题而发展出来的结果&#xff0c;并没有任何的高深之处。 要搞懂堆和栈的概念是如何来的&#xff0c;就需要从计算机诞生时说起了。 …

UE中的运行时Mesh - 学习笔记

UE中的运行时Mesh Runtime Mesh 广泛应用于仿真、游戏及医疗等相关应用领域。 运行时Mesh可以摆脱UE编辑器的依赖&#xff0c;独立开发相对独立的应用程序。 应用示例 地质领域&#xff1a; 模型编辑修改&#xff1a;膨胀 导入、材质设置、补洞及简化&#xff1a; mar…

【Java】韩顺平Java学习笔记 第23章 反射

文章目录 需求和快速入门反射原理反射相关主要类反射的优点和缺点及其优化Class类Class类常用方法获取Class类对象的六种方式有Class对象的类型 类加载动态加载和静态加载类加载时机类加载流程图类加载五个阶段加载阶段连接阶段-验证连接阶段-准备连接阶段-解析总结Initializat…

【Bug记录】operator->返回类型错误导致operator->调用不了

项目场景&#xff1a; 模拟list&#xff0c;出现operator->调用不了的情况&#xff0c;这是什么情况呢&#xff1f;&#xff1f;&#xff1f; 问题描述 这里我是明确写了operator->函数的&#xff1a; 但是却有下面报错&#xff1a; 原因分析&#xff1a; 这里有…

python判断和循环语句

python判断语句 1、单个条件判断 if 条件:满足条件要做的事情1满足条件要做的事情2 else:不满足条件要做的事情3不满足条件要做的事情2 2、多个条件判断&#xff08;满足条件1就不会判断条件2&#xff09; else可以省略不写 if 条件1:满足条件1要做的事情a满足条件1要做的事…

JavaEE从入门到起飞 (三) ~AOP

晚上好&#xff0c;愿这深深的夜色给你带来安宁&#xff0c;让温馨的夜晚抚平你一天的疲惫&#xff0c;美好的梦想在这个寂静的夜晚悄悄成长。 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 了解面向切面编程&#xff08;AOP&#xf…

牛客网每日刷题之 HJ99.自守数(C++)

在不断学习的过程中也不能忘记了基础知识的巩固&#xff0c;在学习新的知识后要学会去举一反三&#xff0c;前不久我刚刚了解了一些关于 string 类的知识&#xff0c;对牛客网的 自守数 有了新的解题思路&#xff0c;让我们一起看看这道题吧 思路解析 a. 整数方法 1. 首先我们知…

C++(2):λ表达式,类函数重载

λ表达式 [&]捕获前面所有 [i] j闭包函数内访问不了&#xff0c;在函数内i只能用不能改 [&I,j] i在函数内可改j不行 未捕获可以充当函数指针使用 new,delete和malloc,free区别 new会调构造函数 malloc不会&#xff0c;delete会调析构函数free不会malloc调用需要强转…

从混沌到秩序:一本书教你掌握互联网内容审核与信息安全的密钥

随着互联网技术的迅猛发展&#xff0c;视频、图片、文字等多媒体内容以前所未有的速度在全球范围内传播与分享&#xff0c;极大地丰富了人们的信息获取渠道和娱乐生活方式。然而&#xff0c;这一繁荣景象背后&#xff0c;也隐藏着内容安全、版权侵犯、虚假信息传播、不良内容泛…

公司邮箱办理流程复杂吗?三步快速开通公司邮箱

对于许多企业而言, 开通公司邮箱是一项重要的基础建设工作。但是, 很多企业主可能会担心这一过程是否过于繁琐。实际上, 只需要注册、设置域名和邮箱, 以及开始使用这三个步骤。本文将为您介绍如何通过Zoho邮箱快速完成公司邮箱的开通。 Zoho邮箱因其卓越的服务质量和可靠性, …

数据传输为什么占用空间变大了

最近在做数据备份&#xff0c;很尴尬的是&#xff0c;不到3T的数据&#xff0c;用一个4T的硬盘拷贝中转到存储服务器上发现放不下。特别是以小文件为主的标注文件的文件夹&#xff0c;几十M几百M变成几十G或者100多G的空间占用。下面是几张图。 ​ ​ ​ 服务器之间拷贝占用空间…

Linux初启征程指南:攻克常见系统指令与权限初理解

有时候觉得&#xff0c;电脑就像一个高贵冷艳的妹纸。 400&#xff0c;是她冷冰冰地说&#xff1a;“我听不懂你在说什么”&#xff1b; 401&#xff0c;是她无情地转身&#xff1a;“我不认识你&#xff0c;别说那些奇怪的话”&#xff1b; 403&#xff0c;是她残酷的拒绝&…

Mysql-窗口函数二

文章目录 1. 前百分之N的问题 排名 row_number1.1 需求1.2 准备工作1.3 分析1.4 实现 2. 前百分之N的问题 ntile2.1 介绍2.2 语法2.2.1 示例2.2.2 结果示例2.2.3 注意事项 2.3 需求2.4 分析2.5 实现 3. 前百分之N的问题 百分比 PERCENT_RANK3.1 语法3.1.1 示例3.1.2 注意事项 3…

函数的练习

1.判断一个数是否为素数 代码如下&#xff1a; #include <stdio.h> int jum(int num) {int i 0;for (i 2;i < num;i){if (num % i 0){return 0;break;}}return 1; }void Jum1(int (*pf)(int)) {int num 0;printf("请输入操作数&#xff1a;");scanf_s…

声压、声强、响度之间的区别与联系

声压、声强、响度是声学中描述声音特性的重要概念&#xff0c;它们之间有一定的关系&#xff0c;但分别反映了不同的物理或感知特性。 1、声压&#xff08;Sound Pressure&#xff09; 声压是声波传播时在介质中引起的压力波动&#xff0c;通常以帕斯卡&#xff08;Pa&#x…

Windows电脑下载安装Kodcloud可道云结合内网穿透秒变私有网盘

文章目录 1.前言2. Kodcloud网站搭建2.1. Kodcloud下载和安装2.2 Kodcloud网页测试 3. cpolar内网穿透的安装和注册4. 本地网页发布4.1 Cpolar云端设置4.2 Cpolar本地设置 5. 公网访问测试6.结语 1.前言 本文主要为大家介绍一款国人自研的在线Web文件管理器可道云&#xff0c;…

苹果手机数据被抹除还能恢复吗?这两个方法强烈推荐

苹果手机数据被抹除还能恢复吗&#xff1f;我们在使用苹果手机时&#xff0c;有时由于误操作、系统故障或升级失败等原因&#xff0c;导致手机照片、备忘录、视频、联系人等数据被意外抹除。 面对这类情况&#xff0c;我们应该怎么办&#xff1f;下面牛小编给大家的分享2个方法…