18、Nacos 教程 - Spring Cloud Alibaba简单入门案例

前言

Spring Cloud Alibaba 综合集成案例,用到了nacos配置中心,注册与发现中心,dubbo协议远程代理,zuul网关。

准备环境

需要先启动nacos,并新建一个命名空间为dev,以下配置文件中用到的namespace均为dev的id。
 

1. Spring Cloud Alibaba综合集成架构

前面介绍,Spring Cloud是一个较为全面的微服务框架,集成了如服务注册发现、配置中心、消息总线、负载均衡、断路器、API网关等功能实现。而在网上经常会发现Spring Cloud与阿里巴巴的Dubbo进行选择对比,这样做是不妥当的,前者是一套较为完整的架构方案,而Dubbo只是服务治理与RPC实现方案。
Dubbo在国内有着非常大的用户群体,但是其周边设施与组件相对不完善。很多开发者用于又很希望享受Spring Cloud的生态,因此也会有一些Spring Cloud与Dubbo一起使用的案例与方法出现,但是一直依赖大部分的Spring Cloud整合Dubbo的使用方案都不完善。直到Spring Cloud Alibaba的出现,才得以解决这样二点问题。
在此之前,我们已经学习了如何使用Spring Cloud Alibaba来集成Nacos与Spring Cloud应用,并且在此之下也如传统的Spring Cloud应用一样地使用Ribbon或Feign来微服务之间的协作。由于Feign是基于Http Restful的调用,在高并发的性能下不理想,RPC方案能否切换到Dubbo?Spring Cloud与阿里系的若干组件能否完美集成呢?
本章将介绍如何集成一个微服务的基础架构,并讨论其合理性。

1.1 总体结构

 

组件说明:

  • API网关: 系统统一入口,屏蔽架构内部结构,统一安全拦截,采用Zuul实现。
  • application-1 :应用1,模拟应用,提供http接口服务
  • service-1:微服务1,模拟微服务,提供dubbo接口服务。
  • service-2:微服务2,模拟微服务,提供dubbo接口服务。

调用流程
所有访问系统的请求都要经过网关,网关转发http请求至application-1,application-1使用dubbo调用service-1完成自身业务,而后service-1调用service-2完成自身业务。至此,完成所有组件的贯穿。
架构中application与service的区别是什么?

  • service 提供了基础服务功能;application组装完成基本服务功能,提供给用户直接可用的业务。
  • service服务粒度小、功能基础,不易发生变化;application提供上游业务功能,紧贴业务需求,容易发生改变。
  • 形成service支撑application的整体架构,增加多变的application甚至不需要变动service。

1.2 工程结构说明

maven工程结构图

springcloudalibabademo  整个父工程
|-api-gateway           API网关:port 10000
|-application-1         应用1,port:10010
|-service-1             服务1
|-|- service-1-api      服务1  api
|-|- service-1-server   服务1 实现,port:10020
|-service-2             服务2             
|-|- service-2-api      服务2  api
|-|- service-2-server   服务2 实现,port:10030

1.3 创建工程结构

1.3.1 父工程springcloudalibabademo

1、 创建maven父工程;
  2、 定义包管理;

<packaging>pom</packaging>

1、 引入依赖;

<!--配置依赖管理-->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.1.0.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.3.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<build>
    <!--配置插件-->
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

1.3.2 application1

 

1.3.3 service-1&service-1-api&service-1-server

 
 
 

1.3.4 service-2&service-2-api&service-2-server

 
 
 

1.3.5 api-gateway

 

1.3.6 项目模块和文件结构

 
 

1.4 实现application1

1、 引入依赖;

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

1、 实现controller;

@RestController
public class Application1Controller {
   
     

    @GetMapping("/service")
    public String service(){
   
     
        return "hello";
    }
}

1、 编写bootstrap.yml配置文件;

server:
  port: ${
   
     port:10010} 启动端口
  servlet:
    context-path: /application1 指定访问头

spring:
  application:
    name: application1
  cloud:
    nacos:
      discovery: 注册发现
        server-addr: 127.0.0.1:8848
        namespace: 869a0c6d-267e-4aa1-96cb-552cb1632c72 dev
        cluster-name: DEFAULT集群
      config:    配置中心
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        namespace: 869a0c6d-267e-4aa1-96cb-552cb1632c72 dev
        group: NACOS_MICROSERVICE_GROUP业务组
  main:
    allow-bean-definition-overriding: true spring boot 2.1 需要设定(防止bean重复覆盖问题)
logging:
  level:
    root: info
    org.springframework: info

1、 编写启动类;

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

1、 启动访问测试一下http://127.0.0.1:10010/application1/service
 

1.5 实现service-1

1.5.1 service-1-api定义接口

service-1-api只是负责定义接口,不负责实现。
所以这里直接在service-1-api下直接新建一个接口

package com.it2.service1.api;

public interface ConsumerService {
   
     

    public String service();
}

 

1.5.2 service-1-server实现

1、 引入service-1-api和配置管理与注册发现相关的依赖;

<dependencies>
        <!--引入service1api的依赖-->
        <dependency>
            <groupId>com.it2</groupId>
            <artifactId>service-1-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-alibaba-nacos-config</artifactId>
        </dependency>
        <!--引入dubbo-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>
    </dependencies>

1、 实现service-1-api的接口;

package com.it2.service1.service;

import com.it2.service1.api.ConsumerService;

//注解标记此类表示此方法暴露为dubbo接口
@org.apache.dubbo.config.annotation.Service
public class ConsumerServiceImpl implements ConsumerService {
   
     

    //dubbo接口实现
    public String service() {
   
     
        return "consumer invoke ";
    }
}

1、 配置bootstrap.yml文件;

server:
  port: ${
   
     port:10020} 启动端口
spring:
  application:
    name: service1
  cloud:
    nacos:
      discovery: 注册发现
        server-addr: 127.0.0.1:8848
        namespace: 869a0c6d-267e-4aa1-96cb-552cb1632c72 dev
        cluster-name: DEFAULT集群
      config:    配置中心
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        namespace: 869a0c6d-267e-4aa1-96cb-552cb1632c72 dev
        group: NACOS_MICROSERVICE_GROUP业务组
  main:
    allow-bean-definition-overriding: true spring boot 2.1 需要设定(防止bean重复覆盖问题)
dubbo:
  scan:
    base-packages: com.it2.service1 服务扫描基准包(扫描被dubbo注解的类)
  protocol:
    name: dubbo 协议
    port: ${
   
     dubbo_port:20881} 协议端口
  registry:
    address: nacos://127.0.0.1:8848
  application:
    qos-enable: falsedubbo运维服务是否开启
  consumer:
    check: false启动时就检查依赖的服务

以上yml内容解析
dubbo开头的dubbo服务的配置

  • dubbo.scan.base-packages:指定Dubbo服务实现类的扫描基准包,将@org.apache.dubbo.config.annotation.Service注解标记的service暴露为dubbo服务
  • dubbo.protocol:Dubbo服务暴露的协议配置,其中子属性name为协议的名称,port为dubbo协议端口,可指定多协议。例如dubbo.protocol.rmi.port=1099
  • dubbo.registry:Dubbo服务注册中心配置,其中子属性address的值"nacos://127.0.0.1:8848",说明dubbo服务注册到nacos,相当于原生的dubbo的xml配置中的<dubbo:registry address=“127.0.0.1:8848” />

Spring Cloud开头的部分
+spring.application.name:spring应用名称,用于spring cloud服务注册和发现。该值在Dubbo Spring Cloud 加持下被视为dubbo.application.name,因此,无需再显示的配置dubbo.application.name

  • spring.cloud.nacos.discovery:nacos服务发现与注册中心,其中子属性server-addr指定nacos服务器主机与端口
  • spring.cloud.nacos.config:nacos配置中心的配置管理,其中子属性server-addr指定nacos服务器主机与端口。

1、 编写启动类;

@SpringBootApplication
@EnableDiscoveryClient
public class Service1Bootstrap {
   
     

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

1、 启动然后再nacos的服务列表可以看到dubbo暴露的20881;
 

1.6 application1调用service1

1、 引入service-1-api的依赖;

<dependency>
    <groupId>com.it2</groupId>
    <artifactId>service-1-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<!--调用接口使用dubbo协议-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>

1、 注入service,实现远程调用;

@RestController
public class Application1Controller {
   
     

    //注入service(基于dubbo协议)
    @org.apache.dubbo.config.annotation.Reference //生成接口代理对象,通过代理对象进行远程调用
    ConsumerService consumerService;

    @GetMapping("/service")
    public String service(){
   
     
        String result=  consumerService.service();//远程调用接口
        return "hello | "+result;
    }
}

1、 启动application1,访问接口[http://127.0.0.1:10010/application1/service](http://127.0.0.1:10010/application1/service;
 
这样就实现了application1通过dubbo协议调用service1

1.7 实现service2

1.7.1 service-2-api

service-2-api只是负责定义接口,不负责实现。
所以这里直接在service-2-api下直接新建一个接口

package com.it2.service2.api;

public interface ProviderService {
   
     

    public String service();
}

 

1.7.2 service-2-server

1、 引入service-2-api和配置管理与注册发现相关的依赖;

<dependencies>
    <!--引入service2api的依赖-->
    <dependency>
        <groupId>com.it2</groupId>
        <artifactId>service-2-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-alibaba-nacos-config</artifactId>
    </dependency>
    <!--引入dubbo-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-dubbo</artifactId>
    </dependency>
</dependencies>

1、 实现service-2-api的接口;

package com.it2.service2.service;

import com.it2.service2.api.ProviderService;

@org.apache.dubbo.config.annotation.Service
public class ProviderServiceImpl implements ProviderService {
   
     
    public String service() {
   
     
        return "provider invoke";
    }
}

1、 配置bootstrap.yml文件;

server:
  port: ${
   
     port:10030} 启动端口
spring:
  application:
    name: service2
  cloud:
    nacos:
      discovery: 注册发现
        server-addr: 127.0.0.1:8848
        namespace: 869a0c6d-267e-4aa1-96cb-552cb1632c72 dev
        cluster-name: DEFAULT集群
      config:    配置中心
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        namespace: 869a0c6d-267e-4aa1-96cb-552cb1632c72 dev
        group: NACOS_MICROSERVICE_GROUP业务组
  main:
    allow-bean-definition-overriding: true spring boot 2.1 需要设定(防止bean重复覆盖问题)
dubbo:
  scan:
    base-packages: com.it2.service2 服务扫描基准包(扫描被dubbo注解的类)
  protocol:
    name: dubbo 协议
    port: ${
   
     dubbo_port:20891} 协议端口
  registry:
    address: nacos://127.0.0.1:8848
  application:
    qos-enable: falsedubbo运维服务是否开启
  consumer:
    check: false启动时就检查依赖的服务

1、 编写启动类;

@SpringBootApplication
@EnableDiscoveryClient
public class Service2Bootstrap {
   
     

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

1、 启动然后再nacos的服务列表可以看到dubbo暴露的20891;
 

1.8 实现service1调用service2

1、 引入service-2-api;

<dependency>
    <groupId>com.it2</groupId>
    <artifactId>service-2-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

1、 实现远程调用;

//注解标记此类表示此方法暴露为dubbo接口
@org.apache.dubbo.config.annotation.Service
public class ConsumerServiceImpl implements ConsumerService {
   
     

    //注入service
    @org.apache.dubbo.config.annotation.Reference  //生成代理对象
    ProviderService providerService;

    //dubbo接口实现
    public String service() {
   
     
        String result=providerService.service();//远程调用接口
        return "consumer invoke | "+result;
    }
}

1、 重启service1,访问application1的接口,就可以看大application1调用service1,而service1调用service2;

 

1.9 实现api-gateway

1.9.1 zuul介绍

什么是网关?

原来的单体架构,所有的服务都是本地的,UI可以直接调用,现在按功能拆分独立的服务,运行独立的一般都在独立的虚拟机上的java进程了。客户端UI如何访问?它的后台有n个服务,前台就需要记住管理n个服务,一般服务下线/更新/升级,前台就需要重新部署,这明显不符合我们拆分的理念,特别当前台是移动应用的时候,通常业务变化的节奏很快。另外,N个小服务的调用也是不小的网络开销。

 

有了网关作为服务统一入口,就可以避免上述问题,不仅如此,服务网关是在微服务前面设置的一道安全屏障,请求需要先到网关,服务会先对请求进行过滤,校验,路由等处理。有了服务网关可以提高微服务的安全性,网关校验请求的合法性,请求不合法将被拦截。

  • 提供统一服务入口,让微服务对前台透明
  • 聚合后台的服务,节省流量,提升性能
  • 提供安全,过滤,流控等API管理功能

什么是Zuul?
Spring Cloud Zuul是整合Netflix公司的Zuul开源项目实现的微服务网关,它实现了请求路由、负载均衡、校验过滤等功能。
https://cloud.spring.io/spring-cloud-netflix/multi/multi__router_and_filter_zuul.html

Zuul与Nginx怎么配置使用?
Zuul与Nginx在实际项目中需要配合使用,如下图,Nginx的作用是反向代理、负载均衡,Zuul的作用是保障微服务的安全访问,拦截微服务请求,校验合法性以及负债均衡。
 

1.9.2 搭建网关工程api-gateway

1、 引入依赖;

<!--用于服务注册和发现-->
<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-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--网关调用微服务使用http协议-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

1、 配置bootstrap.yml;

server:
  port: ${
   
     port:10000} 启动端口
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      discovery: 注册发现
        server-addr: 127.0.0.1:8848
        namespace: 869a0c6d-267e-4aa1-96cb-552cb1632c72 dev
        cluster-name: DEFAULT集群
      config:    配置中心
        server-addr: 127.0.0.1:8848
        file-extension: yaml
        namespace: 869a0c6d-267e-4aa1-96cb-552cb1632c72 dev
        group: NACOS_MICROSERVICE_GROUP业务组
  main:
    allow-bean-definition-overriding: true spring boot 2.1 需要设定(防止bean重复覆盖问题)

1、 配置路由,直接在nacos配置管理下添加配置(命名空间为dev)(因为网关的代理会经常变动,所以这里在nacos后台配置);

zuul:
 routes:
   application1: application1表示application1这个微服务
     stripPrefix: false
     path: /application1/**

 
 
5、 编写api-gateway启动类,网关的启动类需要使用@EnableZuulProxy注解标记此工程为Zuul网关;

@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy  //启动类必须使用EnableZuulProxy注解标识此工程为Zuul网关
public class ApiGatewayBootstrap {
   
     

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

1、 启动网关,可以看到nacos服务类列表中发现了网关;
  2、 此时我们就可以通过网关来请求application1的接口,http://127.0.0.1:10000/application1/service,因为网关的接口是10000,而application1的端口为10010,所以我们的请求是从网关进行的代理转发;

 

2. 代码建议

使用dubbo注解时,由于Service和Reference存在重名,容易引起冲突,建议使用dubbo注解时,使用全名,而不是直接使用@Service和@Reference。如下为示例:

@org.apache.dubbo.config.annotation.Reference

 

@org.apache.dubbo.config.annotation.Service

 

3. 遇到的问题

3.1 Failed to execute goal on project service-1-server: Could not resolve dependencies for project

解决方案见:https://blog.csdn.net/u011628753/article/details/124434986