Spring Cloud 入门之Eureka

8/18/2021 Spring Cloud

# Eureka

# Eureka 介绍

# 整体介绍

  • 背景:在传统应用中,组件之间的调用,通过有规范的约束的接口来实现,从而实现不同模块间良好的协作。但是被拆分成微服务后,每个微服务实例的网络地址都可能动态变化,数量也会变化,使得原来硬编码的地址失去了作用。需要一个中心化的组件来进行服务的登记和管理。

  • 概念:实现服务治理,即管理所有的服务信息和状态。

注册中心相当于买票乘车,只看有没有票(有没有服务),有就去买票(获取注册列表),然后乘车(调用)。不必关心有多少火车在运行

  • 注册中心好处:不用关心有多少提供方。

  • 注册中心有哪些:Eureka,Nacos,Consul,Zookeeper等。

  • 服务注册与发现包括两部分,一个是服务器端,另一个是客户端。

    • Server是一个公共服务,为Client提供服务注册和发现的功能,维护注册到自身的Client的相关信息,同时提供接口给Client获取注册表中其他服务的信息,使得动态变化的Client能够进行服务间的相互调用。

    • Client将自己的服务信息通过一定的方式登记到Server上,并在正常范围内维护自己信息一致性,方便其他服务发现自己,同时可以通过Server获取到自己依赖的其他服务信息,完成服务调用,还内置了负载均衡器,用来进行基本的负载均衡。

  • Eureka:是一个RESTful风格的服务,是一个用于服务发现和注册的基础组件,是搭建Spring Cloud微服务的前提之一,它屏蔽了Server和client的交互细节,使得开发者将精力放到业务上。

  • serverA从serverB同步信息,则serverB是serverA的peer。

  • 上面例子中如果service-url为空,且register-with-eureka,fetch-registry为true,则会报错,Cannot execute request on any known server,因为server同时也是一个client,他会尝试注册自己,所以要有一个注册中心url去注册。

  • Netflix开源的组件。包括server和client两部分。

    https://github.com/Netflix/Eureka

  • 注册中心和微服务间的关系

    《服务注册与发现关系图》

  • client功能

    • 注册:每个微服务启动时,将自己的网络地址等信息注册到注册中心,注册中心会存储(内存中)这些信息。

    • 获取服务注册表:服务消费者从注册中心,查询服务提供者的网络地址,并使用该地址调用服务提供者,为了避免每次都查注册表信息,所以client会定时去server拉取注册表信息到缓存到client本地。

    • 心跳:各个微服务与注册中心通过某种机制(心跳)通信,若注册中心长时间和服务间没有通信,就会注销该实例。

    • 调用:实际的服务调用,通过注册表,解析服务名和具体地址的对应关系,找到具体服务的地址,进行实际调用。

  • server注册中心功能

    • 服务注册表:记录各个微服务信息,例如服务名称,ip,端口等。

      注册表提供 查询API(查询可用的微服务实例)和管理API(用于服务的注册和注销)。

    • 服务注册与发现:注册:将微服务信息注册到注册中心。发现:查询可用微服务列表及其网络地址。

    • 服务检查:定时检测已注册的服务,如发现某实例长时间无法访问,就从注册表中移除。

# 服务注册

例子:api-listen-order

  1. pom.xml
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
1
2
3
4
  1. application.yml
#注册中心
eureka:
  client:
    #设置服务注册中心的URL
    service-url:
      defaultZone: http://root:root@localhost:7900/eureka/
1
2
3
4
5
6

ps:不想注册,设置成false即可,实例演示结果:注册中心没有实例信息。找控制台204信息也没有找到。

spring:
  cloud:
    service-registry:
      auto-registration:
        enabled: false
1
2
3
4
5

注册成功:

DiscoveryClient_API-LISTEN-ORDER/api-listen-order:30.136.133.9:port - registration status: 204
1

后面源码讲手动注册。

PS:

Eureka Server与Eureka Client之间的联系主要通过心跳的方式实现。心跳(Heartbeat)即Eureka Client定时向Eureka Server汇报本服务实例当前的状态,维护本服务实例在注册表中租约的有效性。

Eureka Client将定时从Eureka Server中拉取注册表中的信息,并将这些信息缓存到本地,用于服务发现。

# 服务调用

# Eureka高可用

高可用:可以通过运行多个Eureka server实例并相互注册的方式实现。Server节点之间会彼此增量地同步信息,从而确保节点中数据一致。

# Eureka搭建

# 搭建准备

  • 准备部署节点数对应的服务器,也可以单机部署,下面配置中因为是在同一台服务器上部署,通过端口号来区分

  • 修改本机host文件,绑定一个主机名,单机部署时使用ip地址会有问题

# Eureka 单节点搭建

  1. pom.xml

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    
    <!-- 有的教程中还引入spring-boot-starter-web,其实不用。因为上面的依赖已经包含了它。在pom中点此依赖进去,一共点4次spring-cloud-netflix-eureka-server,发现web的依赖。 -->
    
    
    1
    2
    3
    4
    5
    6
    7
  2. application.yml

    eureka:
      client:
        #是否将自己注册到Eureka Server,默认为true,由于当前就是server,故而设置成false,表明该服务不会向eureka注册自己的信息
        register-with-eureka: false
        #是否从eureka server获取注册信息,由于单节点,不需要同步其他节点数据,用false
        fetch-registry: false
        #设置服务注册中心的URL,用于client和server端交流
        service-url:
          defaultZone: http://localhost:7900/eureka/
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  3. application.properties

    #是否将自己注册到Eureka Server,默认为true,由于当前就是server,故而设置成false,表明该服务不会向eureka注册自己的信息
    eureka.client.register-with-eureka=false
    #是否从eureka server获取注册信息,由于单节点,不需要同步其他节点数据,用false
    eureka.client.fetch-registry=false
    #设置服务注册中心的URL,用于client和server端交流
    eureka.client.service-url.defaultZone=http://localhost:7900/eureka/
    
    1
    2
    3
    4
    5
    6
  4. 代码

    // 启动类上添加此注解标识该服务为配置中心
    @EnableEurekaServer
    
    1
    2
  5. PS:Eureka会暴露一些端点。端点用于Eureka Client注册自身,获取注册表,发送心跳。

  6. 简单看一下eureka server控制台,实例信息区,运行环境信息区,Eureka Server自身信息区。

# Eureka 双节点搭建

  • application.yml

    eureka:
      client:
        #是否将自己注册到其他Eureka Server,默认为true 需要
        register-with-eureka: true
        #是否从eureka server获取注册信息, 需要
        fetch-registry: true
     # 开启监控 management.endpoint.shutdown.enabled=true
    
    ---
    spring:
      profiles: euk1
    server:
      port: 7901
    eureka:
      instance:
        hostname: euk1.com
      client:
        #设置服务注册中心的URL,用于client和server端交流
        #此节点应向其他节点发起请求
        serviceUrl:
          defaultZone: https://euk2.com:79002/eureka/
          
    ---
    spring:
      profiles: euk2
    server:
      port: 7902
    eureka:
      instance:
        hostname: euk2.com
      client:
        #设置服务注册中心的URL,用于client和server端交流
        #此节点应向其他节点发起请求
        serviceUrl:
          defaultZone: https://euk1.com:79001/eureka/
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

    ---用来分割不同的active profiles,Run/Debug中可以指定profiles

    两个节点需要互相注册

# Eureka多节点搭建

  • application.yml

    eureka:
      client:
        #是否将自己注册到其他Eureka Server,默认为true 需要
        register-with-eureka: true
        #是否从eureka server获取注册信息, 需要
        fetch-registry: true
        serviceUrl:
          defaultZone: https://euk1.com:7901/eureka/,http://euk2.com:7902/eureka/,http://euk3.com:7903/eureka/
     # 开启监控 management.endpoint.shutdown.enabled=true
    
    ---
    spring:
      profiles: euk1
    server:
      port: 7901
    eureka:
      instance:
        hostname: euk1.com
    
    ---
    spring:
      profiles: euk2
    server:
      port: 7902
    eureka:
      instance:
        hostname: euk2.com
    
    ---
    spring:
      profiles: euk3
    server:
      port: 7903
    eureka:
      instance:
        hostname: euk3.com
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

两个节点的话,如下图内容 就算成功了

image-20200403193147121

# Eureka原理

eureka来源于古希腊词汇,意为“发现了”

eureka分为两部分,Server端和Client端

# Register

服务注册

想要参与服务注册发现的实例首先需要向Eureka服务器注册信息

注册在第一次心跳发生时提交

# Renew

续租,心跳

Eureka客户需要每30秒发送一次心跳来续租

10:00 00 第一次

10:00 30

10:01 00

10:01 30 最后

更新通知Eureka服务器实例仍然是活动的。如果服务器在90秒内没有看到更新,它将从其注册表中删除实例

# Fetch Registry

Eureka客户端从服务器获取注册表信息并将其缓存在本地。

之后,客户端使用这些信息来查找其他服务。

通过获取上一个获取周期和当前获取周期之间的增量更新,可以定期(每30秒)更新此信息。

节点信息在服务器中保存的时间更长(大约3分钟),因此获取节点信息时可能会再次返回相同的实例。Eureka客户端自动处理重复的信息。

在获得增量之后,Eureka客户机通过比较服务器返回的实例计数来与服务器协调信息,如果由于某种原因信息不匹配,则再次获取整个注册表信息。

# Cancel

Eureka客户端在关闭时向Eureka服务器发送取消请求。这将从服务器的实例注册表中删除实例,从而有效地将实例从通信量中取出。

# Time Lag

同步时间延迟

来自Eureka客户端的所有操作可能需要一段时间才能反映到Eureka服务器上,然后反映到其他Eureka客户端上。这是因为eureka服务器上的有效负载缓存,它会定期刷新以反映新信息。Eureka客户端还定期地获取增量。因此,更改传播到所有Eureka客户机可能需要2分钟。

# Communication mechanism

通讯机制

Http协议下的Rest请求

默认情况下Eureka使用Jersey和Jackson以及JSON完成节点间的通讯

# 服务注册

新建一个web项目,引入starterspring-cloud-starter-netflix-eureka-client

# 客户端配置选项

#续约发送间隔默认30秒,心跳间隔
eureka.instance.lease-renewal-interval-in-seconds=5
#表示eureka client间隔多久去拉取服务注册信息,默认为30秒,对于api-gateway,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
eureka.client.registry-fetch-interval-seconds=5

续约到期时间(默认90秒)

eureka.instance.lease-expiration-duration-in-seconds=60
1
2
3
4
5
6
7
8

# 服务器端配置选项

#关闭自我保护模式
eureka.server.enable-self-preservation=false
#失效服务间隔
eureka.server.eviction-interval-timer-in-ms=3000
1
2
3
4

# Eureka单独使用

# Rest服务调用

官方文档

https://github.com/Netflix/eureka/wiki/Eureka-REST-operations

Operation HTTP action Description
Register new application instance POST /eureka/v2/apps/appID Input: JSON/XMLpayload HTTPCode: 204 on success
De-register application instance DELETE /eureka/v2/apps/appID/instanceID HTTP Code: 200 on success
Send application instance heartbeat PUT /eureka/v2/apps/appID/instanceID HTTP Code: * 200 on success * 404 if instanceIDdoesn’t exist
Query for all instances GET /eureka/v2/apps HTTP Code: 200 on success Output: JSON/XML
Query for all appID instances GET /eureka/v2/apps/appID HTTP Code: 200 on success Output: JSON/XML
Query for a specific appID/instanceID GET /eureka/v2/apps/appID/instanceID HTTP Code: 200 on success Output: JSON/XML
Query for a specific instanceID GET /eureka/v2/instances/instanceID HTTP Code: 200 on success Output: JSON/XML
Take instance out of service PUT /eureka/v2/apps/appID/instanceID/status?value=OUT_OF_SERVICE HTTP Code: * 200 on success * 500 on failure
Move instance back into service (remove override) DELETE /eureka/v2/apps/appID/instanceID/status?value=UP (The value=UP is optional, it is used as a suggestion for the fallback status due to removal of the override) HTTP Code: * 200 on success * 500 on failure
Update metadata PUT /eureka/v2/apps/appID/instanceID/metadata?key=value HTTP Code: * 200 on success * 500 on failure
Query for all instances under a particular vip address GET /eureka/v2/vips/vipAddress * HTTP Code: 200 on success Output: JSON/XML * 404 if the vipAddressdoes not exist.
Query for all instances under a particular secure vip address GET /eureka/v2/svips/svipAddress * HTTP Code: 200 on success Output: JSON/XML * 404 if the svipAddressdoes not exist.

# 服务状态

get: {ip:port}/eureka/status

使用浏览器请求url会返回服务器状态信息


<com.netflix.eureka.util.StatusInfo>
    <generalStats>
        <environment>test</environment>
        <num-of-cpus>4</num-of-cpus>
        <total-avail-memory>384mb</total-avail-memory>
        <current-memory-usage>149mb (38%)</current-memory-usage>
        <server-uptime>00:25</server-uptime>
    </generalStats>
    <applicationStats>
        <registered-replicas></registered-replicas>
        <available-replicas></available-replicas>
        <unavailable-replicas></unavailable-replicas>
    </applicationStats>
    <instanceInfo>
        <instanceId>192.168.1.111:EurekaServer:7901</instanceId>
        <hostName>euk1.com</hostName>
        <app>EUREKASERVER</app>
        <ipAddr>192.168.1.111</ipAddr>
        <status>UP</status>
        <overriddenstatus>UNKNOWN</overriddenstatus>
        <port enabled="true">7901</port>
        <securePort enabled="false">443</securePort>
        <countryId>1</countryId>
        <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
            <name>MyOwn</name>
        </dataCenterInfo>
        <leaseInfo>
            <renewalIntervalInSecs>30</renewalIntervalInSecs>
            <durationInSecs>90</durationInSecs>
            <registrationTimestamp>0</registrationTimestamp>
            <lastRenewalTimestamp>0</lastRenewalTimestamp>
            <evictionTimestamp>0</evictionTimestamp>
            <serviceUpTimestamp>0</serviceUpTimestamp>
        </leaseInfo>
        <metadata>
            <management.port>7901</management.port>
        </metadata>
        <homePageUrl>http://euk1.com:7901/</homePageUrl>
        <statusPageUrl>http://euk1.com:7901/actuator/info</statusPageUrl>
        <healthCheckUrl>http://euk1.com:7901/actuator/health</healthCheckUrl>
        <vipAddress>EurekaServer</vipAddress>
        <secureVipAddress>EurekaServer</secureVipAddress>
        <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
        <lastUpdatedTimestamp>1632739829145</lastUpdatedTimestamp>
        <lastDirtyTimestamp>1632739829314</lastDirtyTimestamp>
    </instanceInfo>
</com.netflix.eureka.util.StatusInfo>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

如果需要json格式 可以加个请求头Accept:application/json

{
    "generalStats": {
        "environment": "test",
        "num-of-cpus": "4",
        "total-avail-memory": "384mb",
        "current-memory-usage": "143mb (37%)",
        "server-uptime": "00:20"
    },
    "applicationStats": {
        "registered-replicas": "",
        "available-replicas": "",
        "unavailable-replicas": ""
    },
    "instanceInfo": {
        "instanceId": "192.168.1.111:EurekaServer:7901",
        "hostName": "euk1.com",
        "app": "EUREKASERVER",
        "ipAddr": "192.168.1.111",
        "status": "UP",
        "overriddenStatus": "UNKNOWN",
        "port": {
            "$": 7901,
            "@enabled": "true"
        },
        "securePort": {
            "$": 443,
            "@enabled": "false"
        },
        "countryId": 1,
        "dataCenterInfo": {
            "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
            "name": "MyOwn"
        },
        "leaseInfo": {
            "renewalIntervalInSecs": 30,
            "durationInSecs": 90,
            "registrationTimestamp": 0,
            "lastRenewalTimestamp": 0,
            "evictionTimestamp": 0,
            "serviceUpTimestamp": 0
        },
        "metadata": {
            "management.port": "7901"
        },
        "homePageUrl": "http://euk1.com:7901/",
        "statusPageUrl": "http://euk1.com:7901/actuator/info",
        "healthCheckUrl": "http://euk1.com:7901/actuator/health",
        "vipAddress": "EurekaServer",
        "secureVipAddress": "EurekaServer",
        "isCoordinatingDiscoveryServer": "false",
        "lastUpdatedTimestamp": "1632739829145",
        "lastDirtyTimestamp": "1632739829314"
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 注册服务信息查看

注册到eureka的服务信息查看

get: {ip:port}/eureka/apps

# 注册服务详细查看

注册到eureka的服务详细信息查看

get: {ip:port}/eureka/apps/{appname}/{id}

# 服务续约

put:{ip:port}/eureka/apps/{appname}/{id}?lastDirtyTimestamp={}&status=up

# 更改服务状态

put:{ip:port}/eureka/apps/{appname}/{id}/status?lastDirtyTimestamp={}&value={UP/DOWN} 对应eureka源码的:InstanceResource.statusUpdate

# 删除状态更新

delete:{ip:port}/eureka/apps/{appname}/{id}/status?lastDirtyTimestamp={}&value={UP/DOWN}

# 删除服务

delete: {ip:port}/eureka/apps/{appname}/{id}

# 元数据

Eureka的元数据有两种:标准元数据和自定义元数据。 标准元数据:主机名、IP地址、端口号、状态页和健康检查等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。 自定义元数据:可以使用eureka.instance.metadata-map配置,这些元数据可以在远程客户端中访问,但是一般不改变客户端行为,除非客户端知道该元数据的含义。

可以在配置文件中对当前服务设置自定义元数据,可后期用户个性化使用

元数据可以配置在eureka服务器和eureka的客户端上

eureka.instance.metadata-map.dalao=mashibing
1

# 服务端:

image-20200408150307456

# 客户端:

{
    "applications": {
        "versions__delta": "1",
        "apps__hashcode": "UP_2_",
        "application": [
            {
                "name": "EUREKA-CONSUMER",
                "instance": [
                    {
                        "instanceId": "localhost:Eureka-Consumer:9001",
                        "hostName": "localhost",
                        "app": "EUREKA-CONSUMER",
                        "ipAddr": "192.168.29.1",
                        "status": "UP",
                        "overriddenStatus": "UNKNOWN",
                        "port": {
                            "$": 9001,
                            "@enabled": "true"
                        },
                        "securePort": {
                            "$": 443,
                            "@enabled": "false"
                        },
                        "countryId": 1,
                        "dataCenterInfo": {
                            "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
                            "name": "MyOwn"
                        },
                        "leaseInfo": {
                            "renewalIntervalInSecs": 30,
                            "durationInSecs": 90,
                            "registrationTimestamp": 1586331982283,
                            "lastRenewalTimestamp": 1586331982283,
                            "evictionTimestamp": 0,
                            "serviceUpTimestamp": 1586331982283
                        },
                        "metadata": {
                            "dalao": "mashibing666",
                            "management.port": "9001",
                            "jmx.port": "10158"
                        },
                        "homePageUrl": "http://localhost:9001/",
                        "statusPageUrl": "http://localhost:9001/actuator/info",
                        "healthCheckUrl": "http://localhost:9001/actuator/health",
                        "vipAddress": "Eureka-Consumer",
                        "secureVipAddress": "Eureka-Consumer",
                        "isCoordinatingDiscoveryServer": "false",
                        "lastUpdatedTimestamp": "1586331982283",
                        "lastDirtyTimestamp": "1586331982260",
                        "actionType": "ADDED"
                    },
                    {
                        "instanceId": "localhost:Eureka-Consumer:9000",
                        "hostName": "localhost",
                        "app": "EUREKA-CONSUMER",
                        "ipAddr": "192.168.29.1",
                        "status": "UP",
                        "overriddenStatus": "UNKNOWN",
                        "port": {
                            "$": 9000,
                            "@enabled": "true"
                        },
                        "securePort": {
                            "$": 443,
                            "@enabled": "false"
                        },
                        "countryId": 1,
                        "dataCenterInfo": {
                            "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
                            "name": "MyOwn"
                        },
                        "leaseInfo": {
                            "renewalIntervalInSecs": 30,
                            "durationInSecs": 90,
                            "registrationTimestamp": 1586331637223,
                            "lastRenewalTimestamp": 1586332057220,
                            "evictionTimestamp": 0,
                            "serviceUpTimestamp": 1586331637223
                        },
                        "metadata": {
                            "dalao": "mashibing",
                            "management.port": "9000",
                            "jmx.port": "10000"
                        },
                        "homePageUrl": "http://localhost:9000/",
                        "statusPageUrl": "http://localhost:9000/actuator/info",
                        "healthCheckUrl": "http://localhost:9000/actuator/health",
                        "vipAddress": "Eureka-Consumer",
                        "secureVipAddress": "Eureka-Consumer",
                        "isCoordinatingDiscoveryServer": "false",
                        "lastUpdatedTimestamp": "1586331637223",
                        "lastDirtyTimestamp": "1586331637182",
                        "actionType": "ADDED"
                    }
                ]
            }
        ]
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

# EurekaClient

EurekaClient 可以在客户端获取eureka服务器上的注册者信息

org.springframework.cloud.client.discovery与com.netflix.discovery.DiscoveryClient

org.springframework.cloud.client.discovery是SpringCloud对注册中心client的抽象封装,提供公用功能

org.springframework.cloud.client.discovery定义用来服务发现的客户端接口,是客户端进行服务发现的核心接口,是spring cloud用来进行服务发现的顶级接口,在common中可以看到其地位。在Netflix Eureka和Consul中都有具体的实现类。

代表通用于服务发现的读操作,例如在 eureka或consul中。

有
	String description();//获取实现类的描述。
	List<String> getServices();//获取所有服务实例id。
	List<ServiceInstance> getInstances(String serviceId);//通过服务id查询服务实例信息列表。
1
2
3
4

com.netflix.discovery.DiscoveryClient为Eureka注册中心客户端的接口,功能更丰富

# 自我保护机制

# 机制

Eureka在CAP理论当中是属于AP , 也就说当产生网络分区时,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.
1

默认情况下,Eureka Server在一定时间内,没有接收到某个微服务心跳,会将某个微服务注销(90S)。但是当网络故障时,微服务与Server之间无法正常通信,上述行为就非常危险,因为微服务正常,不应该注销。

Eureka Server通过自我保护模式来解决整个问题,当Server在短时间内丢失过多客户端时,那么Server会进入自我保护模式,会保护注册表中的微服务不被注销掉。当网络故障恢复后,退出自我保护模式。

思想:宁可保留健康的和不健康的,也不盲目注销任何健康的服务。

# 自我保护触发

客户端每分钟续约数量小于客户端总数的85%时会触发保护机制

自我保护机制的触发条件: (当每分钟心跳次数( renewsLastMin ) 小于 numberOfRenewsPerMinThreshold 时,并且开启自动保护模式开关( eureka.server.enable-self-preservation = true ) 时,触发自我保护机制,不再自动过期租约。) numberOfRenewsPerMinThreshold = expectedNumberOfRenewsPerMin * 续租百分比( eureka.server.renewalPercentThreshold, 默认0.85 ) expectedNumberOfRenewsPerMin = 当前注册的应用实例数 x 2 为什么乘以 2: 默认情况下,注册的应用实例每半分钟续租一次,那么一分钟心跳两次,因此 x 2 。

服务实例数:10个,期望每分钟续约数:10 * 2=20,期望阈值:20*0.85=17,自我保护少于17时 触发。

剔除:

    AbstractInstanceRegistry

    public void evict(long additionalLeaseMs) {
        logger.debug("Running the evict task");

        if (!isLeaseExpirationEnabled()) {
            logger.debug("DS: lease expiration is currently disabled.");
            return;
    }
    此代码意思:if中判断为true,不走此逻辑,走下面的剔除。如果if为false。走此逻辑,不剔除。
1
2
3
4
5
6
7
8
9
10
PeerAwareInstanceRegistryImpl

    @Override
    public boolean isLeaseExpirationEnabled() {
        if (!isSelfPreservationModeEnabled()) {
        //如果打开自我保护,不进入此逻辑。
            // The self preservation mode is disabled, hence allowing the instances to expire.
            return true;
        }
        return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
    }
1
2
3
4
5
6
7
8
9
10
11

# 关闭

eureka.server.enable-self-preservation=false
1

关闭后会提示

image-20200408165155886

# 清理时间

默认60秒

eureka.server.eviction-interval-timer-in-ms=3000
1

# 多网卡选择

  1. ip注册
eureka:
  instance:
    prefer-ip-address: true
表示将自己的ip注册到EurekaServer上。不配置或false,表示将操作系统的hostname注册到server
1
2
3
4
  1. 服务器有多个网卡,eh0,eh1,eh2,只有eh0可以让外部其他服务访问进来,而Eureka client将eh1和eh2注册到Eureka server上,这样其他服务就无法访问该微服务了。

  2. 指定Ip

    eureka:
      instance:
        prefer-ip-address: true
        ip-address: 实际能访问到的Ip
    
    1
    2
    3
    4

    如果设置了此时的ip-address,在元数据查看到就是此ip,其他服务也通过此ip来调用。

    {
    	"host": "127.0.0.1",
    	"port": 8084,
    	"metadata": {
    		"yueyi": "2019",
    		"user.password": "root",
    		"management.port": "8084",
    		"jmx.port": "61378",
    		"user.name": "root"
    	},
    	"secure": false,
    	"uri": "http://127.0.0.1:8084",
    	"instanceId": "api-listen-order:30.136.133.11:port",
    	"serviceId": "API-LISTEN-ORDER",
    	"instanceInfo": {
    		"instanceId": "api-listen-order:30.136.133.11:port",
    		"app": "API-LISTEN-ORDER",
    		"appGroupName": null,
    		"ipAddr": "127.0.0.1",
    		"sid": "na",
    		"homePageUrl": "http://127.0.0.1:8084/",
    		"statusPageUrl": "http://127.0.0.1:8084/actuator/info",
    		"healthCheckUrl": "http://127.0.0.1:8084/actuator/health",
    		"secureHealthCheckUrl": null,
    		"vipAddress": "api-listen-order",
    		"secureVipAddress": "api-listen-order",
    		"countryId": 1,
    		"dataCenterInfo": {
    			"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
    			"name": "MyOwn"
    		},
    		"hostName": "127.0.0.1",
    		"status": "UP",
    		"overriddenStatus": "UNKNOWN",
    		"leaseInfo": {
    			"renewalIntervalInSecs": 1,
    			"durationInSecs": 1,
    			"registrationTimestamp": 1579489514655,
    			"lastRenewalTimestamp": 1579489524146,
    			"evictionTimestamp": 0,
    			"serviceUpTimestamp": 1579489514147
    		},
    		"isCoordinatingDiscoveryServer": false,
    		"metadata": {
    			"yueyi": "2019",
    			"user.password": "root",
    			"management.port": "8084",
    			"jmx.port": "61378",
    			"user.name": "root"
    		},
    		"lastUpdatedTimestamp": 1579489514655,
    		"lastDirtyTimestamp": 1579489514111,
    		"actionType": "ADDED",
    		"asgName": null
    	},
    	"scheme": null
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57

    或者使用spring.cloud.inetutils配置网卡选择

# Eureka 健康检查

由于server和client通过心跳保持 服务状态,而只有状态为UP的服务才能被访问。看eureka界面中的status。

比如心跳一直正常,服务一直UP,但是此服务DB连不上了,无法正常提供服务。

此时,我们需要将 微服务的健康状态也同步到server。只需要启动eureka的健康检查就行。这样微服务就会将自己的健康状态同步到eureka。配置如下即可。

# 开启手动控制

在client端配置:将自己真正的健康状态传播到server。

eureka:
  client:
    healthcheck:
      enabled: true
1
2
3
4

# Client端配置Actuator

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
1
2
3
4

# 改变健康状态的Service

@Service
public class HealthStatusService implements HealthIndicator{

	private Boolean status = true;

	public void setStatus(Boolean status) {
		this.status  = status;
	}

	@Override
	public Health health() {
		// TODO Auto-generated method stub
		if(status)
		return new Health.Builder().up().build();
		return new Health.Builder().down().build();
	}

	public String getStatus() {
		// TODO Auto-generated method stub
		return this.status.toString();
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 测试用的Controller

	@GetMapping("/health")
	public String health(@RequestParam("status") Boolean status) {

		healthStatusSrv.setStatus(status);
		return healthStatusSrv.getStatus();
	}
1
2
3
4
5
6

# 安全配置

# 开启Eureka安全连接

spring.security.user.name=yiming
spring.security.user.password=123
1
2

image-20200408185532993

# 如果服务注册报错

Root name 'timestamp' does not match expected ('instance') for type [simple
1

是默认开启了防止跨域攻击

# 手动关闭

在服务端增加配置类

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// TODO Auto-generated method stub
		http.csrf().disable();
		super.configure(http);
	}

}

1
2
3
4
5
6
7
8
9
10
11
12
13

# 服务间调用

	微服务中,很多服务系统都在独立的进程中运行,通过各个服务系统之间的协作来实现一个大项目的所有业务功能。服务系统间 使用多种跨进程的方式进行通信协作,而RESTful风格的网络请求是最为常见的交互方式之一。

http。

思考:如果让我们写服务调用如何写。
  1. 硬编码。不好。ip域名写在代码中。目的:找到服务。

  2. 根据服务名,找相应的ip。目的:这样ip切换或者随便变化,对调用方没有影响。

    Map<服务名,服务列表> map;

  3. 加上负载均衡。目的:高可用。

spring cloud提供的方式:

  1. RestTemplate
  2. Feign

我个人习惯用RestTemplate,因为自由,方便调用别的第三方的http服务。feign也可以,更面向对象一些,更优雅一些,就是需要配置。

# Rest

#

RESTful网络请求是指RESTful风格的网络请求,其中REST是Resource Representational State Transfer的缩写,直接翻译即“资源表现层状态转移”。
Resource代表互联网资源。所谓“资源”是网络上的一个实体,或者说网上的一个具体信息。它可以是一段文本、一首歌曲、一种服务,可以使用一个URI指向它,每种“资源”对应一个URI。
Representational是“表现层”意思。“资源”是一种消息实体,它可以有多种外在的表现形式,我们把“资源”具体呈现出来的形式叫作它的“表现层”。比如说文本可以用TXT格式进行表现,也可以使用XML格式、JSON格式和二进制格式;视频可以用MP4格式表现,也可以用AVI格式表现。URI只代表资源的实体,不代表它的形式。它的具体表现形式,应该由HTTP请求的头信息Accept和Content-Type字段指定,这两个字段是对“表现层”的描述。
State Transfer是指“状态转移”。客户端访问服务的过程中必然涉及数据和状态的转化。如果客户端想要操作服务端资源,必须通过某种手段,让服务器端资源发生“状态转移”。而这种转化是建立在表现层之上的,所以被称为“表现层状态转移”。客户端通过使用HTTP协议中的四个动词来实现上述操作,它们分别是:获取资源的GET、新建或更新资源的POST、更新资源的PUT和删除资源的DELETE。
1
2
3
4

RestTemplate是Spring提供的同步HTTP网络客户端接口,它可以简化客户端与HTTP服务器之间的交互,并且它强制使用RESTful风格。它会处理HTTP连接和关闭,只需要使用者提供服务器的地址(URL)和模板参数。

第一个层次(Level 0)的 Web 服务只是使用 HTTP 作为传输方式,实际上只是远程方法调用(RPC)的一种具体形式。SOAP 和 XML-RPC 都属于此类。
第二个层次(Level 1)的 Web 服务引入了资源的概念。每个资源有对应的标识符和表达。
第三个层次(Level 2)的 Web 服务使用不同的 HTTP 方法来进行不同的操作,并且使用 HTTP 状态码来表示不同的结果。如 HTTP GET 方法来获取资源,HTTP DELETE 方法来删除资源。
第四个层次(Level 3)的 Web 服务使用 HATEOAS。在资源的表达中包含了链接信息。客户端可以根据链接来发现可以执行的动作。
1
2
3
4

git的restful api

https://developer.github.com/v3/