本文章基于spring-boot-starter-parent 2.0.6RELEASE,spring-cloud-dependencies Finchley.SR2。
Eureka是什么
Eureka 是 Netflix的子模块之一, 用于实现服务注册与发现,是微服务架构中最为核心和基础的模块。Eureka有两个组件:一个是EurekaServer,用于定位服务以实现中间层服务器的负载均衡和故障转移;另个一是EurekaClient(集成在我们的微服务中),用于与Server进行交互,并可以通过服务标识符去获取服务。 Spring Cloud 基于 Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能。
Eureka基础架构
三个核心要素:
- 服务注册中心: Eureka提供的服务端,提供服务注册于发现功能
- 服务提供者: 提供服务的应用,可以是spring boot应用,也可以是其他技术平台且遵循Eureka通信机制的应用。
- 服务消费者: 消费者应用从服务注册中心获取服务列表,从而使消费者可以知道去何处调用其所需要的服务。
下图是来自Eureka官方的基于集群配置的架构图:
- 处于不同节点的Eureka Server通过Replicate进行数据同步;
- Application Service为服务提供者,向Eureka Server注册、服务同步、服务续约等;
- Application Client为服务消费者,可以向Eureka Server获取服务实例列表、服务调用、服务续约、服务下线等;
- Make Remote Call完成一次服务调用。
服务治理机制:
服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。
当服务注册中心Eureka Server检测到服务提供者因为宕机、网络原因不可用时,则在服务注册中心将服务置为DOWN状态,并把当前服务提供者状态向订阅者发布,订阅过的服务消费者更新本地缓存。
服务提供者在启动后,周期性(默认30秒)向Eureka Server发送心跳,以证明当前服务是可用状态。Eureka Server在一定的时间(默认90秒)未收到客户端的心跳,则认为服务宕机,注销该实例。
Eureka使用
下面用一个用户获取订单的例子,来说明Eureka的使用:
Eureka服务注册中心
Dependency:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
application.ym:
server:
port: 8081 #默认值8080
eureka:
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 4000 #设置清理间隔(单位:毫秒 默认是60*1000)
instance:
hostname: localhost
client:
register-with-eureka: false #不把自己作为一个客户端注册到自己身上
fetch-registry: false #不从服务端获取注册信息(因为在这里自己就是服务端,而且已经禁用自己注 册了)
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
在spring boot启动项目上加上注解:@EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class);
}
}
浏览器访问http://localhsot:8081,看到下图则说明服务注册中心启动成功
服务提供者(Order)
Dependency:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml:
server:
port: 6000
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8081/eureka #Eureka服务端提供的注册地址
instance:
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #此实例注册到eureka服务端的唯一的实例ID
prefer-ip-address: true #是否显示IP地址
spring:
application:
name: order-microservice #此实例注册到eureka服务端的name
OrderController:
@RestController
public class OrderController {
@RequestMapping("/getOrder.do")
public Map<String,Object> getOrder() {
Map<String, Object> map = new HashMap();
map.put("key","order");
return map;
}
}
启动类加上@EnableEurekaClient注解:
@SpringBootApplication
@EnableEurekaClient
public class AppOrderApplication {
public static void main(String[] args) {
SpringApplication.run(AppOrderApplication.class);
}
}
服务消费者(User)
Dependency:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml:
server:
port: 5000
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8081/eureka #Eureka服务端提供的注册地址
instance:
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #此实例注册到eureka服务端的唯一的实例ID
prefer-ip-address: true #是否显示IP地址
spring:
application:
name: user-microservice #此实例注册到eureka服务端的name
AppConfig:
@Configuration
public class AppConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
UserController:
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/getOrder.do")
public Object getOrder() {
return restTemplate.getForObject("http://localhost:6000/getOrder.do",Object.class);
}
}
启动类上加上@EnableEurekaClient注解:
@SpringBootApplication
@EnableEurekaClient
public class AppUserApplication {
public static void main(String[] args) {
SpringApplication.run(AppUserApplication.class);
}
}
客户端与服务端启动后,访问Eurek注册中心即可看到服务实例已注册:
浏览器访问http://localhost:5000/getOrder.do ,结果页面如下,说明服务调用成功。
Eureka集群
Eureka Server的设计一开始就考虑了高可用问题,在Eureka的服务治理设计中,所有节点即是服务提供方,也是服务消费方,服务注册中心也是。在上面的单节点服务注册中心的配置文件中,我们设置了下面两个配置:
register-with-eureka: false #不把自己作为一个客户端注册到自己身上
fetch-registry: false #不从服务端获取注册信息(因为在这里自己就是服务端,而且已
Eureka Server的高可用就是将自己作为服务向其他服务注册中心注册自己。
下面进行实例演示,在单台机器上模拟集群环境:
1.修改/etc/host文件,mac上路径为/etc/hosts,Windows系统路径C:\Window\System32\drivers\etc\hosts
。
2.简单起见,就不复制两个工程了,直接通过配置三个profile来启动三个Eureka Server
spring:
application:
name: eureka-server
---
spring:
profiles: peer1
server:
port: 8081
eureka:
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 4000 #设置清理间隔(单位:毫秒 默认是60*1000)
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: http://peer2:8082/eureka,http://peer3:8083/eureka
---
spring:
profiles: peer2
server:
port: 8082
eureka:
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 4000 #设置清理间隔(单位:毫秒 默认是60*1000)
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: http://peer1:8081/eureka,http://peer3:8083/eureka
---
spring:
profiles: peer3
server:
port: 8083
eureka:
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 4000 #设置清理间隔(单位:毫秒 默认是60*1000)
instance:
hostname: peer3
client:
serviceUrl:
defaultZone: http://peer1:8081/eureka,http://peer2:8082/eureka
.yml
文件中 ---
标识文件分割.
3. 启动项目
打成jar包启动方式:
java -jar eurka-server-0.0.1.jar --spring.profiles.active=peer1
java -jar eurka-server-0.0.1.jar --spring.profiles.active=peer2
java -jar eurka-server-0.0.1.jar --spring.profiles.active=peer3
这里使用IDEA启动:
三个配置依次填入peer1、peer2、peer3
4.访问Eureka服务注册中心
http://peer1:8081 http://peer2:8082 http://peer3:8083 即可看到集群搭建成功。
Eureka的自保护机制
服务注册到Eureka Server之后,会维护一个心跳连接,告诉Eureka Server自己还活着。Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现低于的情况,Eureka Server会认为客户端与注册中心出现了网络故障,会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息,此时会出现以下情况:
- Eureka 不再从注册列表中移除因为长时间没有收到心跳而过期的服务。
- Eureka 仍然能够接收新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点可用)
- 当网络稳定后,当前实例新的注册信息会被同步到其它节点中
另外,在保护期间如果实例出现问题,Eureka Server则会出现可用性问题,部分请求会失败。所以客户端也要配套容错机制,比如可以使用请求重试、断路器等机制。
本地开发的时候,可以使用eureka.server.enable-self-preservation=false
参数来关闭保护机制,以确保注册中心可以将不可用的实例正确剔除。
Eureka与Zookeeper的区别
不知道CAP定理的同学请先去了解CAP定理知识。
Zookeeper在设计的时候遵循的是CP原则,即保证一致性和分区容错性,Zookeeper会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时剩余节点会重新进行leader选举,问题在于,选举leader的时间太长:30~120s,且选举 间整个Zookeeper集群是不可用的,这就导致在选举期间注册服务处于瘫痪状态,在云部署的环境下,因网络环境使Zookeeper集群失去master节点是较大概率发生的事情,虽然服务能够最终恢复,但是漫长的选举时间导致长期的服务注册不可用是不能容忍的。
Eureka在设计的时候遵循的是AP原则,即可用性和分区容错性。Eureka各个节点(服务)是平等的, 没有主从之分,几个节点 down掉不会影响正常工作,剩余的节点(服务) 依然可以提供注册与查询服务,而Eureka的客户端在向某个 Eureka注册或发现连接失败,则会自动切换到其他节点,也就是说,只要有一台Eureka还在,就能注册可用(保证可用性), 只不过查询到的信息不是最新的(不保证强一致)。