Gm-aaa's blog.

SpringCloudAlibaba之Nacos服务发现

2024/10/25
loading

什么是服务发现和配置中心?


在SpringCloud的架构中,一个核心的组件就是服务发现、配置中心。它在整个系统项目中主要负责:

  1. 服务发现:其他微服务模块向服务注册中心进行注册,由于这些模块有可能增加、减少或重新部署。所以为了方便管理,在微服务的项目中引入了服务注册中心的概念。
  2. 负载均衡:有了这个服务注册中心,借助于SpringCloud提供的loadbalance 等负载均衡工具,能够容易的实现服务的负载均衡,以实现整个系统的高可用
  3. 去中心化与解耦:服务注册中心可以独立于项目进行部署,实现了松耦合;并且可以由Nacos 配置中心的功能提高整个系统的可维护性和灵活性

Nacos是什么?


简单来说,Nacos 是服务发现和配置中心的一个实现。而诸如此类的,还有例如EurekaConsulZookeeper 等。

Nacos 是alibaba开源的,在国内经历了多次“双十一”、“618”活动的考验,稳定性也是比较可靠的。

它支持的服务包含:

  1. Kubernetes Service
  2. gRPC & Dubbo RPC Service
  3. Spring Cloud RESTful Service

功能的话比较啰嗦,详情可以查看官方文档

nacos单机部署实战


直接开始。首先,我们要知道,nacos 就是一个软件,独立于微服务项目而运行,而运行起来的nacos就相当于服务端,我们微服务中的各个模块就相当于客户端。所以,要运行服务端必备的三大步骤:

  1. 下载

    https://github.com/alibaba/nacos/releases 选择合适的版本进行下载

    注意:版本一定要和项目中SpringCloudSpringCloudAlibaba的版本相兼容,具体可以查看:

    https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明

    https://sca.aliyun.com/docs/2023/overview/version-explain/?spm=5176.29160081.0.0.74805a752XWBvK

    在这里我使用的版本环境如下:

    • 操作系统:Windows10 专业版
    • IDE: IDEA-2024.2.4
    • JDK:OracleJDK-17.0.12
    • SpringCloud:2022.0.4
    • SpringCloudAlibaba:2022.0.0.0
    • Nacos:2.2.3

    https://b2f8dfc.webp.li/2025-02-671b8e3e6470b.webp

  2. 安装和运行

    下载解压后,进入nacos/bin目录下,打开cmd命令提示符运行单机部署

    startup.cmd -m standalone

    显示这个界面就代表运行成功了

    https://b2f8dfc.webp.li/2025-02-671b900c847ec.webp

    nacos默认运行在8848端口,此时访问localhost:8848/nacos 如果能正常显示页面就代表部署服务成功了

    有两个注意的点,是我在部署时遇到的坑,详细的可以去官网查看文档说明

    https://b2f8dfc.webp.li/2025-02-671b914dd47a4.webp

    https://b2f8dfc.webp.li/2025-02-671b8c83c7a0f.webp

代码实战


nacos运行起来之后,不要关闭cmd窗口否则会停止服务的运行。此时,我们就可以在我们的微服务项目中给每个模块添加服务发现了,具体的父工程的pom.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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>love.mpc520</groupId>
<artifactId>demo_cloud</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>commons</module>
<module>cloud-provider-pay8001</module>
<module>cloud-consumer-order80</module>
<module>cloud-nacos-config-3333</module>
<module>cloud-sentinel-8899</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<hutool.version>5.8.22</hutool.version>
<lombok.version>1.18.26</lombok.version>
<druid.version>1.2.20</druid.version>
<mybatis.springboot.version>3.0.2</mybatis.springboot.version>
<mybatis-plus.version>3.5.4.1</mybatis-plus.version>
<mariadb.version>3.3.2</mariadb.version>
<swagger3.version>2.2.0</swagger3.version>
<fastjson2.version>2.0.40</fastjson2.version>
<spring.boot.test.version>3.1.5</spring.boot.test.version>
<spring.boot.version>3.1.5</spring.boot.version>
<spring.cloud.version>2022.0.4</spring.cloud.version>
<spring.cloud.alibaba.version>2022.0.0.0</spring.cloud.alibaba.version>
</properties>

<dependencyManagement>
<dependencies>
<!--springboot 3.2.0-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springCloud 2023.0.0-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--springCloud alibaba 2022.0.0.0-->
<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>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${spring.cloud.alibaba.version}</version>
</dependency>
<!--SpringBoot集成mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--mariadb数据库驱动 -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>${mariadb.version}</version>
</dependency>
<!--SpringBoot集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${swagger3.version}</version>
</dependency>
<!--huTool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<!-- spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.test.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

</project>

微服务模块的pom.xml需要引入

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

其次是微服务模块的application.yml

spring:
application:
name: service-test #指定服务名,会出现在nacos的服务管理面板,其他服务调用时也会用到这个名字
cloud:
nacos:
server-addr: localhost:8848 #指定nacos的运行地址
server:
address: 0.0.0.0
port: 8899 #指定微服务模块运行的端口

在主启动类上多添加一个@EnableDiscoveryClient 注解,代表开启服务发现的功能,允许nacos服务端发现当前微服务模块

其次,再构建一个用于测试的controller

@RestController
@Slf4j
public class OrderController {
@GetMapping("/test")
public String addOrder(){
return "OK";
}
}

最后,启动项目,能够在nacos的web界面中看到类似于这个样子,有服务出现就算成功。

https://b2f8dfc.webp.li/2025-02-671b96d758c41.webp

在这里还有一个实战中遇到的坑

  • 在系统多网卡环境下,有可能nacos会运行在虚拟网卡上,发现的服务也会发现到虚拟网卡的IP上。我具体的解决方案是:因为实在折腾不动这个,就直接在系统中禁用其他所有虚拟网卡。问题完美解决

服务A通过nacos调用服务B


要想实现通过服务名去调用某个服务,这时可以引入负载均衡组件loadbalance ,如果将刚才我们写好的测试controller的这个模块比喻成一个服务的提供者,那么接下来要做的就是就是造出来一个消费者。在新模块的 pom.xml 中添加(在之前的依赖基础上添加)

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

其次,我们使用Spring提供的RestTemplate这个API进行测试,新建一个config的配置类

@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced//赋予restTemplate负载均衡的能力
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

由于需要调用另一个服务,我们可以使用注入的方式,在application.yml中显式声明一下要调用的目标服务的服务名

server:
port: 81
address: 0.0.0.0
spring:
application:
name: service-consume
cloud:
nacos:
discovery:
server-addr: localhost:8848
main:
allow-bean-definition-overriding: true #允许bean覆盖
service-target:
servicename: service-test #要调用的目标服务名

构建一个用于测试的controller:

@RestController
@Slf4j
public class OrderController {
private final RestTemplate restTemplate;
private final LoadBalancerClient loadBalancerClient;

@Value("${service-target.servicename}") //注入配置中声明的服务名
private String serviceName;

public OrderController(RestTemplate restTemplate, LoadBalancerClient loadBalancerClient) {
this.restTemplate = restTemplate;
this.loadBalancerClient = loadBalancerClient;
}

@GetMapping("/test")
public String addOrder(PayDTO payDTO){
ServiceInstance instance = loadBalancerClient.choose(serviceName);
String url=instance.getUri()+"/test";
System.out.println("-----------------------"+url+"-------------------------");
return restTemplate.getForObject(url, String.class);
}

}

最后,同时启动两个微服务,访问消费者这一端的/test 路径(端口81),当返回OK 时,就代表大功告成。

另外,为了测试loadbalance负载均衡功能生效,我们可以复制一个生产者的模块,并在复制的模块的启动参数上加上-Dserver.port=<其他闲置端口> 或直接修改application.ymlserver.port 。同时启动两个服务的提供者和服务消费者。

多次访问,查看控制台输出的url 的端口变化。

负载均衡


负载均衡是一个很宽泛的概念,并不局限于微服务中。其他类似的:

  • Nginx代理多个相同服务时,为了使每个服务都能够正常运行,往往通过一定的策略使其分散在每个服务上
  • 还有比较形象的例子,当你想喝蜜雪冰城时,楼下的店爆满;而街对面就有另一家,这时往往会选择去到街对面的店

所谓均衡,并不是一定要均匀地使请求散步在每个服务上。有时候可能蜜雪冰城的A店比较大,能够承载更多的客户。B店比较小,承载的客户量较少。这时如果均匀地分布的话,对于B店来说就难以承载。

负载均衡策略


常见的负载均衡策略有

  • 轮询:这也是loadbalance默认的负载均衡策略,就是将请求挨个散步在每个服务上
  • 权重:顾名思义,权重较大的被分配得到的请求数就会更多
  • ip哈希:根据客户端和服务端的ip,经过哈希算法得到一个字符串,它会缓存在服务端上,对于相同的ip,不管访问多少次都是同一个服务的实例

其他地负载均衡算法还有很多,这里只介绍这几种。

CATALOG
  1. 1. 什么是服务发现和配置中心?
  2. 2. Nacos是什么?
  3. 3. nacos单机部署实战
  4. 4. 代码实战
    1. 4.1. 服务A通过nacos调用服务B
  5. 5. 负载均衡
    1. 5.1. 负载均衡策略