03从零开始学习微服务之服务发现

1 服务发现架构

为了开始讨论服务发现架构,我们需要了解4个概念。这些一般概念在所有服务发现实现中是共通的。

  • 服务注册:服务如何使用服务发现代理进行注册?
  • 服务地址的客户端查找:服务客户端查找服务信息的方法是什么?
  • 信息共享:如何跨节点共享服务信息?
  • 健康监测:服务如何将它的健康信息传回给服务发现代理?

图4-2 展示了这4 个概念的流程,以及在服务发现模式实现中通常发生的情况。

2 客户端负载均衡 

当服务消费者需要调用一个服务时:

  1. 它将联系服务发现服务,获取它请求的所有服务实例,然后在服务消费者的机器上本地缓存数据。
  2. 每当客户端需要调用该服务时,服务消费者将从缓存中查找该服务的位置信息。通常,客户端缓存将使用简单的负载均衡算法,如“轮询”负载均衡算法,以确保服务调用分布在多个服务实例之间。
  3. 然后,客户端将定期与服务发现服务进行联系,并刷新服务实例的缓存。客户端缓存最终是一致的,但是始终存在这样的风险:在客户端联系服务发现实例以进行刷新和调用时,调用可能会被定向到不健康的服务实例上。如果在调用服务的过程中,服务调用失败,那么本地的服务发现缓存失效,服务发现客户端将尝试从服务发现代理刷新数据。

3 Spring和Netflix Eureka进行服务发现实战

  1. 随着服务的启动,许可证和组织服务将通过Eureka 服务进行注册。这个注册过程将告诉Eureka每个服务实例的物理位置和端口号,以及正在启动的服务的服务ID 。
  2. 当许可证服务调用组织服务时,许可证服务将使用Netflix Ribbon 库来提供客户端负载均衡。Ribbon将联系Eureka服务去检索服务位置信息,然后在本地进行缓存。
  3.  Netflix Ribbon 库将定期对Eureka服务进行ping 操作,并刷新服务位置的本地缓存。

3.1 构建Spring Eureka服务

3.1.1 添加pom依赖

    <dependencies>
        <!-- 导入Eureka服务的依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

3.1.2 配置application.yml

要设置的关键属性是server.port 属性,它用于设置Eureka 服务的默认端口。eureka.client.registerWithEureka属性会告知服务,在Spring Boot Eureka应用程序启动时不要通过Eureka服务注册,因为它本身就是Eureka服务。eureka.client. fetchRegistry属性设置为false,以便Eureka 服务启动时,它不会尝试在本地缓存注册表信息。在运行Eureka 客户端时,为了缓存通过Eureka 注册的Spring Boot服务,我们需要更改eureka.client.fetchRegistry的值。

server:
  port: ${PORT:50101} #服务端口
spring:
  application:
    name: eureka_server #指定服务名
eureka:
  client:
    registerWithEureka: false #服务注册,是否将自己注册到Eureka服务中
    fetchRegistry: false #服务发现,是否从Eureka中获取注册信息
  server:
    wait-time-in-ms-when-sync-empty: 5

3.1.3 标注引导类以启动Eureka服务器

只需要使用一个新的注解@EnableEurekaServer,就可以让我们的服务成为一个Eureka服务。

@EnableEurekaServer //标识此工程是一个EurekaServer
@SpringBootApplication
public class GovernCenterApplication {
    public static void main(String[] args) {
        SpringApplication.run(GovernCenterApplication.class,args);
    }
}

3.2 通过Spring Eureka注册服务

3.2.1 pom中添加依赖

   <!-- 导入Eureka服务的依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

3.2.2 配置aplication.yml 文件

每个通过Eureka注册的服务都会有两个与之相关的组件: 应用程序ID和实例ID 。应用程序ID用于表示一组服务实例。在基于Spring Boot 的微服务中,应用程序ID始终是由spring.application.name 属性设置的值。实例ID是一个随机数,用于代表单个服务实例。一个服务可以开多个实例,每个实例都使用相同的服务名。

在默认情况下,Eureka 在尝试注册服务时,将会使用主机名让外界与它进行联系。这种方式在基于服务器的环境中运行良好,在这样的环境中,服务会被分配一个DNS支持的主机名。但是,在基于容器的部署(如Docker)中,容器将以随机生成的主机名启动,并且该容器没有DNS记录。如果没有将eureka.instance.preferipAddress 设置量为true,那么客户端应用程序将无法正确地解析主机名的位置,因为该容器不存在DNS记录。设置preferipAddress属性将通知Eureka服务,客户端想要通过IP地址进行通告。

server:
  port: ${PORT:31001}
spring:
  application:
    name: helloworld
eureka:
  client:
    registerWithEureka: true #服务注册开关
    fetchRegistry: true #服务发现开关
    serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址,多个中间用逗号分隔
      defaultZone: ${EUREKA_SERVER:http://localhost:50101/eureka/}
  instance:
    prefer-ip-address:  true  #将自己的ip地址注册到Eureka服务中
    ip-address: ${IP_ADDRESS:127.0.0.1}
    instance-id: ${spring.application.name}:${server.port} #指定实例id

3.2.3 对外提供的方法

package com.hello;

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

@RestController
public class Hello {
    @RequestMapping("/hello")
    public String sayHello(){
        return "hello";
    }
}

3.2.4 查看服务

访问Eurake服务,查看注册的helloworld服务

3.3 使用服务发现来查找服务

有3个不同的Spring/Netflix客户端库:

  • Spring DiscoveryClient;
  • 启用了RestTemplate 的Spring DiscoveryClient;
  • Netflix Feign 客户端。

3.3.1 Spring DiscoveryClient客户端

3.3.1.1 启动DiscoveryClient

在启动类中添加@EnableDiscoveryClient注解

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

    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

3.3.1.2 编写客户端

DiscoveryClient是与Ribbon交互的类。要检索通过Eureka注册的所有服务实例,可以使用getInstances( )方法传入要查找的服
务的关键字,以检索Service Instance对象的列表。

@Component
public class HelloworldDiscoveryClient {
    @Autowired
    private DiscoveryClient discoveryClient;

    public String callHello() {
        RestTemplate restTemplate = new RestTemplate();
        List<ServiceInstance> instances =
                discoveryClient.getInstances("helloworld");
        if (instances.size() == 0) {
            return null;
        }
        String serviceUri =
                String.format("%s/hello",
                        instances.get(0).getUri().toString());
        String forObject = restTemplate.getForObject(
                serviceUri, String.class);

        return forObject;
    }
}

3.3.1.3 客户端测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class TestDiscoveryClient {

    @Autowired
    HelloworldDiscoveryClient helloworldDiscoveryClient;
    @Test
    public void discoveryTest(){
        helloworldDiscoveryClient.callHello();
    }
}

 3.3.1.4 存在的问题

在实际运用中,只有在服务需要查询Ribbon以了解哪些服务和服务实例巳经通过它注册时,才应该直接使用DiscoveryClient。上述代码存在以下几个问题。

  • 没有利用Ribbon的客户端负载均衡
  • 开发人员做了太多的工作

为了解决以上问题,我们可以直接使用带有Ribbon功能的Spring RestTemplate调用服务

3.3.2 Spring RestTemplate客户端

3.3.2.1 开启Spring RestTemplate负载均衡功能

要使用带有Ribbon 功能的Rest Template 类,需要使用Spring Cloud注解@LoadBalanced来定义RestTemplate bean的构造方法。

@LoadBalanced
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

3.3.2.2 编写客户端

URL比较特殊:URL中的服务器名称与通过Eureka 注册的组织服务的应用程序ID相同。

@Component
public class HelloworldRestTemplateClient {
    @Autowired
    private RestTemplate restTemplate;
    public String callHello() {
        String serviceUri = "http://helloworld/hello";
        String forObject = restTemplate.getForObject(
                serviceUri, String.class);
        return forObject;
    }
}

3.3.2.3 客户端测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class TestRestTemplateClient {

    @Autowired
    HelloworldRestTemplateClient restTemplateClient;
    @Test
    public void discoveryTest(){

        restTemplateClient.callHello();
    }
}

3.3.3 Netflix Feign 客户端

Feign库采用不同的方法来调用REST服务,方法是让开发人员首先定义一个Java 接口,然后使用Spring Cloud 注解来标注接口, 以映射Ribbon将要调用的基于Eureka的服务。Spring Cloud 框架将动态生成一个代理类, 用于调用目标REST服务。除了编写接口定义,开发人员不需要编写其他调用服务的代码。

推荐使用这个方法来发现服务。

3.3.3.1 pom依赖

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

3.3.3.2 在启动类中启动Feign客户端

添加新的注解@EnableFeignClients

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

3.3.3.3 编写客户端

我们通过使用@FeignClient 注解来开始这个Feign 示例,并将这个接口代表的服务的应用程序ID传递给它。接下来,在这个接口中定义一个callHello()方法,该方法可以由客户端调用以触发组织服务。注意:该客户端利用了Spring MVC的注解@RequestMapping。

@FeignClient("helloworld")
public interface HelloworldFeignClient {
    @RequestMapping("/hello")
    String callHello();
}

3.3.3.4 客户端测试

@SpringBootTest
@RunWith(SpringRunner.class)
public class TestFeignClient {

    @Autowired
    HelloworldFeignClient feignClient;
    @Test
    public void discoveryTest(){
        String hello = feignClient.callHello();
        System.out.println(hello);
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值