点击:Spring Cloud & Spring Cloud Alibaba完整学习路线
Spring Cloud 二 之Eureka
1 Eureka基础知识
1.1 什么是服务治理
Spring Cloud封装了Netflix 公司开发的 Eureka模块来实现服务治理。
在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
1.2 什么是服务注册
Eureka采用了CS的设计架构,Eureka sever作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Sever并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。|
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地NPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))
下左图是Eureka系统架构,右图是Dubbo的架构,请对比:
1.3 Eureka两组件
Eureka包含两个组件:Eureka Server和Eureka Client
- Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。 - EurekaClient通过注册中心进行访问
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
2 单机Eureka构建
2.1 Eureka服务注册中心构建
- 新建module cloud-eureka-server7001
- 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>clou2020</artifactId> <groupId>com.atguigu.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-eureka-server7001</artifactId> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${ project.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test --> <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> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> </project>
- yml文件
server: port: 7001 eureka: instance: hostname: localhost #eureka服务端的实例名字 client: register-with-eureka: false #表识不向注册中心注册自己 fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务 service-url: defaultZone: http://${ eureka.instance.hostname}:${ server.port}/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
- 主启动类
@EnableEurekaServer 标识为server@SpringBootApplication @EnableEurekaServer public class EurekaMain7001 { public static void main(String[] args) { SpringApplication.run(EurekaMain7001.class, args); } }
- 测试
http://localhost:7001/
2.2 将cloud-provider-payment8001注册进服务注册中心
注意:1.X和2.X的区别
1.X版本不易区别server端和client端
- 修改payment8001模块pom文件
在pom文件中添加如下配置:<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
- 修改payment8001模块yml文件
完整yml文件:server: port: 8001 spring: application: name: cloud-payment-service datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root eureka: client: register-with-eureka: true # 标识是否把自己注册进服务中心 fetchRegistry: true #是否从Eurekaserver抓取已有的注册信息,默置为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 service-url: defaultZone: http://localhost:7001/eureka mybatis: mapperLocations: classpath:mapper/*.xml type-aliases-package: com.atguigu.springcloud.entities
- 修改payment8001模块启动类
添加@EnableEurekaClient,标识为client@SpringBootApplication @EnableEurekaClient public class PaymentMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentMain8001.class, args); } }
- 测试
可以看到application就是我们在payment8001模块yml文件中配置的application:name
2.3 将cloud-consumer-order80注册进服务注册中心
- 注册过程同payment8001模块注册过程一样,可参考payment8001模块注册过程
- 测试
测试请求地址:http://localhost/consumer/payment/get/1
3 集群Eureka构建
问题:微服务RPC远程服务调用最核心的是什么?
高可用,试想你的注册中心只有一个only one,它出故障了那就呵呵(~v―)"了,会导致整个为服务环境不可用,所以,搭建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>clou2020</artifactId> <groupId>com.atguigu.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-eureka-server7002</artifactId> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${ project.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test --> <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> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> </project>
-
修改映射配置
找到C:\Windows\System32\drivers\etc路径下的hosts文件,
修改映射配置添加进hosts文件:127.0.0.1 eureka7001.com 127.0.0.1 eureka7002.com
-
新建cloud-eureka-server7002的yml配置文件
server: port: 7002 eureka: instance: hostname: eureka7002.com #eureka服务端的实例名字 client: register-with-eureka: false #表识不向注册中心注册自己 fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务 service-url: defaultZone: http://eureka7001.com:7001/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
-
修改cloud-eureka-server7001的yml配置文件
server: port: 7001 eureka: instance: hostname: eureka7001.com #eureka服务端的实例名字 client: register-with-eureka: false #表识不向注册中心注册自己 fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务 service-url: defaultZone: http://eureka7002.com:7002/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
-
cloud-eureka-server7002启动类
@SpringBootApplication @EnableEurekaServer public class EurekaMain7002 { public static void main(String[] args) { SpringApplication.run(EurekaMain7002.class, args); } }
-
测试
-
将cloud-provider-payment8001注册到上面两台Eureka集群配置中
修改cloud-provider-payment8001的yml文件:eureka: client: register-with-eureka: true # 标识是否把自己注册进服务中心 fetchRegistry: true #是否从Eurekaserver抓取已有的注册信息,默置为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 service-url: # defaultZone: http://localhost:7001/eureka defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
-
将cloud-consumer-order80注册到上面两台Eureka集群配置中
修改cloud-provider-payment8001的yml文件:eureka: client: register-with-eureka: true # 标识是否把自己注册进服务中心 fetchRegistry: true #是否从Eurekaserver抓取已有的注册信息,默置为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 service-url: # defaultZone: http://localhost:7001/eureka defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
-
启动测试
先启动cloud-eureka-server7001,再启动cloud-eureka-server7002,然后启动cloud-provider-payment8001和cloud-consumer-order80。
可以看到Eureka集群中注册的两个服务,
测试:http://localhost/consumer/payment/get/1
4 生产者cloud-provider-payment的高可用
-
新建cloud-provider-payment8002模块
-
将cloud-provider-payment8001模块java下的包拷贝到cloud-provider-payment8002模块
-
将cloud-provider-payment8001模块resource下的文件拷贝到cloud-provider-payment8002模块
-
将cloud-provider-payment8001模块的pom文件dependencies内的内容的复制到cloud-provider-payment8002模块pom文件内
-
修改启动类
@SpringBootApplication @EnableEurekaClient public class PaymentMain8002 { public static void main(String[] args) { SpringApplication.run(PaymentMain8002.class, args); } }
-
修改cloud-provider-payment8002模块的yml文件
完整yml文件:server: port: 8002 spring: application: name: cloud-payment-service datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root mybatis: mapperLocations: classpath:mapper/*.xml type-aliases-package: com.atguigu.springcloud.entities eureka: client: register-with-eureka: true # 标识是否把自己注册进服务中心 fetchRegistry: true #是否从Eurekaserver抓取已有的注册信息,默置为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 service-url: # defaultZone: http://localhost:7001/eureka defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
-
修改cloud-provider-payment8001模块的PaymentController
**添加了serverPort属性,**目的是区分哪个服务提供的服务
完整代码:@RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; @PostMapping(value = "/payment/create") public CommonResult create(@RequestBody Payment payment) { int result = paymentService.create(payment); log.info("*****插入结果*****" + result); if(result > 0) { return new CommonResult(200, "插入数据成功,serverPort:" + serverPort, result); } else { return new CommonResult(444, "插入数据失败,serverPort:" + serverPort, null); } } @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id) { Payment payment = paymentService.getPaymentById(id); log.info("*****查询结果*****" + payment); if(payment != null) { return new CommonResult(200, "查询成功,serverPort:" + serverPort, payment); } else { return new CommonResult(444, "没有对应记录的哦,serverPort:" + serverPort, null); } } }
-
同理修改cloud-provider-payment8002模块的PaymentController
代码同上 -
修改cloud-consumer-order80模块的ConsumerController
完整代码:@RestController @Slf4j public class ConsumerController { // public static final String PAYMENT_URL = "http://localhost:8001"; // 通过在eureka注册过得微服务名称调用 public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE"; @Resource private RestTemplate restTemplate; @GetMapping("/consumer/payment/create") public CommonResult<Payment> create(Payment payment) { return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class); } @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayment(@PathVariable("id") Long id) { return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class); } }
-
修改cloud-consumer-order80模块的ApplicationContextConfig
@Configuration public class ApplicationContextConfig { @Bean @LoadBalanced // 目的是在服务高可用的情况下提供负载均衡,决定调用哪一个服务 public RestTemplate getRestTemplate() { return new RestTemplate(); } }
-
测试,启动cloud-provider-payment8001服务
在浏览器输入:http://eureka7001.com:7001/
在浏览器输入:http://eureka7002.com:7002/
可以看到新的生产服务模块已经注册进Eureka集群中 -
浏览器中测试地址:http://localhost/consumer/payment/get/1
5 actuator微服务信息完善
注意:要先引入依赖,之前的pom文件已引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 主机名称:服务名称修改
修改cloud-provider-payment8001和cloud-provider-payment8002模块的yml配置文件,增加了instance:
下面是cloud-provider-payment8002的yml文件,可同理配置cloud-provider-payment8001模块yml文件。
浏览器输入地址:http://localhost:8001/actuator/healtheureka: client: register-with-eureka: true # 标识是否把自己注册进服务中心 fetchRegistry: true #是否从Eurekaserver抓取已有的注册信息,默置为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 service-url: # defaultZone: http://localhost:7001/eureka defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版 instance: instance-id: payment8002
看到服务没有出现假死,同理查看8002
再次查看Eureka服务注册中心,可以看到服务名已经变为yml中配置的了:
- 访问服务有服务IP显示
修改cloud-provider-payment8001和cloud-provider-payment8002模块的yml配置文件,在instance字段增加了prefer-ip-address:
鼠标移到服务名称上,左下角可以看到ip地址:instance: instance-id: payment8002 prefer-ip-address: true # 访问路径可以显示ip地址
6 服务发现Discovery
- 目的:对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
- 修改cloud-provider-payment8001的Controller
完整代码:@RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; @Resource private DiscoveryClient discoveryClient; @PostMapping(value = "/payment/create") public CommonResult create(@RequestBody Payment payment) { int result = paymentService.create(payment); log.info("*****插入结果*****" + result); if(result > 0) { return new CommonResult(200, "插入数据成功,serverPort:" + serverPort, result); } else { return new CommonResult(444, "插入数据失败,serverPort:" + serverPort, null); } } @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id) { Payment payment = paymentService.getPaymentById(id); log.info("*****查询结果*****" + payment); if(payment != null) { return new CommonResult(200, "查询成功,serverPort:" + serverPort, payment); } else { return new CommonResult(444, "没有对应记录的哦,serverPort:" + serverPort, null); } } @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; } }
- 在cloud-provider-payment8001模块启动类上加注解@EnableDiscoveryClient
- 测试
可以看到eureka中注册了两个服务,且cloud-payment-service下有两个实例。
7 Eureka自我保护
7.1 理论知识
-
概述
保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护,—旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式。
- 什么是自我保护模式?
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。 - 为什么会产生Eureka自我保护机制?
为了让EurekaClient可以正常运行,但是与EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。
它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
- 什么是自我保护模式?
-
导致原因
一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存,属于CAP里面的AP分支。
7.2 如何关闭Eureka自我保护
一般生产环境中不会禁止自我保护的。
7.2.1 修改注册中心eureakeServer端7001
- 出厂默认,自我保护机制是开启的
eureka.server.enable-self-preservation = true
- 修改yml文件
server: port: 7001 eureka: instance: hostname: eureka7001.com #eureka服务端的实例名字 client: register-with-eureka: false #表识不向注册中心注册自己 fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务 # service-url: # defaultZone: http://eureka7002.com:7002/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址 server: enable-self-preservation: false # 设置接受心跳时间为2s,2s接受不到心跳就删除(默认为90s) eviction-interval-timer-in-ms: 2000
- 关闭效果
7.2.2 修改生产者客户端eureakeClient端8001
-
修改yml配置文件
eureka: client: register-with-eureka: true # 标识是否把自己注册进服务中心 fetchRegistry: true #是否从Eurekaserver抓取已有的注册信息,默置为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 service-url: defaultZone: http://eureka7001.com:7001/eureka # defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版 instance: instance-id: payment8001 prefer-ip-address: true # 访问路径可以显示ip地址 #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒) lease-renewal-interval-in-seconds: 1 #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务 lease-expiration-duration-in-seconds: 2
-
测试
启动eureka7001服务和payment-service8001服务,然后关闭payment-service8001服务,可以看到payment-service8001服务已经被删除了。