Nacos-服务发现

什么是服务发现

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

在这里插入图片描述

什么是服务之间的协作呢?你比如哈,Service A是订单服务,Service B是商品服务,创建订单的时候,需要调用商品,也即是订单服务会调用商品服务,这就是服务之间的协作。其中被调用的叫做生产方,调用别的服务的叫做消费方,也即是说Service A叫做消费方,Service B叫做生产方。

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

Service B(服务生产者)

Service B是服务的生产方,暴露/service服务地址,实现代码如下:

@SpringBootApplication
@RestController
public class SpringRestProviderBootstrap {
	public static void main(String[] args) {
		SpringApplication. run(SpringRestProviderBootstrap.class, args);
}
	@GetMapping(value = "/service") //暴露服务
    public String service(){
   		 return "provider invoke" ;
    }
}

配置文件:

server.port = 56010

Service A(服务消费者)

在服务消费者当中,必须要知道生产方的端口号和ip地址。

实现代码:

@SpringBootApplication
@RestController
public class SpringRestConsumerBootstrap {
	public static void main(String[] args) {
		SpringApplication. run(SpringRestConsumerBootstrap.class, args);
}
	@Value("${provider . address}")
	private String providerAddress;
	@GetMapping(value = "/service")
	public String service(){
		RestTemplate restTemplate = new RestTemplate();
		//调用服务
		String providerResult = restTemplate . getFor0bject("http://" + providerAddress +
"/service" ,String. class);
		return "consumer invoke|+ providerResult;
	}
}


配置文件:

server.port = 56020
#服务生产方地址
provider.address = 127.0.0.1:56010

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

consumer invoke | provider invoke

看上去很完美,但是,仔细考虑一下,此方案对于微服务应用而言行不同。

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

在这里插入图片描述

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

在这里插入图片描述

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

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

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

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

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

主流服务发现与配置中心对比

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

在这里插入图片描述

在这里插入图片描述

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

Nacos服务发现快速入门

本小节,我们将演示如何使用Spring Cloud Alibaba Nacos Discovery为Spring Cloud应用程序与Nacos的无缝集成。通过一些原生的Spring cloud注解,我们可以快速来实现Spring cloud微服务的服务发现机制,并使用Nacos Server作为服务发现中心,统一管理所有微服务。

Spring Cloud服务协作流程

现在,我们对Spring cloud内的一些组件还不了解,为了能够完全理解快速入门程序,我们需要学习以下内容。

Spring Cloud常见的集成方式是使用Feign+Ribbon技术来完成服务间远程调用及负载均衡的,如下图:

在这里插入图片描述

负载均衡的概念

一个服务可能有多个实例,所以要用到负载均衡。

在SpringCloud服务协议流程中, ServiceA通过负载均衡调用ServiceB,'下边来了解一下负载均衡 :
负载均衡就是将用户请求(流量)过-定的策略 .分摊在多个服务实例上执行,它是系统处理高并发、缓解网络压力和进行服务端扩容的重要手段之一。 它分为服务端负载均衡和客户端负载均衡。

服务端负载均衡

img

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

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

img

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

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

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

RoundRobinRule**(默认):轮询,即按一定的顺序轮换获取实例的地址。
RandomRile:随机,即以随机的方式获取实例的地址。
AvailabilityFilteringRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,以及并发的连接数量超过阈值的服务然后对剩余的服务列表按照轮询策略进行访问**;
WeightedResponseTimeRule:根据平均响应时间计算所有服务的权重响应时间越快,服务权重越大,被选中的机率越高;
刚启动时,如果统计信息不足则使用RoundRobinRule策略等统计信息足够时,会切换到
WeightedResponse TimeRule
RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败,则在指定时间内会进行重试,获取可用的服务;
BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个 并发量最小的服务;

Fegin介绍

Feiga是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API。

Feign的英文表意为“假装,伪装,彩”,可以理解为将HTTP报文请求方式伪装为简单的java接口调用方式。
参考第1章节的ServiceA调用ServiceB的例子,我们使用Feign实现这个过程,代码如下:

@SpringBootApplication
@RestController
public class SpringRestProviderBootstrap {
	public static void main(String[] args) {
		SpringApplication. run(SpringRestProviderBootstrap.class, args);
}
	@GetMapping(value = "/service") //暴露服务
	public String service(){
		return "provider invoke";
	}
}

Feign调用方式

1.声明Feign客户端

生成一个动态代理对象,从服务中心拿服务实例地址,发起远程调用

@FeignClient(value = "serviceB")
public interface ServiceBAgent {
    /**
    *根据用户名查询账号信息
    本
    @param username用户名
    @return账号信息
    */
	@GetMapping(value = "/service")
	public String service();
}

2.业务调用

@Autowired
private ServiceBAgent serviceBAgent. ;
//....略
serviceBAgent . service();
//....略

serviceB的具体访问地址, Feign会交由ribbon获取,若该服务有多个实例地址, ribbon会采用指定的负载均衡策略选取实例。

Feign兼容spring的web注解(如: @GetMapping) ,它会分析声明Feign客户端方法中的Spring注解,得出Http请求method、参数信息以及返回信息结构。

当业务调用Feign客户端方法时,会调用代理类,根据以上分析结果,由代理类完成实际的参数封装、远程http请求,返回结果封装等操作。

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

Feign默认集成了Ribbon,可直接使用

还需要在spring cloud启动类中标注@EnableFeignClients ,表明此项目开启Feign客户端:

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

创建父工程

创建一个maven工程,如下图:

在这里插入图片描述

Pom.xml文件中要引入的依赖:

<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>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <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>
                <version>2.5.3</version>
            </plugin>
        </plugins>
    </build>

在这里插入图片描述

服务生产者

以下步骤演示了如何将一个服务生产者注册到Nacos。

创建一个服务生产者的maven子工程,如下图:

在这里插入图片描述

在这里插入图片描述

1.pom.xml的配置

包括Spring Cloud Feign组件,Spring Cloud Alibaba Nacos Discovery组件以及Spring boot web相关组件依赖。

<dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</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-openfeign</artifactId>
        </dependency>
    </dependencies>

在这里插入图片描述

2.application.yml配置。一些关于Nacos基本的配置也必须在application.yml(也可以是application.properties)配置,如下所示:application.yml

server:
  port: 56010  #启动端口

spring:
  application:
    name: quickstart-provider
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

logging:
  level:
    root: info
    org.springframework: info

在这里插入图片描述

Note:spring.cloud.nacos.discovery.server-addr指定了Nacos Server的网络地址和端口号。

3.Provider(生产者)服务实现

@RestController
public class ProviderController {
    private static final Logger LOG= LoggerFactory.getLogger(ProviderController.class);
    @GetMapping("/service")
    public String service(){
        Log.info("provider invoke");
        return "provider invoke";
    }
}

在这里插入图片描述

4.启动类启动

在这里插入图片描述

在这里插入图片描述

服务消费者

1.创建服务消费者模块

在这里插入图片描述

2.往服务消费者的pom.xml中添加依赖,添加的依赖和服务生产者中的相同,这里就不再演示了。

3.application.yml配置

server:
  port: 56020  #启动端口 命令行注入

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

在这里插入图片描述

4.Provider(生产者)远程代理定义

@FeignClient(value = "quickstart-provider")
public interface ProviderClient {
    @GetMapping("/service")
    public String service();
}

在这里插入图片描述

@FeignClient注解就可以实现服务发现,即在消费服务中会调用生产服务,这样就建立起了服务之间的关系。

5.Consumer(消费者)服务实现

@RestController
public class ConsumerController {
    private static final Logger LOG= LoggerFactory.getLogger(ConsumerController.class);
    
    @Autowired
    private ProviderClient providerClient;
    
    @GetMapping("/service")
    public String service(){
        LOG.info("consumer invoke");
        String providerResult=providerClient.service();
        return "consumer invoke"+"|"+providerResult;
    }
}

在这里插入图片描述

6.写一个启动类

在这里插入图片描述

启动类成功启动之后,会在nacos注册中心多出来一个服务,如下图:

在这里插入图片描述

在这里插入图片描述

测试服务发现

在消费服务中调用生产服务,如下图:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

访问消费服务的接口,如下图:

在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐