Nacos服务发现与配置

文章目录

Nacos服务发现与配置

一、服务发现基础

1.1 微服务架构概述

为适配企业业务快速迭代、提升研发效率、降低交付成本,软件架构逐步演进为微服务架构 :将单体系统按业务域拆分为若干独立微服务,各服务运行于独立进程,通过RESTful、RPC 等轻量级协议完成跨进程通信。微服务具备高内聚、低耦合特性,服务职责单一、可独立部署、扩展与维护。

微服务架构典型特征:

  1. 服务层按业务边界垂直拆分为独立微服务单元
  2. 单个微服务职责单一,聚焦特定业务能力
  3. 服务间采用RESTful、RPC等轻量级通信协议
  4. 天然支持前后端分离架构模式

微服务架构图

1.2 服务发现核心原理

1.2.1 传统静态配置调用方案

微服务架构中,服务消费方需获取服务提供方的网络位置(IP:Port) 使用restTemplate完成远程调用。传统方案采用硬编码/配置文件 静态指定地址,图例如下。

创建父工程/导入maven依赖

直接创建SpringBoot项目还是创建Maven项目都行,反正依赖给了

导入pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>  
    </parent> 
    <groupId>com</groupId>
    <artifactId>springBootNacos</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springBootNacos</name>

    <dependencies>
    	<!-- ServerA与ServerB都需要所以直接父工程引入 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>
ServerB模块创建(服务生产方)

继续创建子模块ServerB(选maven项目)

选择父项目,输入模块名

添加启动类

java 复制代码
package com;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ServerBBootstrap {
    public static void main(String[] args) {
        SpringApplication.run(ServerBBootstrap.class,args);
    }
}

创建提供服务的ServerBController

java 复制代码
package com.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ServerBController {

    @GetMapping("/service")
    public String server(){
        System.out.println("ServerB run");
        return "ServerB run";
    }
}

创建application.yml配置文件

yml 复制代码
server:
  port: 56010
ServerA模块创建(服务消费方)

继续创建子模块ServerA(选maven项目)

选择父项目,输入模块名


添加启动类

java 复制代码
package com;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ServerABootstrap {
    public static void main(String[] args) {
        SpringApplication.run(ServerABootstrap.class,args);
    }
}

创建application.yml配置文件

yml 复制代码
 server:
  port: 56020
myServer:
  url: 127.0.0.1:56010

创建消费服务的ServerAController

java 复制代码
package com.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class ServerAController {

    @Value("${myServer.url}")
    private String url;
    @GetMapping("service")
    public String service() {
        RestTemplate restTemplate = new RestTemplate();
        //调用服务
        String providerResult = restTemplate.getForObject("http://" + url + "/service", String.class);
        return "ServerA run | " + providerResult;
    }
    
}
请求测试

访问http://localhost:56020/service,输出以下内容:

问题

配置文件写死提供者地址无法应对动态扩缩容、IP 变更、多实例负载均衡

1.3.2 服务发现流程

上边的例子对于微服务应用而言行不通。首先,微服务可能是部署在云环境的,服务实例的网络位置或许是动态分配的。另外,每一个服务一般会有多个实例来做负载均衡,由于宕机或升级,服务实例网络地址会经常动态改变。再者,每一个服务也可能应对临时访问压力增加新的服务节点。正如 下图所示:

基于以上的问题,服务之间如何相互发现?服务如何管理?这就是服务发现的问题了。

服务发现就是服务消费方通过服务发现中心智能发现服务提供方,从而进行远程调用的过程。 如下图:

服务发现标准流程

  • (1)服务注册:服务启动时向注册中心上报 IP、端口、服务名
    在每个服务启动时会向服务发现中心 上报自己的网络位置。这样,在服务发现中心内部会形成一个服务注册表服务注册表是服务发现的核心部分,是包含所有服务实例的网络地址的数据库。
  • (2)服务同步:客户端定时拉取服务注册表并本地缓存
    服务发现客户端 会定期从服务发现中心 同步服务注册表 ,并缓存在客户端。
  • (3)服务发现:消费者按服务名获取可用实例列表
    当需要对某服务进行请求时,服务实例通过该注册表,定位目标服务网络地址。
  • (4)负载均衡:客户端从多实例中选择一个发起调用
    若目标服务存在多个网络地址,则使用负载均衡算法从多个服务实例中选择出一个,然后发出请求。
  • (5)健康检查:注册中心自动剔除不健康实例
    定期检查服务实例,将无法使用的服务实例删除

二、服务发现

2.1 Nacos基本使用

2.1.1 Nacos 简介

Nacos(Naming Configuration Service)是阿里开源的服务发现 + 配置管理一体化组件,专注于微服务的服务发现、配置管理、流量管理。

官方介绍是这样的:

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您实现动态服务发现、服务配置管理、服务及流量管理。 Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Nacos 是构建以"服务"为中心的现代应用架构的服务基础设施。

官网地址: https://nacos.io

2.1.2 服务发现产品对比

目前市面上用的比较多的服务发现中心有:Nacos、Eureka、Consul和Zookeeper。

对比项目 Nacos Eureka Consul Zookeeper
一致性协议 支持AP和CP模型 AP模型 CP模型 CP模型
健康检查 TCP/HTTP/MYSQL/Client Beat Client Beat TCP/HTTP/gRPC/Cmd Keep Alive
负载均衡策略 权重/metadata/Selector Ribbon Fabio -
雪崩保护
自动注销实例 支持 支持 不支持 支持
访问协议 HTTP/DNS HTTP HTTP/DNS TCP
监听支持 支持 支持 支持 支持
多数据中心 支持 支持 支持 不支持
跨注册中心同步 支持 不支持 支持 不支持
SpringCloud集成 支持 支持 支持 不支持
Dubbo集成 支持 不支持 不支持 支持
k8s集成 支持 不支持 支持 不支持

从上面对比可以了解到,Nacos作为服务发现中心,具备更多的功能支持项,且从长远来看Nacos在以后的版本会支持SpringCLoud+Kubernetes的组合,填补2者的鸿沟,在两套体系下可以采用同一套服务发现和配置管理的解决方案,这将大大的简化使用和维护的成本。另外,Nacos 计划实现 Service Mesh,也是未来微服务发展的趋势。

2.1.3 Nacos特性

  • 服务发现与服务健康检查

    Nacos使服务更容易注册,并通过DNS或HTTP接口发现其他服务,Nacos还提供服务的实时健康检查,以防止向不健康的主机或服务实例发送请求。

  • 动态配置管理

    动态配置服务允许您在所有环境中以集中和动态的方式管理所有服务的配置。Nacos消除了在更新配置时重新部署应用程序,这使配置的更改更加高效和灵活。

  • 动态DNS服务

    Nacos提供基于DNS 协议的服务发现能力,旨在支持异构语言的服务发现,支持将注册在Nacos上的服务以域名的方式暴露端点,让三方应用方便的查阅及发现。

  • 服务和元数据管理

    Nacos能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略。

2.1.4 Nacos安装启动

环境要求

Nacos 依赖 Java环境来运行。如果您是从代码开始构建并运行Nacos,还需要为此配置 MavenJDK环境,请确保是在对应版本环境中安装使用,具体可以在Nacos下载中查看

环境要求(Spring Boot 3.x 本文环境配套)

  • 64 位操作系统:Linux/Unix/Mac/Windows
  • JDK 17+(Spring Boot 3.x 强制要求)
  • Maven 3.6.0+
  • Nacos Server:3.1.1(本文使用版本)
下载

下载地址:https://nacos.io/download/nacos-server/

配置

1. Nacos 2.2.0 及以上版本的安全增强机制:为了防止未授权访问,强制要求配置 Token 签名密钥,若未配置则会在启动时阻塞并提示输入。

  • 打开 Nacos 安装目录下的 conf/application.properties 文件找到或添加以下配置项,自定义一个密钥字符串(大约在214行)
yml 复制代码
# Token 签名密钥(使用Base64加密)
nacos.core.auth.plugin.nacos.token.secret.key=YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=

提示:密钥可自定义,只要不为空即可,生产环境建议使用复杂随机字符串。

2. Nacos 2.3.0+ 版本新增的服务身份识别安全机制,用于防止集群间身份伪造,强制要求配置服务身份密钥,未配置则启动时阻塞。

  • 打开 Nacos 安装目录下的 conf/application.properties 文件
    找到或添加以下两个核心配置项(大约在207、208行)
yml 复制代码
# 服务身份标识(自定义字符串,建议唯一)
nacos.core.auth.server.identity.key=serverIdentity
# 服务身份密钥(自定义字符串,长度建议 ≥32 位)
nacos.core.auth.server.identity.value=your-custom-secret-key-123456789

提示:key 和 value 都不能为空,且集群内所有节点必须保持一致。

3. Nacos 3.x 版本外置 MySQL 必须配置

  • 安装数据库,版本要求:5.6.5+,mysql8以下
  • 初始化mysql数据库,新建数据库nacos_config,数据库初始化文件:${nacoshome}/conf/mysql-schema.sql
  • 修改${nacoshome}/conf/application.properties文件,增加支持mysql数据源配置(目前只支持
    mysql),添加mysql数据源的url、用户名和密码。
yml 复制代码
# 启用外置数据源
spring.datasource.platform=mysql
# 数据库数量(单机填1)
db.num=1
# 数据库连接地址(替换为你的 MySQL IP:端口/库名)
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
# MySQL 账号
db.user.0=root
# MySQL 密码
db.password.0=你的MySQL密码

大家可以直接复制

yml 复制代码
nacos.server.main.port=8848
management.metrics.export.elastic.enabled=false
management.metrics.export.influx.enabled=false
nacos.config.push.maxRetryTime=50
nacos.naming.empty-service.auto-clean=true
nacos.naming.empty-service.clean.initial-delay-ms=50000
nacos.naming.empty-service.clean.period-time-ms=30000
nacos.ai.mcp.registry.port=9080
nacos.server.contextPath=/nacos
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.max-days=30
server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i %{Request-Source}i
server.tomcat.basedir=file:.
server.error.include-message=ALWAYS
nacos.console.port=8080
nacos.console.contextPath=
nacos.console.remote.server.context-path=/nacos
nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-ui/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**
nacos.core.auth.system.type=nacos
nacos.core.auth.enabled=false
nacos.core.auth.admin.enabled=true
nacos.core.auth.console.enabled=true
nacos.core.auth.caching.enabled=true
# 服务身份标识(自定义字符串,建议唯一)
nacos.core.auth.server.identity.key=serverIdentity
# 服务身份密钥(自定义字符串,长度建议 ≥32 位)
nacos.core.auth.server.identity.value=your-custom-secret-key-123456789
nacos.core.auth.plugin.nacos.token.cache.enable=false
nacos.core.auth.plugin.nacos.token.expire.seconds=18000
# Token 签名密钥(长度建议 ≥32 位)
nacos.core.auth.plugin.nacos.token.secret.key=YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
nacos.istio.mcp.server.enabled=false
nacos.k8s.sync.enabled=false
# 启用外置数据源
spring.datasource.platform=mysql
# 数据库数量(单机填1)
db.num=1
# 数据库连接地址(替换为你的 MySQL IP:端口/库名)
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?serverTimezone=GMT%2B8&characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true
# MySQL 账号
db.user.0=root
# MySQL 密码
db.password.0=123456
# 强制单机模式(核心配置)
nacos.core.mode=standalone
# 禁用地址服务器查找(解决 jmenv.tbsite.net 解析问题)
nacos.core.cluster.lookup.type=none
# 关闭集群自动发现(可选,增强单机稳定性)
nacos.core.cluster.enabled=false
# 强制单机模式(核心配置)
nacos.core.mode=standalone
# 禁用地址服务器查找(解决 jmenv.tbsite.net 解析问题)
nacos.core.cluster.lookup.type=none
# 关闭集群自动发现(可选,增强单机稳定性)
nacos.core.cluster.enabled=false
启动

解压后进入 bin 目录

  • 单机启动命令:
    Linux/Mac:sh startup.sh -m standalone
    Windows:startup.cmd -m standalone(2.X后版本不建议双击启动)
  • 控制台地址:http://127.0.0.1:8080

2.2 RESTful服务发现

RESTful服务发现,就是在使用nacos注册服务后,还是使用RestTemplate进行远程调用

2.2.1 环境搭建

Spring Cloud是一套微服务开发框架集合,包括微服务开发的方方页面,Spring Cloud是一套微服务开发的标准, 集成了很多优秀的开源框架,比如有名的Netflix公司的众多项目。
Spring Cloud 项目地址:https://spring.io/projects/spring-cloud
本测试环境采用阿里开源的Spring Cloud Alibaba微服务开发框架,Spring Cloud Alibaba是阿里巴巴公司基于Spring Cloud标准实现一套微服务开发框架集合,它和Netflix一样都是Spring Cloud微服务开发实现方案。
Spring Cloud Alibaba项目地址:https://github.com/alibaba/spring-cloud-alibaba

创建父工程添加如下依赖(不创建也行,就是坐标在每个项目都粘一遍)

xml 复制代码
    <properties>
        <!-- 升级为 JDK 17 -->
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <!-- Spring Boot 3.x 稳定版本 -->
        <spring-boot.version>3.2.4</spring-boot.version>
        <!-- Spring Cloud 2023.x 适配 Spring Boot 3.x -->
        <spring-cloud.version>2023.0.1</spring-cloud.version>
        <!-- Spring Cloud Alibaba 适配版本 -->
        <spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
    </properties>

	<!-- 应该是根据实际的注册方与发现方导入对应的依赖,但是这直接父工程导入后面子模块就不用配置pom.xml了 -->
    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--  适配的 LoadBalancer -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>


    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot 依赖管理(3.x 版本) -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud 依赖管理(适配 Spring Boot 3.x) -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud Alibaba 依赖管理(适配 Spring Boot 3.x + JDK 17) -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

2.2.2 服务注册

服务注册,就是正常创建可以被访问的服务,然后注册到nacos中,这一步nacos自动帮我们做了,我们只需要按照如下流程进行代码书写与配置即可

  1. 创建子模块书写Web功能代码
  2. 修改application.yml配置文件配置nacos
  3. 在启动添加注解@EnableDiscoveryClient开启自动注册(可以省略)

创建子模块书写Web功能代码

已在父模块导入依赖,所以子模块直接创建书写代码配置即可

java 复制代码
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AController {
    @GetMapping("/test")
    public String test() {
        System.out.println("AController run");
        return "AController run";
    }
}

修改application.yml配置文件配置nacos

yml 复制代码
server:
  # 配置不同服务端口 只要能区分开就可以
  port: 56010
spring:
  application:
    # 服务名称 就是注册到nacos的名称 也是后面服务发现访问的服务名称
    name: nacos-a
# 以下配置在此案例中非必须(使用默认值即可)
  cloud:
    nacos:
      # 服务配置
      discovery:
        # Nacos 服务端地址
        server-addr: localhost:8848
        # 命名空间(命名空间配置)
        namespace: public
        # 分组(配置分组)
        group: DEFAULT_GROUP

在启动添加注解@EnableDiscoveryClient开启自动注册(可以省略)

在 Spring Cloud 2023.x(Spring Boot 3.x)版本中,@EnableDiscoveryClient 注解不再是必须的。

@SpringBootApplication 注解已经包含了组件扫描和自动配置

Spring Cloud Alibaba 2023.0.1.0 会自动配置 Nacos 服务发现

只要添加了 spring-cloud-starter-alibaba-nacos-discovery 依赖,服务发现功能就会自动启用

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
//@EnableDiscoveryClient
public class ARun {
    public static void main(String[] args) {
        SpringApplication.run(ARun.class, args);
    }
}

之后可以启动并请求看是否可以正常请求(前提),并打开nacos页面查看服务是否注册成功

2.2.3 服务发现

服务发现,就是当前模块中获取nacos中注册的服务信息,并通过远程调用执行的过程,我们只需要按照如下流程进行代码书写与配置即可

  1. 书写Nacos配置类
  2. 修改application.yml配置文件配置nacos
  3. 创建子模块书写Web功能代码,在对应位置获取服务并调用

书写Nacos配置类

已在父模块导入依赖,所以子模块直接创建书写代码配置即可

Spring Cloud Alibaba 2023.0.1.0 版本在 Spring Boot 3.x 环境下的一个已知兼容性 Bug。

这个版本的 NacosLoadBalancerClientConfiguration 配置类在非 Spring Boot 3.x 环境下,会因为 @Conditional 注解匹配逻辑异常,导致负载均衡上下文的 key(服务名)被置为 null,最终触发 ConcurrentHashMap 的空指针报错。

官方的标准解决方案是:创建一个自定义的 LoadBalancer 配置类,显式指定使用 Nacos 作为负载均衡器,绕过自动配置的逻辑限制。

java 复制代码
package com.config;

import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.loadbalancer.NacosLoadBalancer;
import jakarta.annotation.Resource;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.web.client.RestTemplate;

/**
 * 自定义负载均衡配置,解决 Spring Cloud Alibaba 2023.0.1.0 与 Nacos 3.X 的兼容性问题
 */
@Configuration
// 全局配置,对所有服务生效
@LoadBalancerClients(defaultConfiguration = NacosConfig.NacosLoadBalancerClientConfiguration.class)
public class NacosConfig {

    /**
     * 禁用默认的负载均衡器自动配置
     * 这里排除默认配置,避免冲突
     */
    @Configuration
    public static class NacosLoadBalancerClientConfiguration {
        @Resource
        LoadBalancerClientFactory loadBalancerClientFactory;

        /**
         * 配置 Nacos 负载均衡器
         * @param environment 环境变量
         * @return Nacos 负载均衡器
         */
        @Bean
        public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
                Environment environment,
                NacosDiscoveryProperties nacosDiscoveryProperties) {
            // 获取当前服务名称
            String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
            // 使用 Nacos 提供的负载均衡器
            return new NacosLoadBalancer(
                    loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
                    name,
                    nacosDiscoveryProperties);
        }

    }
        /**
         * 配置 RestTemplate 启用负载均衡
         * @return RestTemplate 实例
         */
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }

修改application.yml配置文件配置nacos

仅进行了服务发现的配置,如果当前服务发现的功能也需要被其他服务发现调用,可以参照服务注册配置进行操作

yml 复制代码
server:
  port: 56020
# 以下配置在此案例中非必须
spring:
    # 负载均衡器配置
    loadbalancer:
      # 启用负载均衡器
      enabled: true
      # 配置服务实例列表供应商
      service-instance-list-suppliers:
        discovery:
          enabled: true
# 禁用 Ribbon如果有(防止与 LoadBalancer 冲突)
ribbon:
  enabled: false

创建子模块书写Web功能代码,在对应位置获取服务并调用

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class Bcontroller {
    @Autowired
    private RestTemplate restTemplate;
     @GetMapping("/test")
    public String test() {
        /**
         * 使用 RestTemplate 调用远程服务
         * http://nacos-a 会通过 LoadBalancer 自动解析为具体的服务实例地址(这是Nacos3调用标准方式)
         */
        String forObject = restTemplate.getForObject("http://nacos-a/test", String.class);
        return "Bcontroller run  | " + forObject;
    }
}

2.2.4 负载均衡

负载均衡:将请求按策略分发至多个服务实例,提升并发能力与可用性。

服务端负载均衡:如 Nginx,由中间件统一分配流量。

客户端负载均衡:如 Ribbon,消费方本地维护实例列表,本地选择目标实例。

服务器端负载均衡:

在负载均衡器中维护一个可用的服务实例清单,当客户端请求来临时,负载均衡服务器按照某种配置好的规则(负载均衡算法)从可用服务实例清单中选取其一去处理客户端的请求。这就是服务端负载均衡。

例如Nginx,通过Nginx进行负载均衡,客户端发送请求至Nginx,Nginx通过负载均衡算法,在多个服务器之间选择一个进行访问。即在服务器端再进行负载均衡算法分配。

客户端服务负载均衡:

环境准备

先按照服务注册创建多个子模块并启动,注意修改端口与返回内容

权重负载均衡

我们之前自定义了NacosConfig在服务发现中配置的NacosLoadBalancer是使 Nacos提供的负载均衡器

默认是按照权重进行负载均衡

点击详情进入

根据端口设置对应服务的权重

最后去重新请求测试

轮询负载均衡

顾名思义就是按照服务的顺序进行请求

删除之前配置的Nacos默认负载均衡器,将如下代码进行替代(注意是替代)

java 复制代码
        /**
         * 轮询策略(按顺序选择实例)
         * @return 轮询负载均衡器
         */
        @Bean
        public ReactorLoadBalancer<ServiceInstance> roundRobinLoadBalancer(
                Environment environment) {
            String serviceName = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
            return new org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer(
                    loadBalancerClientFactory.getLazyProvider(serviceName, ServiceInstanceListSupplier.class),
                    serviceName);
        }
随机负载均衡

顾名思义随机进行服务请求

删除之前配置的Nacos默认负载均衡器,将如下代码进行替代(注意是替代)

java 复制代码
        /**
         * 随机策略(随机选择实例)
         * @return 随机负载均衡器
         */
        @Bean
        public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
                Environment environment) {
            String serviceName = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
            return new org.springframework.cloud.loadbalancer.core.RandomLoadBalancer(
                    loadBalancerClientFactory.getLazyProvider(serviceName, ServiceInstanceListSupplier.class),
                    serviceName);
        }

2.2.5 服务发现数据模型

Namespace 隔离设计

命名空间 (Namespace)用于进行租户粒度的隔离,Namespace的常用场景之一是不同环境的隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

命名空间管理

前面已经介绍过,命名空间(Namespace)是用于隔离多个环境的(如开发、测试、生产),而每个应用在不同环境的同一个配置(如数据库数据源)的值是不一样的。因此,我们应针对企业项目实际研发流程、环境进行规划。 如某软件公司拥有开发DEV、测试TEST、生产PROD三套环境,那么我们应该针对这三个环境分别建立三个namespace。


在编写程序时,指定的namespace参数一定要填写命名空间****ID,而不是名称(这里测试环境没有使用自定生成的空间ID,不建议这样)

在服务注册与服务发现对应位置添加如下配置,修改对应信息

yml 复制代码
spring:
  application:
    # 服务名称 就是注册名称
    name: nacos-a
  cloud:
    nacos:
      # 服务发现配置
      discovery:
        # Nacos 服务端地址(与配置中心一致)
        server-addr: localhost:8848
        # 命名空间(与配置中心一致)
        namespace: public
        # 分组(与配置中心一致)
        group: DEFAULT_GROUP

2.3 Dubbo服务发现

Dubbo是阿里巴巴公司开源的RPC框架,在国内有着非常大的用户群体,但是其微服务开发组件相对Spring Cloud来说并不那么完善。

Spring Cloud Alibaba微服务开发框架集成了Dubbo,可实现微服务对外暴露Dubbo协议的接口,Dubbo协议相比RESTful协议速度更快。

2.3.1 为什么使用Dubbo

Dubbo 是高性能 Java RPC 框架,相比 RESTful 具备更低延迟、更高吞吐量。Spring Cloud Alibaba 集成 Dubbo 与 Nacos,实现:

  • 对外:网关提供 RESTful 接口适配前端
  • 对内:微服务间采用 Dubbo RPC 通信
  • 注册 / 配置中心:统一使用 Nacos

Dubbo使用RPC协议 RPC:RPC是远程过程调用(Remote Procedure Call)的缩写形式,调用RPC远程方法就像调用本地方法一样,非常方便。

微服务采用Dubbo协议的系统架构图:

组件说明:

1、客户端:前端或外部系统

2、API网关:系统唯一入口,路由转发

3、Application-1 :应用1,前端提供Http接口,接收用户的交互请求

4、Service-1 :微服务1,提供业务逻辑处理服务

5、Service-2:微服务2,提供业务逻辑处理服务交互流程:

1、网关负责客户端请求的统一入口,路由转发,前端通过网关请求后端服务。

2、网关收到前端请求,转发请求给应用。

3、应用接收前端请求,调用微服务进行业务逻辑处理

4、微服务为应用提供业务逻辑处理的支撑,为应用提供Dubbo协议接口

优势分析:

此架构同时提供RESTful和Dubbo接口服务,应用层对前端提供RESTful接口,RESTful是互联网通用的轻量级交互协议,方便前端接入系统;微服务层向应用层提供Dubbo接口,Dubbo接口基于RPC通信协议速度更快。

2.3.2 环境搭建

父工程:仍然使用我们上面案例创建的项目

Application1:使用使用我们上面案例创建的消费者模块

Service1微服务:需要新建

Service2微服务:需要新建

api网关:本课程暂时不配置
Service微服务工程结构规范

  • Dubbo 服务采用API 与实现分离结构:
  • xxx-api:存放接口,供消费方依赖
  • xxx-server:存放接口实现,提供 Dubbo 服务

父工程导入dubbo依赖

xml 复制代码
        <!-- Apache Dubbo 官方 starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>3.2.10</version>
        </dependency>

        <!-- Dubbo Nacos 注册中心支持 -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-nacos-spring-boot-starter</artifactId>
            <version>3.2.10</version>
        </dependency>

2.3.3 service2 微服务开发

service2对外暴露dubbo协议的接口,考虑远程接口可能会被其它多个服务调用,这里将service2的接口单独抽取出api工程

service2微服务工程的结构
service2-api模块
java 复制代码
package com.api;

public interface Service2Api {
    String dubboService2();
}
service2-server模块

配置application.yml

yml 复制代码
server:
  port: 56030 #启动端口 命令行注入
dubbo:
  application:
    # 应用名称(必需),用于标识服务提供者/消费者
    name: dubbo-service2
    # 关闭 QOS,避免端口冲突
    qos-enable: false
  scan:
    #dubbo 服务扫描基准包
    base-packages: com.server
  protocol:
    #dubbo 协 议
    name: dubbo
    #dubbo 协议端口
    port: 20891
  registry:
    address: nacos://127.0.0.1:8848?namespace=public&group=DEFAULT_GROUP
  consumer:
    check: false #启动时就否检查依赖的服务

定义service

java 复制代码
package com.server;

import com.api.Service2Api;
import org.apache.dubbo.config.annotation.DubboService;

@DubboService
public class Service2ApiImpl implements Service2Api {
    @Override
    public String dubboService2() {
        return "dubboService2";
    }
}
application1调用service2

application.yml添加dubbo配置

yml 复制代码
# Dubbo 配置
dubbo:
  application:
    # 应用名称(必需),用于标识服务提供者/消费者
    name: B
    # 关闭 QOS,避免端口冲突
    qos-enable: false
  protocol:
    #dubbo 协 议
    name: dubbo
    # 不启动协议端口(作为消费者不需要暴露端口)
    port: -1
  registry:
    # 注册中心地址,使用 Nacos
    address: nacos://127.0.0.1:8848?namespace=public&group=DEFAULT_GROUP
  consumer:
    # 启动时不检查服务提供者是否存在
    check: false
    # 调用超时时间(毫秒)
    timeout: 3000

在controller中添加dubbo调用

java 复制代码
    /**
     * Dubbo 服务消费者引用
     * 通过 @DubboReference 注解注入远程 Dubbo 服务提供者 Service2Api 的代理对象
     */
    @DubboReference
    private Service2Api service2Api;

    /**
     * 处理 /service2 请求的控制器方法
     * 通过 Dubbo RPC 远程调用 Service2 服务提供者的 dubboService2 方法
     * @return 返回包含本地标识和远程服务调用结果的字符串
     */
    @GetMapping(value = "/service2")
    public String service2() {
        // 远程调用 service2
        String providerResult = service2Api.dubboService2();
        return "Bcontroller run  | " + providerResult;
    }

2.3.4 service1微服务开发

service1采用和service2相同的工程结构。

实现service1对外暴露dubbo接口,并用实现service1调用service2

service1-api模块
java 复制代码
package com.api;

public interface Service1Api {
    String dubboService1();
}
service1-server模块调用service2


配置application.yml

yml 复制代码
server:
  port: 56031 #启动端口 命令行注入
dubbo:
  application:
    # 应用名称(必需),用于标识服务提供者/消费者
    name: dubbo-service1
    # 关闭 QOS,避免端口冲突
    qos-enable: false
  scan:
    #dubbo 服务扫描基准包
    base-packages: com.server
  protocol:
    #dubbo 协 议
    name: dubbo
    #dubbo 协议端口
    port: 20891
  registry:
    address: nacos://127.0.0.1:8848?namespace=public&group=DEFAULT_GROUP
  consumer:
    check: false #启动时就否检查依赖的服务

定义service

java 复制代码
package com.server;

import com.api.Service1Api;
import com.api.Service2Api;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;

@DubboService
public class Service1ApiImpl implements Service1Api {

        @DubboReference
        Service2Api service2Api;

        public String dubboService1() {
            return "dubboService1 | "+service2Api.dubboService2();
        }
}
application1调用service1

application1的application.yml中dubbo配置无需修改

在controller中添加dubbo调用

java 复制代码
    @DubboReference
    private Service1Api service1Api;

    @GetMapping(value = "/service1")
    public String service1() {
        //远程调用service1
        String providerResult = service1Api.dubboService1();
        return "Bcontroller run  | " + providerResult;
    }

2.4 配置管理

2.4.1 配置中心

什么是配置

应用程序在启动和运行的时候往往需要读取一些配置信息,配置基本上伴随着应用程序的整个生命周期,比如:数据库连接参数、启动参数等。

配置主要有以下几个特点:

  • 配置是应用运行的只读外部参数,特征
  • 独立于程序,可外部修改
  • 贯穿应用全生命周期(启动 / 运行)
  • 支持多加载方式(文件、环境变量、DB、配置中心)
  • 不同环境 / 集群需差异化配置
什么是配置中心

在微服务架构中,当系统从一个单体应用,被拆分成分布式系统上一个个服务节点后,配置文件也必须跟着迁移(分割),这样配置就分散了,不仅如此,分散中还包含着冗余,如下图:

下图显示了配置中心的功能,配置中心将配置从各应用中剥离出来,对配置进行统一管理,应用自身不需要自己去管理配置。

配置中心的服务流程如下:

1、用户在配置中心更新配置信息。

2、服务A和服务B及时得到配置更新通知,从配置中心获取配置。

主流配置中心对比

目前市面上用的比较多的配置中心有:Spring Cloud Config、Apollo、Nacos和Disconf等。由于Disconf不再维护,下面主要对比一下Spring Cloud Config、Apollo和Nacos。

对比项目 Spring Cloud Config Apollo Nacos
配置实时推送 支持(Spring Cloud Bus) 支持(HTTP长轮询1s内) 支持(HTTP长轮询1s内)
版本管理 支持(Git) 支持 支持
配置回滚 支持(Git) 支持 支持
灰度发布 支持 支持 不支持
权限管理 支持(依赖Git) 支持 不支持
多集群 支持 支持 支持
多环境 支持 支持 支持
监听查询 支持 支持 支持
多语言 只支持Java 主流语言,提供了Open API 主流语言,提供了Open API
配置格式校验 不支持 支持 支持
单机读(QPS) 7(限流所致) 9000 15000
单击写(TPS) 5(限流所致) 1100 1800
3节点读(QPS) 21(限流所致) 27000 45000
3节点写(TPS) 5(限流所致) 3300 5600

2.4.2 Nacos发布配置

访问:nacos控制台 → 配置管理 → 配置列表→创建配置

输入配置信息

  • Data ID:common-config.yaml
  • Group:DEFAULT_GROUP
  • 格式:YAML
yml 复制代码
common:
  name: application1 config

2.4.3 Nacos配置获取

添加nacos-config与bootstrap的依赖

xml 复制代码
		<!-- 添加 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>

创建bootstrap.yml并添加配置

注意:要使用配置中心就要在bootstrap.yml中来配置,bootstrap.yml配置文件的加载顺序要比application.yml要优先。

yml 复制代码
spring:
  application:
    name: common-config
#  config:
#    import:
#      - nacos:common-name-config.yaml
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848 # 配置中心地址
        file-extension: yaml
        namespace: public
        group: DEFAULT_GROUP
        import-check:
          enabled: false

在controller中添加请求接口

java 复制代码
    /**
     * 从 Nacos 配置中心注入的 common.name 配置项
     * 使用 @Value 注解在字段级别直接注入
     */
    @Value("${common.name}")
    private String common_name;

    /**
     * 获取配置值(方式 1:@Value 注解注入)
     * 直接返回已注入的 common_name 字段值
     *
     * @return 返回配置项 common.name 的值
     */
    @GetMapping(value = "/configs")
    public String getvalue() {
        return common_name;
    }

    /**
     * Spring 应用上下文,用于动态获取配置属性
     */
    @Autowired
    private ConfigurableApplicationContext applicationContext;

    /**
     * 获取配置值(方式 2:通过 Environment 动态获取)
     * 从应用上下文中获取 Environment,然后动态读取配置属性
     * 支持配置热更新,适合需要实时获取最新配置的场景
     *
     * @return 返回配置项 common.name 的最新值
     */
    @GetMapping(value = "/configs_new")
    public String getConfigs() {
        return applicationContext.getEnvironment().getProperty("common.name");
    }

除了上述实现动态更新外,我们还可以在类上添加注解@RefreshScope来实现。

java 复制代码
@RefreshScope
@RestController
public class Bcontroller {
    //...
}

2.4.4 Nacos配置中心配置获取配置

在前面服务发现时在命名空间管理中我们已经分别介绍了namespace与group,但2.4.3中Nacos配置获取的配置中还是存在问题

读取默认的配置文件

默认会使用服务名与配置文件类型拼接读取配置

读取指定自定义名配置文件
读取多个自定义名配置文件
读取多个不同自定义配置文件

写在最后

本文使用的各工具版本较新,每个工具升级都可能出现新的问题,所以是一边写一遍验证的,所以书写不易,如有问题望大家可以理解。

Demo代码地址https://gitee.com/Amour123/nacos_demo.git

相关推荐
uzong2 小时前
为什么是你来做?面试中犀利问题的底层逻辑是什么和标准回答模版
后端·面试
chikaaa2 小时前
RabbitMQ 核心机制总结笔记
java·笔记·rabbitmq·java-rabbitmq
Sailing2 小时前
🚀AI 写代码越来越快,但我开始不敢上线了
前端·后端·面试
咕叽吧咔2 小时前
LeetBook乐扣题库 142. 环形链表 II
java·数据结构·leetcode·链表
Sylvia33.2 小时前
体育数据API实战:用火星数据实现NBA赛事实时比分与状态同步
java·linux·开发语言·前端·python
Coder-coco2 小时前
家政服务管理系统|基于springboot + vue家政服务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·家政服务管理系统
程序员鱼皮2 小时前
万字干货 | OpenClaw 进阶玩法大全:技能 / 多 Agent / 省钱 / 安全,50+ 实战技巧一次学会
前端·后端·ai编程
人道领域2 小时前
Day | 07 【苍穹外卖 :用户端添加购物车】
java·开发语言·数据库·后端·苍穹外卖
不像程序员的程序媛2 小时前
springboot对于@PathVariable自动解码问题
java·前端·javascript