基础知识

  • 什么是服务治理
    Spring Cloud封装了 Netflix公司开发的 Eureka模块来实现服务治理
      在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
  • 什么是服务注册与发现
      Eureka采用了CS的设计架构, Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server来监控系统中各个微服务是否正常运行。

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

1610803428144.png

Eureka包含两个组件: Eureka ServerEureka和 Client

  • Eureka Server提供服务注册服务
      各个微服务节点通过配置启动后,会在 EurekaServer中进行注册,这样 Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
  • EurekaClient通过注册中心进行访问
      是一个Java客户端,用于简化 Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin-)负载算法的负载均衡器。在应用启动后,将会向 Eureka Server发送心跳(默认周期为30秒)。如果 Eureka Server在多个心跳周期内没有接收到某个节点的心跳, EurekaServer将会从服务注册表中把这个务节点移除(默认90秒)

EurekaServer服务端安装

引言

Eureka 又称 服务注册中心,全部服务都需要进行注册才能使用,也是微服务架构中必不可少的一个组件。

Eureka是服务注册中心,不是服务调用

Eureka 服务端

创建module

创建一个叫cloud-eureka-server7001的module

改pom

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>cloud2020</artifactId>
  7. <groupId>com.sgy.cloud2020</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>cloud-eureka-server7001</artifactId>
  12. <dependencies>
  13. <!-- eureka-server -->
  14. <dependency>
  15. <groupId>org.springframework.cloud</groupId>
  16. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  17. </dependency>
  18. <!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
  19. <dependency>
  20. <groupId>com.sgy.cloud2020</groupId>
  21. <artifactId>cloud-api-commons</artifactId>
  22. <version>${project.version}</version>
  23. </dependency>
  24. <!--监控-->
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-actuator</artifactId>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.projectlombok</groupId>
  31. <artifactId>lombok</artifactId>
  32. <optional>true</optional>
  33. </dependency>
  34. </dependencies>
  35. </project>

Cannot resolve org.springframework.cloud:spring-cloud-starter-netflix-eureka-server:unknown

首先 看一下错误:

1610803428177.png

  原因很简单,是因为没有指定依赖的版本号,但是我明明在父依赖中引入了spring-cloud-alibaba-dependencies,他会管理所有的组件版本号,那为什么没有管理改组件的版本号呢

这是因为我将<scope>import</scope>写成了<scope>runtime</scope>

应该改成如下

<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>

启动类

package com.sgy.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * Created by AaronShen on 2020/5/27
 */
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7001.class,args);
    }
}

改yml

server:
  port: 7001

eureka:
  instance:
    hostname: localhost #eureka服务端的实例名称
  client:
    # false表示不向注册中心注册自己
    register-with-eureka: false
    # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
    fetch-registry: false
    service-url:
      # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
      # 单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      # 相互注册
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server:
    #关闭自我保护模式,保证不可用服务被及时删除
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 2000

测试

访问: http://localhost:7001

1610803428210.png

现在还没有服务注册

EurekaClient端cloud-provider-paymen8090将注册进EurekaServe成为服务提供者

改pom

<?xml version="1.0" encoding="UTF-8"?>
<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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.sgy.cloud2020</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-payment8090</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.sgy.cloud2020</groupId>
            <artifactId>cloud-provider</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>


    </dependencies>
</project>

修改主启动类

package com.sgy.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * Created by AaronShen on 2020/5/26
 */
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8090 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8090.class,args);
    }
}

改yml

一定要写spring.application.name,因为服务注册是根据name进行注册的

server:
  port: 8090

spring:
  application:
    name: cloud-provider-payment
  datasource:
    username: blog
    password: 123456
    url: jdbc:mysql://192.168.200.10:3306/cloud?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&serverTimezone=GMT%2B8
    driver-class-name: com.mysql.cj.jdbc.Driver
    # 使用我们自己的druid数据源
    type: com.alibaba.druid.pool.DruidDataSource

    initialSize: 10 #初始化连接个数
    minIdle: 5    #最小连接个数
    maxActive: 500 #最大连接个数
    maxWait: 60000 #最大等待时间
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    #   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka
      #defaultZone: http://eureka7001.com:7001/eureka/

mybatis:
  # config-location和configuration不能同时配置,否则会抛出异常
  # 一般只配置configuration即可,会自动找到mybatis全局配置文件
  #  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mapper/*Mapper.xml
  # 对应实体类的路径,只能指定具体的包,多个配置可以使用英文逗号隔开
  type-aliases-package: com.sgy.payment
  configuration:
    # Mybatis SQL语句控制台打印
    #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 开启驼峰命名规则
    # 在数据库中字段可以采用驼峰命名规则,mybatis会把 下划线去掉并把下划线后面的首字母认为是大写
    map-underscore-to-camel-case: true

logging:
  level:
    # 注意注意注意 一定要修改成自己的包名
    com.sgy: debug
  file:
    path: log/
    name: log/com.sgy.payment-dev.log
    clean-history-on-start: true
  pattern:
    console: "%d{yyyy-MM-dd} [%thread] %-5level %logger{50} ===> %msg%n"
    file: "%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ===> %msg%n"

测试

浏览器访问:http://127.0.0.1:7001

1610803428251.png

EurekaClient端cloud-consumer-order80将注册进EurekaServe成为服务消费者)

改pom

<?xml version="1.0" encoding="UTF-8"?>
<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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.sgy.cloud2020</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-order80</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.sgy.cloud2020</groupId>
            <artifactId>cloud-consumer</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!--eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

改启动类

package com.sgy.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * Created by AaronShen on 2020/5/26
 */
@SpringBootApplication
@EnableEurekaClient
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
    }
}

修改yml

server:
  port: 80

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka
      #defaultZone: http://eureka7001.com:7001/eureka/
spring:
  application:
    name: cloud-order-service

测试

浏览器访问:http://127.0.0.1:7001

1610803428289.png

Eureka集群

集群原理说明

集群的搭建:互相注册,互相守望

1610803428383.png

问题:微服务RPC远程服务调用最核心的是什么

高可用,试想你的注册中心只有一个 only one,它出故障了那就呵呵()”了,会导致整个为服务环境不可用,所以

搭建Eureka集群

再创建一个cloud-eureka-server7002

改pom

<?xml version="1.0" encoding="UTF-8"?>
<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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.sgy.cloud2020</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-eureka-server7002</artifactId>
    <dependencies>
        <!-- eureka-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.sgy.cloud2020</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!--监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

    </dependencies>
</project>

改主启动类

package com.sgy.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * Created by AaronShen on 2020/5/27
 */
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer7002 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7002.class,args);
    }
}

改本地域名解析映射

127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

在命令行窗口输入ipconfig /flushdns执行,刷新本地的DNS缓存数据。

改yml [cloud-eureka-server7002]

server:
  port: 7002

eureka:
  instance:
    hostname: eureka7002.com #eureka服务端的实例名称
  client:
    # false表示不向注册中心注册自己
    register-with-eureka: false
    # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
    fetch-registry: false
    service-url:
      # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
      # 单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      # 相互注册
      defaultZone: http://eureka7001.com:7001/eureka/
  server:
    #关闭自我保护模式,保证不可用服务被及时删除
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 2000

改yml [cloud-eureka-server7001]

server:
  port: 7001

eureka:
  instance:
    hostname: eureka7001.com #eureka服务端的实例名称
  client:
    # false表示不向注册中心注册自己
    register-with-eureka: false
    # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
    fetch-registry: false
    service-url:
      # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
      # 单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      # 相互注册
      defaultZone: http://eureka7002.com:7002/eureka/
  server:
    #关闭自我保护模式,保证不可用服务被及时删除
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 2000

测试

1610803428419.png

1610803428451.png

订单支付两服务注册进Eureka集群

改支付服务pom

server:
  port: 8090

spring:
  application:
    name: cloud-payment-server

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka,http://eureka7001.com:7001/eureka/ #集群版
      #defaultZone: http://eureka7001.com:7001/eureka/

改订单服务pom

server:
  port: 80

spring:
  application:
    name: cloud-consumer-server

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka,http://eureka7001.com:7001/eureka/ #集群版
      #defaultZone: http://eureka7001.com:7001/eureka/

测试

1610803428491.png

1610803428526.png

Eureka自我保护

理论知识

  保护模式主要用于一组客户端和 Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,
Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
如果在 Eureka Server的首页看到以下这段提示,则说明 Eureka进入了保护模式:

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE

一句话解释

一句话:某时刻某一个微服务不可用了, Eureka不会立刻清理,依旧会对该微服务的信息进行保存

为什么会产生 Eureka自我保护机制?

  为了防止 EurekaClient可以正常运行,但是与 EurekaServer网络不通情况下, EurekaServer不会立刻将 Client服务剔除

什么是自我保护模式?

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

  自我保护机制:默认情况下 EurekaClientE定时向端发送心跳包,如果 Eurekaserver在端在一定时间内(默认90秒没有收到EurekaClient发送心跳包,便会直接从服务注册列表中剔除该服务,但是在短时间(90秒中)内丢失了大量的服务实例心跳,这时候 EurekaServer会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是 EurekaClient为出现宕机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误,因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的)

在自我保护模式中, Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。

  它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着

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

如何禁止自我保护

EurekaServer7001配置文件

server:
  port: 7001

eureka:
  instance:
    hostname: eureka7001.com #eureka服务端的实例名称
  client:
    # false表示不向注册中心注册自己
    register-with-eureka: false
    # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要检索服务
    fetch-registry: false
    service-url:
      # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
      # 单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      # 相互注册
      # 集群版本
      # defaultZone: http://eureka7002.com:7002/eureka/
      # 单机版本
      defaultZone: http://eureka7001.com:7001/eureka/
  server:
    #关闭自我保护模式,保证不可用服务被及时删除
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 2000

支付提供者8090的配置文件

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7002.com:7002/eureka,http://eureka7001.com:7001/eureka/ #集群版
      #defaultZone: http://eureka7001.com:7001/eureka/
  instance:
    instance-id: payment8090
    prefer-ip-address: true #访问路径可以显示ip地址
    # Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2

spring cloud 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。

  • 默认为90秒
  • 如果该值太大,则很可能将流量转发过去的时候,该instance已经不存活了。
  • 如果该值设置太小了,则instance则很可能因为临时的网络抖动而被摘除掉。
  • 该值至少应该大于leaseRenewalIntervalInSeconds

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

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

  • 默认30秒

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秒

测试环境参考配置

eureka server

eureka:
  server:
    enable-self-preservation: false           # 关闭自我保护模式(缺省为打开)
    eviction-interval-timer-in-ms: 5000       # 续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms)

eureka client

eureka:
  instance:
    lease-renewal-interval-in-seconds: 5      # 心跳时间,即服务续约间隔时间(缺省为30s)
    lease-expiration-duration-in-seconds: 10  # 发呆时间,即服务续约到期时间(缺省为90s)
  client:
    healthcheck:
      enabled: true                           # 开启健康检查(依赖spring-boot-starter-actuator)

zuul

eureka:
  client:
    registry-fetch-interval-seconds: 5 # 默认为30秒