【SpringCloud学习笔记】服务注册中心Eureka

/ 微服务 / 没有评论 / 1330浏览

什么是服务治理

SpringCloud封装了Netflix公司的Eureka模块来实现服务治理。

在传统的PRC远程方法调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要服务治理。管理服务于服务之间的依赖关系、可以实现服务调用、负载均衡、容错等,实现服务与注册。

Eureka简介

Eureka是Netflix中的一个开源框架。它和 zookeeper、Consul一样,都是用于服务注册管理的,同样,Spring-Cloud 还集成了Zookeeper和Consul。在项目中使用Spring Cloud Euraka的原因是它可以利用Spring Cloud Netfilix中其他的组件,如zull等,因为Euraka是属于Netfilix的。

Eureka 采用了CS设计架构,Eureka Server作为服务注册功能的服务器,她是服务注册中心。系统中的其他微服务,使用Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息,比如:服务地址、通讯地址等一别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用PRC远程调用框架核心设计实现。在于注册中心,因为注册中心管理每个服务与服务器之间依赖关系。在任何RPC远程框架中,都会有一个注册中心。

Eureka client既可以作为服务的生产者,又可以作为服务的消费者。具体结构如下图:

image-20210223090116189

Eureka组件

img

Eureka Server

提供服务注册功能,各个微服务节点通过配置启动后会在Eureka中进行注册。这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。

Eureka Client

通过注册中心访问,是一个JAVA客户端,用于简化和Server的交付,客户端同事也具备一个内置的,使用轮询负载算法的负载均衡器。在应用启动后;会将向Eureka Server发送心跳(默认30s)。若Server在多个心跳周期内没有接收到某个节点的心跳,Server将会从服务注册表中吧这个节点移除(默认90s)。

Eureka实践联系

Eureka Server

  1. 创建maven工程

    创建一个名为:cloud-eureka-server-7001 的普通maven工程

  2. 配置pom.xml

        <artifactId>cloud-eureka-server-7001</artifactId>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
            </dependency>
            <!-- boot web actuator -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
        </dependencies>
    
  3. 配置application.yml

    创建并配置application.yml

    server:
      port: 7001
    eureka:
      instance:
        appname: localhost
      client:
        register-with-eureka: false
        fetch-registry: false
        service-url:
          defaultZone: http://${eureka.instance.appname}:${server.port}/eureka/
      server:
        enable-self-preservation: false
        eviction-interval-timer-in-ms: 2000
    
  4. 编写启动类

    编写CloudEurekaServerApplication启动类

    @SpringBootApplication
    @EnableEurekaServer //开启Eureka服务器端
    public class CloudEurekaServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(CloudEurekaServerApplication.class,args);
        }
    
    }
    
  5. 启动程序并访问后台

    2021-02-22 13:47:37.934  INFO 3877 --- [           main] c.n.eureka.DefaultEurekaServerContext    : Initializing ...
    2021-02-22 13:47:37.940  INFO 3877 --- [           main] c.n.eureka.cluster.PeerEurekaNodes       : Adding new peer nodes [http://localhost:7001/eureka/]
    2021-02-22 13:47:38.200  INFO 3877 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON encoding codec LegacyJacksonJson
    2021-02-22 13:47:38.200  INFO 3877 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using JSON decoding codec LegacyJacksonJson
    2021-02-22 13:47:38.200  INFO 3877 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML encoding codec XStreamXml
    2021-02-22 13:47:38.200  INFO 3877 --- [           main] c.n.d.provider.DiscoveryJerseyProvider   : Using XML decoding codec XStreamXml
    2021-02-22 13:47:38.577  INFO 3877 --- [           main] c.n.eureka.cluster.PeerEurekaNodes       : Replica node URL:  http://localhost:7001/eureka/
    2021-02-22 13:47:38.597  INFO 3877 --- [           main] c.n.e.registry.AbstractInstanceRegistry  : Finished initializing remote region registries. All known remote regions: []
    2021-02-22 13:47:38.600  INFO 3877 --- [           main] c.n.eureka.DefaultEurekaServerContext    : Initialized
    2021-02-22 13:47:38.623  INFO 3877 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
    2021-02-22 13:47:38.836  INFO 3877 --- [           main] o.s.c.n.e.s.EurekaServiceRegistry        : Registering application LOCALHOST with eureka with status UP
    2021-02-22 13:47:38.843  INFO 3877 --- [      Thread-39] o.s.c.n.e.server.EurekaServerBootstrap   : Setting the eureka configuration..
    2021-02-22 13:47:38.845  INFO 3877 --- [      Thread-39] o.s.c.n.e.server.EurekaServerBootstrap   : Eureka data center value eureka.datacenter is not set, defaulting to default
    2021-02-22 13:47:38.847  INFO 3877 --- [      Thread-39] o.s.c.n.e.server.EurekaServerBootstrap   : Eureka environment value eureka.environment is not set, defaulting to test
    2021-02-22 13:47:38.897  INFO 3877 --- [      Thread-39] o.s.c.n.e.server.EurekaServerBootstrap   : isAws returned false
    2021-02-22 13:47:38.900  INFO 3877 --- [      Thread-39] o.s.c.n.e.server.EurekaServerBootstrap   : Initialized server context
    2021-02-22 13:47:38.900  INFO 3877 --- [      Thread-39] c.n.e.r.PeerAwareInstanceRegistryImpl    : Got 1 instances from neighboring DS node
    2021-02-22 13:47:38.900  INFO 3877 --- [      Thread-39] c.n.e.r.PeerAwareInstanceRegistryImpl    : Renew threshold is: 1
    2021-02-22 13:47:38.901  INFO 3877 --- [      Thread-39] c.n.e.r.PeerAwareInstanceRegistryImpl    : Changing status to UP
    2021-02-22 13:47:38.931  INFO 3877 --- [      Thread-39] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
    2021-02-22 13:47:39.437  INFO 3877 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 7001 (http) with context path ''
    2021-02-22 13:47:39.441  INFO 3877 --- [           main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 7001
    2021-02-22 13:47:39.781  INFO 3877 --- [           main] i.h.cloud.CloudEurekaServerApplication   : Started CloudEurekaServerApplication in 17.963 seconds (JVM running for 26.447)
    2021-02-22 13:47:40.115  INFO 3877 --- [on(6)-127.0.0.1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
    2021-02-22 13:47:40.115  INFO 3877 --- [on(6)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
    2021-02-22 13:47:40.131  INFO 3877 --- [on(6)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 15 ms
    

启动成功后访问界面:

image-20210222135804112

Eureka Client

对之前的项目进行Eureka 客户端改造

  1. 修改pom.xml

            <!-- dependencies 中添加一个eureka客户端的配置 -->
    				<dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
  2. 修改application.yml配置

    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:7001/eureka
    
  3. 修改启动类

    @SpringBootApplication
    @EnableEurekaClient //添加该注解启动Eureka客户端
    public class CloudProviderPaymentApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(CloudProviderPaymentApplication.class,args);
        }
    
    }
    
  4. 启动项目后查看后台界面

    image-20210222141445512

另外一个消费者项目改造类似,在此不赘述。

Eureka集群搭建

为了达到服务的高可用,通常都采用集群的方式。

搭建Eureka集群

在之前Eureka Server 7001的基础上在创建一个服务器端,具体操作如下:

准备工作

修改hosts文件添加两个域名映射。

# Add by huzd for eureka culster demo
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

创建Eureka Server 7002

复制7001修改如下配置:

修改pom.xml 文件
<artifactId>cloud-eureka-server-7002</artifactId>

修改application.yml

server:
  port: 7002

eureka:
  instance:
    appname: eureka7002.com
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
  server:
    enable-self-preservation: true
    eviction-interval-timer-in-ms: 2000
spring:
  application:
    name: CLOUD-EUREKA-SERVER-7002

启动类无需修改。

更新Eureka Server 7001

修改application.yml
server:
  port: 7001

eureka:
  instance:
    appname: eureka7001.com
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka/
  server:
    enable-self-preservation: true
    eviction-interval-timer-in-ms: 2000
spring:
  application:
    name: CLOUD-EUREKA-SERVER-7001

启动两个Eureka服务器端

访问后台管理

界面如下图所示:

image-20210223095658586

image-20210223095757942

服务挂载Eureka集群

支付模块挂载Eureka集群

只需要修改applicaton.yml配置即可

server:
  port: 8001
  servlet:
    context-path: /

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
    # 集群方式配置
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
#      defaultZone: http://localhost:7001/eureka
    healthcheck:
      enabled: true

spring:
  application:
    name: cloud-provider-payment-8001
  datasource:
    url: jdbc:mysql://localhost:3306/cloud?useUnicode=true&characterEncoding=utf-8&useSSL=false
    type: com.alibaba.druid.pool.DruidDataSource
    username: root
    password: sitechdss
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: info.huzd.cloud.bean
logging:
  level:
   root: debug

消费者模块挂载Eureka集群

同上

启动集群挂载服务

启动顺序:

  1. 启动集群节点7001
  2. 启动集群节点7002
  3. 启动支付模块
  4. 启动消费模块

启动后访问控制台截图如下所示:

image-20210223110740234

测试服务

访问:http://localhost/consumer/payment/get/7 一切正常。

搭建服务提供者集群

我在之前8001服务器提供者程序的基础上复制创建8002服务提供者

创建maven项目cloud-provider-payment-8002

复制cloud-provider-payment-8001 pom.xml的依赖配置、复制cloud-provider-payment-8001项目的src。

在调用的controller中添加端口信息

    @Value("${server.port}")
    Integer port;  //注入配置文件端口号

    @PostMapping("/save")
    @ResponseBody
    public CommonResult save(@RequestBody Payment payment) {
        Integer result = paymentService.save(payment);
        CommonResult cr = null;
        if(result>0){
            cr = new CommonResult(200,"添加成功(服务端口:"+port+")",result); //返回信息中添加端口信息。
        }else{
            cr = new CommonResult(400,"添加失败(服务端口:"+port+")",null);
        }
        log.debug("payment create success!!");
        return cr;
    }

修改application.yml

server:
  port: 8002  #端口号改为8002;其他保持与cloud-provider-payment-8001配置一致。
  servlet:
    context-path: /

启动集群环境

启动顺序:

  1. 启动集群节点7001
  2. 启动集群节点7002
  3. 启动支付模块8001
  4. 启动支付模块8002
  5. 启动消费模块

改写消费者调用地址

修改cloud-consumer-order-80

@Controller
@RequestMapping("/consumer")
@Slf4j
public class OrderController {

    public static final String PAYMENT_URL = "http://CLOUD-PROVIDER-PAYMENT/";
//    public static final String PAYMENT_URL = "http://localhost:8001/payment/";

    @Resource
    RestTemplate restTemplate;

    @GetMapping("/payment/save")
    @ResponseBody
    public CommonResult<Payment> createPayment(Payment payment){
        System.out.println("请求地址:"+PAYMENT_URL+"save");
        return restTemplate.postForObject(PAYMENT_URL+"/payment/save",payment,CommonResult.class);
    }

    @GetMapping("/payment/get/{id}")
    @ResponseBody
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    }

}

添加负载均衡注解

@Configuration
public class ApplicationConfig {

    @Bean
    @LoadBalanced //添加负载均衡注解
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

启动测试

启动消费者80项目,在浏览器中访问:http://localhost/consumer/payment/get/1 会看见调用返回信息中端口地址交替的变化。

Actuator信息完善

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
#      defaultZone: http://localhost:7001/eureka
    healthcheck:
      enabled: true
  instance:
    appname: cloud-provider-payment  			 #在后台管理系统中显示的名称 如下图所示。这里如果为空默认使用 spring.application.name 
    instance-id: cloudProviderPayment8002  #唯一的实例名称 在系统中status列中显示
    prefer-ip-address: true #鼠标放在status连接上时候专为真是IP地址。

image-20210223194648076

服务发现

开启服务发现功能

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient #开启服务发现功能
public class CloudProviderPayment8001Application {

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

}

在controller中查看相关信息

    @Resource
    DiscoveryClient discoveryClient;

    @GetMapping("/discovery")
    @ResponseBody
    public Object discovery(){
        discoveryClient.getServices().stream().forEach(serviceName->{
            System.out.println("ServiceName:"+serviceName);
            discoveryClient.getInstances(serviceName).stream().forEach(s->{
                System.out.println("Service["+serviceName+"]:"+s.getHost()+":"+s.getPort()+" - "+s.getInstanceId() + " = "+s.getUri());
            });
        });

        return discoveryClient;
    }

在练习测试的过程中发现了一些问题。 server.application.name 和 eureka.instance.appname 命名的优先级机制等。

配置不当会造成上面代码第10行,无法获取相关信息。

运行结果截图:

image-20210224151937350

自我保护机制

简介

默认情况下,如果EurekaServer在一定时间内没有接受到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险--因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题--当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。

机制

Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,但是在保护期内如果服务刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。

我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka 的保护机制,一旦开启了保护机制,则服务注册中心维护的服务实例就不是那么准确了,此时我们可以使用eureka.server.enable-self-preservation=false来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除(不推荐)。

自我保护模式被激活的条件是:在 1 分钟后,Renews (last min) < Renews threshold

这两个参数的意思:

如何关闭

服务器端设置

配置:eureka.server.enable-self-preservation & eureka.server.eviction-interval-timer-in-ms

server:
  port: 7001
eureka:
	....
  server:
    enable-self-preservation: false  #关闭自我保护机制
    eviction-interval-timer-in-ms: 2000 #设置服务器端有效性检查时间间隔为2秒
spring:
  application:
    name: CLOUD-EUREKA-SERVER-7001

客户端设置

配置eureka.instance.lease-renewal-interval-in-seconds & eureka.instance.lease-expiration-duration-in-seconds

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
#      defaultZone: http://localhost:7001/eureka
    healthcheck:
      enabled: true
  instance:
    instance-id: cloudProviderPayment8001
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 1 #eureka客户端向服务端发送心跳的间隔,单位秒,默认30
    lease-expiration-duration-in-seconds: 2  #eureka服务端在收到最后一次心跳后等待时间上限,单位为秒,默认90,,超时将剔除服务

启动测试

  1. 启动server7001

  2. 启动server7002

  3. 查看eureka控制台

    新的提示 HE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.

  4. 启动payment8001

  5. 启动payment8002

  6. 查看eureka控制台,

  7. 停止payment8001

  8. 刷新eureka控制台

配置详解

eureka.client.registry-fetch-interval-seconds

表示eureka client间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒

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

leaseExpirationDurationInSeconds,表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance。

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

leaseRenewalIntervalInSeconds,表示eureka client发送心跳给server端的频率。如果在leaseExpirationDurationInSeconds后,server端没有收到client的心跳,则将摘除该instance。除此之外,如果该instance实现了HealthCheckCallback,并决定让自己unavailable的话,则该instance也不会接收到流量。

eureka.server.enable-self-preservation

是否开启自我保护模式,默认为true。

默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。

Eureka通过“自我保护模式”来解决这个问题——当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。

综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

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

eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒