Eureka 简介
服务治理
Spring Cloud封装了Netflix公司开发的Eureka模块来实现服务治理 。
服务治理 :在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务与服务之间依赖关系 ,可以实现服务调用、负载均衡、容错等,实现服务发现与注册 。
Eureka采用了CS的设计架构,Eureka Sever作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
服务注册与服务发现
服务注册 :在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上。
服务发现 :另一方(消费者服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,之后再实现本地RPC调用。
总结:
服务注册 :将服务信息注册进注册中心
服务发现 :从注册中心上获取服务信息
RPC远程调用框架核心设计思想在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何RPC远程框架中,都会有一个注册中心存放服务地址相关信息(接口地址)
Eureka包含两个组件:Eureka Server 和 Eureka Client
Eureka Server :提供服务注册服务。各个微服务节点通过配置启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
Eureka Client :注册到注册中心的服务。是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器(Ribbon)。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
Eureka Server
创建名为cloud-eureka-server7001
的Maven工程
修改pom.xml
,添加依赖:
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupid > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-eureka</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-server</artifactId > </dependency >
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 <?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 > cloud2021</artifactId > <groupId > com.zhao.springcloud</groupId > <version > 1.0.0-SNAPSHOT</version > </parent > <modelVersion > 4.0.0</modelVersion > <artifactId > cloud-eureka-server7001</artifactId > <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-server</artifactId > </dependency > <dependency > <groupId > com.lun.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-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 > </project >
修改application.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 7001 eureka: instance: hostname: locathost client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
创建主启动类
1 2 3 4 5 6 7 8 9 10 11 import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication @EnableEurekaServer public class EurekaMain7001 { public static void main (String[] args) { SpringApplication.run(EurekaMain7001.class, args); } }
测试运行EurekaMain7001
,浏览器输入http://localhost:7001/
回车,会查看到Spring Eureka服务主页。
Eureka Client
创建Eureka Client端:cloud-provider-payment8001
订单提供者服务,并注册进Eureka Server成为服务提供者provider。
创建cloud-provider-payment8001
模块(订单提供者服务)作为Eureka Client端
修改pom.xml
,添加依赖:
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency >
修改application.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 spring: application: name: cloud-payment-service eureka: client: register-with-eureka: true fetchRegistry: true service-url: defaultZone: http://localhost:7001/eureka instance: instance-id: payment8001 prefer-ip-address: true
创建主启动类
1 2 3 4 5 6 7 8 9 10 11 12 import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@SpringBootApplication @EnableEurekaClient public class PaymentMain001 { public static void main (String[] args) { SpringApplication.run(PaymentMain001.class, args); } }
测试:启动cloud-provider-payment8001
和cloud-eureka-server7001
工程。浏览器输入 - http://localhost:7001/。
Eureka 集群原理
服务注册 :将服务信息注册进注册中心
服务发现 :从注册中心上获取服务信息
实质:存key服务名,取value调用地址
流程:
先启动Eureka注册中心
启动服务提供者payment支付服务
支付服务启动后会把自身信息(以服务地址URL以别名方式注册进Eureka)
消费者order服务在需要调用接口时,使用服务别名去注册中心获取实际的RPC远程调用地址
消费者调用地址后,底层实际是利用HttpClient技术实现远程调用
消费者获得的服务地址后会缓存在本地jvm内存中,默认每间隔30秒更新—次服务调用地址
问题:微服务RPC远程服务调用最核心的是什么?
答:高可用,试想注册中心假设只有一个,万一它出故障了,会导致整个为服务环境不可用。
解决办法:搭建Eureka注册中心集群 ,实现负载均衡 + 故障容错。互相注册,相互守望 。
Eureka 集群环境构建
创建cloud-eureka-server7002
工程作为第二个Eureka Server,与cloud-eureka-server7001
相互注册。
首先修改本地的host映射:
1 2 127.0.0.1 eureka7001.com 127.0.0.1 eureka7002.com
修改cloud-eureka-server7001
的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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/
修改cloud-eureka-server7002
配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 server: port: 7002 eureka: instance: hostname: eureka7002.com client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://eureka7001.com:7001/eureka/
将支付服务8001微服务注册到上面2台Eureka集群配置中
1 2 3 4 5 6 7 8 9 10 11 eureka: client: register-with-eureka: true fetchRegistry: true service-url: defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka instance: instance-id: payment8001 prefer-ip-address: true
支付服务集群配置:https://blog.csdn.net/u011863024/article/details/114298270
支付服务搭建集群后,订单服务需要开启负载均衡 功能,此注解在Spring Cloud提供的LoadBalacer
包下
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.springframework.cloud.client.loadbalancer.LoadBalanced;@Configuration public class ApplicationContextConfig { @LoadBalanced @Bean public RestTemplate getRestTemplate () { return new RestTemplate(); } }
此时PAYMENT_URL
需要写成支付服务对应的服务名: http://CLOUD-PAYMENT-SERVICE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Slf4j @RestController public class OrderController { public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE" ; @Autowired private RestTemplate restTemplate; @GetMapping(value = "/consumer/payment/create") public CommonResult 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) { log.info("1211" ); return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class); } }
Discovery 服务发现
对于注册进Eureka里面的微服务,可以通过服务发现来获得该服务的信息。
修改cloud-provider-payment8001
的Controller
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 @RestController @Slf4j public class PaymentController { ... @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 PaymentMain001 { public static void main (String[] args) { SpringApplication.run(PaymentMain001.class, args); } }
Eureka 自我保护机制
保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:
1 EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THANTHRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUSTTO BE SAFE
导致原因:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。
Eureka属于CAP里面的AP类型(ZooKeeper属于CP),即保证服务高可用,即使数据不一致也必须要可用。
为什么会产生Eureka自我保护机制:为了EurekaClient可以正常运行,在与Eureka Server网络不通情况下,Eureka Server不会立刻将Eureka Client服务剔除
什么是自我保护模式:默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与Eureka Server之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
自我保护机制:默认情况下Eureka Client定时向Eureka Server端发送心跳包。如果Eureka Server在一定时间内(默认90秒)没有收到Eureka Client发送心跳包,便会直接从服务注册列表中剔除该服务,但是在短时间(90秒)内丢失了大量的服务实例心跳,这时候Eureka Server会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是Eureka Client未出现宕机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误,因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的)。
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
三个注册中心异同点
组件名
语言
CAP
对外暴露接口
Spring Cloud集成
Eureka
Java
AP
可配支持
HTTP
Consul
Go
CP
支持
HTTP/DNS
Zookeeper
Java
CP
支持客户端
已集成
CAP:
C:Consistency (强一致性)
A:Availability (可用性)
P:Partition tolerance (分区容错性)
最多只能同时较好的满足两个。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求。因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
CA - 单点集群,满足—致性,可用性的系统,通常在可扩展性上不太强大。
CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
AP 架构(Eureka)
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。结论:违背了一致性C的要求,只满足可用性和分区容错,即AP。
CP 架构(ZooKeeper/Consul)
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。结论:违背了可用性A的要求,只满足一致性和分区容错,即CP。例如ZooKeeper在进行Leader选举时不能响应客户端的请求,此时不满足高可用性A,而是选择了满足数据一致性。