SpringCloud EureKa组件
06、Eureka介绍
6.1 初识Eureka
首先我们来解决第一个问题,服务的管理。
问题分析
在刚才的案例中,user-service对外提供服务,需要对外暴露自己的地址。而user-consumer(消费者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。
滴滴出行
网约车出现以前,人们出门打车只能叫出租车。一些私家车主想做出租司机却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,可能半天都等不到车,私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。相当于一个想要,另一个想给,就是缺少引子,缺乏管理啊。
后来类似于滴滴这样的网约车平台出现了,所有想载客的私家车主全部到滴滴上进行注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。
此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!
Eureka做什么?
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。
同时,服务提供方与Eureka之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。
这就实现了服务管理的自动注册与发现、状态监控。
6.2 原理图
基本架构
- EurekaServer:就是服务注册中心(可以是一个集群),对外暴露自己的地址
- 提供者:启动后向EurekaServer注册自己的服务信息(ip、端口、微服务名)
- 消费者:向EurekaServer订阅服务,服务启动时会拉取一次服务列表,并且通过定时任务定期更新服务列表
- 心跳(续约):提供者定期通过发送http请求至Eureka的方式刷新自己的状态
注意:
1、Eureka分两个部分: EurekaServer服务端 + EurekaClient客户端(服务提供者或服务消费者)
2、服务注册、服务心跳续约、服务拉取等都是通过调用eurekaServer的http接口实现
07、Eureka服务端:注册中心
目标:搭建eureka-server模块,作为注册中心。
7.1 实现步骤
-
第一步:修改父工程springcloud-demo的pom文件,添加spring-cloud依赖配置,这点和加入springBoot父工程作用类似,此依赖中加入了许多依赖包的限定。
-
<properties> <mapper.verion>2.1.5</mapper.verion> <springcloud.version>Greenwich.SR2</springcloud.version> <!--修改mysql版本,默认是8.X.X--> <mysql.version>5.1.47</mysql.version> </properties> <dependencyManagement> <dependencies> <!-- spring-cloud (导入pom文件) scope: import 只能在<dependencyManagement>元素里面配置 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${springcloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 通用mapper启动器 --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>${mapper.version}</version> </dependency> </dependencies> </dependencyManagement>
- 第二步:创建模块: eureka-server
-
第三步:配置eureka-server依赖: pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>springcloud-demo</artifactId> <groupId>cn.itcast</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>eureka-server</artifactId> <dependencies> <!-- 配置eureka服务端启动器(集成了web启动器) --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> </project>
-
第四步:编写启动类 : EurekaServerApplication
package cn.itcast; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer // 声明当前应用为eureka服务(启用eureka服务) @SpringBootApplication public class EurekaServerApplication { public static void main(String[] args){ SpringApplication.run(EurekaServerApplication.class, args); } }
-
package cn.itcast; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer // 声明当前应用为eureka服务(启用eureka服务) @SpringBootApplication public class EurekaServerApplication { public static void main(String[] args){ SpringApplication.run(EurekaServerApplication.class, args); } }
-
第五步:编写配置文件: application.yml,这里要注意一点是eureka-server本身也是一个客户端(与高可用有关,后续章节会讲到),所以也需要配置服务端的地址,目前服务端就是自己,因此配置自己的地址即可。
server: port: 8761 # eureka服务端,默认端口 spring: application: name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId) eureka: client: service-url: # EurekaServer的地址,现在是自己的地址,如果做集群,需要写其它服务实例(节点)的地址。 defaultZone: http://localhost:8761/eureka fetch-registry: false # 不拉取服务 register-with-eureka: false # 不注册服务
-
第六步:启动服务,并访问:http://127.0.0.1:8761
7.2 小结
Eureka服务端开发三要素:
-
添加eureka服务端启动器
-
添加eureka服务端注解
-
添加eureka客户端配置,指定server地址
08、Eureka客户端:服务注册
目标:实现user-service服务注册到eurekaServer中。(客户端注册到服务端)
8.1 实现步骤
-
第一步:在user-service模块中添加eureka客户端启动器依赖
<!-- 配置eureka客户端启动器 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
-
第二步:在启动类上开启Eureka客户端,添加 @EnableDiscoveryClient 来开启Eureka客户端
@SpringBootApplication @MapperScan("cn.itcast.user.mapper") @EnableDiscoveryClient // 开启Eureka客户端(2.1.x版本不加也行) public class UserApplication { public static void main(String[] args){ SpringApplication.run(UserApplication.class, args); } }
-
第三步:修改application.yml,添加eureka客户端配置
server: port: 9001 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/springcloud username: root password: root application: # 应用名称(服务id) name: user-service # 配置eureka服务端地址 eureka: client: service-url: defaultZone: http://localhost:8761/eureka
注意:这里我们添加了spring.application.name属性来指定应用名称,将来会作为服务的id使用。
-
第四步:重启项目,访问Eureka监控页面查看:
说明:我们发现user-service服务已经注册成功了。
8.2 小结
Eureka客户端开发三要素
目标:实现user-consumer消费者从eurekaServer中发现服务。(通过服务id发现服务)
9.1 实现步骤
-
第一步:在user-consumer模块中添加eureka客户端启动器依赖
<!-- 配置eureka客户端启动器 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
-
第二步:在启动类上开启Eureka客户端,添加 @EnableDiscoveryClient 来开启Eureka客户端
package cn.itcast.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableDiscoveryClient // 开启Eureka客户端 public class ConsumerApplication { public static void main(String[] args){ SpringApplication.run(ConsumerApplication.class, args); } @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
-
第三步:修改application.yml,添加eureka客户端配置
server: port: 8080 spring: application: name: user-consumer # 应用名称 eureka: client: service-url: # eurekaServer地址 defaultZone: http://localhost:8761/eureka
-
第四步:修改Controller代码
package cn.itcast.consumer.controller; import cn.itcast.consumer.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; @RestController @RequestMapping("/consumer") public class ConsumerController { /** 注入发现者 */ @Autowired private DiscoveryClient discoveryClient; @Autowired private RestTemplate restTemplate; /** 根据主键id查询用户 */ @GetMapping("/{id}") public User findOne(@PathVariable("id")Long id){ // 根据服务id获取该服务的全部服务实例 List<ServiceInstance> instances = discoveryClient .getInstances("user-service"); // 获取第一个服务实例(因为目前我们只有一个服务实例) ServiceInstance serviceInstance = instances.get(0); // 获取服务实例所在的主机 String host = serviceInstance.getHost(); // 获取服务实例所在的端口 int port = serviceInstance.getPort(); // 定义服务实例访问URL String url = "http://" + host + ":" + port + "/user/" + id; System.out.println("服务实例访问URL: " + url); return restTemplate.getForObject(url, User.class); } }
-
第五步:访问consumer,可以发现依旧可以访问成功
9.2 小结
Eureka客户端开发三要素(同提供者)
- eureka客户端启动器
- eureka客户端注解
- eureka客户端配置,指定server地址
说明:服务注册与服务发现都属于Eureka客户端,只是我们人为的把Eureka客户端分成了服务提供端与服务消费端两个角色。实际上Eureka只有服务端(注册中心) 与 客户端(服务注册或服务发现)。
10、Eureka服务端:高可用
10.1 高可用介绍
Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka注册中心。
服务同步
当存在多个Eureka Server节点时,每个节点都配置其他节点的地址,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的服务注册请求转发到集群中的其他节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。
而作为客户端,需要把信息注册到每个Eureka中:
如果有三个EurekaServer节点,则每一个节点都需指定其他节点的地址,例如:有三个节点端口分别为8761、8762、8763,则:
- 8761节点的配置中指定8762和8763的地址
- 8762节点的配置中指定8761和8763的地址
- 8763节点的配置中指定8761和8762的地址
说明:
1、不仅仅是注册服务,心跳续约、服务下线等其他请求也会转发到其他节点。
2、若某服务A服务注册的时候,收到注册请求的节点(server1)转发请求到其他节点(server2)失败(例如网络波动),等到服务A进行心跳续约的时候,server1收到心跳续约请求,并转发到server2,server2若发现该服务在自己缓存中不存在,就会把该服务注册到自己。
3、若某服务A服务注册的时候,收到注册请求的节点(server1)转发请求到其他节点(server2),发现server2不可用,连接不上,此时server1内部有一个批处理流,会保存本次转发失败的请求,每隔1s钟重试一次,这样等server2节点恢复了,就可以收到该注册请求了,实现最终数据一致性。
10.2 高可用配置
我们假设要搭建两个EurekaServer的集群,端口分别为:8761和8762
10.2.1 实现步骤
-
第一步:修改eureka-server的配置(application.yml)
server: port: ${ port:8761} # eureka服务端,默认端口 spring: application: name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId) eureka: client: service-url: # Eureka服务地址;如果是集群则是其它服务地址,后面要加/eureka defaultZone: ${ defaultZone:http://localhost:8761/eureka} fetch-registry: true # 拉取服务 register-with-eureka: true # 注册服务
说明:
1、在上述配置文件中的${}表示在jvm启动时候若能找到对应port或者defaultZone参数则使用传入的参数,若无则使用冒号后面的默认值。
2、把service-url的值改成了其他EurekaServer的地址,而不是自己。
2、fetch-registry和register-with-eureka最好是都设置为true,这样server在启动的时候,会去service-url中配置的其他节点
中拉取已有的服务列表。
-
第二步:每一台在启动的时候指定端口port和defaultZone配置
- 8761节点配置:
- 8762节点配置
修改复制后的配置
- 第三步:依次启动8761、8762节点,浏览器访问8761或8762:
-
第四步:eureka客户端修改配置,由于此时EurekaServer节点不止一个,因此user-service注册服务或者user-consumer获取服务的时候,service-url参数需要变化,这样即使某个节点不可用了,也可以使用其他的节点,实现高可用。
# 配置eureka eureka: client: service-url: # EurekaServer地址,多个地址以','隔开 defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka
11、Eureka客户端:服务提供者的其他配置
服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。
服务注册
服务提供者在启动时,会检测配置属性中的: eureka.client.register-with-eureka=true 参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个restful风格的http请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。
-
第一层Map的Key就是服务id,一般是配置中的 spring.application.name 属性
-
第二层Map的key是服务的实例id。一般host+ serviceId + port,例如:localhost:user-service:9001
值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。
Map<String, Map<String, instance>> allMap;
Map<String, instance> instanceMap;
instanceMap.put(“localhost:userService:9001”, instance);
instanceMap.put(“localhost:userService:9002”, instance);
allMap.put(“userService”, instanceMap);
服务注册时默认使用的是主机名,如果想用ip进行注册,可以在user-service中添加配置:
# 配置eureka
eureka:
instance:
ip-address: 127.0.0.1 # ip地址
prefer-ip-address: true # 更倾向于使用ip,而不是host名称
修改完后先后重启user-service和user-consumer
服务续约
在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew)
有两个重要参数可以修改服务续约的行为:
# 配置eureka
eureka:
instance:
lease-renewal-interval-in-seconds: 30 # 服务续约(renew)的间隔时间,默认为30秒
lease-expiration-duration-in-seconds: 90 # 服务失效时间,默认值90秒
- lease-renewal-interval-in-seconds:服务续约(renew)的间隔时间,默认为30秒
- lease-expiration-duration-in-seconds:服务失效时间,默认值90秒
也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中剔除,这两个值在生产环境不要修改,默认即可。
12、Eureka客户端:服务消费者的其他配置
获取服务列表
当服务消费者启动时,会检测 eureka.client.fetch-registry=true 参数的值,如果为true,则会从Eureka Server服务的列表拉取下来,然后缓存在本地。并且 每隔30秒 会重新获取并更新数据。可以通过下面的参数来修改:
eureka:
client:
registry-fetch-interval-seconds: 30 # 获取服务间隔时间(默认30秒)
13、Eureka服务端:失效剔除及自我保护
服务下线
当手动发送服务下线的REST请求给Eureka Server或debug模式下关闭服务,告诉服务注册中心:“我要下线了”。EurekaServer 接受到请求之后,将该服务置为下线状态,也就是从服务列表中剔除。
失效剔除
当服务由于内存溢出等原因变得不可用,亦或是正常关闭服务,此时服务注册中心并未收到“服务下线”的请求。服务注册中心在启动时会创建一个定时任务,每隔一段时间(默认为60秒)将当前服务列表中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。
可以通过以下参数对其进行修改,单位是毫秒。 某个服务实例宕机了,最长多久检测到并且剔除 = 60 + 90
eureka.server.eviction-interval-timer-in-ms: 60000 # eureka-server 服务剔除定时任务执行周期(毫秒)
-
测试失效剔除步骤:
-
修改eureka-server配置:
eureka: client: service-url: defaultZone : ${ defaultZone:http://localhost:8761/eureka/} fetch-registry: true register-with-eureka: true server: eviction-interval-timer-in-ms: 4000 # 设置4s执行一次检测定时任务 enable-self-preservation: false # 关闭自我保护机制
-
修改user-service配置:
eureka: client: service-url: #配置eureka server 服务地址 defaultZone: http://localhost:8761/eureka/ fetch-registry: false # 拉取服务 register-with-eureka: true # 注册服务 instance: prefer-ip-address: true # 指定更偏向用ip ip-address: 127.0.0.1 lease-renewal-interval-in-seconds: 5 # 5s发送一次心跳续约 lease-expiration-duration-in-seconds: 15 # 15s未发送心跳续约就失效
-
启动eureka-server和user-service,然后再关闭user-service
-
那么最迟15 + 4 = 19s + 15(eureka服务剔除多加了一个失效时间)左右可以在注册中心控制台界面看到user-service服务剔除效果。
-
自我保护
我们同时关停多个注册的服务,可能在Eureka面板看到如下一条警告,这是触发了Eureka的自我保护机制。
在生产环境下,因为网络延迟等原因,EurekaServer未收到的心跳续约数量非常多,超标了,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。因此会开启自我保护机制,EurekaServer在这段时间内不会剔除任何服务实例(否则服务其实是好的,岂不是误杀了),直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好容错处理。
自我保护检查周期:默认每分钟检查一次
计算公式:最后一分钟实际受到的客户端实例心跳续约数(Renews ) < (每分钟应该受到心跳续约的总数 * 85%) = Renews threshold
-
Renews threshold
:Eureka Server 期望每分钟收到客户端实例续约的总数 *85%。 -
Renews (last min)
:Eureka Server 最后 1 分钟实际收到客户端实例续约的总数。 -
若启动两个实例,通过计算可得出:
每分钟应发心跳续约总数:2 * 60/30 = 4 (2为服务实例个数、一分钟60s、每个实例每30s发送一次心跳续约)
可得出:阈值 = 4 * 85 % = 3.4,结果会取4 ,因此,如果上一分钟收到的心跳续约数< 4 便于触发自我保护
可以通过下面的配置来关闭自我保护:
eureka:
server:
enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
Memorial Day is 512 days |