Eureka服务注册与发现

Eureka服务注册与发现

服务治理

Spring Cloud封装了Netflix公司开发的Eureka模块来实现服务治理。

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

服务注册与发现

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

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

​ 左边是Eureka系统架构,右边是Dubbo的架构

1596525780055

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

Eureka Server提供服务注册服务

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

EurekaClient通过注册中心进行访问

是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器.在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接受到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。

1596526701490

服务端安装

新建model:cloud-eureka-server7001

pom.xml

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
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<groupId>com.kayleh.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</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-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>

applicaiton.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
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/

主启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.kayleh.springcloud;

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

/**
* @Author: Wizard
* @Date: 2020/8/4 15:57
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class, args);
}
}

访问 http://localhost:7001

支付微服务8001入驻进EurekaServer

微服务8001的pom文件:添加坐标

1
2
3
4
5
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

application.yml下添加

1
2
3
4
5
6
7
8
9
eureka:
client:
# 表示是否将自己注册进eurekaServer默认为true
register-with-eureka: true
# 表示从eurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址.
defaultZone: http://localhost:7001/eureka

主启动类添加client注解

1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}

微服务注册名配置说明

1596529308640

访问Eureka出现红字原因:

自我保护机制.

配置微服务80进驻Eureka;

改pom,添加坐标

改yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 80

spring:
application:
name: cloud-order-service

eureka:
client:
# 表示是否将自己注册进eurekaServer默认为true
register-with-eureka: true
# 表示从eurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址.
defaultZone: http://localhost:7001/eureka

主启动类添加client注解.

集群eureka构建

eureka集群原理分析

1596544509634

解决办法:搭建eureka注册中心集群,实现负载均衡+故障容错

构建集群(单机走向集群)

新建model:cloud-eureka-server7002

复制微服务7001的pom.xml

修改C:\Windows\System32\drivers\etc目录下的hosts文件

添加进hosts文件

1
2
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

修改7001和7002的application.yml (如果是三台集群的话,在service-url下继续写,用逗号分隔开)

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
7001:
server:
port: 7001

eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
# false表示不向注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务.
fetch-registry: false
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
--------------------------------------------------------
7002:
server:
port: 7002

eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
# false表示不向注册中心注册自己
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务.
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/

7002启动类加注解

访问

1
2
eureka7001.com:7001
eureka7002.com:7002

将80和8001模块注册进eureka, 修改yaml文件

1
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

测试

1
http://localhost/consumer/payment/get/1

支付服务提供者8001集群环境构建

新建cloud-provider-payment8002

pom.xml和8001一致

copy 8001的yml文件到8002,修改端口号

1
2
server:
port: 8002

主启动类,业务类 直接cpoy8001

修改8001和8002的controller

1
2
3
4
5
6
7
添加
@Value("${server.port}")
private String serverPort;

修改打印
"插入数据库成功,serverPort" + serverPort
"查询成功,serverPort:" + serverPort

负载均衡

修改消费者80模块的OrderController的订单服务访问地址

1
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

1596602970422

修改ApplicationContextConfig

1
2
3
4
5
6
7
8
9
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
添加@LoadBalanced开启RestTemplate的负载均衡

测试

1
http://localhost/consumer/payment/get/1

可以看到8001端口和8002端口交替出现.

Ribbon和Eureka整合后,Consumer可以直接调用服务而不用关心地址和端口号,且该服务还有负载功能.

axtuator微服务信息完善

当前问题

1596603735423

暴露主机名

修改cloud-provider-payment8001和8002 的yml

1
2
3
4
5
6
7
8
9
10
11
12
13
eureka:
client:
# 表示是否将自己注册进eurekaServer默认为true
register-with-eureka: true
# 表示从eurekaServer抓取已有的注册信息,默认为true。单节点无所
fetch-registry: true
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址.
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
instance:
instance-id: payment8001
添加instance配置

更改之后

1596604101892

点开链接,测试

1
http://wizard:8002/actuator/health

访问地址显示ip地址

修改8001,8002的yml,添加

1
2
3
instance:
instance-id: payment8001
prefer-ip-address: true

服务发现Discovery

对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息.

修改8001的controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
添加
@Resource
private DiscoveryClient discoveryClient;

@GetMapping(value = "/payment/discovery")
public Object discovery() {
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("----------element:" + element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri());
}
return this.discoveryClient;
}

启动类添加注解

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}

测试

1
2
3
http://localhost:8001/payment/discovery
------------------------------------------
{"services":["cloud-order-service","cloud-payment-service"],"order":0}

1596605686156

Eureka的自我保护机制

故障现象:

1596605819802

导致原因:

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

属于CAP里面的AP分支

1596606071261

1596606189669

1596606288542

关闭自我保护

修改7001的yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
添加 
server:
enable-self-preservation: false
---------------------------------------
eureka:
instance:
hostname: eureka7001.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
server:
#关闭自我保护机制,保证不可用服务被及时删除
enable-self-preservation: false
#时间间隔
eviction-interval-timer-in-ms: 2000

8001的yml加入

1
2
3
4
5
6
7
instance:
instance-id: payment8001
prefer-ip-address: true
# eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-expiration-duration-in-seconds: 1
# eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-renewal-interval-in-seconds: 2

Zookepper

需要Linux安装Zookepper

新建工程cloud-provider-payment8004

pom.xml

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
<dependencies>
<!-- Zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>

<dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<groupId>com.kayleh.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
</dependencies>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8004

#服务别名
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
# zookeeper的机器ip加端口号
connect-string: 192.168.111.144:2181

启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.kayleh.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
* @Author: Wizard
* @Date: 2020/8/5 16:58
*/
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class, args);
}
}

PaymentController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.kayleh.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
* @Author: Wizard
* @Date: 2020/8/5 17:00
*/
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;

@RequestMapping(value = "/payment/zk")
public String paymentzk() {
return "Springcloud with zookeeper:" + serverPort + "\t" + UUID.randomUUID().toString();
}
}

jar包冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--        Zookeeper客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!-- 排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加zookeeper3.4.9-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>

测试

1596618784943

访问

1
localhost:8004/payment/zk

1596618927219

是临时节点,项目停止后,连接会持续一小段时间,然后丢失。重新连接后是另一个UUID的Zookepper。

1596626952182

订单服务注册zookeeper

新建cloud-consumerzk-order80

复制80的pom

yml

1
2
3
4
5
6
7
8
9
10
11
server:
port: 80

#服务别名
spring:
application:
name: cloud-consumer-order
cloud:
zookeeper:
# zookeeper的机器ip加端口号
connect-string: 192.168.111.144:2181

启动:

1
2
3
4
5
6
7
8
9
package com.kayleh.springcloud;

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

config:

1
2
3
4
5
6
7
8
9
10
package com.kayleh.springcloud.config;

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

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.kayleh.springcloud.controller;

@RestController
@Slf4j
public class OrderZkController {
public static final String INVOKE_URL = "http://cloud-provider-payment";
@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment) {
return restTemplate.postForObject(INVOKE_URL + "/payment/create", payment, CommonResult.class);
}

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

服务访问地址INVOKE_URL填linux上的zookeeper名称

1596627798682

测试,启动 80zk 和 8004.

1596628332087

访问

1
http://localhost/consumer/payment/zk

Consul

1596629165834

https://www.consul.io/downloads.html

1596629552902

启动

1
consul agent -dev

访问

1
http://localhost:8500/ui/

服务提供者注册进Consul

新建模块cloud-providerConsul-payment8006

pom.xml

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
<dependencies>
<!--consul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>

<dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<groupId>com.kayleh.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
</dependencies>

yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 8006

#服务别名
spring:
application:
name: consul-provider-payment
###consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
# hostname: 127.0.0.1

主启动类

controller

1
2
3
4
5
6
7
8
9
10
11
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;

@RequestMapping(value = "/payment/consul")
public String paymentConsul() {
return "Springcloud with consul:" + serverPort + "\t" + UUID.randomUUID().toString();
}
}

测试

1
http://localhost:8006/payment/consul

服务消费者注册进Consul

新建模块cloud-consumer-consul-order80

pom.xml

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
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

主启动类

复制cloud-consumerzk-order80模块的ApplicationContextConfig

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@Slf4j
public class OrderConsulController {
public static final String INVOKE_URL = "http://consul-provider-payment";
@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/payment/consul")
public String paymentInfo() {
String result = restTemplate.getForObject(INVOKE_URL + "/payment/consul", String.class);
return result;
}

}

三个注册中心的异同

1596714189759

1596714223828

1596714422562

C : Consistency(强一致性)

A : Availability(可用性)

P : Partition tolerance(分区容错性)

CAP理论关注粒度是数据,而不是整体系统设计的策略

AP架构

当网络分区出现后,为了保证可用性,系统B可用返回旧值,保证系统的可用性

结论:违背了一致性C的要求,只满足可用性和分区容错,即AP

1596714606488

CP架构

当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。

结论:违背了可用性A的要求,只满足一致性和分区容错,即CP

1596714860983