Eureka简单学习
这是个人学习笔记,如有错误,还望海涵、斧正。
一、简介
Eureka 是一个专门用于服务发现的服务器,一些服务注册到该服务器,而另一些服务通过该服务器查找其所要调用执行的服务。
其本身是一个基于 REST 的服务,主要用于定位运行在 AWS 域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。
可以充当服务发现服务器的组件很多,例如 Zookeeper、Consul、Eureka 等。
下图是一张Eureka的体系架构:
(图片引自:https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance)
二、使用方法
通过idea的Spring Initializr创建一个springcloud项目
1.创建server
(1)导入Eureka Server依赖
(2)修改application配置文件为yml格式
直接改后缀名就行(个人习惯,yml更舒服)
(3)修改配置文件
eureka:
instance:
hostname: localhost #指定Eureka主机
client:
register-with-eureka: false #是否向Eureka注册自己(主机不注册自己)
fetch-registry: false #指定此客户端是否可以获取eureka的注册信息
service-url: #暴露服务中心地址
defaultZone: http://localhost:8081/eureka
server:
port: 8081
(4)启动
给启动类加上 开启Eureka的注解 @EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
public class TigerApplication {
public static void main(String[] args) {
SpringApplication.run(TigerApplication.class, args);
}
}
启动项目,访问项目地址http://localhost:8081/,成功启动后如下图
2.创建provider
有两种方式供选择:
第一种方式
引用依赖
嫌麻烦就直接复制刚刚创建的server项目,重命名为eureka-provider-8082。然后手动修改pom文件(复制粘贴下面的的依赖就行)
*删掉删掉删掉删掉原来的server依赖,就是下面这个:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
然后引入一个client的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
provider引入上面两个依赖就够了,但是为了和数据库做连接,我们还要引入下面的依赖:
<!--数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--修改MySQL驱动版本-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
除了数据库,作为一个web项目还要引入以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
下面两个是辅助用的依赖,可引可不引。lombok用来生成实体类的get/set方法,actuator用来配置监控终端的显示信息
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--actuator 依赖-->
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置
配置文件(info为actuator配置)
eureka:
instance:
hostname: localhost #指定Eureka主机
instance-id: eureka-provider-8082 #指定当前客户端在服务中心注册的名称
# 指定服务中心
client:
# register-with-eureka: true #是否向Eureka注册自己(主机不注册自己)
# fetch-registry: true #指定此客户端是否可以获取eureka的注册信息
service-url: #暴露服务中心地址
defaultZone: http://localhost:8081/eureka
server:
port: 8082
spring:
# 指定当前微服务对外暴露的名称
application:
name: eureka-provider-1
# 配置spring-data-jap
jpa:
# 是否在spring容器启时创建表
generate-ddl: true
show-sql: true
hibernate:
ddl-auto: none
# 配置数据源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///test_my?useUnicode=true&characterEncoding=utf8
username: root
password: root
logging:
# 设置日志输出格式
pattern:
console: level-%level %msg%n
level:
root: info
org.hibernate: info
org.hibernate.type.descriptor.sql.BasicBinder: trace
org.hibernate.type.descriptor.sql.BasicExtractor: trace
com.abc.provider: debug
# Eureka工作界面中instance-id处链接的信息 参数都是自定义的
info:
author: tiger
app.name: eureka-provider-8082
app.desc: a desc
第二种方式
重新创建一个项目,在依赖处选择照下图选择就行
配置
配置文件:
由于数据库连接驱动不同,所以datasource配置和第一种方法的不太一样。其他配置则一致
eureka:
instance:
hostname: localhost #指定Eureka主机
instance-id: eureka-provider-8082 #指定当前客户端在服务中心注册的名称
# 指定服务中心
client:
# register-with-eureka: true #是否向Eureka注册自己(主机不注册自己)
# fetch-registry: true #指定此客户端是否可以获取eureka的注册信息
service-url: #暴露服务中心地址
defaultZone: http://localhost:8081/eureka
server:
port: 8083
spring:
# 指定当前微服务对外暴露的名称
application:
name: eureka-provider-1
# 配置spring-data-jap
jpa:
# 是否在spring容器启时创建表
generate-ddl: true
show-sql: true
hibernate:
ddl-auto: none
# 配置数据源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///test_my?useUnicode=true&&characterEncoding=utf8&serverTimezone=GMT
username: root
password: root
logging:
# 设置日志输出格式
pattern:
console: level-%level %msg%n
level:
root: info
org.hibernate: info
org.hibernate.type.descriptor.sql.BasicBinder: trace
org.hibernate.type.descriptor.sql.BasicExtractor: trace
com.abc.provider: debug
# Eureka工作界面中instance-id处链接的信息 参数都是自定义的
info:
author: tiger
app.name: eureka-provider-8082
app.desc: a desc
启动
在启动类上加上启动注解@EnableEurekaClient,项目跑起来后在 http://localhost:8081/ 上可以看到已经注册进去了
至于controller service dao entity 这些,相信大家都会的,这里就不写出来了。
3.创建consumer
依赖
配置
eureka:
instance:
hostname: localhost #指定Eureka主机
instance-id: eureka-consumer-8083 #指定当前客户端在服务中心注册的名称
# 指定服务中心
client:
# register-with-eureka: true #是否向Eureka注册自己(主机不注册自己)
# fetch-registry: true #指定此客户端是否可以获取eureka的注册信息
service-url: #暴露服务中心地址
defaultZone: http://localhost:8081/eureka
server:
port: 8083
spring:
# 指定当前微服务对外暴露的名称
application:
name: eureka-consumer-1
# Eureka工作界面中instance-id处链接的信息 参数都是自定义的
info:
author: tiger
app.name: eureka-provider-8082
app.desc: a desc
启动
启动类加上注解@EnableEurekaClient,启动完成后在 http://localhost:8081/ 上可以看到已经注册进去了
4.创建集群
由于都使用同一个机器进行测试,先配一下host文件,加入以下配置
#eureka server
127.0.0.1 eureka-server-8081
127.0.0.1 eureka-server-8088
127.0.0.1 eureka-server-8089
然后将之前端口为8081的eureka-server项目复制两份,共三个项目组成集群
三个项目的yml文件配置如下:
eureka-server-8081:
eureka:
instance:
hostname: eureka-server-8081 #指定Eureka主机
client:
register-with-eureka: false #是否向Eureka注册自己(主机不注册自己)
fetch-registry: false #指定此客户端是否可以获取eureka的注册信息
service-url: #暴露服务中心地址
defaultZone: http://eureka-server-8081:8081/eureka,http://eureka-server-8088:8088/eureka,http://eureka-server-8089:8089/eureka
server:
port: 8081
eureka-server-8088:
eureka:
instance:
hostname: eureka-server-8088 #指定Eureka主机
client:
register-with-eureka: false #是否向Eureka注册自己(主机不注册自己)
fetch-registry: false #指定此客户端是否可以获取eureka的注册信息
service-url: #暴露服务中心地址
defaultZone: http://eureka-server-8081:8081/eureka,http://eureka-server-8088:8088/eureka,http://eureka-server-8089:8089/eureka
server:
port: 8088
eureka-server-8089:
eureka:
instance:
hostname: eureka-server-8089 #指定Eureka主机
client:
register-with-eureka: false #是否向Eureka注册自己(主机不注册自己)
fetch-registry: false #指定此客户端是否可以获取eureka的注册信息
service-url: #暴露服务中心地址
defaultZone: http://eureka-server-8081:8081/eureka,http://eureka-server-8088:8088/eureka,http://eureka-server-8089:8089/eureka
server:
port: 8089
可以看到,要组成集群,则将所有的server的地址都配置到defaultZone上即可,每个地址通过逗号分隔(注意逗号不能有空格,因为yml里空格是有效字符)
然后分别启动三个项目,得出以下界面,即为集群搭建成功:
http://eureka-server-8081:8081/、
http://eureka-server-8088:8088/、
http://eureka-server-8089:8089/
这时候启动 provider 和 consumer 的微服务,只要 provider 和 consumer 的 defaultZone 中配了上面任意一个server 的地址,就可以在三个 server 中都注册。
当然,provider 和 consumer 最好将三个地址都配上,这样某个 server 挂掉后, 还能继续使用其他 server
启动 provider 和 consumer 后,我们查看三个 server 的界面,他们都会存在同样的服务列表如下:
三、底层原理
Eureka 注册信息管理机制
描述
Eureka 的注册信息则是通过“缓存 + 存储”的双 Map 机制完成的。
Eureka 的数据存储分了两层:数据存储层和数据缓存层。
Eureka Client 在拉取服务信息时,先从缓存层获取,若获取不到,则先把数据存储层的数据加载到缓存中,再从缓存中获取。(也就是说,Eureka Client永远都是从缓存层拉取服务信息)
数据存储层
数据存放位置
数据存储层中的信息是存放在内存中的,不做持久化。
原因:
1.若不是所有 Eureka Server 都宕机,则宕机的 Server 重启后,会从其它 Server 中同步注册信息
2.若全部 Eureka Server 宕机,则重新接受客户端的注册
数据存放方式
用于存储注册信息的是一个双层 ConcurrentHashMap。如:Map<String, Map<String, Lease>>
第一层的 key 是 spring.application.name
第二层的 key 是微服务主机的 InstanceId(即配置文件里的 instance-id),value 是 Lease 对象(续租对象)。Lease 对象包含了服务详情和服务治理相关的信息。
当出现客户端向 Eureka 服务器提交 register、renew 和 cancel 请求时,都会修改这 个双层 Map 中的数据。
数据缓存层
一级缓存 readOnlyCacheMap
一级缓存中的数据只要不被替换就会一直存在,客户端拉取注册信息时就是从这个缓存中拉取
二级缓存 readWriteCacheMap
二级缓存中的数据会随着客户端的 register、renew 和 cancel 请求而更新,因为当客户端发起这些请求,数据存储层的数据会被更新,为了保证缓存层和存储层数据的一致性,二级缓存会从存储层从新加载数据。
新数据的加载不会立即引发一级缓存的更新或清空,一级缓存会定期从二级缓存中同步数据。
Eureka 的自我保护机制
现象
在上面启动 provider 和 consumer 项目的时候,我们都能在 server 的界面上看到一句红色的警告
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开启了自我保护机制,翻译过来就是:
紧急情况!当微服务主机联系不上时,Eureka 不能够正确判断它们是否处于 up 状态。当更新(指收到的微服务主机的心跳)小于阈值时,为了安全,微服务主机将不再失效。
原因
默认情况下,Eureka Server 在 90 秒内(微服务默认30秒发一次心跳)没有收到某微服务的心跳,则判断改为服务的主机宕机,将该微服务从服务注册信息表中删除。
但很多时候,因为网络抖动等原因,导致微服务主机发送的心跳没有被 Eureka Server 收到,从而被 Eureka Server 删除。
若短时间内网络恢复正常,但由于服务列表中删除了该微服务,所以该微服务不能再提供服务。
为了防止上述情况发生,Eureka 便有了自我保护机制。
即在短时间内,如果 Eureka Server 丢失了较多的服务,即收到的心跳数量小于阈值,为了保证系统的可用性,Eureka会进入自我保护模式,此时服务列表只可以读取、注册,不可以删除。这样便给了那些因为网络抖动而被Eureka Server 误认为宕机的微服务主机重新复活的机会。当网络恢复正常,微服务主机得以继续发送心跳,当心跳数达到阈值以上,则退出自我保护模式。
关于阈值:自我保护机制的默认阈值是85%。当一定时间内收到的心跳数小于总微服务数量的85%时,开启自我保护模式。
从页面上的两个参数,我们可以判断出来什么时候会开启自我保护
Renews threshold 是 15分钟内每分钟应该收到的数量。
Renews (last min) 是 最后一分钟实际收到的数量。
注意,Eureka默认你的是微服务应用是100个,所以(100*85%)/15 取整后为 5,即每分钟应该收到5个以上的心跳。
所以,很容易判断,当Renews (last min) < Renews threshold时,自我保护就会开启。
但由上我们又知道,如果注册到 Eureka 的 微服务不达到一定的数量,这个自我保护机制并不能由明显效果。
下面给出自定义自我保护阈值和关闭自我保护的参数:
eureka:
server:
renewal-percent-threshold: 0.75 #默认0.85
enable-self-preservation: true # 关闭自我保护机制