Eureka是Netflix开源的服务发现组件,本身是一个基于REST的服务。它包含Server和Client两部分。Spring Cloud将它集成在子项目Spring Cloud Netflix中,从而实现微服务的注册与发现:
Eureka就相当于微服务的注册中心,注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就这里找到服务的地址,进行调用。
Eureka包含两个组件,Eureka Server 和 Eureka Client,它们的作用如下:
Eureka通过心跳检测、健康检查和客户端缓存等机制,提高了系统的灵活性、可伸缩性和可用性!
为什么需要Eureka呢?
例如:A服务调用B服务,需要在A服务代码里显式通过硬编码IP地址调用,如下。
restTemplate.getForObject("http://127.0.0.1:8080/product/1", Product.class);
那如果有很多个服务,比如A、B、C、D等等,它们之间互相调用,那要是IP地址变了的话,全都得手动更新。
在服务多的情况下,手动来维护这些静态配置就是噩梦!
Spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接, 我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。
常见的注册中心
之间的区别:
组件名 | 语言 | CAP | 一致性算法 | 服务健康检查 | 对外暴露接口 |
---|---|---|---|---|---|
Eureka | Java | AP | 无 | 可配支持 | HTTP |
Consul | Go | CP | Raft | 支持 | HTTP/DNS |
Zookeeper | Java | CP | Paxos | 支持 | 客户端 |
Nacos | Java | AP | Raft | 支持 | HTTP |
选择什么类型的服务注册与发现组件可以根据自身项目要求决定。
1:新建Spring Boot项目Eureka,添加依赖Eureka Server
2:启动类
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
3:配置文件application.yml
server:
port: 8761
eureka:
client:
register-with-eureka: false #是否将自己注册到Eureka服务中,本身就是所有无需注册
fetch-registry: false #要不要去注册中心获取其他服务的注册信息
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。默认为http://localhost:8761/eureka/
4:运行访问:http://localhost:8761/
注意:不是http://localhost:8761/eureka/
由图可知,Eureka Server的首页展示了很多信息!
1:新建Spring Boot项目Eureka1,添加依赖Eureka Client
2:启动类:
@EnableDiscoveryClient //也可使用@EnableEurekaClient注解替代@EnableDiscoveryClient
@SpringBootApplication
public class Eureka1Application {
public static void main(String[] args) {
SpringApplication.run(Eureka1Application.class, args);
}
}
3:配置文件application.yml
server:
port: 8020
spring:
application:
name: eureka1 #指定注册到Eureka Server上的应用名称
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true #将自己的IP注册到Eureka Server上,不配置或为false。则表示注册微服务所在操作系统的hostname到Eureka Server
4:运行测试:
启动项目Eureka
启动项目Eureka1
如果启动Eureka Client报错:
Description:
Field optionalArgs in org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration$RefreshableEurekaClientConfiguration required a bean of type 'com.netflix.discovery.AbstractDiscoveryClientOptionalArgs' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.netflix.discovery.AbstractDiscoveryClientOptionalArgs' in your configuration.
则需要在pom.xml中加入或者父pom.xml中加入
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
这个问题在之前的版本中是没有的,在比较新的版本就会报错!
可以看到Eureka1已注册到Eureka Server上了;
微服务第一次注册成功之后,每30秒会发送一次心跳将服务的实例信息注册到注册中心。通知 EurekaServer 该实例仍然存在。如果超过90秒没有发送更新,则服务器将从注册信息中将此服务移除;
Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现低于的情况(在单机调试的时候很容易满足,实际在生产环境上通常是由于网络不稳定导致),Eureka Server会将当前的实例注册信息保护起来,同时提示这个警告。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。
如果关闭自我保护
通过设置 eureka.server.enable-self-preservation=false来关闭自我保护功能。
eureka:
server:
enable-self-preservation: false
Eureka Client会定时连接Eureka Server,获取服务注册表中的信息并缓存在本地。微服务在消费远程API时总是使用本地缓存中的数据。因此一般来说,即使Eureka Server发生宕机,也不会影响到服务之间的调用。但如果Eureka Server宕机时,某些微服务也出现了不可用的情况,Eureka Client中的缓存若不被更新,就可能会影响到微服务的调用,甚至影响到整个应用系统的高可用性。
Eureka Server可以通过运行多个实例并相互注册的方式实现高可用部署,Eureka Server实例会彼此增量地同步信息,从而确保所有节点数据一致。事实上,节点之间相互注册是Eureka Server的默认行为。
1:新建Spring Boot项目Eureka3,添加依赖Eureka Server
2:启动类
@EnableEurekaServer
@SpringBootApplication
public class Eureka3Application {
public static void main(String[] args) {
SpringApplication.run(Eureka3Application.class, args);
}
}
3:配置系统的hosts(系统盘:\Windows\System32\drivers\etc\hosts)
127.0.0.1 peer1
127.0.0.1 peer2
4:application.yml
spring:
application:
name: eureka3
---
spring:
profiles: peer1 # 指定profile=peer1
server:
port: 8761
eureka:
instance:
hostname: peer1 # 指定当profile=peer1时,主机名是peer1
client:
serviceUrl:
defaultZone: http://peer2:8762/eureka/ # 将自己注册到peer2这个Eureka上面去
---
spring:
profiles: peer2
server:
port: 8762
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/
我们定义了peer1 和 peer2这两个Profile。当应用以peer1 这个Profile启动时,配置该主机名为peer1,并将其注册到 http://peer2:8762/eureka/;反之,当应用以peer2 这个Profile启动时,配置该主机名为peer2,并将其注册到http://peer1:8761/eureka/;
5:运行测试
以peer1 这个Profile启动
以peer2 这个Profile启动
访问:http://peer1:8761/
访问:http://peer2:8762/
DS Replicas表示Eureka Server的相邻节点,即该服务器,从哪里同步数据;
看application.yml配置可知,比如节点peer1 设置是从8762端口的节点同步数据,所以peer1中的DS Replicas是8762端口所对应的名字peer2。
peer2以此类推即可!
1:修改项目Eureka1配置文件application.yml
server:
port: 8020
spring:
application:
name: eureka1
eureka:
client:
serviceUrl:
defaultZone: http://peer2:8762/eureka/,http://peer1:8761/eureka/
instance:
prefer-ip-address: true
2:运行测试:
由图可知,已经将微服务注册到Eureka Server集群上了。
当然,微服务即使只配置Eureka Server集群中的某个节点,也能正常注册到Eureka Server集群,因为多个Eureka Server之间的数据会相互同步!
server:
port: 8020
spring:
application:
name: eureka1
eureka:
client:
serviceUrl:
defaultZone: http://peer2:8762/eureka/
instance:
prefer-ip-address: true
运行结果与上面是一致的!
Eureka Server是允许匿名访问的,下面我们来构建一个需要登录才能访问的Eureka Server。
1:新建Spring Boot项目Eureka4,添加依赖Eureka Server、Security
2:启动类
@EnableEurekaServer
@SpringBootApplication
public class Eureka4Application {
public static void main(String[] args) {
SpringApplication.run(Eureka4Application.class, args);
}
}
3:配置文件application.yml
spring:
security:
basic:
enabled: true # 开启基于HTTP basic的认证
user:
name: user # 配置登录的账号是user
password: abc123 # 配置登录的密码是password123
server:
port: 8761 # 指定该Eureka实例的端口
eureka:
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://localhost:8761/eureka/
看到很多文章Security的配置是这样的:
security:
basic:
enabled: true # 开启基于HTTP basic的认证
user:
name: user # 配置登录的账号是user
password: abc123 # 配置登录的密码是password123
少了Spring前缀,虽然这样安全认证也能生效。但是输入的密码就不再是我们在application.yml里面配置的了,而是在控制台打印的:
Using generated security password: 2bb0be16-7e3f-4470-9756-40eb2e79676f
加上Spring前缀的话,控制台就不会再打印密码了。我们就使用我们配置的密码
4:运行测试
访问:http://localhost:8761/
如何才能将微服务注册到需认证的Eureka Server上呢?答案非常简单,只需将eureka.client.serviceUrl.defaultZone配置为http://user:password@EUREKA_HOST:EUREKA_PORT/eureka/这种形式,就可以将HTTP basic认证添加到Eureka Client了。
Eureka Client的application.yml修改为如下配置:
eureka:
client:
serviceUrl:
defaultZone: http://user:abc123@localhost:8761/eureka/
如果启动报错:
com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
这是因为新版的security默认启用了csrf检验,则需要在Eureka Server中新增此配置类:
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
super.configure(http);
}
}
Eureka 的元数据有两种:
1:新建Spring Boot项目Eureka,添加依赖Eureka Server
2:启动类
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
3:配置文件application.yml
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://localhost:8761/eureka/
4:新建Spring Boot项目Eureka1,添加依赖Eureka Client
5:启动类
@EnableDiscoveryClient
@SpringBootApplication
public class Eureka2Application {
public static void main(String[] args) {
SpringApplication.run(Eureka2Application.class, args);
}
}
6:配置文件application.yml
server:
port: 8020
spring:
application:
name: eureka1
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
metadata-map:
my-metadata: 我自定义的元数据 #自定义的元数据,key和value可以随便写
7:Controller
@RestController
public class MyController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/user-instance")
public List<ServiceInstance> showInfo()
{
//DiscoveryClient.getInstances(serviceId)可查询指定微服务在Eureka上的实例列表
return this.discoveryClient.getInstances("eureka1");
}
}
11:运行测试
启动项目Eureka
启动项目Eureka1
访问:http://localhost:8761/eureka/apps
<applications>
<versions__delta>1versions__delta>
<apps__hashcode>UP_1_apps__hashcode>
<application>
<name>EUREKA1name>
<instance>
<instanceId>DESKTOP-MC17MVP:eureka1:8020instanceId>
<hostName>DESKTOP-MC17MVPhostName>
<app>EUREKA1app>
<ipAddr>10.6.9.196ipAddr>
<status>UPstatus>
<overriddenstatus>UNKNOWNoverriddenstatus>
<port enabled="true">8020port>
<securePort enabled="false">443securePort>
<countryId>1countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwnname>
dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30renewalIntervalInSecs>
<durationInSecs>90durationInSecs>
<registrationTimestamp>1611045945024registrationTimestamp>
<lastRenewalTimestamp>1611046095290lastRenewalTimestamp>
<evictionTimestamp>0evictionTimestamp>
<serviceUpTimestamp>1611045493921serviceUpTimestamp>
leaseInfo>
<metadata>
<management.port>8020management.port>
<my-metadata>我自定义的元数据my-metadata>
metadata>
<homePageUrl>http://DESKTOP-MC17MVP:8020/homePageUrl>
<statusPageUrl>http://DESKTOP-MC17MVP:8020/actuator/infostatusPageUrl>
<healthCheckUrl>http://DESKTOP-MC17MVP:8020/actuator/healthhealthCheckUrl>
<vipAddress>eureka1vipAddress>
<secureVipAddress>eureka1secureVipAddress>
<isCoordinatingDiscoveryServer>falseisCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1611045945024lastUpdatedTimestamp>
<lastDirtyTimestamp>1611045944811lastDirtyTimestamp>
<actionType>ADDEDactionType>
instance>
application>
applications>
访问:http://localhost:8030/user-instance
[
{
"scheme": "http",
"host": "DESKTOP-MC17MVP",
"port": 8020,
"metadata": {
"management.port": "8020",
"my-metadata": "我自定义的元数据"
},
"secure": false,
"instanceId": "DESKTOP-MC17MVP:eureka1:8020",
"serviceId": "EUREKA1",
"uri": "http://DESKTOP-MC17MVP:8020",
"instanceInfo": {
"instanceId": "DESKTOP-MC17MVP:eureka1:8020",
"app": "EUREKA1",
"appGroupName": null,
"ipAddr": "10.6.9.196",
"sid": "na",
"homePageUrl": "http://DESKTOP-MC17MVP:8020/",
"statusPageUrl": "http://DESKTOP-MC17MVP:8020/actuator/info",
"healthCheckUrl": "http://DESKTOP-MC17MVP:8020/actuator/health",
"secureHealthCheckUrl": null,
"vipAddress": "eureka1",
"secureVipAddress": "eureka1",
"countryId": 1,
"dataCenterInfo": {
"@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name": "MyOwn"
},
"hostName": "DESKTOP-MC17MVP",
"status": "UP",
"overriddenStatus": "UNKNOWN",
"leaseInfo": {
"renewalIntervalInSecs": 30,
"durationInSecs": 90,
"registrationTimestamp": 1611045945024,
"lastRenewalTimestamp": 1611046215995,
"evictionTimestamp": 0,
"serviceUpTimestamp": 1611045493921
},
"isCoordinatingDiscoveryServer": false,
"metadata": {
"management.port": "8020",
"my-metadata": "我自定义的元数据"
},
"lastUpdatedTimestamp": 1611045945024,
"lastDirtyTimestamp": 1611045944811,
"actionType": "ADDED",
"asgName": null
}
}
]
可以看到,使用DiscoveryClient的API获得了用户微服务的各种信息,其中包含了标准元数据和自定义元数据;
Eureka Server提供了一些REST端点。非JVM的微服务可使用这些REST端点操作Eureka,从而实现注册与发现。
可以使用XML或者JSON与这些端点通信,默认是XML。
示例:注册微服务到Eureka Server上:
1:新建Spring Boot项目Eureka,添加依赖Eureka Server
2:启动类
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
3:配置文件application.yml
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://localhost:8761/eureka/
4:编写XML,命名为rest-api-test.xml
<instance>
<instanceId>itmuch:rest-api-test:9000instanceId>
<hostName>itmuchhostName>
<app>REST-API-TESTapp>
<ipAddr>127.0.0.1ipAddr>
<vipAddress>rest-api-testvipAddress>
<secureVipAddress>rest-api-testsecureVipAddress>
<status>UPstatus>
<port enabled="true">9000port>
<securePort enabled="false">443securePort>
<homePageUrl>http://127.0.0.1:9000/homePageUrl>
<statusPageUrl>http://127.0.0.1:9000/infostatusPageUrl>
<healthCheckUrl>http://127.0.0.1:9000/healthhealthCheckUrl>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwnname>
dataCenterInfo>
instance>
5:运行测试
启动项目Eureka
postman访问:http://localhost:8761/eureka/apps/rest-api-test,并添加刚刚编写的XML
此时查看Eureka Server首页,会发现微服务已经成功注册:
示例:查看某微服务的所有实例
访问:http://localhost:8761/eureka/apps/rest-api-test
从中,可以看到此微服务的所有实例,以及实例的详细信息!
示例:注销微服务实例
访问:http://localhost:8761/eureka/apps/rest-api-test/itmuch:rest-api-test:9000
例如:某台服务器有eth0、eth1和eth2三块网卡,但是只有eth1可以被其他的服务器访问;如果Eureka Client将eth0或者eth2注册到Eureka Server上,其他微服务就无法通过这个IP调用该微服务的接口
1:忽略指定名称的网卡
spring:
cloud:
inetutils:
ignored-interfaces:
- docker0
- veth.*
2:使用正则表达式,指定使用的网络地址
spring:
cloud:
inetutils:
preferredNetworks:
- 192.168
- 10.0
3:只使用站点本地地址
spring:
cloud:
inetutils:
useOnlySiteLocalInterfaces: true
4:手动指定IP地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
可知,Status一栏有一个UP,表示应用程序状态正常。应用状态还有其他取值,例如:DOWN、OUT_OF_SERVICE、UNKNOWN。只有标记为UP的微服务会被请求。
Eureka Server与Eureka Client之间使用心跳机制来确定Eureka Client的状态,默认情况下,服务器端与客户端的心跳保持正常,应用程序就会始终保持“UP”。
Spring Boot Actuator提供了/health端点,该端点可展示应用程序的健康信息。那么如何才能将该端点中的健康状态传播到Eureka Server呢?
要实现这一点,只需启用Eureka的健康检查。这样,应用程序就会将自己的健康状态传播到Eureka Server。
eureka:
client:
healthcheck:
enabled: true #开启健康检查(依赖spring-boot-actuator)
访问:http://localhost:8761/actuator/health
1:Eureka注册服务慢
默认情况下,服务注册到Eureka Server的过程较慢。在开发或测试时,常常希望能够加速这一过程,从而提升工作效率。
服务的注册涉及到周期性心跳,默认30秒一次(通过客户端配置的serviceUrl)。只有当实例、服务器端和客户端的本地缓存中的元数据都相同时,服务才能被其他客户端发现(所以可能需要三次心跳)。可以使用参数eureka.instance.leaseRenewalIntervalInSeconds修改时间间隔,从而加快客户端连接到其他服务的过程。在生成环境中最好坚持使用默认值,因为在服务器内部有一些计算,它们会对续约做出假设。
所以我们只需将:
eureka.instance.leaseRenewalIntervalInSeconds
#设成一个更小的值,该配置用于设置Eureka Client向Eureka Server发送心跳的时间间隔
#默认是30,单位秒。在生产环境中,建议坚持使用默认值
2:已停止的微服务节点注销慢或不注销
在开发环境下,常常希望Eureka Server能迅速有效地注销已停止的微服务实例。然而,由于Eureka Server清理无效节点周期长(默认90秒),以及自我保护模式等原因,可能会遇到微服务注销慢甚至不注销的问题。解决方案如下:
//设为false,关闭自我保护,从而保证会注销微服务
eureka.server.enable-self-preservation
//清理间隔(单位毫秒,默认是60*1000)
eureka.server.eviction-interval-timer-in-ms
//设为true,开启健康检查(需要spring-boot-starter-actuator依赖)
eureka.client.healthcheck.enabled
//续约更新时间间隔(默认30秒)
eureka.instance.lease-renewal-interval-in-seconds
//续约到期时间(默认90秒)
eureka.instance.lease-expiration-duration-in-seconds
建议这些配置仅在开发或测试使用,生产环境建议坚持使用默认值!
3:自定义微服务的Instance ID
Instance ID用于标识注册到Eureka Server上的微服务实例,例如:DESKTOP-NS58NPF.mshome.net:eureka1:8020就是Instance ID。
在Spring Cloud中,服务的Instance ID的默认值是:
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
如果想要自定义这部分内容,只需在微服务中配置eureka.instance.instance-id属性即可,例如:
spring:
application:
name: eurekaclinet
eureka:
instance:
instance-id: ${spring.cloud.client.ipAddress}:${server.port}
4:Eureka的spring问题总结与解决
一种是应用名称UNKNOWN,一种是应用状态UNKNOWN:
应用名称UNKNOWN
应用名称UNKNOWN显然不合适,首先是微服务的名称不够语义化,无法直观看出这是哪个微服务;更重要的是,我们常常使用应用名称消费对应微服务的接口。
一般来说,有两种情况会导致该问题发送:
微服务实例状态UNKNOWN
该问题一般由健康检查导致:
eureka.client.healthcheck.enabled=true必须配置在application.yml中,而不能配置在bootstrap.yml中,否则一些场景下会导致应用状态UNKNOWN的问题。
参考书籍:Spring Cloud与Docker微服务架构实战
参考视频、笔记:https://www.bilibili.com/video/BV1eE41187Ug
以上只是学习所做的笔记,以供日后参考!!!