实验4 使用Nacos实现服务的注册与发现

实验4 使用Nacos实现服务的注册与发现

目的与要求

了解 Nacos 的概念、注册中心是什么、掌握 Nacos Server 的部署、Nacos Client 的搭建。

实验内容

2.1 了解 Nacos 的概念。

2.2 注册中心是什么。

2.3 掌握 Nacos Server 的部署。

2.4 Nacos Client 的搭建。

实验指导

3.1 什么是 Nacos?

(1)官方解释

一个更易于构建云原生应用的动态服务发现(Nacos Discovery)、服务配置(Nacos Config)和服务管理平台。其实就是注册中心 + 配置中心 + 服务管理平台。

(2)上节课说到:将被调用服务地址维护在调用端的代码中,会导致非常多的问题,维护就是最大的问题。在非常错综复杂的服务调用关系里,服务的治理是迫切需要解决的。

为了解决上述问题,我们也曾尝试过几种方案,我们一起来了解一下注册中心的演变过程以及设计思想:

  • 方案1:将服务的调用地址维护在数据库中,每次需要调用的时候需要先去请求数据库,获取调用服务的 URL,然后再组装 URL 调用。在高并发场景下,性能就存在很大问题,每次请求都要去查询一下数据。当然,可以通过缓存解决这个问题。还有个更重要的问题,如果我现在要对 Archive 模块进行水平扩展,也就是会同时部署多个 Archive 服务,也就是说数据库中,Name=Archive 的数据会有多条,那必然需要进行负载均衡。我们可以先吧 Archive 服务的列表从数据库拉取到本地,然后在本地做负载均衡处理,这属于客户端负载均衡,这是非常麻烦的一件事。
  • 方案2:将服务列表维护在 Nginx,对运维人员影响很大,每次发生变动都要修改 Nginx 配置文件,并且人为修改配置文件非常容易出错。
  • 方案3:注册中心。
    从注册到获取接口,都不需要我们自己做,注册中心已经帮我们实现好了。这其实和数据库维护注册信息比较类似,只是这里很多工作都是注册中心帮我们做了。但是这里还存在一个问题,如果说 Archive 服务部署了多个, 其中一个 Archive 服务发生了宕机,那我们在 Engineer 服务中调用的时候,依然还会从注册中心获取到已经宕机的 Archive 服务。所以我们对注册中心进一步完善。引入了心跳机制。
    每个服务会在本地维护一个定时任务,每五秒会像注册中心发送一条心跳,如果超过 5s 没有接收到心跳,注册中心便会将该服务状态更新为宕机 ,如果超过 30s 没有发送心跳,该服务便会从注册中心剔除掉 ,变为不可用。
    • 注销接口: 服务停止的时候,也会调用注销接口,将该服务在注册中心注销。

并且定时任务还会不断实时获取最新的注册表缓存在本地,保证调用的服务都是处于健康 的状态。还实现了客户端的负载均衡器

(3)Nacos Discovery 核心功能

Nacos Discovery 官方文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Nacos-discovery

  • 服务注册: Nacos Client 会通过发送 REST 请求的方式向 Nacos Server 注册自己的服务,提供自身的元数据,比如 ip 地址、端口等信息。Nacos Server 接收到注册请求后,就会把这些元数据信息存储在一个双层的内存 Map 中。
  • 服务心跳: 在服务注册后,Nacos Client 会维护一个定时心跳来持续通知 Nacos Server,说明服务一直处于可用状态,防止被剔除。默认 5s 发送一次心跳。
  • 服务同步: Nacos Server 集群之间会互相同步服务实例,用来保证服务信息的一致性。 leader raft
  • 服务发现: 服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个 REST 请求给 Nacos Server,获取上面 注册的服务清单,并且缓存在 Nacos Client 本地,同时会在 Nacos Client 本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存。
  • 服务健康检查: Nacos Server 会开启一个定时任务用来检查注册服务实例的健康情况,对于超过 15s 没有收到客户端心跳 的实例会将它的 healthy 属性置为 false (客户端服务发现时不会发现),如果某个实例超过 30 秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册。
3.2 Nacos Server 部署

(1)Nacos Server 的源码,地址 https://github.com/alibaba/nacos/

(2)下载安装包,地址 https://github.com/alibaba/nacos/releases

版本很多,注意一下下载的版本,看一下之前的 Spring Cloud Alibaba 的版本对应关系,按照之前的配置,我们应该选择 Nacos Version 1.4.1 版本。点击 Assets,选择 .zip 文件类型下载。

(3)修改为单机运行模式。

默认是集群运行模式,我们本地运行就单机模式运行即可。修改方式如下:

右键, \nacos-server-1.4.1\bin\startup.cmd -> 编辑,打开后将 set MODE = "cluster" 修改为 set MODE = "standalone",保存。

(4)查看 application.properties 文件。

如果不配置数据源,默认就是存在内存中。如果我么你想让数据源持久化,可以配置数据库连接地址。

接下来我们就配置一下数据源:

  • 使用 nacos-mysql.sql 文件初始化一个数据库,我们起名为 nacos
  • 修改 application.properties 文件,将刚刚新建的 nacos 数据库配置为数据源。
properties 复制代码
### If use MySQL as datasource:
spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123456

双击 startup.cmd 启动 nacos,显示 nacos is starting with standalone 证明正在以单机模式启动 nacos。

启动成功好,浏览器地址栏输入 http://localhost:8848/nacos 进入 nacos 管理平台。

此网站初始账号nacos,初始密码nacos

注意: nacos 的路径一定不要出现中文,否则有可能报错。

3.3 在微服务中添加服务注册发现

(1)给 Engineer、Archive、Worker 三个模块添加 Nacos 的依赖,pom.xml 文件如下:

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

注意: 为什么没有指定版本号,我们需要在父工程中加一个依赖,用来做版本管理,否则会导致多个子工程中版本的混乱。在父工程的 pom 文件中添加下面依赖:

xml 复制代码
<dependencyManagement>
    <dependencies>
        <!-- Spring Cloud Alibaba 版本管理 -->
        <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>

这样做的目的就是,必须在子工程中显示添加依赖,jar 包才能被继承。

(2)修改配置文件 application.yml,以 Engineer 模块为例

yaml 复制代码
# engineer模块
server:
  port: 8081

spring:
  application:
    name: engineer-service   # 服务名称(Nacos 中显示的名字)
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        username: nacos
        password: nacos
        namespace: public    # 默认就是 public,不写也可以
  • name:项目的名字,nacos 会把这个名字作为服务名称
  • server-addr:nacos 的地址
  • namespace:命名空间

另外两个文件同理

yaml 复制代码
# archive模块
server:
  port: 8082

spring:
  application:
    name: archive-service
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        username: nacos
        password: nacos
        namespace: public
yaml 复制代码
# worker模块
server:
  port: 8083

spring:
  application:
    name: worker-service
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        username: nacos
        password: nacos
        namespace: public

(3)启动 Engineer 服务,控制台会打印如下日志,代表服务注册成功。

先确保nacos正在运行中

运行EngineerApplication.java

提示:nacos registry, DEFAULT_GROUP engineer-service 192.168.1.108:8081 register finished 注册成功,之后打开 nacos 控制台,打开服务管理-服务列表,就会发现 engineer-service 已经被注册进来了。

同理,再把其他两个模块注册进来。

(4)将 Engineer 服务停掉,观看 nacos 服务列表的变化。从服务列表消失了,这就是调用了 Nacos 的注销接口。

(5)服务之间的调用

engineer模块下的pom.xml里面加上:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

EngineerController.java

java 复制代码
package com.etoak.java.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/engineer")
public class EngineerController {

    @Autowired
    RestTemplate restTemplate;

    @RequestMapping("/add")
    public String add(){
        System.out.println("添加工程基础信息..");

        // 调用 Archive 模块接口(上传文件)
//        String result = restTemplate.getForObject(
//                "http://localhost:8082/archive/upload",
//                String.class
//        );
        String result = restTemplate.getForObject(
                "http://archive-service/archive/upload",
                String.class
        );

        System.out.println("工程文件上传结果:" + result);
        System.out.println("工程添加成功!");
        return "工程添加成功!";
    }
}

ArchiveController.java

java 复制代码
//package com.etoak.java.controller;
//
//import org.springframework.web.bind.annotation.RequestMapping;
//import org.springframework.web.bind.annotation.RestController;
//
//@RestController
//@RequestMapping("/archive")
//public class ArchiveController {
//
//    @RequestMapping("/add")
//    public String add(){
//        System.out.println("工程添加成功!");
//        return "工程添加成功!";
//    }
//}
package com.etoak.java.controller;

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

//@RestController
//@RequestMapping("/archive")
//public class ArchiveController {
//
//    @RequestMapping("/upload")
//    public String upload(){
//        System.out.println("工程添加成功!");
//        return "工程添加成功!";
//    }
//}
@RestController
@RequestMapping("/archive")
public class ArchiveController {

    @RequestMapping("/upload")
    public String upload() {
        System.out.println("Archive:正在上传工程文件...");
        return "上传成功!";
    }
}

ArchiveApplication:

java 复制代码
package com.etoak.java;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ArchiveApplication {

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

RestTemplateConfig:

java 复制代码
// engineer模块
package com.etoak.java.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced   // ★★★ 核心,启用 Nacos 服务名解析
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
  1. 先启动 Nacos(startup.cmd,确保 8848 可以访问)
  2. 启动 ArchiveApplication
  3. 重新启动 EngineerApplication(因为改了 RestTemplateConfig)

调用 http://localhost:8081/engineer/add

打印台输出:

3.4 Nacos 默认的负载均衡机制:轮询

(1)运行三个 Archive 服务,比较简便的方式,在 IDEA 的 Services 面板,右键 ArchiveApplication,选择 Copy Configuration,打开面板。

修改 Name:ArchiveApplication8072

修改 Override configuration properties,添加 server.port 修改端口号为 8072,如果没有这个面板,点击 Modify options 把对应的按钮勾选上。

保存之后, 先启动 8082 那个 ArchiveApplication ,再启动rchiveApplication8072,观察 Nacos 管理面板,会发现 Archive-service 的健康实例数为 2。

(2)轮询的机制:

修改 ArchiveController,代码如下:

java 复制代码
package com.etoak.java.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/archive")
public class ArchiveController {

    @Value("${server.port}")
    String port;

    @RequestMapping("/upload")
    public String upload(){
        System.out.println("端口号为:" + port + "的服务开始运行,项目文件上传成功!");
        return "项目文件上传成功!";
    }
}

重启服务:

  • Nacos(不关一直开着)
  • Archive 的两个实例:
  • ArchiveApplication(8082)
  • ArchiveApplication8072(8072)
  • EngineerApplication(8081)

再调用http://localhost:8081/engineer/add

并观察效果。会发现端口号在 8072 和 8082 之间来回切换。

3.5 服务的提供者和消费者

所有的服务我们都成为 Nacos 客户端服务,但是客户端服务又可以分为服务消费者(Consumer)服务提供者(Provider)。调用者就是服务消费者,被调用这就是服务提供者。

3.6 扩展知识:

(1)保护阈值:设置 0-1 之间的值,比如 0.5。

(2)永久实例:就算是服务宕机了,Nacos 中也不会被删除该实例。

实例默认都是临时实例,可以通过修改 application.yml。

yaml 复制代码
server:
  port: 8081
spring:
  application:
    name: engineer-service
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        username: nacos
        password: nacos
        namespace: public
        # ephemeral: true  # 默认是临时实例
        ephemeral: false   # false 代表非临时实例(永久实例)

当服务宕机,Nacos 不会删除该实例,而是将该实例的健康状态标识为不健康,所以同一个服务就可能会存在两种不同健康状态的实例:健康的和不健康的。

雪崩保护:健康实例数/总实例数 < 保护阈值 时触发,触发之后,会将不健康的实例也提供给消费者使用,以防止洪峰流量到来时,无法支撑的问题。比如现在有两个实例,分别可以承受 10W 并发量,此时其中一台宕机,并且洪峰流量达到 20W,为了保护健康状态的实例,雪崩保护机制会将非健康的实例也拿出来提供服务,分担并发量,以防止压力太大,将健康的实例也崩溃掉。

雪崩就是:所有的实例都崩溃掉。

相关推荐
DemonAvenger16 分钟前
Redis缓存穿透、击穿与雪崩:从问题剖析到实战解决方案
数据库·redis·性能优化
whn197718 分钟前
达梦数据库的整体负载变化查看
java·开发语言·数据库
倔强的石头_33 分钟前
性能飙升!KingbaseES V9R2C13 Windows安装与优化特性深度实测
数据库
梦里不知身是客1134 分钟前
Doris 中主键模型的读时合并模式
数据库·sql·linq
GanGuaGua39 分钟前
MySQL:复合查询
数据库·mysql·oracle
gugugu.40 分钟前
MySQL事务深度解析:从ACID到MVCC的实现原理
数据库·mysql·oracle
狮子也疯狂43 分钟前
【天翼AI-星辰智能体平台】| 基于Excel表实现智能问数助手智能体开发实战
人工智能·oracle·excel
DechinPhy44 分钟前
使用Python免费合并PDF文件
开发语言·数据库·python·mysql·pdf
杨了个杨89821 小时前
PostgreSQL 完全备份与还原
数据库·postgresql
爱吃KFC的大肥羊1 小时前
Redis持久化详解(一):RDB快照机制深度解析
数据库·redis·缓存