Eureka详解
服务消费者模式
获取服务
消费者启动的时候,使用服务别名,会发送一个rest请求到服务注册中心获取对应的服务信息,然后会缓存到本地jvm客户端中,同时客户端每隔30秒从服务器上更新一次。
服务下线
在系统运行过程中必然会面临关闭或重启服务的某个实例的情况,在服务关闭期我们自然不希望客户端会继续调用关闭了的实例。所以在客户端程序中,当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给Eureka Server, 告诉服务中心:“我要下线了”。服务端在接收到请求之后,将该服务状态置为下线(DOWN),并将该下线事件传播出去。
服务注册模式
失效剔除
有些时候,我们的服务实例并不一定会正常下线,可能由于内存溢出、网络故障原因使得服务不能正常工作,而服务注册中心并未收到“服务下线”的请求。为了从服务表中将这些无法提供服务的实例剔除,Eureka Server在启动的时候会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除出去。
自我保护
当我们在本地调试基于Eureka的程序时,基本上都会碰到这样一个问题,在服务注册中心的信息面板中出现类似下面的红色警告信息
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY 'RERENENALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIREDTO BE SAFE.
实际上,该警告就是触发了Eureka Server的自我保护机制。之前我们介绍过,服务注册到Eureka Server之后,会维护个心跳连接,告诉Eureka Server自己还活着。Eureka Server在运行期间,段时间内大量实例获取不到心跳,就会触发该机制。单机调试的时候很容易满足,实际在生产环境上通常是由于网络不稳定导致),EuServer会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息。但是,在这段保护期间内实例若出现问题,那么客户端很容易拿到实际已经不存服务实例,会出现调用失败的情况,所以客户端必须要有容错机制,比如可以使用请使用重试、断路器等机制。
由于本地调试很容易触发注册中心的保护机制,这会使得注册中心维护的服务实仍那么准确。所以,我们在本地进行开发的时候,可以使用eureka.server.enablself preservation=false参数来关闭保护机制,以确保注册中心可以将不可用的实例正确剔除。
Eureka闭源了怎么办?
Eureka闭源了,可以使用其他的注册代替:Consul、Zookeeper
使用Zookeeper来替换Eureka
Zookeeper简介
Zookeeper是一个分布式协调工具,可以实现服务注册与发现、注册中心、消息中间件、分布式配置中心等。
环境搭建
- 启动zk服务器端
- Maven依赖信息
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.M7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
</dependencies>
<!-- 注意: 这里必须要添加, 否者各种依赖有问题 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
- application.yml
会员配置文件
###订单服务的端口号
server:
port: 8002
###服务别名----服务注册到注册中心名称
spring:
application:
name: zk-member
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
订单配置文件
###订单服务的端口号
server:
port: 8003
###服务别名----服务注册到注册中心名称
spring:
application:
name: zk-order
cloud:
zookeeper:
connect-string: 127.0.0.1:2181
- 会员服务api
package com.demo.zookeeperclient.api.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class IndexController {
@Value("${server.port}")
public String port;
@GetMapping("member")
public String index(){
return "端口号"+port+"我是会员服务";
}
}
- 会员服务启动类
会员服务启动类加上@EnableDiscoveryClient
package com.demo.zookeeperclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ZookeeperClientApplication {
public static void main(String[] args) {
SpringApplication.run(ZookeeperClientApplication.class, args);
}
}
- 订单服务api
package com.demo.zookeeperclient2.api.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class IndexController {
@Autowired
RestTemplate restTemplate;
@Value("${server.port}")
public String port;
// 订单服务调用会员服务
@RequestMapping("/order")
public String getOrder() {
String serviceUrl ="http://zk-member/member";
String result = restTemplate.getForObject(serviceUrl, String.class);
return "订单服务调用会员服务result:" + result;
}
//获取注册中心上信息
@RequestMapping("/getServiceUrl")
public List<String> getServiceUrl() {
List<ServiceInstance> list = discoveryClient.getInstances("zk-member");
List<String> services = new ArrayList<>();
for (ServiceInstance serviceInstance : list) {
if (serviceInstance != null) {
services.add(serviceInstance.getUri().toString());
}
}
return services;
}
}
- 订单服务启动类
package com.demo.zookeeperclient2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
public class ZookeeperClient2Application {
public static void main(String[] args) {
SpringApplication.run(ZookeeperClient2Application.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@LoadBalanced 实现本地负载均衡
效果
启动member服务,调用order服务。
修改端口号启动两台member服务,调用order服务
负载均衡效果
Zookeeper与Eureka区别
Zookeeper是保证CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
Eureka是保证AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
1. Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
2. Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
3. 当网络稳定时,当前实例新的注册信息会被同步到其它节点中
因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。