服务发现和注册和Eureka

Spring Cloud和云计算没有关系,只是一个基于Spring Boot的快速构建分布式系统的工具集。

 

一 Spring Cloud特点

# 约定优于配置

# 开箱即用,快速启动

# 适用于各种环境,可以部署在PC server或者 云环境

# 轻量级的组件

# 组件的支持很丰富,功能齐全

# 选型中立

 

二 服务提供者和服务消费者

 

三 服务发现和注册

为什么需要服务注册与发现

# 服务重启或者升级后IP地址变化

# 水平伸缩后服务实例的变化

# 同一个节点运行多个服务

所以需要一种注册机制,帮助我们去获取响应。

 

核心机制:

将实例的信息注册到注册中心

调用者通过注册中心查找服务

调用者获取服务实例列表

调用者通过负载均衡通信

 

3.1 基本流程

服务发现和注册和Eureka_第1张图片

首先:服务消费者和服务注册者向服务发现组件注册

其次:服务消费者要调用的时候会从服务发现组件中进行查询

 

 

需求:

# 每一个服务实例都会在启动的时候通过HTTP/REST或者Thrift等方式发布远程API

# 服务端实例的具体数量及位置会发生动态变化

# 虚拟机与容器通常会被分配动态IP地址

 

 

3.2 服务发现组件的功能

# 服务注册表: 是一个记录当前可用服务实例的网络信息的数据库,是服务发现机制的核心。服务注册表提供查询API和管理API,使用API 获得可用的服务实例,使用管理API实现注册和注销。

# 服务注册

# 健康检查

 

3.3 服务发现的方式

3.3.1 客户端发现

它的主要特点是客户端决定服务实例的网络位置,并且对请求进行负载均衡。客户端查询服务注册表(可用服务实例数据库),使用负载均衡算法选择一个实例,并发出请求。典型代表Eureka或者ZK

服务发现和注册和Eureka_第2张图片

客户端发现模式的优缺点

优点:

不需要很多的网络跳转

缺点:

客户端和服务注册表耦合

需要为应用程序每一种编程语言、框架等建立客户端发现逻辑,比如 Netflix Prana就为非JVM客户端提供一套基于HTTP代理服务发现方案

 

# 服务器端发现

向某一服务发送请求,客户端会通过向运行位置已知的路由器或者负载均衡器发送请求。他们会查询服务注册表,并向可用的服务实例转发该请求。典型代表Consul + Nginx

服务发现和注册和Eureka_第3张图片

服务器端发现模式优缺点:

优点:

客户端无需实现发现功能,只需要向路由器或者负载均衡器发送请求即可

缺点:

除非成为云环境的一部分,否则该路由机制必须作为另一系统组件进行安装与配置。为实现可用性和一定的接入能力,还需要为其配置一定数量的副本。

相较于客户端发现,服务器端发现机制需要更多的网络跳转。

 

3.4 服务发现组件Eureka

3.4.1 什么是Eureka

 

Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。Spring Cloud将它集成在其他子项目spring-cloud-netflix中,以实现spring cloud服务发现功能。

 

3.4.2 Eureka原理

先需要明白AWS几个概念:

Region: AWS云服务在全球不同的地方都有数据中心,比如北美、南美和欧洲亚洲等。与此对应,根据地理位置我们把某个地区的基础设施服务集合称为一个区域。不同区域之间是相互独立的。说白了就类似于不同地方的机房。

Available Zone: 基于容灾背景提出,简单而言,就是相同region区域不同的机房

 

# 首先是服务注册到Eureka

# 每30s发送心跳检测重新进行租约,如果客户端不能多次更新租约,它将在90s内从服务器注册中心移除。

# 注册信息和更新会被复制到其他Eureka 节点,来自任何区域的客户端科可以查找到注册中心信息,每30s发生一次复制来定位他们的服务,并进行远程调用

# 客户端还可以缓存一些服务实例信息,所以即使Eureka全挂掉,客户端也是可以定位到服务地址的

 

服务发现和注册和Eureka_第4张图片

3.4.3 搭建Euraka Server

首先:搭建parent项目

<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.0http://maven.apache.org/xsd/maven-4.0.0.xsd">

      <modelVersion>4.0.0modelVersion>

 

      <modules>

           <module>microservice-consumermodule>

           <module>microservice-providermodule>

           <module>microservice-discovery-eurekamodule>

      modules>

      <groupId>com.microservicegroupId>

      <artifactId>microserviceartifactId>

      <version>1.0-SNAPSHOTversion>

      <packaging>pompackaging>

 

      <parent>

           <groupId>org.springframework.bootgroupId>

           <artifactId>spring-boot-starter-parentartifactId>

           <version>1.5.6.RELEASEversion>

      parent>

 

      <dependencyManagement>

           <dependencies>

                 <dependency>

                      <groupId>org.springframework.cloudgroupId>

                      <artifactId>spring-cloud-dependenciesartifactId>

                      <version>Dalston.SR3version>

                      <type>pomtype>

                      <scope>importscope>

                 dependency>

           dependencies>

      dependencyManagement>

      <dependencies>

           <dependency>

                 <groupId>org.springframework.cloudgroupId>

                 <artifactId>spring-cloud-starter-configartifactId>

           dependency>

           <dependency>

                 <groupId>org.springframework.cloudgroupId>

                 <artifactId>spring-cloud-starter-eurekaartifactId>

           dependency>

 

           <dependency>

                 <groupId>mysqlgroupId>

                 <artifactId>mysql-connector-javaartifactId>

                 <version>5.1.44version>

           dependency>

      dependencies>

      <build>

           <finalName>${project.artifactId}finalName>

           <plugins>

                

                 <plugin>

                      <groupId>org.apache.maven.pluginsgroupId>

                      <artifactId>maven-resources-pluginartifactId>

                      <configuration>

                            <encoding>UTF-8encoding>

                      configuration>

                 plugin>

                

                 <plugin>

                      <groupId>org.apache.maven.pluginsgroupId>

                      <artifactId>maven-compiler-pluginartifactId>

                      <configuration>

                            <source>1.8source>

                            <target>1.8target>

                            <encoding>UTF-8encoding>

                      configuration>

                 plugin>

                 <plugin>

                      <groupId>org.springframework.bootgroupId>

                      <artifactId>spring-boot-maven-pluginartifactId>

                 plugin>

           plugins>

           <pluginManagement>

                 <plugins>

                     

                      <plugin>

                            <groupId>org.apache.tomcat.mavengroupId>

                            <artifactId>tomcat7-maven-pluginartifactId>

                            <version>2.2version>

                      plugin>

                 plugins>

           pluginManagement>

      build>

project>

 

其次:搭建Euraka Server

<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.0http://maven.apache.org/xsd/maven-4.0.0.xsd">

      <modelVersion>4.0.0modelVersion>

      <packaging>warpackaging>

 

      <parent>

           <artifactId>microserviceartifactId>

           <groupId>com.microservicegroupId>

           <version>1.0-SNAPSHOTversion>

      parent>

 

      <artifactId>microservice-discovery-eurekaartifactId>

 

      <dependencies>

           <dependency>

                 <groupId>org.springframework.cloudgroupId>

                 <artifactId>spring-cloud-starter-eureka-serverartifactId>

           dependency>

           <dependency>

                 <groupId>org.springframework.bootgroupId>

                 <artifactId>spring-boot-starter-securityartifactId>

           dependency>

      dependencies>

project>

 

然后:在application.xml中配置application.yml文件

security:

  basic:

    enabled: true

  user:

    name: user

    password: password

server:

  port: 8761

eureka:

  client:

    register-with-eureka: false

    fetch-registry: false

    service-url:

      defaultZone:http://user:password@localhost:8761/eureka

 

   

最后:创建EurekaApplication,并添加@SpringBootApplication

@EnableEurekaServer

public class EurakaApplication{

      public static void main(String[] args) throws Exception {

           SpringApplication.run(EurakaApplication.class, args);

      }

 

}

 

就可以启动Eureka Server了

3.4 将微服务注册到Eureka上

首先:确认当前maven环境下是否引入了Eureka相关的配置,并且添加如下依赖

           <dependency>

                 <groupId>org.springframework.bootgroupId>

                 <artifactId>spring-boot-starter-actuatorartifactId>

           dependency>

其次:在微服务启动类上添加@EnableEurekaClient注解,使得他成为一个Eureka Client,在启动的 时候就向Eureka Server注册。

然后:在微服务的应用程序中application.yml配置文件中,我们不要添加如下配置,因为这表示是服务器,客户端我们是需要向Eureka注册的

eureka:

  client:

    register-with-eureka: false

fetch-registry: false

应该可以的配置有健康检查和路径配置

eureka:

  client:

    healthcheck:

      enabled: true

    service-url:

      defaultZone:http://nicky:123abcABC@localhost:8761/eureka

最后:在启动类添加@EnableEurekaClient注解,表示这个类可以作为Eureka 客户端,启动之后可以向Eureka注册

@SpringBootApplication

@EnableEurekaClient

public class UserServiceRunner{

      public static void main(String[] args) throws Exception {

           SpringApplication.run(UserServiceRunner.class, args);

      }

}

 

3.5 Eureka配置项

eureka.client.allow-redirects:是否允许重定向Eureka客户端请求到其他或者备份服务器,默认为fasle

eureka.client.eureka-connection-idle-timeout-seconds:HTTP连接到eureka服务器可以在关闭之前保持空闲的时间(几秒钟)。

eureka.client.eureka-server-connect-timeout-seconds:表示连接Eureka服务器,等待多长时间算超时

eureka.client.eureka-server-port: Eureka Server端口

eureka.client.eureka-server-d-n-s-name:获取要查询的DNS名称以获得eureka服务器的列表。

eureka.client.eureka-server-read-timeout-seconds:示在从eureka服务器读取数据之前需要等待多长时间(以秒为单位)

eureka.client.eureka-server-total-connections:从eureka客户端到所有eureka服务器的所允许连接总数。

eureka.client.eureka-server-total-connections-per-host:设置每一个主机所允许的到Eureka Server连接的数量

eureka.client.fetch-registry: 是否允许客户端向Eureka 注册表获取信息,一般服务器为设置为false,客户端设置为true

eureka.client.register-with-eureka:是否允许向Eureka Server注册信息,默认true,如果是服务器端,应该设置为false

eureka.client.fetch-remote-regions-registry:逗号分隔的区域列表,用于获取eureka注册信息

eureka.client.g-zip-content:从服务器端获取数据是否需要压缩

eureka.client.prefer-same-zone-eureka: 是否优先使选择相同Zone的实例,默认为true

eureka.client.registry-fetch-interval-seconds:多长时间从Eureka Server注册表获取一次数据,默认30s

eureka.client.service-url:可用区域映射,列出完全合格的url与eureka服务器通信。每个值可以是一个URL,也可以是一个逗号分隔的替代位置列表。

eureka.dashboard.enabled: 是否启用Eureka首页,默认为true

eureka.dashboard.path: 默认为/

eureka.instance.appname:在eureka注册的应用程序的名称。

eureka.instance.app-group-name:在eureka注册的应用程序的组名称

eureka.instance.health-check-url: 健康检查绝对路径

eureka.instance.health-check-url-path:健康检查相对路径

eureka.instance.hostname:设置主机名

eureka.instance.instance-id:设置注册实例的id

eureka.instance.lease-expiration-duration-in-seconds:设置多长时间意味着租约到期,默认90

eureka.instance.lease-renewal-interval-in-seconds:表示Eureka客户端需要发送心跳到eureka服务器的频率(以秒为单位),以表明它仍然存在。指定的期间内如果没有收到心跳leaseExpirationDurationInSeconds

eureka.instance.metadata-map:可以设置元数据

eureka.instance.prefer-ip-address: 实例名以IP,但是建议hostname,默认为false

 

四 负载均衡

4.1 Ribbon的介绍和架构

实现负载均衡,我们可以通过服务器端和客户端做负载均衡。服务器端做负载均衡,比如可以使用Nginx。而客户端做负载均衡,就是客户端有一个组件,知道有哪些可用的微服务,实现一个负载均衡的算法。

 

Ribbon工作流程主要分为两步:

第一:先选择Eureka Server,优先选择在同一个Zone且负载较少的Server;

第二:再根据用户指定的策略,再从server取到的服务注册列表中选择一个地址。其中Ribbon提供了很多种策略,例如轮询round bin,随机Random,根据响应时间加权。

服务发现和注册和Eureka_第5张图片

4.2 使用Ribbon进行负载均衡

4.2.1 基本用法

要使用Ribbon进行负载均衡,那么就需要引入对应的依赖,如果已经因如果Eureka的依赖,那么就不需要再次引入Ribbon的依赖了。

           <dependency>

                 <groupId>org.springframework.cloudgroupId>

                 <artifactId>spring-cloud-starter-ribbonartifactId>

           dependency>

然后在启动类上加上注解@LoadBalanced,则负载均衡生效。

@SpringBootApplication

@EnableEurekaClient

public classMovieServiceRibbonRunner {

      @Bean

      @LoadBalanced

      public RestTemplaterestTemplate(){

           return new RestTemplate();

      }

 

      public static void main(String[] args) {

           SpringApplication.run(MovieServiceRibbonRunner.class, args);

      }

}

 

我们可以修改application.yml文件的instance_id属性:

eureka:

  client:

    healthcheck:

      enabled: true

    service-url:

      defaultZone:http://nicky:123abcABC@localhost:8761/eureka

  instance:

instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}

 

在消费者Controller中,访问虚拟的ip,即我们微服务的名称

@RestController

public class MovieController {

     

      @Autowired

      private RestTemplate restTemplate;

     

      @GetMapping("/movie/{id}")

      public User findById(@PathVariable Long id) {

 

           returnthis.restTemplate.getForObject("http://microservice-provider-user/user/"+id, User.class);

      }

}

 

最后启动两个服务提供者实例,可以修改端口实现。

4.2.2 通过代码自定义配置Ribbon

首先,该类需要加上@Configuration注解,但是注意,如果加上这个注解,他就不能包含在注解@ComponentScan或者@SpringBootApplication所指定包扫描路径。

@Configuration

public classRibbonTestConfiguration {

      @Autowired

      IClientConfig config;

     

      @Bean

      public IRuleribbonRule(IClientConfig config){

           return new RandomRule();

      }

}

其次:在启动类上加上@RibbonClient注解,指定名称和configuration文件类

@SpringBootApplication

@EnableEurekaClient

@RibbonClient(name="microservice-provider-user",configuration=RibbonTestConfiguration.class)

public classMovieServiceRunner {

      @Bean

      @LoadBalanced

      public RestTemplaterestTemplate(){

           return new RestTemplate();

      }

 

      public static void main(String[] args) {

           SpringApplication.run(MovieServiceRunner.class, args);

      }

}

 

4.2.3 通过配置文件自定义Ribbon

即我们可以通过yml或者properties配置文件,进行配置,然后使用我们配置的选项。

注意:这里有些优先级的顺序问题:

配置文件定义的优先级大于通过使用Java代码@RibbonClient的优先级大于使用spring默认的优先级

支持以下属性:

NFLoadBalancerClassName:应该实现 ILoadBalancer

NFLoadBalancerRuleClassName:应该实现 IRule

NFLoadBalancerPingClassName:应该实现IPing

NIWSServerListClassName:应该实现 ServerList

NIWSServerListFilterClassName:应该实现 ServerListFilter

 

application.yml中配置如下:

microservice-provider-user:

  ribbon:

    NFLoadBalancerRuleClassName:com.netflix.loadbalancer.RandomRule

 

五 Feign (声明式的REST Client)

5.1 简介及基础使用

Feign是一个声明式的web服务客户端,它使得写web服务客户端更加容易。使用Feign创建一个接口并对其进行注解。它具有可插拔的注解支持,包括Feign自己的注解以及jax-rs注释。Feign还支持可插拔的的编码器和解码器。Spring Cloud增加了对Spring MVC注解的支持,并使用了在Spring Web中默认使用的相同的HttpMessageConverters。Spring Cloud集成了Ribbon和灵感,在使用时提供了负载均衡的http客户端。

 

加入Feign的依赖到Maven

      org.springframework.cloud

      spring-cloud-starter-feign

然后创建接口,并且添加注解@EnableFeignClients

@SpringBootApplication

@EnableEurekaClient

@EnableFeignClients

public classMovieServiceRunner {

      public static void main(String[] args) {

           SpringApplication.run(MovieServiceRunner.class, args);

      }

}

 

写真正的接口去调用其他微服务,以供本应用调用

创建接口:

首先在接口加上注解@FeignClient,指定需要调用微服务名字

其次:如果启动的时候,提示Http Method不正确我们需要使用以前的老的注解,即:

@RequestMapping(method= RequestMethod.GET, value = "/user/{id}"),而不能使用:

@GetMapping("/user/{id}")

 

然后:如果提示PathVariableannotation was empty on param 0,那么我们就需要

 

还有就是:微服务提供方和消费者两边的HTTP 方法必须最好一致,如果一边是GET 请求,另外一边是POST请求,就会报错。而且如果传递的是一个负载对象,即使指定的是GET请求,也会作为POST请求,一般做法就是不传递对象,而是传递单个参数

比如@ReqquestParam(“name”) String name,@ReqquestParam(“age”) int age之类的

 

public UserfindById(@PathVariable("id") Long id); 而不是:

public UserfindById(@PathVariable Long id); 就可以

这相当于是Feign的坑吧

@FeignClient("microservice-provider-user")

public interface UserFeignClient {

      @GetMapping("/user/{id}")

      public User findById(@PathVariable Long id);

}

 

然后在其他类中就可以调用了:

@RestController

public class MovieController {

 

      @Autowired

      private UserFeignClient userFeignClient;

 

      @GetMapping("/movie/{id}")

      public User findById(@PathVariable Long id) {

 

           return userFeignClient.findById(id);

      }

}

 

5.2 覆写Feign的配置

Spring Cloud允许你完全控制Feign 客户端通过声明一些额外的注解,即@FeignClient(name = "stores",configuration = FooConfiguration.class)

 

一般来说,我们不需要使用在自定义的FooConfiguration上使用@Configuration组件,如果要使用我们需要把它从包含@ComponentScan或者@SpringBootApplication注解所扫描的包中排除掉。和Ribbon类似。

 

假设我们现在定义了一个Configuration1这个类,使用feign默认的契约,而不是使用SpringMvcContract,即:

@Configuration

public class Configuration1 {

      @Bean

      public ContractfeignContract() {

 

           return newfeign.Contract.Default();

      }

}

然后在Feign的接口处,指定@FeignClient的configuration,重写配置,使用刚才定义的Configuration1,即:

@FeignClient(name="microservice-provider-user",configuration=Configuration1.class)

public interface UserFeignClient {

 

      @GetMapping("/user/{id}")

      public User findById(@PathVariable("id") Long id);

}

但是在这儿我们依然用的是SpringMVC的注解,GetMapping,这里虽然没有报错,但是启动的时候就会报错了。比如java.lang.IllegalStateException: MethodfindById not annotated with HTTP method type (ex. GET, POST)

所以,接口处我们应该替换为Feign自己的注解。

@FeignClient(name="microservice-provider-user",configuration=Configuration1.class)

public interface UserFeignClient {

 

      @RequestLine("GET /user/{id}")

      public User findById(@Param("id") Long id);

}

 

 

Feign支持请求响应GZIP压缩:

feign.compression.request.enabled=true

feign.compression.response.enabled=true

 

5.3 feign的日志

Feign的默认日志级别是DEBUG 级别。

@Bean

Logger.Level feignLoggerLevel(){

      return Logger.Level.FULL;

}

 

六 常见问题解决方案

6.1 Eureka环境以及cloud配置

Eureka可以运行AWS环境和非AWS环境上,Eureka也可以以测试环境和生产环境运行。

如果需要运行在AWS环境上,则需要通过-Deureka.datacenter=cloud指定运行在AWS上,在yml中我们可以通过eureka.datacenter:cloud指定。

eureka.datacenter:cloud

如果需要运行在测试或者生产环境,我们需要通过-Deureka.environment来指定。如果指定测试环境eureka.environment: test, 如果运行在生产环境,则指定eureka.environment:product

 

6.2 自我保护提示

 

6.3 Eureka注册服务慢的问题如何解决

作为实例还涉及到与注册中心的周期性心跳,默认持续时间为30秒(通过serviceUrl)。在实例、服务器、客户端都在本地缓存中具有相同的元数据之前,服务不可用于客户端发现(所以可能需要3次心跳)。你可以使用eureka.instance.leaseRenewalIntervalInSeconds配置,这将加快客户端连接到其他服务的过程。

在生产中,最好坚持使用默认值,因为在服务器内部有一些计算,他们对续约做出假设。

 

6.4如何解决Eureka Server不踢出已关停的节点的问题

服务器端:

# 关闭自我保护

eureka.server.enable-self-preservation:false

# 缩小清理间隔(单位毫秒,默认是60*1000)

eureka.server.eviction-interval-timer-in-ms:  5000

 

客户端:

开启健康检查(需要spring-boot-starter-actuator依赖)

eureka.client.healthcheck.enabled= true    

租期更新时间间隔(默认30秒)

eureka.instance.lease-renewal-interval-in-seconds=10

租期到期时间(默认90秒)

eureka.instance.lease-expiration-duration-in-seconds=30

 

6.5 Eureka HA配置(假设三个节点)

6.5.1 在hosts文件中加入如下配置

127.0.0.1  peer1

127.0.0.1  peer2

127.0.0.1  peer3

 

6.5.2 在application.yml中加入以下配置

---

spring:

  profiles: peer1

  application:

    name: EUREKA-HA

server:

  port: 8761

eureka:

  instance:

    hostname: peer1

  client:

    serviceUrl:

      defaultZone:http://peer2:8762/eureka/,http://peer3:8763/eureka/

---

spring:

  profiles: peer2

  application:

    name: EUREKA-HA

server:

  port: 8762

eureka:

  instance:

    hostname: peer2

  client:

    serviceUrl:

      defaultZone:http://peer1:8761/eureka/,http://peer3:8763/eureka/

---

spring:

  profiles: peer3

  application:

    name: EUREKA-HA

server:

  port: 8763

eureka:

  instance:

    hostname: peer3

  client:

    serviceUrl:

      defaultZone:http://peer1:8761/eureka/,http://peer2:8762/eureka/


你可能感兴趣的:(微服务/spring,cloud)