微服务之Nacos

1 版本说明

官网地址:

https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

1.1 2021.x 分支

适配 SpringBoot 2.4, Spring Cloud 2021.x 版本及以上的Spring Cloud Alibaba 版本如下表(最新版本用*标记):

(注意,该分支 Spring Cloud Alibaba 版本命名方式进行了调整, 未来将对应 Spring Cloud 版本, 前三位为 Spring Cloud 版本,最后一位为扩展版本,比如适配 Spring Cloud 2021.0.1 版本对应的 Spring Cloud Alibaba 第一个版本为:2021.0.1.0,第个二版本为:2021.0.1.1,依此类推)

Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version
2021.0.5.0* Spring Cloud 2021.0.5 2.6.13
2021.0.4.0 Spring Cloud 2021.0.4 2.6.11
2021.0.1.0 Spring Cloud 2021.0.1 2.6.3
2021.1 Spring Cloud 2020.0.1 2.4.2

1.2 2.2.x 分支

适配 Spring Boot 为 2.x, Spring Cloud Hoxton 版本及以下的 Spring Cloud Alibaba 版本如下表(最新版本用*标记):

Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version
2.2.8.RELEASE* Spring Cloud Hoxton.SR12 2.3.12.RELEASE
2.2.7.RELEASE Spring Cloud Hoxton.SR12 2.3.12.RELEASE
2.2.6.RELEASE Spring Cloud Hoxton.SR9 2.3.2.RELEASE
2.1.4.RELEASE Spring Cloud Greenwich.SR6 2.1.13.RELEASE
2.2.1.RELEASE Spring Cloud Hoxton.SR3 2.2.5.RELEASE
2.2.0.RELEASE Spring Cloud Hoxton.RELEASE 2.2.X.RELEASE
2.1.2.RELEASE Spring Cloud Greenwich 2.1.X.RELEASE
2.0.4.RELEASE(停止维护,建议升级) Spring Cloud Finchley 2.0.X.RELEASE
1.5.1.RELEASE(停止维护,建议升级) Spring Cloud Edgware 1.5.X.RELEASE

1.3 组件版本关系

每个 Spring Cloud Alibaba 版本及其自身所适配的各组件对应版本如下表所示:

Spring Cloud Alibaba Version Sentinel Version Nacos Version RocketMQ Version Dubbo Version Seata Version
2.2.8.RELEASE 1.8.4 2.1.0 4.9.3 ~ 1.5.1
2021.0.1.0 1.8.3 1.4.2 4.9.2 ~ 1.4.2
2.2.7.RELEASE 1.8.1 2.0.3 4.6.1 2.7.13 1.3.0
2.2.6.RELEASE 1.8.1 1.4.2 4.4.0 2.7.8 1.3.0
2021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE 1.8.0 1.4.1 4.4.0 2.7.8 1.3.0
2.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE 1.8.0 1.3.3 4.4.0 2.7.8 1.3.0
2.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE 1.7.1 1.2.1 4.4.0 2.7.6 1.2.0
2.2.0.RELEASE 1.7.1 1.1.4 4.4.0 2.7.4.1 1.0.0
2.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE 1.7.0 1.1.4 4.4.0 2.7.3 0.9.0
2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE 1.6.3 1.1.1 4.4.0 2.7.3 0.7.1

2 理解服务发现

2.1 理解服务发现

2.1.1 测试环境

在微服务架构中,整个系统会按职责能力划分为多个服务,通过服务之间协作来实现业务目标。这样在我们的代码中免不了要进行服务间的远程调用,服务的消费方要调用服务的生产方,为了完成一次请求,消费方需要知道服务生产方的网络位置(IP地址和端口号)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AjaMeq7W-1693272596958)(assets\image-20210316120231655.png)]

我们通过Spring boot技术很容易实现:

1、创建alibaba_cloud父工程

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>cn.yunhe</groupId>
    <artifactId>alibaba_cloud</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
  <dependencyManagement>
        <dependencies>
            <!--SpringBoot-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.22</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

2、Service B(服务生产者)

创建服务提供者 order_server。

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">
    <parent>
        <artifactId>alibaba_cloud</artifactId>
        <groupId>cn.yunhe</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order_server</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

Service B是服务的生产方,暴露/getOrder服务地址,实现代码如下: 1、创建Controller

package cn.yunhe.controller;

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

@RestController
@RequestMapping("/order")
public class OrderController {
    
    

    @GetMapping("/getOrder")
    public String getOrder(){
    
    
        return "获取订单详情";
    }
}

2、创建启动类

package cn.yunhe;

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

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

配置文件:

创建application.yml,内容如下:

server:
  port: 9200

3、Service A(服务消费者)

创建consumer_server服务消费工程。

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">
    <parent>
        <artifactId>alibaba_cloud</artifactId>
        <groupId>cn.yunhe</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>consumer_server</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

实现代码:

1、创建controller

package cn.yunhe.controller;

import cn.yunhe.api.OrderApi;
import cn.yunhe.service.UserService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    
    

    @GetMapping("/getOrder")
    public String getOrder(){
    
    
        RestTemplate restTemplate = new RestTemplate();
        String url = "http://127.0.0.1:9200/order/getOrder";
        String orderStr = restTemplate.getForObject(url, String.class);
        return "Consumer::"+orderStr;
    }
}

2、创建启动类

package cn.yunhe;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

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

配置文件:

创建application.yml,内容如下:

server:
  port: 9100

访问http://localhost:9100/consumer/getOrder/,输出以下内容:

Consumer::获取订单详情

2.2.2 服务发现流程

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bjywHEa3-1693272596960)(assets\image-20210316120254867.png)]

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sYEho2Ri-1693272596961)(assets\image-20210316120304796.png)]

上图中服务实例本身并不记录服务生产方的网络地址,所有服务实例内部都会包含服务发现客户端

(1)在每个服务启动时会向服务发现中心上报自己的网络位置。这样,在服务发现中心内部会形成一个服务注册表服务注册表是服务发现的核心部分,是包含所有服务实例的网络地址的数据库。

(2)服务发现客户端会定期从服务发现中心同步服务注册表 ,并缓存在客户端。

(3)当需要对某服务进行请求时,服务实例通过该注册表,定位目标服务网络地址。若目标服务存在多个网络地址,则使用负载均衡算法从多个服务实例中选择出一个,然后发出请求。

总结,在微服务环境中,由于服务运行实例的网络地址是不断动态变化的,服务实例数量的动态变化 ,因此无法使用固定的配置文件来记录服务提供方的网络地址,必须使用动态的服务发现机制用于实现微服务间的相互感知。 各服务实例会上报自己的网络地址,这样服务中心就形成了一个完整的服务注册表,各服务实例会通过服务发现中心来获取访问目标服务的网络地址,从而实现服务发现的机制。

3 Nacos 服务发现

3.1 Nacos简介

3.1.1 服务发现产品对比

目前市面上用的比较多的服务发现中心有: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,也是未来微服务发展的趋势。

3.1.2 Nacos简介

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NwYRklSS-1693272596961)(assets/image-20210316120357611.png)]

Nacos是阿里的一个开源产品,它是针对微服务架构中的服务发现、服务治理、配置管理的综合型解决方案。

官方介绍是这样的:

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

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

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

3.1.3 Nacos特性

Nacos主要提供以下四大功能:

1. 服务发现与服务健康检查

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

2. 动态配置管理

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

3. 动态DNS服务

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

4. 服务和元数据管理

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

这里1、3、4说明了服务发现的功能特性。

3.2 安装Nacos Server

3.2.1 预备环境准备

Nacos 依赖 Java 环境来运行。如果您是从代码开始构建并运行Nacos,还需要为此配置 Maven环境,请确保是在以下版本环境中安装使用:

  1. 64 bit OS,支持 Linux/Unix/Mac/Windows,推荐选用 Linux/Unix/Mac。

  2. 64 bit JDK 1.8+;下载 & 配置

  3. Maven 3.2.x+;下载 & 配置

3.2.2 下载安装包

进入到github中的nacos网址,选择Releases

https://github.com/alibaba/nacos

选择版本进行下载

3.2.3 启动服务器

nacos的默认端口是8848,需要保证8848默认端口没有被其他进程占用。进入安装程序的bin目录:

Windows启动方式: 启动命令:

cmd startup.cmd或者双击startup.cmd运行文件。

启动成功,可通过浏览器访问 http://127.0.0.1:8848/nacos ,打开如下nacos控制台登录页面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pbd31pIu-1693272596962)(assets\image-20210316100921240.png)]

使用默认用户名:nacos,默认密码:nacos 登录即可打开主页面。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-77HO0iqA-1693272596962)(assets\image-20210316100945265.png)]

3.3 RESTful服务发现

3.3.1 测试环境

在alibaba_cloud父工程中添加依赖管理

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.22</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

分别在服务提供及服务消费工程中添加依赖,此依赖的作用是服务发现

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

3.3.2 服务注册

在服务提供工程中配置nacos服务发现相关的配置:

order_server服务提供:

server:
  port: 9200
spring:
  application:
    name: order-server
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

启动nacos

启动服务提供

观察nacos服务列表,order-server注册成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-52sxaHD2-1693272596962)(assets\image-20220902145738251.png)]

服务名称:每个服务在服务注册中心的标识,相当于Java中的类名。

服务实例:网络中提供服务的实例,具有IP和端口,相当于Java中的对象,一个实例即为运行在服务器上的一个进程。

3.3.3 服务发现

在服务消费工程中配置nacos服务发现相关的配置:

consumer_server服务消费

server:
  port: 9100
spring:
  application:
    name: consumer-server
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

修改Controller中远程调用的代码:

@Autowired
LoadBalancerClient loadBalancerClient;

@GetMapping("/getOrder")
public String getOrder(){
    
    
    RestTemplate restTemplate = new RestTemplate();
    ServiceInstance choose = loadBalancerClient.choose("order-server");
    URI uri = choose.getUri();
    String url = uri+"/order/getOrder";
    String orderStr = restTemplate.getForObject(url, String.class);
    return "Consumer::"+orderStr;
}

执行流程:

1、服务提供方将自己注册到服务注册中心

2、服务消费方从注册中心获取服务地址

3、进行远程调用

3.3.4 负载均衡

在RESTful服务发现的流程中,ServiceA通过负载均衡调用ServiceB,下边来了解一下负载均衡

负载均衡就是将用户请求(流量)通过一定的策略,分摊在多个服务实例上执行,它是系统处理高并发、缓解网络 压力和进行服务端扩容的重要手段之一。它分为服务端负载均衡客户端负载均衡

服务器端负载均衡:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WEEQxXuq-1693272596963)(assets\image-20210316103219732.png)]

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

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

客户端服务负载均衡:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E57sUopL-1693272596964)(assets\image-20210316103240358.png)]

上边使用的LoadBalancerClient就是一个客户端负载均衡器,具体使用的是Ribbon客户端负载均衡器。

Ribbon在发送请求前通过负载均衡算法选择一个服务实例,然后进行访问,这是客户端负载均衡。即在客户 端就进行负载均衡的分配。

Ribbon是一个客户端负载均衡器,它的责任是从一组实例列表中挑选合适的实例,如何挑选?取决于负载均衡策略

Ribbon核心组件IRule是负载均衡策略接口,它有如下实现,大家仅做了解:

1.轮询策略:RoundRobinRule(默认)

2.权重策略:WeightedResponseTimeRule.根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。

3.随机策略:RandomRule

4.最小连接数策略:BestAvailableRule。也叫最小并发数策略,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。

5.重试策略:RetryRule。按照轮询策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null。

6.可用敏感性策略:AvailabilityFilteringRule。先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例。

7.区域敏感策略:ZoneAvoidanceRule,根据服务所在区域(zone)的性能和服务的可用性来选择服务实例,在没有区域的环境下,该策略和轮询策略类似。

准备测试环境:

复制order_server工程,修改端口、pom的artifactId和启动类,导入启动多个服务提供方进程,查看调用效果

复制order_server修改目录名:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PI1wxQbr-1693272596964)(assets\1.png)]

修改pom的artifactId:

修改启动类的名称:

修改端口号:

server:
  port: 9201

然后再服务提供方的方法中输出端口,启动测试观察负载均衡效果!

单个服务策略,可通过下面方式在服务消费方的 配置文件中修改默认的负载均衡策略:

order-server: 
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

即服务提供方的服务名称:

​ com.netflix.loadbalancer.RandomRule:负载均衡类路径。

全局策略设置案例—修改为随机策略:

@Configuration
public class RibbonGlobalLoadBalanceConfig {
    
    
  @Bean
  public IRule ribbonRule(){
    
    
  	return new RandomRule();
   }
}

3.3.5 小结

服务注册与发现流程:

1、服务提供方将自己注册到服务注册中心

2、服务消费方从注册中心获取服务地址

3、通过客户端负载均衡器进行远程调用

3.4 整合OpenFeign客户端

由于OpenFeign集成了Ribbon,所以可以自动实现负载均衡。

3.4.1.consumer_server添加依赖

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

3.4.2.修改启动类

添加注解@EnableFeignClients

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

3.4.3.创建Feign接口

package cn.yunhe.api;

@FeignClient(value = "order-server")
public interface OrderApi {
    
    
    @GetMapping("/order/getOrder")
    String getOrder();
}

3.4.4.修改Controller

@Autowired
private OrderApi orderApi;

@GetMapping("/getOrder2")
public String getOrder2(){
    
    
    String orderStr = orderApi.getOrder();
    return "Consumer::"+orderStr;
}

发送http://localhost:9100/consumer/getOrder请求,结果如下:

Consumer::获取订单详情

3.5 Dubbo服务发现

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

来说并不那么完善。

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

RPC:RPC是远程过程调用(Remote Procedure Call)的缩写形式,调用RPC远程方法就像调用本地方法一样,非常方便,后边案例讲解具体过程。

3.5.1 Dubbo服务架构

下图是微服务采用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通信协议速度更快。

本架构采用阿里开源的Nacos,集服务发现和配置中心于一身,支持RESTful及Dubbo服务的注册。

3.5.2 测试环境

父工程:alibaba_cloud。

application1:使用consumer_server。

service1微服务:需要新建

service2微服务:暂不演示,只演示servce1向consumer提供服务

api网关:后边的课程内容讲解。

3.5.3 service1微服务

service1为user_server服务,对外暴露dubbo协议的接口,考虑远程接口可能会被其它多个服务调用,这里将user_server的接口单独抽取出api工程,user_server微服务工程的结构如下:

user_server_api:存放接口,独立成一个工程方便被其它服务工程依赖。user_server_service:存放接口实现,即dubbo服务的实现部分。

3.5.3.1 定义user_server_api

1、创建user_server工程

<?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">
    <parent>
        <artifactId>alibaba_cloud</artifactId>
        <groupId>cn.yunhe</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user_server</artifactId>
    <packaging>pom</packaging>
</project>

2、创建user_server_api工程

<?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">
    <parent>
        <artifactId>user_server</artifactId>
        <groupId>cn.yunhe</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user_server_api</artifactId>

</project>

3、定义接口

package cn.yunhe.service;

public interface UserService {
    
    
    String getUser();
}

3.5.3.2 定义user_server_service

1、创建user_server_service工程

<?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">
    <parent>
        <artifactId>user_server</artifactId>
        <groupId>cn.yunhe</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user_server_service</artifactId>

    <dependencies>
        <dependency>
            <groupId>cn.yunhe</groupId>
            <artifactId>user_server_api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>
</project>

2、定义接口实现

package cn.yunhe.service;

import org.apache.dubbo.config.annotation.DubboService;

@DubboService
public class UserServiceImp implements UserService {
    
    
    @Override
    public String getUser() {
    
    
        return "获取用户信息!";
    }
}

3、定义启动类

package cn.yunhe;

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

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

4、定义配置文件application.yml

server:
  port: 7100
spring:
  application:
    name: user-server
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
dubbo:
  scan:
    base-packages: cn.yunhe.service
  protocol:
    name: dubbo
    port: 20811
  registry:
    address: nacos://127.0.0.1:8848
  consumer:
    check: false #关闭启动时检查

5、启动user_server

启动成功观察nacos的服务列表

6、bootstrap.yml配置说明

bootstrap.yml内容中以dubbo开头的为dubbo服务 的配置:

  1. dubbo.scan.base-packages: 指定 Dubbo 服务实现类的扫描基准包,将@DubboService注解标注的service暴露为dubbo服务。
  2. dubbo.protocol: Dubbo 服务暴露的协议配置,其中子属性name为协议名称。port为dubbo协议端口。可以指定多协议。
  3. dubbo.registry: Dubbo 服务注册中心配置,其中子属性address的值 “nacos://127.0.0.1:8848”,说明dubbo服务注册到nacos。相当于原生dubbo的xml配置中的<dubbo:registry address=“10.20.153.10:9090” />

bootstrap.yml内容的上半部分为SpringCloud的相关配置:

  1. spring.application.name: Spring 应用名称,用于 Spring Cloud 服务注册和发现。该值在 Dubbo Spring Cloud 加持下被视作 dubbo.application.name,因此,无需再次配置。

  2. spring.cloud.nacos.discovery: Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口。

3.5.4 consumer_server调用user_server

3.5.4.1 引用user_server

在consumer_server工程中引用user_server_api依赖和dubbo相关依赖

<dependency>
    <groupId>cn.yunhe</groupId>
    <artifactId>user_server_api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

3.5.4.2 实现远程调用

修改consumer_server工程的ConsumerController:

package cn.yunhe.controller;

@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    
    
    @DubboReference
    private UserService userService;
    
    @GetMapping("/getUser")
    public String getUser(){
    
    
        String userStr = userService.getUser();
        return "Consumer::"+userStr;
    }
}

3.6 服务发现数据模型

3.6.1 Namespace 隔离设计

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

从一个租户(用户)的角度来看,如果有多套不同的环境,那么这个时候可以根据指定的环境来创建不同的namespce,以此来实现多环境的隔离。例如,你可能有开发,测试和生产三个不同的环境,那么使用一套nacos 集群可以分别建以下三个不同的 namespace。如下图所示:

3.6.2 命名空间管理

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

建立好所有namespace后,在配置管理服务管理模块下所有页面,都会包含用于切换namespace(环境)的tab按钮,如下图:

注意:

1.namesace 为 public 是 nacos 的一个保留空间,如果您需要创建自己的 namespace,不要和 public重名,以一个实际业务场景有具体语义的名字来命名,以免带来字面上不容易区分自己是哪一个namespace。

2.在编写程序获取配置集时,指定的namespace参数一定要填写命名空间ID,而不是名称。

3.6.3 数据模型

Nacos在经过阿里内部多年生产经验后提炼出的数据模型,则是一种服务-集群-实例的三层模型,这样基本可以满 足服务在所有场景下的数据存储和管理。

nacos服务发现的数据模型如下:

服务

对外提供的软件功能,通过网络访问预定义的接口。

服务名

服务提供的标识,通过该标识可以唯一确定要访问的服务。

实例

提供一个或多个服务的具有可访问网络地址(IP:Port)的进程,启动一个服务,就产生了一个服务实例。

元信息

Nacos数据(如配置和服务)描述信息,如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标 签 (label),从作用范围来看,分为服务级别的元信息、集群的元信息及实例的元信息。

集群

服务实例的集合,服务实例组成一个默认集群, 集群可以被进一步按需求划分,划分的单位可以是虚拟集群,相同集群下的实例才能相互感知。

通过数据模型可知:

应用通过Namespace、Service、Cluster(DEFAULT)的配置,描述了该服务向哪个环境(如开发环境)的哪个集群注册实例。

例子如下:

指定namespace的id:a1f8e863-3117-48c4-9dd3-e9ddc2af90a8(注意根据自己环境设置namespace的id)

指定集群名称:DEFAULT表示默认集群,可不填写

spring:
 application:
   name:  transaction-service
 cloud:
   nacos:
     discovery:
       server-addr: 127.0.0.1:7283 # 注册中心地址
       namespace: a1f8e863-3117-48c4-9dd3-e9ddc2af90a8 # 开发环境
       cluster-name: DEFAULT # 默认集群,可不填写

注意: 集群作为实例的隔离,相同集群的实例才能相互感知。

注意: namespace、cluster-name若不填写都将采取默认值,namespace的默认是public命名空间, cluster-name的默认值为DEFAULT集群。

3.6.4 创建dev命名空间,将所有的服务添加到namespace中

略。

4 Nacos配置管理

3.1 理解配置中心

3.1.1 什么是配置

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

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

配置是独立于程序的只读变量

配置对于程序是只读的,程序通过读取配置来改变自己的行为,但是程序不应该去改变配置 配置伴随应用的整个生命周期

配置贯穿于应用的整个生命周期,应用在启动时通过读取配置来初始化,在运行时根据配置调整行为。 比如:启动时需要读取服务的端口号、系统在运行过程中需要读取定时策略执行定时任务等。

配置可以有多种加载方式

常见的有程序内部hard code,配置文件,环境变量,启动参数,基于数据库等配置。

同一份程序在不同的环境(开发,测试,生产)、不同的集群(如不同的数据中心)经常需要有不同的配置,所以需要有完善的环境、集群配置管理

3.1.2 什么是配置中心

在微服务架构中,当系统从一个单体应用,被拆分成分布式系统上一个个服务节点后,配置文件也必须跟着迁移

(分割),这样配置就分散了,不仅如此,分散中还包含着冗余,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-us6vMaBU-1693272596965)(assets/11.png)]

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

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

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

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

总得来说,配置中心就是一种统一管理各种应用配置的基础服务组件。

3.1.3 主流配置中心对比

目前市面上用的比较多的配置中心有: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

​ 从配置中心角度来看,性能方面Nacos的读写性能最高,Apollo次之,Spring Cloud Config依赖Git场景不适合开放的大规模自动化运维API。

​ 功能方面Apollo最为完善,nacos具有Apollo大部分配置管理功能,而Spring Cloud Config不带运维管理界面,需要自行开发。Nacos的一大优势是整合了注册中心、配置中心功能,部署和操作相比Apollo都要直观简单,因此它简化了架构复杂度,并减轻运维及部署工作。

​ 综合来看,Nacos的特点和优势还是比较明显的,下面我们一起进入Nacos的世界。

3.2 Nacos配置管理

3.2.1 发布配置

首先在nacos发布配置,consumer_server服务从nacos读取配置。

浏览器访问 http://127.0.0.1:8848/nacos ,打开nacos控制台,并点击菜单配置管理->配置列表: 在Nacos添加如下的配置:

consumer_server:

Namespace: dev
DataID:  consumer-server.yaml
Group:  DEFAULT_GROUP
配置格式:      YAML
配置内容:    
common:
  name: application1 config

3.2.2 获取配置

要想从配置中心获取配置添加nacos-config的依赖:

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

新建配置文件bootstrap.yml并添加配置:

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848 # 配置中心地址
        namespace: a1f8e863-3117-48c4-9dd3-e9ddc2af90a8
        file-extension: yaml
        group: DEFAULT_GROUP

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

consumer_server服务读取配置文件中的信息:

@Value("${common.name}") 
private String common_name;

@GetMapping(value = "/configs")
public String getvalue(){
    
    
	return common_name;
}

基于上面的例子,若要实现配置的动态更新,只需要在controller类上添加注解:

@RefreshScope
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    
    
    ...
}

我们通过nacos控制台更新common.name的配置值,再次访问web端点/configs,发现应用程序能够获取到最新的配置值,说明spring-cloud-starter-alibaba-nacos-config 支持配置的动态更新。

我们可以通过配置spring.cloud.nacos.config.refresh.enabled=false来关闭动态刷新。

3.2.3 配置管理模型

对于Nacos配置管理,通过Namespace、group、Data ID能够定位到一个配置集。

配置集(Data ID)

在系统中,一个配置文件通常就是一个配置集,一个配置集可以包含了系统的各种配置信息,例如,一个配置集可 能包含了数据源、线程池、日志级别等配置项。每个配置集都可以定义一个有意义的名称,就是配置集的ID即Data ID。

配置项

配置集中包含的一个个配置内容就是配置项。它代表一个具体的可配置的参数与其值域,通常以 key=value 的形式存在。例如我们常配置系统的日志输出级别(logLevel=INFO|WARN|ERROR) 就是一个配置项。

配置分组(Group)

配置分组是对配置集进行分组,通过一个有意义的字符串(如 Buy 或 Trade )来表示,不同的配置分组下可以有相同的配置集(Data ID)。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP 。配置分组的常见场景:可用于区分不同的项目或应用,例如:学生管理系统的配置集可以定义一个group为:STUDENT_GROUP。

命名空间(Namespace)

​ **命名空间(namespace)**可用于进行不同环境的配置隔离。例如可以隔离开发环境、测试环境和生产环境,每个环境的配置是不同的。不同的命名空间下,可以存在相同名称的配置分组(Group) 或 配置集。

最佳实践

Nacos抽象定义了Namespace、Group、Data ID的概念,具体这几个概念代表什么,取决于我们把它们看成什么,这里推荐给大家一种用法,如下图:

Namespace:代表不同环境,如开发、测试、生产环境。

Group:代表某项目,如XX医疗项目、XX电商项目

DataId:每个项目下往往有若干个工程,每个配置集(DataId)是一个工程的主配置文件

获取某配置集的代码

获取配置集需要指定:

​ 1、nacos服务地址,必须指定

​ 2、namespace,如不指定默认public。在config中指定namespace,例子如下:

config:
  server-addr: 127.0.0.1:8848 # 配置中心地址
  file-extension: yaml
  namespace: a1f8e863-3117-48c4-9dd3-e9ddc2af90a8 # 开发环境
  group: DEFAULT_GROUP # xx业务组

​ 3、group,如不指定默认 DEFAULT_GROUP

​ 见上边第2点的例子。

​ 4、dataId,若不指定默认为名称为应用名称+配置文件扩展名

3.3 自定义扩展的 Data Id 配置

3.3.1 ext-config扩展配置

Spring Cloud Alibaba Nacos Config可支持自定义 Data Id 的配置。 一个完整的配置案例如下所示:

server:
  port: 9100
spring:
  application:
    name: consumer-server
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: 122c7220-e69e-4363-ae89-bc7af9133bba
      config:
        server-addr: 127.0.0.1:8848 # 配置中心地址
        namespace: 122c7220-e69e-4363-ae89-bc7af9133bba
        file-extension: yaml
        ext-config[0]:
          data-id: ext-common.yaml
          group: COMMON_GROUP
          refresh: true

可以看到:

通过 spring.cloud.nacos.config.ext-config[n].data-id 的配置方式来支持多个 Data Id 的配置。

通过 spring.cloud.nacos.config.ext-config[n].group 的配置方式自定义 Data Id 所在的组,不明确配置的话,默认是 DEFAULT_GROUP。

通过spring.cloud.nacos.config.ext-config[n].refresh 的配置方式来控制该 Data Id 在配置变更时,是否支持应用中可动态刷新, 感知到最新的配置值。默认是不支持的。

spring.cloud.nacos.config.ext -config[n].data-id 的值必须带文件扩展名,文件扩展名既可支持
properties,又可以支持 yaml/yml。 此时 spring.cloud.nacos.config.file -extension 的配置对自定义扩
展配置的 Data Id 文件扩展名没有影响。

测试:

配置ext-config-common01.yaml:

配置ext-config-common02.yaml:

编写测试代码:

@Value("${common.name}")
private String name;
@Value("${common.addr}")
private String address;

@GetMapping(value = "/configs") 
public String getvalue(){
    
    
    return  name+address;
}

重启consumer_server工程

通过测试发现:

扩展配置优先级是 spring.cloud.nacos.config.ext -config[n].data-id 其中 n 的值越大,优先级越高。

5 服务续约和下线

Nacos客户端默认5秒一次向Nacos服务端发送一次心跳包。

如果心跳包的间隔时间超过了15秒,则标记该服务为非健康实例。

如果心跳包超过30秒,那么Nacos服务端会把此服务实例从服务列表删除,也就是当心跳包超过15秒会标记为非健康实例,再有15秒就会把这个失效实例剔除!

6 注册中心数据结构

  1. Nacos注册中心数据结构也是一个双层map:
Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

外层map的key为namespace,value为Map<String, Service>。

内层的key为group::serviceName,value为service类。

  1. 对于Service类也有个重要的Map为:

    Map<String, Cluster> clusterMap = new HashMap<String, Cluster>();
    

    注册相关的核心数据结构就是clusterMap了,key是个string,保存的是clustername,value是个Cluster类型。

  2. 来看cluster类,核心数据结构是两个Set

    private Set<Instance> persistentInstances = new HashSet<>();
    
    private Set<Instance> ephemeralInstances = new HashSet<>();
    

    第一个是用来存储临时实例,第二个是用来存储持久化实例,有个关键点,什么情况会存储在临时实例,什么情况下会存储持久化实例,这个是由客户端的配置来决定的,默认情况下客户端配置ephemeral=true,如果你想把实例用持久化的方式来存储,可以设置ephemeral=false,这样在客户端发起注册的时候会把这个参数带到Nacos Server,Nacos Server就会按照持久化的方式来存储。

t-config-common01.yaml:

配置ext-config-common02.yaml:

编写测试代码:

@Value("${common.name}")
private String name;
@Value("${common.addr}")
private String address;

@GetMapping(value = "/configs") 
public String getvalue(){
    
    
    return  name+address;
}

重启consumer_server工程

通过测试发现:

扩展配置优先级是 spring.cloud.nacos.config.ext -config[n].data-id 其中 n 的值越大,优先级越高。

5 服务续约和下线

Nacos客户端默认5秒一次向Nacos服务端发送一次心跳包。

如果心跳包的间隔时间超过了15秒,则标记该服务为非健康实例。

如果心跳包超过30秒,那么Nacos服务端会把此服务实例从服务列表删除,也就是当心跳包超过15秒会标记为非健康实例,再有15秒就会把这个失效实例剔除!

6 注册中心数据结构

  1. Nacos注册中心数据结构也是一个双层map:
Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

外层map的key为namespace,value为Map<String, Service>。

内层的key为group::serviceName,value为service类。

  1. 对于Service类也有个重要的Map为:

    Map<String, Cluster> clusterMap = new HashMap<String, Cluster>();
    

    注册相关的核心数据结构就是clusterMap了,key是个string,保存的是clustername,value是个Cluster类型。

  2. 来看cluster类,核心数据结构是两个Set

    private Set<Instance> persistentInstances = new HashSet<>();
    
    private Set<Instance> ephemeralInstances = new HashSet<>();
    

    第一个是用来存储临时实例,第二个是用来存储持久化实例,有个关键点,什么情况会存储在临时实例,什么情况下会存储持久化实例,这个是由客户端的配置来决定的,默认情况下客户端配置ephemeral=true,如果你想把实例用持久化的方式来存储,可以设置ephemeral=false,这样在客户端发起注册的时候会把这个参数带到Nacos Server,Nacos Server就会按照持久化的方式来存储。

猜你喜欢

转载自blog.csdn.net/zhangchen124/article/details/132554497