前言:
spring-cloud-starter-netflix-eureka 和 eureka的区别: 前者是对后者的封装,以保证其在spring cloud中的运行。后者为纯粹的servlet应用,需要打war运行。
注:netflix eureka 2.x 目前已停更
一.介绍
1.eureka介绍:略
2.CAP原理
在分布式环境中,存在一个著名的CAP原理,C-数据一致性;A-服务可用性;P-服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。
eureka 注册中心符合AP,而zookeeper则符合CP,具体两者的区别会在最后常见问题下进行详细说明。
一.参数配置
1.eureka server 配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
1.1 yml配置 (此配置并未囊括所有,如需要其他则自行配置)
#eureka server刷新readCacheMap的时间,注意,client读取的是readCacheMap,这个时间决定了多久会把readWriteCacheMap的缓存更新到readCacheMap上
#默认30s
eureka.server.responseCacheUpdateIntervalMs=3000
#eureka server缓存readWriteCacheMap失效时间,这个只有在这个时间过去后缓存才会失效,失效前不会更新,过期后从registry重新读取注册服务信息,registry是一个ConcurrentHashMap。
#由于启用了evict其实就用不太上改这个配置了
#默认180s
eureka.server.responseCacheAutoExpirationInSeconds=180
#启用主动失效,并且每次主动失效检测间隔为3s
#默认60s
eureka.server.eviction-interval-timer-in-ms=3000
#服务过期时间配置,超过这个时间没有接收到心跳EurekaServer就会将这个实例剔除
#注意,EurekaServer一定要设置eureka.server.eviction-interval-timer-in-ms否则这个配置无效,这个配置一般为服务刷新时间配置的三倍
#默认90s
eureka.instance.lease-expiration-duration-in-seconds=15
#服务刷新时间配置,每隔这个时间会主动心跳一次
#默认30s
eureka.instance.lease-renewal-interval-in-seconds=5
#eureka client刷新本地缓存时间
#默认30s
eureka.client.registryFetchIntervalSeconds=5
#eureka客户端ribbon刷新时间
#默认30s
ribbon.ServerListRefreshInterval=1000
eureka.instance.preferIpAddress=true
#关闭自我保护
eureka.server.enable-self-preservation=false
eureka.client.serviceUrl.defaultZone=http://localhost:8211/eureka/,http://localhost:8211/eureka/
1.2 main启动注解
package com.my.test.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer //开启eureka服务
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication .class, args);
}
}
2.eureka client 配置
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.1 yml 配置 (此配置并未囊括所有,如需要其他则自行配置)
#服务过期时间配置,超过这个时间没有接收到心跳EurekaServer就会将这个实例剔除
#注意,EurekaServer一定要设置eureka.server.eviction-interval-timer-in-ms否则这个配置无效,这个配置一般为服务刷新时间配置的三倍
#默认90s
eureka.instance.lease-expiration-duration-in-seconds=15
#服务刷新时间配置,每隔这个时间会主动心跳一次
#默认30s
eureka.instance.lease-renewal-interval-in-seconds=5
#eureka client刷新本地缓存时间
#默认30s
eureka.client.registryFetchIntervalSeconds=5
#eureka客户端ribbon刷新时间
#默认30s
ribbon.ServerListRefreshInterval=1000
eureka.instance.preferIpAddress=true
#关闭自我保护
eureka.server.enable-self-preservation=false
#最好每个实例不同顺序,按照离自己最近的Eureka地址放到最前面
eureka.client.serviceUrl.defaultZone=http://localhost:8211/eureka/,http://localhost:8211/eureka/
2.2 main 配置
package com.my.test.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient //此处也可以用 @EnableDiscoveryClient 替代
@SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication .class, args);
}
}
二.运行流程
1.服务调用关系
服务提供者
1.1 启动后,向注册中心发起register请求,注册服务
1.2 在运行过程中,定时向注册中心发送renew心跳,证明“我还活着”。
1.3 停止服务提供者,向注册中心发起cancel请求,清空当前服务注册信息。
服务消费者
1.4 启动后,从注册中心拉取服务注册信息
1.5 在运行过程中,定时更新服务注册信息。
1.6 服务消费者发起远程调用:
2.服务续约
Application Service内的Eureka Client后台启动一个定时任务,跟Eureka Server保持一个心跳续约任务,每隔一段时间(默认30S)向Eureka Server发送一次renew请求,进行续约,告诉Eureka Server我还活着,防止被Eureka Server的Evict任务剔除。
3.服务下线
Application Service应用停止后,向Eureka Server发送一个cancel请求,告诉注册中心我已经退出了,Eureka Server接收到之后会将其移出注册列表,后面再有获取注册服务列表的时候就获取不到了,防止消费端消费不可用的服务。
4.服务剔除
Eureka Server启动后在后台启动一个Evict任务,对一定时间内没有续约的服务进行剔除。
三.数据存储结构
1.接口服务层
负责接收并转换对象
2.二级缓存层
2.1.一级缓存
无过期时间,保存服务信息的对外输出数据结构。
2.2.二级缓存
本质上是guava的缓存,包含失效机制,保存服务信息的对外输出数据结构。
3.数据存储层
包含了服务详情和服务治理相关的属性
四.附属功能
1.身份验证
注册eureka 的时候,通过Spring Security(注:旧版本和新版本的配置会在差异)增加账号密码验证。
导入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
增加Spring Security 配置类(注:再此类中也可以增加配置其他的安全操作,比如失败或成功等之后的一些处理逻辑,有兴趣的可以深入学习Spring Security)
package com.my.test.eureka.config;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* 注册中心(带-账号安全)
* @ClassName SecurityConfig
* @Description
* @Auther Hari
* @Date 2019/1/21 9:35
* @Version 1.0
**/
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
super.configure(http);
}
}
yml中增加账号密码(账号密码也可以在上面的配置类中进行设置)
spring:
security:
user:
name: admin
password: admin
配置完成后,访问注册中心的网页就可以看到提示输入账号密码了。
服务注册到eureka上的url则如下
http://账号:密码@host:port/eureka
2.健康检查
默认情况下,Eureka使用客户端心跳来确定客户端是否启动。除非另有规定,否则发现客户端将不会根据Spring Boot执行器传播应用程序的当前运行状况检查状态。这意味着成功注册后Eureka将永远宣布申请处于“UP”状态。通过启用Eureka运行状况检查可以改变此行为,从而将应用程序状态传播到Eureka。因此,每个其他应用程序将不会在“UP”之外的状态下将流量发送到应用程序。
开启健康检查
eureka:
client:
healthcheck:
enabled: true
3.状态页和健康指标
eureka:
instance:
status-page-url-path: /actuator/info #eureka注册中心的url link
health-check-url-path: /actuator/health #健康检查的url
4.负载均衡
5.高可用性,区域和地区
当存在多个机房时,同一个机房的服务调同一个机房的服务,如同一个机房的服务调不通,则再调用其他机房的服务。
配置
Server 1
eureka:
instance:
metadata-map:
zone: zone-1 #服务所属zone
client:
prefer-same-zone-eureka: true
region: shenzhen #地区
availability-zones:
shenzhen: zone-1,zone-2 #机房12
service-url:
zone-1: http://localhost:8761/eureka/
zone-2: http://localhost:8762/eureka/
Server 2
eureka:
instance:
metadata-map:
zone: zone-2 #服务所属zone
client:
prefer-same-zone-eureka: true
region: shenzhen #地区
availability-zones:
shenzhen: zone-2,zone-1 #机房21
service-url:
zone-1: http://localhost:8761/eureka/
zone-2: http://localhost:8762/eureka/
Client 1
eureka:
instance:
metadata-map:
zone: zone-1 #服务所属zone
client:
prefer-same-zone-eureka: true
region: shenzhen #地区
availability-zones:
shenzhen: zone-1,zone-2 #机房12
service-url:
zone-1: http://localhost:8761/eureka/
zone-2: http://localhost:8762/eureka/
根据如上配置,则client 会先调用server1,如不通则再次调用server2
6.ip地址偏好
在某些情况下,Eureka优先发布服务的IP地址而不是主机名。将eureka.instance.preferIpAddress设置为true,并且当应用程序向eureka注册时,它将使用其IP地址而不是其主机名。
7.同行意识
通过运行多个实例并请求他们相互注册,可以使Eureka更具弹性和可用性。事实上,这是默认的行为,所以你需要做的只是为对方添加一个有效的serviceUrl,例如
---
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: http://peer2/eureka/
---
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: http://peer1/eureka/
8.独立模式
只要存在某种监视器或弹性运行时间(例如Cloud Foundry),两个高速缓存(客户机和服务器)和心跳的组合使独立的Eureka服务器对故障具有相当的弹性。在独立模式下,您可能更喜欢关闭客户端行为,因此不会继续尝试并且无法访问其对等体。例
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
请注意,serviceUrl指向与本地实例相同的主机。
9.自我保护机制
自我保护机制的工作机制是如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:
1、Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
2、Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用
3、当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
配置
eureka:
server:
enable-self-preservation: false #开启或者禁用自我保护
eviction-interval-timer-in-ms: 3000 #心跳检测时间
四.集群部署
简单的多eureka配置(可采取多区域多机房配置,具体参考上:5.高可用性,区域和地区)
server 1
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: http://peer2/eureka/
server 2
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: http://peer1/eureka/
client 1
eureka:
instance:
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 15
prefer-ip-address: true
client:
serviceUrl:
defaultZone: http://peer2/eureka/,http://peer1/eureka/ #同时注册多个注册中心
五.其他
1.实现对eureka监听
package com.my.test.eureka.listener;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.shared.Applications;
import com.netflix.eureka.EurekaServerContextHolder;
import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ClassName EurekaListener
* @Description 监听eureka 服务注册情况
* @Auther Hari
* @Date 2019/1/21 10:55
* @Version 1.0
**/
@Configuration
@Slf4j
@EnableScheduling
public class EurekaListener implements ApplicationListener {
private ConcurrentHashMap<String,LostInstance> lostInstanceMap = new ConcurrentHashMap<>();
private int defalutNotifyInterval[] = {0,60,120,240,480};
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
// 如果服务退出
if (applicationEvent instanceof EurekaInstanceCanceledEvent) {
// 对应执行
EurekaInstanceCanceledEvent event = (EurekaInstanceCanceledEvent) applicationEvent;
//获取eureka 注册的服务列表
PeerAwareInstanceRegistry registry = EurekaServerContextHolder.getInstance().getServerContext().getRegistry();
Applications applications = registry.getApplications();
applications.getRegisteredApplications().forEach((registeredApplication) -> {
registeredApplication.getInstances().forEach((instance) -> {
//获取对应的服务信息
if (instance.getInstanceId().equals(event.getServerId())) {
String id = instance.getInstanceId();
log.info("服务ID:{},服务已退出!",id);
//放入退出队列
lostInstanceMap.remove(id);
lostInstanceMap.put(id, new LostInstance(instance));
}
});
});
}
//服务注册
if (applicationEvent instanceof EurekaInstanceRegisteredEvent) {
EurekaInstanceRegisteredEvent event = (EurekaInstanceRegisteredEvent) applicationEvent;
//则移出退出队列
log.info("服务ID:{},服务注册成功!",event.getInstanceInfo().getInstanceId());
lostInstanceMap.remove(event.getInstanceInfo().getInstanceId());
}
}
@Scheduled(cron = "0/30 * * * * ?")
private void notifyLostInstance(){
lostInstanceMap.entrySet().forEach((lostInstanceMap)->{
String key = lostInstanceMap.getKey();
LostInstance lostInstance = lostInstanceMap.getValue();
DateTime dt = new DateTime(lostInstance.getLostTime());
if(dt.plusSeconds(defalutNotifyInterval[lostInstance.getCurrentInterval()]).isBeforeNow()){
log.info("服务:{}已失效,IP为:{},失效时间为:{},请马上重启服务!",new Object[]{lostInstance.getInstanceId(),lostInstance.getIPAddr(),dt.toString()});
}
});
}
class LostInstance extends InstanceInfo {
protected int currentInterval = 0;
protected Date lostTime;
public LostInstance(InstanceInfo ii) {
super(ii);
this.lostTime = new Date();
}
public Date getLostTime() {
return lostTime;
}
public void setLostTime(Date lostTime) {
this.lostTime = lostTime;
}
public int getCurrentInterval(){
return currentInterval++%4;
}
}
}
六.常见问题
1.服务器上多网卡的情况下Eureka的部署问题 (详细:https://blog.csdn.net/neosmith/article/details/53126924)
场景:服务器上分别配置了eth0, eth1和eth2三块网卡,只有eth1的地址可供其它机器访问,eth0和eth2的 IP 无效。在这种情况下,服务注册时Eureka Client会自动选择eth0作为服务ip, 导致其它服务无法调用。
解决方案:
1.1. 通过上面源码分析可以得知,spring cloud肯定能配置一个网卡忽略列表。通过查文档资料得知确实存在该属性:
spring.cloud.inetutils.ignored-interfaces[0]=eth0 # 忽略eth0, 支持正则表达式
因此,第一种方案就是通过配置application.properties让应用忽略无效的网卡。
1.2.当网查遍历逻辑都没有找到合适ip时会走JDK的InetAddress.getLocalHost()。该方法会返回当前主机的hostname, 然后会根据hostname解析出对应的ip。因此第二种方案就是配置本机的hostname和/etc/hosts文件,直接将本机的主机名映射到有效IP地址。
1.3.添加以下配置:
# 指定此实例的ip
eureka.instance.ip-address=
# 注册时使用ip而不是主机名
eureka.instance.prefer-ip-address=true
2.Eureka服务注册慢的问题
作为一个实例也包括定期心跳到注册表(通过客户端的serviceUrl),默认持续时间为30秒。在实例,服务器和客户端在其本地缓存中都具有相同的元数据(因此可能需要3个心跳)之前,客户端才能发现服务。您可以使用eureka.instance.leaseRenewalIntervalInSeconds更改期限,这将加快客户端连接到其他服务的过程。在生产中,最好坚持使用默认值,因为服务器内部有一些计算可以对租赁更新期进行假设。
3.本机Netflix EurekaClient的替代方案
可使用@EnableDiscoveryClient注解
你不必使用原始的Netflix EurekaClient,通常使用一个包装器会更方便。Spring Cloud提供Fegin(REST客户端构建器)和Spring RestTemplate去使用Eureka service的逻辑标识符替代物理URLS。配置带固定的物理服务器集合的Ribbon,你可以简单的设置.ribbon.listOfServers的物理服务器地址(或者hostname)集合,并使用逗号分隔符分开,是客户端的ID。
你也可以使用 org.springframework.cloud.client.discoveryClient,它提供了一个简单的API而不是特定于Netflix。
在类中如下
@Autowired
private DiscoveryClient discoveryClient;
public String serviceUrl(){
List<ServiceInstane> list = discoveryClient.getInstances("STORES");
if(list!=null && list.siz()>0) {
return list.get(0).getUri();
}
return null;
}
4.eureka 和zookeeper的区别 (详:https://www.cnblogs.com/springsource/p/9379275.htm )
根据CAP原理,eureka属于AP可用性强,zookeeper属于CP为强一致性。在zookeeper出现故障时,会重新选择leader,这需要时间 ,在此期间服务不可用,而且少于三个后,服务同样不可用。
而eureka只要大于等于一个,就可以继续提供服务,并且不存在选择leader等情况,可以立即切换注册中心。
写的不好,还请见谅。部分内容来源网络。如有意见留言交流。