【Java开发】Spring Cloud 06 :分布式配置管理-Nacos Config

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
在微服务架构中我们会使用一个分布式的“配置中心”来管理所有的配置文件和配置项本章节将介绍 Nacos 配置中心的特性以及这些特性在微服务体系中所发挥的作用。在 Spring Boot 应用中我们习惯于使用传统的配置管理方式将各种配置项都维护在 application.yml 或 application.properties 文件中。从完成业务逻辑的角度来看这样做是没问题的。但在微服务架构中我们可以采取一种更“优雅”的方式组织配置文件实现高效灵活的配置管理。

1 传统配置管理的弊端

先回顾下一下传统的配置管理途径都有哪些这些途径在使用上有哪些弊端。然后我们再来了解微服务架构下的“配置中心”是如何解决这些问题的。

我们通常可以采用四种途径在程序中指定配置项它们分别是硬编码、配置文件、环境 / 启动变量、数据库动态获取我们先来了解一下这四种配置管理方式是如何实现的。

  • 硬编码最简单粗暴的方式在 Bean 初始化的上下文中直接通过在代码中 hardcode 的方式指定配置信息

  • 配置文件使用 application 和 bootstrap 配置文件来设置配置项这是目前比较“优雅”常用的方式

  • 环境 / 启动变量通过操作系统的环境变量或者启动命令中的 -D 参数传入配置项

  • 数据库 / 缓存动态获取将配置项保存在数据库里每次执行一个 select 语句实现动态查询。

看似我们有了不少办法来实现配置管理但实际上以上的几种途径或多或少都有一些弊端。

从职责分离的角度来讲硬编码无法将“业务逻辑”与“配置管理”拆分开来。尽管硬编码实现起来最为简单它仍然是我最不推荐的一种方式。

从灵活性的角度来讲无论你用的是硬编码、配置文件还是环境 / 启动变量的方式只要你需要对配置项进行变更你就必须对代码 / 启动命令进行修改然后重新打包并部署应用无法做到在运行期灵活变更配置项。

数据库 / 缓存动态获取的方式和上面几种相比具备了一定的灵活性。但从版本控制的角度来看配置项也是一种“代码资源”采用数据库 / 缓存控制并不能很好地实现配置版本的控制和历史版本回滚。而面对高并发场景时如果我们采用数据库方案还要时刻关注 DB 的性能指标以免被流量打崩。

除此以外多格式支持和安全性也是需要考虑的因素。对于用户名密码之类的敏感数据如果明目张胆地放在代码库中那么将显著增加“删库跑路”事件的发生几率。

到这里你会发现传统的配置管理方式或多或少都存在着一些弊端。你也可能会问分布式配置中心可以解决这些问题吗答案是当然。接下来了解下分布式配置中心有哪些具体的作用。

2 分布式配置中心

在微服务的架构体系中我们会使用一个中心化的分布式配置中心作为配置文件的管理者。

在应用程序端我们只将一些必要的配置项添加到配置文件中如 application.yml 和 bootstrap.yml而大部分的配置项都被保存在配置中心集群里。客户端在启动的时候从配置中心获取所有的配置项用于各个组件的初始化。

以 Nacos Config 为例

高可用性微服务组件的高可用性是首要目标。配置中心并不是一个中心化的单点应用而是一个通过集群对外提供服务的组件。在一致性算法的基础上集群中各个节点之间会互相同步配置数据或者从统一数据源读取配置数据。即便个别节点挂掉也不影响整个集群的可用性

环境隔离特性Nacos 支持通过 Namespace 属性指定当前配置项所在的环境你可以为自己的应用系统创建开发环境、预发环境和生产环境不同环境之间的配置文件是相互隔离的

多格式支持Nacos 支持多种不同格式的配置内容你可以使用纯文本、JSON、XML、YAML 和 Properties 多种文件后缀

访问控制Nacos 实现了权限管理功能你可以在控制台创建用户账号和权限组限制某个账号可以访问哪些命名空间并配置账号的读写权限只读、只写、读写。通过这种方式你可以保障敏感信息如数据库用户名和密码的安全

职责分离配置项从 jar 包中抽离了出来修改配置项再也不需要重新编译打包应用程序了完美实现了配置项管理与业务代码之间的职责分离

版本控制和审计功能配置项也是一种代码而且配置 bug 往往比代码中的 bug 造成的影响更大。因此在微服务架构中我们需要确保配置中心具备完善的版本控制和审计功能。

从图中你可以看出通过 Nacos 的“历史版本”功能你可以查看任何一个配置文件的历史修改记录包括改动的时间和操作人。针对每一个改动记录我们可以查看这一版本的配置详情或者做线上配置项的回滚操作。

除了上面我们提到的功能以外Nacos 还可以支持多文件源读取以及运行期配置变更。尤其是动态变更推送更是微服务架构下不可或缺的配置管理能力。

Nacos 具备很高的灵活性你可以在项目中指定从多个 Nacos 配置文件中获取信息这些文件可以是不同名称、不同格式的配置文件。这个特性允许你对配置文件做更细致的“职责隔离”。比如你可以把 Redis 连接信息做成一个独立的配置文件让集群中的所有应用消费同一个文件来初始化 Redis Connection。

当配置项发生变化的时候服务端可以通过监听变更事件的方式从 Nacos 服务器获取到最新的配置信息。这个功能就是配置项动态更新它可以让你在不重启应用程序的前提下更新配置信息这在微服务系统中大有用途

列举几个配置项动态更新的使用场景帮助你理解它的作用

1.1 业务开关

动态配置的一个作用是通过业务开关控制功能的开启 / 关闭。比如在做主链路规划的时候我们经常需要在一些非关键服务上预留一个“人工降级”开关在业务运行期对特定业务做定向熔断。

对于一些大需求点的功能更新经常涉及到上下游多个微服务的改动但每个微服务的上线时间往往是不一样的。这时候我们就可以在代码中预留一个“业务开关”在当前服务上线之初开关处于关闭状态待所有上下游服务都完成了上线之后再通过开关开启新功能。如果出现异常情况还可以通过这个功能开关切换回老的执行逻辑。

1.2 业务规则更新

对于一些更新比较频繁的业务数据我们可以把这部分数据放到配置中心中。比如说在搭建新零售平台的商品中心的时候会将一些运营文案信息部署到配置中心。这样一来我们就可以根据运营活动随时更新资源位的布局、样式以及展位商品。

1.3 灰度发布验证

如果你即将发布新的配置项变更但是在应用到整个集群之前你想先挑几台服务器测试一下那么你可以使用 Nacos 的 Beta 发布功能将配置项定向推送到特定 IP 地址的 Client 机器完成线上测试。利用 Beta 发布 + 业务开关的组合你还可以在线上定向开启特定 IP 服务器的业务开关实现轻量级的灰度测试。

对于开发人员来说“动态属性推送”应该是我们在工作中最常用到的功能点了尤其在拥抱变化的互联网公司更是如此。因为互联网行业的需求变动非常频繁如何巧妙地利用配置中心的动态推送功能将“变化的需求部分”和“不变的代码部分”隔离开来是开发业务场景时需要着重考虑的。

3 集成 Nacos Config 实现配置项动态刷新

接下来将应用程序接入到 Nacos 获取配置项然后再来实现动态配置项刷新。本章节选择 coupon-customer-serv 作为改造目标因为 customer 服务的业务场景比较丰富便于我们来演示各个不同的场景和用法。

接入 Nacos 配置中心的第一步就是要添加 Nacos Config 和 Bootstrap 依赖项。

3.1 添加依赖项

我们打开 coupon-customer-serv 的 pom 文件在 pom 中添加以下两个依赖项。

<!-- 添加Nacos Config配置项 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- 读取bootstrap文件 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

第一个依赖项是 Nacos 配置中心的依赖包。尽管我们已经在 customer 服务中添加过了 Nacos 的依赖项但此依赖项非彼依赖项初学者很容易搞混。Nacos 既能用作配置管理也能用作服务注册如果你想要引入 Nacos 的服务发现功能需要添加的是 nacos-discovery 包而如果你想引入的是 Nacos 的配置管理功能则需要添加 nacos-config 包

第二个依赖项是为了让程序在启动时能够加载本地的 bootstrap 配置文件因为 Nacos 配置中心的连接信息需要配置在 bootstrap 文件而非 application.yml 文件中。在 Spring Cloud 2020.0.0 版本之后bootstrap 文件不会被自动加载你需要主动添加 spring-cloud-starter-bootstrap 依赖项来开启 bootstrap 的自动加载流程。

为什么集成 Nacos 配置中心必须用到 bootstrap 配置文件呢这就要说到 Nacos Config 在项目启动过程中的优先级了。

如果你在 Nacos 配置中心里存放了访问 MySQL 数据库的 URL、用户名和密码而这些数据库配置会被用于其它组件的初始化流程比如数据库连接池的创建。为了保证应用能够正常启动我们必须在其它组件初始化之前从 Nacos 读到所有配置项之后再将获取到的配置项用于后续的初始化流程。

因此在服务的启动阶段你需要通过某种途径将 Nacos 配置项加载的优先级设置为最高。

而在 Spring Boot 规范中bootstrap 文件通常被用于应用程序的上下文引导bootstrap.yml 文件的加载优先级是高于 application.yml 的。如果我们将 Nacos Config 的连接串和参数添加到 bootstrap 文件中就能确保程序在启动阶段优先执行 Nacos Config 远程配置项的读取任务。这就是我们必须将 Nacos Config 连接串配置在 bootstrap 中的原因。

依赖项添加完成之后我们就可以去配置 Nacos Config 的连接串了。

3.2 添加本地 Nacos Config 配置项

首先我们需要在 coupon-customer-impl 项目的 resource 文件夹中创建 bootstrap.yml 配置文件。接下来你需要在 bootstrap.yml 文件中添加一些 Nacos Config 配置项下边是常用的配置项

spring:
  # 必须把name属性从application.yml迁移过来否则无法动态刷新
  application:
    name: coupon-customer-serv
  cloud:
    nacos:
      config:
        # nacos config服务器的地址
        server-addr: localhost:8848
        file-extension: yml
        # prefix: 文件名前缀默认是spring.application.name
        # 如果没有指定命令空间则默认命令空间为PUBLIC
        namespace: dev
        # 如果没有配置Group则默认值为DEFAULT_GROUP
        group: DEFAULT_GROUP
        # 从Nacos读取配置项的超时时间
        timeout: 5000
        # 长轮询超时时间
        config-long-poll-timeout: 10000        
        # 轮询的重试时间
        config-retry-time: 2000
        # 长轮询最大重试次数
        max-retry: 3
        # 开启监听和自动刷新
        refresh-enabled: true
        # Nacos的扩展配置项数字越大优先级越高
        extension-configs:
          - dataId: redis-config.yml
            group: EXT_GROUP
            # 动态刷新
            refresh: true
          - dataId: rabbitmq-config.yml
            group: EXT_GROUP
            refresh: true

下面我就带你了解一下代码中的的配置项我把这些配置项分为了几大类我们分别来看一下。

文件定位配置项主要用于匹配 Nacos 服务器上的配置文件。

  • namespaceNacos Config 的 namespace 和 Nacos 服务发现阶段配置的 namespace 是同一个概念和用法。我们可以使用 namespace 做多租户multi-tenant隔离方案或者隔离不同环境。我指定了 namespace=dev应用程序只会去获取 dev 这个命名空间下的配置文件

  • group概念和用法与 Nacos 服务发现中的 group 相同如未指定则默认值为 DEFAULT_GROUP应用程序只会加载相同 group 下的配置文件

  • prefix需要加载的文件名前缀默认为当前应用的名称即 spring.application.name一般不需要特殊配置

  • file-extension需要加载的文件扩展名默认为 properties我改成了 yml。你还可以选择 xml、json、html 等格式。

超时和重试配置项

  • timeout从 Nacos 读取配置项的超时时间单位是 ms默认值 3000 毫秒

  • config-retry-time获取配置项失败的重试时间

  • config-long-poll-timeout长轮询超时时间单位为 ms

  • max-retry最大重试次数。

长轮询机制的工作原理当 Client 向 Nacos Config 服务端发起一个配置查询请求时服务端并不会立即返回查询结果而是会将这个请求 hold 一段时间。如果在这段时间内有配置项数据的变更那么服务端会触发变更事件客户端将会监听到该事件并获取相关配置变更如果这段时间内没有发生数据变更那么在这段“hold 时间”结束后服务端将释放请求。采用长轮询机制可以降低多次请求带来的网络开销并降低更新配置项的延迟。

通用配置

  • server-addrNacos Config 服务器地址

  • refresh-enabled: 是否开启监听远程配置项变更的事件默认为 true。

扩展配置

  • extension-configs如果你想要从多个配置文件中获取配置项那么你可以使用 extension-configs 配置多源读取策略。extension-configs 是一个 List 的结构每个节点都有 dataId、group 和 refresh 三个属性分别代表了读取的文件名、所属分组、是否支持动态刷新。

在实际的应用中我们经常需要将一个公共配置项分配给多个微服务使用比如多个服务共享同一份 Redis、RabbitMQ 中间件连接信息。这时我们就可以在 Nacos Config 中添加一个配置文件并通过 extension-configs 配置项将这个文件作为扩展配置源加到各个微服务中。这样一来我们就不需要在每个微服务中单独管理通用配置了。

3.3 添加配置文件到 Nacos Config Server

首先我们在本地启动 Nacos 服务器打开配置管理模块下的“配置列表”页面再切换到“开发环境”命名空间下即 dev 环境。

然后我们点击页面右上角的➕符号创建三个配置文件coupon-customer-serv.yml默认分组、redis-config.ymlEXT_GROUP 分组和 rabbitmq-config.ymlEXT_GROUP 分组。

注意添加配置时可能会出现报错大概率是因为
nacos 初始化sql与nacos版本不一致
nacos 2.1.0版本之后初始化数据库中config_info 和 his_config_info 表中新增了encrypted_data_key密钥字段
nacos.2.1.0 及之前数据库初始化脚本为nacos-mysql.sql2.2.0 之后重命名为mysql-schema.sql

接下来你就可以将原本配置在本地 application.yml 中的配置项转移到 Nacos Config 中了由于 Data ID 后缀是 yml所以在编辑配置项的时候你需要在页面上选择“YAML”作为配置格式。

以 coupon-customer-serv.yml 为例在新建配置的页面中指定了 Data ID 为 coupon-customer-serv.yml、Group 为默认分组 DEFAULT_GROUP、配置格式为 YAML。在“配置内容”输入框中将 spring.datasource 的配置项添加了进去。除此之外添加了一个特殊的业务属性disableCouponRequest:true待会儿你就会用到这个属性实现动态业务开关推送

填好配置项的内容之后你就可以点击“发布”按钮来创建配置文件了。redis-config.yml 和 rabbitmq-config.yml 两个配置文件将在后面的章节中用到我们目前还不需要向这两个文件中添加配置项。

一切配置妥当之后我们就可以去启动应用程序来验证集成效果了。为了测试应用程序能否正确读取远程配置项你可以打开 coupon-customer-impl 模块的 application.yml 文件将其中的 datasource 相关配置注释掉然后尝试重新启动服务。如果项目启动正常你将会在日志文件看到配置文件的订阅通知。

INFO c.a.n.client.config.impl.ClientWorker    : [fixed-localhost_8848-dev] [subscribe] coupon-customer-serv.yml+DEFAULT_GROUP+dev

INFO c.a.nacos.client.config.impl.CacheData   : [fixed-localhost_8848-dev] [add-listener] ok, tenant=dev, dataId=coupon-customer-serv.yml, group=DEFAULT_GROUP, cnt=1

// 省略其它配置文件的加载日志

接下来你可以尝试调用本地数据库的 CRUD 接口如果业务正常运作那么就说明你的程序可以从 Nacos Config 中获取到正确的数据库配置信息。

你可以使用同样的方法将一些配置项信息迁移到 Nacos Config 中。当你需要更改配置项的时候就不用每次都重新编译并发布应用了只需要改动 Nacos Config 中的配置即可。这样一来我们就实现了“配置管理”“业务逻辑”的职责分离。

别忘了前边 Nacos Config 中添加了一个 disableCouponRequest 配置项接下来我就用它做一个动态配置推送的场景控制用户领券功能的打开和关闭。

3.4 动态配置推送

首先我们打开 CouponCustomerController 类声明一个布尔值的变量 disableCoupon并使用 @Value 注解将 Nacos 配置中心里的 disableCouponRequest 属性注入进来。

@Value("${disableCouponRequest:false}")
private Boolean disableCoupon;

在上面的代码中我们给 disableCouponRequest 属性设置了一个默认值“false”这样做的目的是加一层容错机制。即便 Nacos Config 连接异常无法获取配置项应用程序也可以使用默认值完成启动加载。

然后我们找到用户领券接口 requestCoupon在其中添加一段业务逻辑根据 disableCoupon 属性的值控制是否发放优惠券如果值为“true”则暂停领券。

@PostMapping("requestCoupon")
public Coupon requestCoupon(@Valid @RequestBody RequestCoupon request) {
    if (disableCoupon) {
        log.info("暂停领取优惠券");
        return null;
    }
    return customerService.requestCoupon(request);
}

最后别忘了在 CouponCustomerController 类头上添加一个 RefreshScope 注解有了这个注解Nacos Config 中的属性变动就会动态同步到当前类的变量中。如果不添加 RefreshScope 注解即便应用程序监听到了外部属性变更那么类变量的值也不会被刷新。

@RefreshScope
public class CouponCustomerController {
}

到这里我们就完成了所有改造工作。你可以启动应用程序然后登录 Nacos 控制台并打开 coupon-customer-serv.yml 文件的编辑窗口将 disableCouponRequest 的值由 true 改为 false并调用 requestCoupon 服务查看接口逻辑的变化。

总结

本项目使用 Nacos Config 作为配置中心实现了配置项和业务逻辑的职责分离然后落地了一个动态属性推送的场景。

配置中心还有一个重要功能是“配置回滚”。如果你错误地修改了某些业务项引起了系统故障这时候你可以执行一段 rollback 操作将配置项改动退回到之前的某一个历史版本。在 Nacos 控制台的“配置管理 -> 历史版本”菜单中你可以查看某个配置项的历史修改记录并指定回滚的版本。

除此之外我们还可以在 Nacos 上查看某个文件的监听列表了解目前有多少实例监听了指定配置文件的动态改动事件。你可以点击“配置管理 -> 监听查询”来访问这个功能。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: SpringJava