Eureka 介绍
1.背景:在传统应用中,组件之间的调用,通过有规范的约束的接口来实现,从而实现不同模块间良好的协作。但是被拆分成微服务后,每个微服务实例的网络地址都可能动态变化,数量也会变化,使得原来硬编码的地址失去了作用。需要一个中心化的组件来进行服务的登记和管理。
2.概念:实现服务治理,即管理所有的服务信息和状态。
3.注册中心好处:不用关心有多少提供方。
4.注册中心有哪些:Eureka,Nacos,Consul,Zookeeper等。
5.服务注册与发现包括两部分,一个是服务器端,另一个是客户端。
Server是一个公共服务,为Client提供服务注册和发现的功能,维护注册到自身的Client的相关信息,同时提供接口给Client获取注册表中其他服务的信息,使得动态变化的Client能够进行服务间的相互调用。
Client将自己的服务信息通过一定的方式登记到Server上,并在正常范围内维护自己信息一致性,方便其他服务发现自己,同时可以通过Server获取到自己依赖的其他服务信息,完成服务调用,还内置了负载均衡器,用来进行基本的负载均衡。
6.Eureka:是一个RESTful风格的服务,是一个用于服务发现和注册的基础组件,是搭建Spring Cloud微服务的前提之一,它屏蔽了Server和client的交互细节,使得开发者将精力放到业务上。
7.serverA从serverB同步信息,则serverB是serverA的peer。
8.Netflix开源的组件。包括server和client两部分。
client功能
1.注册:每个微服务启动时,将自己的网络地址等信息注册到注册中心,注册中心会存储(内存中)这些信息。
2.获取服务注册表:服务消费者从注册中心,查询服务提供者的网络地址,并使用该地址调用服务提供者,为了避免每次都查注册表信息,所以client会定时去server拉取注册表信息到缓存到client本地。
3.心跳:各个微服务与注册中心通过某种机制(心跳)通信,若注册中心长时间和服务间没有通信,就会注销该实例。
4.调用:实际的服务调用,通过注册表,解析服务名和具体地址的对应关系,找到具体服务的地址,进行实际调用。
server注册中心功能
1.服务注册表:记录各个微服务信息,例如服务名称,ip,端口等。
2.注册表提供 查询API(查询可用的微服务实例)和管理API(用于服务的注册和注销)。
3.服务注册与发现:注册:将微服务信息注册到注册中心。发现:查询可用微服务列表及其网络地址。
4.服务检查:定时检测已注册的服务,如发现某实例长时间无法访问,就从注册表中移除。
Eureka 单节点搭建
1.pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2.application.yml
eureka:
client:
#是否将自己注册到Eureka Server,默认为true,由于当前就是server,故而设置成false,表明该服务不会向eureka注册自己的信息
register-with-eureka: false
#是否从eureka server获取注册信息,由于单节点,不需要同步其他节点数据,用false
fetch-registry: false
#设置服务注册中心的URL,用于client和server端交流
service-url:
defaultZone: http://localhost:7900/eureka/
3.application.properties
#是否将自己注册到Eureka Server,默认为true,由于当前就是server,故而设置成false,表明该服务不会向eureka注册自己的信息
eureka.client.register-with-eureka=false
#是否从eureka server获取注册信息,由于单节点,不需要同步其他节点数据,用false
eureka.client.fetch-registry=false
#设置服务注册中心的URL,用于client和server端交流
eureka.client.service-url.defaultZone=http://localhost:7900/eureka/
4.代码
启动类上添加此注解标识该服务为配置中心
@EnableEurekaServer
5.PS:Eureka会暴露一些端点。端点用于Eureka Client注册自身,获取注册表,发送心跳。
6.简单看一下eureka server控制台,实例信息区,运行环境信息区,Eureka Server自身信息区。
Eureka高可用
高可用:可以通过运行多个Eureka server实例并相互注册的方式实现。Server节点之间会彼此增量地同步信息,从而确保节点中数据一致。可选择两个节点或者三个节点,具体搭建略有区别,可参照官网。
Eureka原理
eureka分为两部分,Server端和Client端
Register
服务注册
想要参与服务注册发现的实例首先需要向Eureka服务器注册信息
注册在第一次心跳发生时提交
Renew
续租,心跳
Eureka客户需要每30秒发送一次心跳来续租
10:00 00 第一次
10:00 30
10:01
10:01 30 最后
更新通知Eureka服务器实例仍然是活动的。如果服务器在90秒内没有看到更新,它将从其注册表中删除实例
Fetch Registry
Eureka客户端从服务器获取注册表信息并将其缓存在本地。
之后,客户端使用这些信息来查找其他服务。
通过获取上一个获取周期和当前获取周期之间的增量更新,可以定期(每30秒)更新此信息。
节点信息在服务器中保存的时间更长(大约3分钟),因此获取节点信息时可能会再次返回相同的实例。Eureka客户端自动处理重复的信息。
在获得增量之后,Eureka客户机通过比较服务器返回的实例计数来与服务器协调信息,如果由于某种原因信息不匹配,则再次获取整个注册表信息。
Cancel
Eureka客户端在关闭时向Eureka服务器发送取消请求。这将从服务器的实例注册表中删除实例,从而有效地将实例从通信量中取出。
Time Lag
同步时间延迟
来自Eureka客户端的所有操作可能需要一段时间才能反映到Eureka服务器上,然后反映到其他Eureka客户端上。这是因为eureka服务器上的有效负载缓存,它会定期刷新以反映新信息。Eureka客户端还定期地获取增量。因此,更改传播到所有Eureka客户机可能需要2分钟。
Communication mechanism
通讯机制
Http协议下的Rest请求
默认情况下Eureka使用Jersey和Jackson以及JSON完成节点间的通讯
Eureka单独使用–Rest服务调用
官方文档
https://github.com/Netflix/eureka/wiki/Eureka-REST-operations
/eureka/status 服务状态
使用浏览器请求url会返回服务器状态信息(xml格式)
<com.netflix.eureka.util.StatusInfo>
<generalStats>
<environment>test</environment>
<num-of-cpus>16</num-of-cpus>
<total-avail-memory>526mb</total-avail-memory>
<current-memory-usage>183mb (34%)</current-memory-usage>
<server-uptime>00:00</server-uptime>
</generalStats>
<applicationStats>
<registered-replicas></registered-replicas>
<available-replicas></available-replicas>
<unavailable-replicas></unavailable-replicas>
</applicationStats>
<instanceInfo>
<instanceId>localhost</instanceId>
<hostName>localhost</hostName>
<app>UNKNOWN</app>
<ipAddr>192.168.29.1</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN</overriddenstatus>
<port enabled="true">8080</port>
<securePort enabled="false">443</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30</renewalIntervalInSecs>
<durationInSecs>90</durationInSecs>
<registrationTimestamp>0</registrationTimestamp>
<lastRenewalTimestamp>0</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>0</serviceUpTimestamp>
</leaseInfo>
<metadata>
<management.port>8080</management.port>
<jmx.port>7649</jmx.port>
</metadata>
<homePageUrl>http://localhost:8080/</homePageUrl>
<statusPageUrl>http://localhost:8080/actuator/info</statusPageUrl>
<healthCheckUrl>http://localhost:8080/actuator/health</healthCheckUrl>
<vipAddress>unknown</vipAddress>
<secureVipAddress>unknown</secureVipAddress>
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1586328420409</lastUpdatedTimestamp>
<lastDirtyTimestamp>1586328420519</lastDirtyTimestamp>
</instanceInfo>
</com.netflix.eureka.util.StatusInfo>
如果需要json格式 可以加个请求头Accept:application/json
{
"generalStats": {
"environment": "test",
"num-of-cpus": "16",
"total-avail-memory": "517mb",
"current-memory-usage": "45mb (8%)",
"server-uptime": "00:03"
},
"applicationStats": {
"registered-replicas": "",
"available-replicas": "",
"unavailable-replicas": ""
},
"instanceInfo": {
"instanceId": "localhost",
"hostName": "localhost",
"app": "UNKNOWN",
"ipAddr": "192.168.29.1",
"status": "UP",
"overriddenStatus": "UNKNOWN",
"port": {
"$": 8080,
"@enabled": "true"
},
"securePort": {
"$": 443,
"@enabled": "false"
},
"countryId": 1,
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"leaseInfo": {
"renewalIntervalInSecs": 30,
"durationInSecs": 90,
"registrationTimestamp": 0,
"lastRenewalTimestamp": 0,
"evictionTimestamp": 0,
"serviceUpTimestamp": 0
},
"metadata": {
"management.port": "8080",
"jmx.port": "7649"
},
"homePageUrl": "http://localhost:8080/",
"statusPageUrl": "http://localhost:8080/actuator/info",
"healthCheckUrl": "http://localhost:8080/actuator/health",
"vipAddress": "unknown",
"secureVipAddress": "unknown",
"isCoordinatingDiscoveryServer": "false",
"lastUpdatedTimestamp": "1586328420409",
"lastDirtyTimestamp": "1586328420519"
}
注册到eureka的服务信息查看
get: {ip:port}/eureka/apps
注册到eureka的具体的服务查看
get: {ip:port}/eureka/apps/{appname}/{id}
服务续约
put:{ip:port}/eureka/apps/{appname}/{id}?lastDirtyTimestamp={}&status=up
更改服务状态
put:{ip:port}/eureka/apps/{appname}/{id}/status?lastDirtyTimestamp={}&value={UP/DOWN}
对应eureka源码的:InstanceResource.statusUpdate
删除状态更新
delete:{ip:port}/eureka/apps/{appname}/{id}/status?lastDirtyTimestamp={}&value={UP/DOWN}
删除服务
delete: {ip:port}/eureka/apps/{appname}/{id}
元数据
Eureka的元数据有两种:标准元数据和自定义元数据。
标准元数据:主机名、IP地址、端口号、状态页和健康检查等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。
自定义元数据:可以使用eureka.instance.metadata-map配置,这些元数据可以在远程客户端中访问,但是一般不改变客户端行为,除非客户端知道该元数据的含义。
可以在配置文件中对当前服务设置自定义元数据,可后期用户个性化使用
元数据可以配置在eureka服务器和eureka的客户端上
eureka.instance.metadata-map.dalao=mashibing
服务端:
客户端:
{
"applications": {
"versions__delta": "1",
"apps__hashcode": "UP_2_",
"application": [
{
"name": "EUREKA-CONSUMER",
"instance": [
{
"instanceId": "localhost:Eureka-Consumer:9001",
"hostName": "localhost",
"app": "EUREKA-CONSUMER",
"ipAddr": "192.168.29.1",
"status": "UP",
"overriddenStatus": "UNKNOWN",
"port": {
"$": 9001,
"@enabled": "true"
},
"securePort": {
"$": 443,
"@enabled": "false"
},
"countryId": 1,
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"leaseInfo": {
"renewalIntervalInSecs": 30,
"durationInSecs": 90,
"registrationTimestamp": 1586331982283,
"lastRenewalTimestamp": 1586331982283,
"evictionTimestamp": 0,
"serviceUpTimestamp": 1586331982283
},
"metadata": {
"dalao": "mashibing666",
"management.port": "9001",
"jmx.port": "10158"
},
"homePageUrl": "http://localhost:9001/",
"statusPageUrl": "http://localhost:9001/actuator/info",
"healthCheckUrl": "http://localhost:9001/actuator/health",
"vipAddress": "Eureka-Consumer",
"secureVipAddress": "Eureka-Consumer",
"isCoordinatingDiscoveryServer": "false",
"lastUpdatedTimestamp": "1586331982283",
"lastDirtyTimestamp": "1586331982260",
"actionType": "ADDED"
},
{
"instanceId": "localhost:Eureka-Consumer:9000",
"hostName": "localhost",
"app": "EUREKA-CONSUMER",
"ipAddr": "192.168.29.1",
"status": "UP",
"overriddenStatus": "UNKNOWN",
"port": {
"$": 9000,
"@enabled": "true"
},
"securePort": {
"$": 443,
"@enabled": "false"
},
"countryId": 1,
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"leaseInfo": {
"renewalIntervalInSecs": 30,
"durationInSecs": 90,
"registrationTimestamp": 1586331637223,
"lastRenewalTimestamp": 1586332057220,
"evictionTimestamp": 0,
"serviceUpTimestamp": 1586331637223
},
"metadata": {
"dalao": "mashibing",
"management.port": "9000",
"jmx.port": "10000"
},
"homePageUrl": "http://localhost:9000/",
"statusPageUrl": "http://localhost:9000/actuator/info",
"healthCheckUrl": "http://localhost:9000/actuator/health",
"vipAddress": "Eureka-Consumer",
"secureVipAddress": "Eureka-Consumer",
"isCoordinatingDiscoveryServer": "false",
"lastUpdatedTimestamp": "1586331637223",
"lastDirtyTimestamp": "1586331637182",
"actionType": "ADDED"
}
]
}
]
}
}
EurekaClient
EurekaClient 可以在客户端获取eureka服务器上的注册者信息
org.springframework.cloud.client.discovery与com.netflix.discovery.DiscoveryClient
org.springframework.cloud.client.discovery是SpringCloud对注册中心client的抽象封装,提供公用功能
org.springframework.cloud.client.discovery定义用来服务发现的客户端接口,是客户端进行服务发现的核心接口,是spring cloud用来进行服务发现的顶级接口,在common中可以看到其地位。在Netflix Eureka和Consul中都有具体的实现类。
代表通用于服务发现的读操作,例如在 eureka或consul中。
有
String description();//获取实现类的描述。
List<String> getServices();//获取所有服务实例id。
List<ServiceInstance> getInstances(String serviceId);//通过服务id查询服务实例信息列表。
com.netflix.discovery.DiscoveryClient为Eureka注册中心客户端的接口,功能更丰富
自我保护机制
Eureka在CAP理论当中是属于AP , 也就说当产生网络分区时,Eureka保证系统的可用性,但不保证系统里面数据的一致性
默认开启,服务器端容错的一种方式,即短时间心跳不到达仍不剔除服务列表里的节点
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
默认情况下,Eureka Server在一定时间(90S)内,没有接收到某个微服务心跳,会将某个微服务注销。但是当网络故障时,微服务与Server之间无法正常通信,上述行为就非常危险,因为微服务正常,不应该注销。
Eureka Server通过自我保护模式来解决整个问题,当Server在短时间内丢失过多客户端时,那么Server会进入自我保护模式,会保护注册表中的微服务不被注销掉。当网络故障恢复后,退出自我保护模式。
思想:宁可保留健康的和不健康的,也不盲目注销任何健康的服务。
自我保护触发
客户端每分钟续约数量小于客户端总数的85%时会触发保护机制
自我保护机制的触发条件:
(当每分钟心跳次数( renewsLastMin ) 小于 numberOfRenewsPerMinThreshold 时,并且开启自动保护模式开关( eureka.server.enable-self-preservation = true ) 时,触发自我保护机制,不再自动过期租约。)
numberOfRenewsPerMinThreshold = expectedNumberOfRenewsPerMin * 续租百分比( eureka.server.renewalPercentThreshold, 默认0.85 )
expectedNumberOfRenewsPerMin = 当前注册的应用实例数 x 2
为什么乘以 2:
默认情况下,注册的应用实例每半分钟续租一次,那么一分钟心跳两次,因此 x 2 。
服务实例数:10个,期望每分钟续约数:10 * 2=20,期望阈值:20*0.85=17,自我保护少于17时 触发。
关闭自我保护
eureka.server.enable-self-preservation=false
关闭后会提示
清理时间
默认60秒,可酌情修改
eureka.server.eviction-interval-timer-in-ms=3000
多网卡选择
1.ip注册
eureka:
instance:
prefer-ip-address: true
表示将自己的ip注册到EurekaServer上。不配置或false,表示将操作系统的hostname注册到server
2.服务器有多个网卡,eh0,eh1,eh2,只有eh0可以让外部其他服务访问进来,而Eureka client将eh1和eh2注册到Eureka server上,这样其他服务就无法访问该微服务了。
3.指定Ip
eureka:
instance:
prefer-ip-address: true
ip-address: 实际能访问到的Ip
如果设置了此时的ip-address,在元数据查看到就是此ip,其他服务也通过此ip来调用。
{
"host": "127.0.0.1",
"port": 8084,
"metadata": {
"yueyi": "2019",
"user.password": "root",
"management.port": "8084",
"jmx.port": "61378",
"user.name": "root"
},
"secure": false,
"uri": "http://127.0.0.1:8084",
"instanceId": "api-listen-order:30.136.133.11:port",
"serviceId": "API-LISTEN-ORDER",
"instanceInfo": {
"instanceId": "api-listen-order:30.136.133.11:port",
"app": "API-LISTEN-ORDER",
"appGroupName": null,
"ipAddr": "127.0.0.1",
"sid": "na",
"homePageUrl": "http://127.0.0.1:8084/",
"statusPageUrl": "http://127.0.0.1:8084/actuator/info",
"healthCheckUrl": "http://127.0.0.1:8084/actuator/health",
"secureHealthCheckUrl": null,
"vipAddress": "api-listen-order",
"secureVipAddress": "api-listen-order",
"countryId": 1,
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"hostName": "127.0.0.1",
"status": "UP",
"overriddenStatus": "UNKNOWN",
"leaseInfo": {
"renewalIntervalInSecs": 1,
"durationInSecs": 1,
"registrationTimestamp": 1579489514655,
"lastRenewalTimestamp": 1579489524146,
"evictionTimestamp": 0,
"serviceUpTimestamp": 1579489514147
},
"isCoordinatingDiscoveryServer": false,
"metadata": {
"yueyi": "2019",
"user.password": "root",
"management.port": "8084",
"jmx.port": "61378",
"user.name": "root"
},
"lastUpdatedTimestamp": 1579489514655,
"lastDirtyTimestamp": 1579489514111,
"actionType": "ADDED",
"asgName": null
},
"scheme": null
}
或者使用spring.cloud.inetutils配置网卡选择
Eureka 健康检查
由于server和client通过心跳保持 服务状态,而只有状态为UP的服务才能被访问。看eureka界面中的status。
比如心跳一直正常,服务一直UP,但是此服务DB连不上了,无法正常提供服务。
此时,我们需要将 微服务的健康状态也同步到server。只需要启动eureka的健康检查就行。这样微服务就会将自己的健康状态同步到eureka。配置如下即可。
开启手动控制
在client端配置:将自己真正的健康状态传播到server。
eureka:
client:
healthcheck:
enabled: true
Client端配置Actuator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
改变健康状态的Service
@Service
public class HealthStatusService implements HealthIndicator{
private Boolean status = true;
public void setStatus(Boolean status) {
this.status = status;
}
@Override
public Health health() {
// TODO Auto-generated method stub
if(status)
return new Health.Builder().up().build();
return new Health.Builder().down().build();
}
public String getStatus() {
// TODO Auto-generated method stub
return this.status.toString();
}
测试用的Controller
@GetMapping("/health")
public String health(@RequestParam("status") Boolean status) {
healthStatusSrv.setStatus(status);
return healthStatusSrv.getStatus();
}
安全配置
开启Eureka安全连接
spring.security.user.name=yiming
spring.security.user.password=123
如果服务注册报错
Root name 'timestamp' does not match expected ('instance') for type [simple
是默认开启了防止跨域攻击
手动关闭
在服务端增加配置类
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.csrf().disable();
super.configure(http);
}
}
服务间调用
微服务中,很多服务系统都在独立的进程中运行,通过各个服务系统之间的协作来实现一个大项目的所有业务功能。服务系统间 使用多种跨进程的方式进行通信协作,而RESTful风格的网络请求是最为常见的交互方式之一。
http。
思考:如果让我们写服务调用如何写。
硬编码。不好。ip域名写在代码中。目的:找到服务。
根据服务名,找相应的ip。目的:这样ip切换或者随便变化,对调用方没有影响。
Map<服务名,服务列表> map;
加上负载均衡。目的:高可用。
spring cloud提供的方式:
RestTemplate
Feign
我个人习惯用RestTemplate,因为自由,方便调用别的第三方的http服务。feign也可以,更面向对象一些,更优雅一些,就是需要配置。