微服务Spring Cloud17_Eureka注册中心5
一、认识Eureka
首先我们来解决第一问题,服务的管理。
问题分析
在刚才的案例中,user-service对外提供服务,需要对外暴露自己的地址。而consumer-demo(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复 杂的互联网环境,一个项目可能会拆分出十几,甚至几十个微服务。此时如果还人为管理地址,不仅开发困难,将来 测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。
DevOps的思想是系统可以通过一组过程、方法或系统;提高应用发布和运维的效率,降低管理成本。
网约车
这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。 一个想要,一个愿意给,就是缺少引子,缺乏管理啊。
此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息 (联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。
此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你 面前,为你服务,完美!
Eureka做什么?
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。
同时,服务提供方与Eureka之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。
这就实现了服务的自动注册、发现、状态监控。
Eureka的主要功能是进行服务管理,定期检查服务状态,返回服务地址列表。
二、 原理图
基本架构:
- Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
- 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
- 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
- 心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
步骤:
0. 服务提供者实例化服务
1. 把实例化的服务注册到Eureka注册中心
2. Eureka记录服务user.service记录下服务的地址http://localhost:9091、http://localhost:9092
3. 消费者从Eureka获取服务列表
4. 消费者基于负载均衡算法从地址列表选择一个服务地址调用服务
5. 服务提供者会定期发送心跳(服务是否存活、有效的续约状态)到注册中心
6. 注册中心检查那些没有定期发送心跳的服务,将其在一定时间内剔除出服务地址列表
三、入门案例
Eureka是服务注册中心,只做服务注册;自身并不提供服务也不消费服务。可以搭建web工程使用Eureka,可以使用SpringBoot方式搭建。
搭建步骤:
1.创建工程
2.添加启动器依赖
3.编写启动引导类(添加Eureka的服务注解)和配置文件
4.修改配置文件(端口、应用名称...)
5.启动测试
实现:
1、搭建eureka-server工程
接下来创建一个项目 eureka-server ,启动一个Eureka Server Application服务注册中心。
项目中的 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>heima-springcloud</artifactId> <groupId>com.itheima</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.itheima</groupId> <artifactId>eureka-server</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> </project>
1)编写启动类:eureka-server\src\main\java\com\itheima\EurekaServerApplication.java
package com.itheima; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
//声明当前应用为eureka服务 @EnableEurekaServer @SpringBootApplication public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class); } }
2)编写配置文件:eureka-server\src\main\resources\application.yml
server: port: 10086 spring: application: name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId) eureka: client: service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。 defaultZone: http://127.0.0.1:10086/eureka register-with-eureka: false # 不注册自己到eureka fetch-registry: false #不拉取
3)启动服务
启动 eureka-server 访问:http://127.0.0.1:10086
2、服务注册
注册服务,就是在服务上添加Eureka的客户端依赖,客户端代码会自动把服务注册到EurekaServer中。
目标:将user-service的服务注册到eureka并在consumer-demo中可以根据服务名称调用
分析:
-
- 服务注册:在服务提供工程user-service上添加Eureka客户端依赖;自动将服务注册到EurekaServer服务地址列表。
- 添加依赖;
- 改造启动引导类;添加开启Eureka客户端发现的注解;
- 修改配置文件;设置Eureka服务地址
- 服务发现:在服务消费工程consumer-demo上添加Eureka客户端依赖;可以使用工具类根据服务名称获取对应的服务地址列表。
- 添加依赖;
- 改造启动引导类;添加开启Eureka客户端发现的注解;
- 修改配置文件;设置Eureka服务地址
- 改造处理器类Consumer Controller,可以使用工具类DiscoveryClient根据服务名称获取对应服务地址列表。
- 服务注册:在服务提供工程user-service上添加Eureka客户端依赖;自动将服务注册到EurekaServer服务地址列表。
1)添加依赖
我们在user-service中添加Eureka客户端依赖:
<!-- Eureka客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
2)修改启动类
在启动类上开启Eureka客户端功能:通过添加 @EnableDiscoveryClient 来开启Eureka客户端功能
@SpringBootApplication @MapperScan("com.itheima.user.mapper") @EnableDiscoveryClient // 开启Eureka客户端发现功能 public class UserServiceDemoApplication { public static void main(String[] args) { SpringApplication.run(UserServiceDemoApplication.class, args); } }
3)修改配置文件
编写user-service\src\main\resources\application.yml配置文件为如下:
server: port: 9091 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/springcloud username: root password: root application: #应用名 name: user-service mybatis: type-aliases-package: cn.itcast.user.pojo eureka: client: service-url: defaultZone: http://localhost:10086/eureka
注意: 这里我们添加了spring.application.name属性来指定应用名称,将来会作为服务的id使用。
4)测试
重启 user-service 项目-执行启动类,访问Eureka监控页面
我们发现user-service服务已经注册成功了
3、服务发现
接下来我们修改 consumer-demo ,尝试从EurekaServer获取服务。
方法与服务提供者类似,只需要在项目中添加EurekaClient依赖,就可以通过服务名称来获取信息了!
1)添加依赖
找到 consumer-demo\pom.xml 添加如下依赖
<!-- Eureka客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
2)修改启动类
修改 consumer-demo\src\main\java\com\itheima\consumer\ConsumerApplication.java 开启Eureka客户端
package com.itheima.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 public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
3)新增配置文件
新增 consumer-demo\src\main\resources\application.yml 配置文件
server: port: 8080 spring: application: name: consumer-demo # 应用名称 eureka: client: service-url: # EurekaServer地址 defaultZone: http://127.0.0.1:10086/eureka
4)修改处理器
修改 consumer-demo\src\main\java\com\itheima\consumer\controller\ConsumerController.java 代码, 用DiscoveryClient类的方法,根据服务名称,获取服务实例。
package com.itheima.consumer.controller; import com.itheima.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 RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; @GetMapping("/{id}") public User queryById(@PathVariable Long id){ String url="http://localhost:9091/user/"+id; List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("user-service"); ServiceInstance serviceInstance = serviceInstanceList.get(0); url="http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/user/"+id; return restTemplate.getForObject(url,User.class); } }
5)Debug跟踪运行
重启 consumer-demo 项目;然后再浏览器中再次访问 http://localhost:8080/consumer/8 ;在代码中debug跟进查看最终拼接要访问的URL:
四、Eureka基础架构
Eureka架构中的三个核心角色:
- 服务注册中心
Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server
- 服务提供者
提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即 可。本例中就是我们实现的user-service
- 服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的consumer-demo
五、Eureka Server高可用配置
Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。
Eureka Server是一个web应用,可以启动多个实例(配置不同端口)保证Eureka Server的高可用。
将Eureka Server作为一个服务注册到其它Eureka Server,这样多个Eureka Server之间就能够互相发现对方,同步服务,实现Eureka Server集群。
1、服务同步:
多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一 个节点,都可以获取到完整的服务列表信息。
而作为客户端,需要把信息注册到每个Eureka中:
如果有三个Eureka,则每一个EurekaServer都需要注册到其它几个Eureka服务中,例如:有三个分别为10086、 10087、10088,则:
-
- 10086要注册到10087和10088上
- 10087要注册到10086和10088上
- 10088要注册到10086和10087上
2、启动两台eureka-server实例,在eureka管理界面看到两个实例:
动手搭建高可用的EurekaServer:我们假设要搭建两台EurekaServer的集群,端口分别为:10086和10087
1)修改原来的EurekaServer配置;修改 eureka-server\src\main\resources\application.yml 如下:
server: port: ${port:10086} spring: application: # 应用名称,会在eureka中作为服务的id(serviceId) name: eureka-server eureka: client: service-url: # eureka服务地址;如果是集群则是其它服务器地址,后面要加/eureka defaultZone: ${defaultZone:http://127.0.0.1:10086/eureka} # 是否注册自己,自身不提供服务所以不注册 #register-with-eureka: false #集群配置,需要注册自己 # 是否拉取服务 #fetch-registry: false #集群配置,拉取服务同步
所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务,注册到其它EurekaServer上,这样多个 EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:
注意把register-with-eureka和fetch-registry修改为true或者注释掉
在上述配置文件中的${}表示在jvm启动时候若能找到对应port或者defaultZone参数则使用,若无则使用后面的默认值
-
- 把service-url的值改成了另外一台EurekaServer的地址,而不是自己
2)在启动的时候可以指定端口port和defaultZone配置(${}中名称一致):
修改原来的启动配置组件;在如下界面中的VM options中
设置-Dport=10086 -DdefaultZone=http:127.0.0.1:10087/eureka,-Dport可以不指定,因为配置文件中已经指定了
复制一份并修改;在如下界面中的VM options中
设置 -Dport=10087 -DdefaultZone=http:127.0.0.1:10086/eureka
3)启动测试;同时启动两台eureka server
4)客户端注册服务到集群
如果user-service不改动只注册到10086这台eureka server中,那么10086会同步服务到10087这台eureka server中。
EurekaServer不止一个,其中一个挂掉要保证服务仍旧可用,因此 user-service 项目注册服务或者 consumer-demo 获取服务的时候,service-url参数需要修改为如下:
eureka: client: service-url: # EurekaServer地址,多个地址以','隔开 defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
为了方便上课和后面内容的修改,在测试完上述配置后可以再次改回单个eureka server的方式。
六、Eureka客户端与服务端配置
目标:配置eureka客户端user-service的注册、续约等配置项,配置eureka客户端consumer-demo的获取服务间隔时间;了解失效剔除和自我保护
分析:
- Eureka客户端工程
- user-service 服务提供工程
- 服务地址使用ip方式
- 续约
- consumer-demo 服务消费工程
- 获取服务地址的频率
- user-service 服务提供工程
- Eureka服务端工程 eureka-server
- 失效剔除
- 自我保护
1、Eureka客户端
服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。
服务注册:
服务提供者在启动时,会检测配置属性中的: eureka.client.register-with-erueka=true 参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。
- 第一层Map的Key就是服务id,一般是配置中的 spring.application.name 属性
- 第二层Map的key是服务的实例id。一般host+ serviceId + port,例如: localhost:user-service:8081
- 值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。
默认注册时使用的是主机名或者localhost,如果想用ip进行注册,可以在user-service中添加配置如下:
eureka:
instance:
ip-address: 127.0.0.1 # ip地址
prefer-ip-address: true # 更倾向于使用ip,而不是host名
修改完后先后重启 user-service 和 consumer-demo ;在调用服务的时候(访问http://localhost:8080/consumer/8)就已经变成ip地址;
需要注意的是:不是在eureka中的控制台服务实例状态显示。
服务续约:
在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);
有两个重要参数可以修改服务续约的行为;可以在 user-service 中添加如下配置项:
eureka:
instance:
lease-expiration-duration-in-seconds: 90
lease-renewal-interval-in-seconds: 30
-
- lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒
- lease-expiration-duration-in-seconds:服务失效时间,默认值90秒
也就是说,默认情况下每隔30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳, EurekaServer就会认为该服务宕机,会定时(eureka.server.eviction-interval-timer-in-ms设定的时间)从服务列表中移除,这两个值在生产环境不要修改,默认即可。
获取服务列表:
当服务消费者启动时,会检测 eureka.client.fetch-registry=true 参数的值,如果为true,则会从Eureka Server服务的列表拉取只读备份,然后缓存在本地。并且每隔30秒会重新拉取并更新数据。可以在 consumer-demo 项目中通过下面的参数来修改:
eureka:
client:
registry-fetch-interval-seconds: 30
2、Eureka服务端
如下的配置都是在Eureka Server服务端进行:
服务下线:
当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。
失效剔除:
有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间 (默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。
可以通过 eureka.server.eviction-interval-timer-in-ms 参数对其进行修改,单位是毫秒。
eureka: server: #服务失效剔除时间间隔,默认60秒 eviction-interval-timer-in-ms:60000
自我保护:
我们关停一个服务,很可能会在Eureka面板看到一条警告:
这是触发了Eureka的自我保护机制。当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的比例是否低于了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生 产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错。
可以通过下面的配置来关停自我保护:
eureka:
server:
enable-self-preservation: false # 关闭自我保护模式(缺省为打开)