Spring-Cloud 学习笔记-(3)注册中心Eureka
1、前言
1.1、上个章节我们做了什么?
-
user-service:作为服务提供者对外提供一个借口,根据用户id 查询用户基本信息
-
order-service:作为服务调用者,通过RestTemplate远程调用user-service
流程如下:
2.1、本章节我们讲会做什么?
服务的注册与发现
2、什么是Eureka
2.1、问题分析
在上一章的案例中,user-service对外提供服务,需要对外暴露自己的地址。而order-service(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。
2.2、滴滴打车
这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。
此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。
此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!
2.3、Eureka做什么?
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。
同时,服务提供方与Eureka之间通过心跳
机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。
这就实现了服务的自动注册、发现、状态监控。
3、原理
3.1、架构图
该图片来自于Eureka开源代码的文档,地址为https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance
从图上可以看得出Eureka主要有两个角色:Eureka-Server(服务端)和Eureka-Client(客户端),而客户端又可以分为ApplicationService(服务的提供者)和ApplicationClient(服务的调用者)
3.2、服务注册于发现:
Eureka-Client向Eureka-Server注册,并将自己的信息以key,value形式(key:serviceId,value:ip地址、端口等信息)注册到Eureka-Server,Eureka-Server再同步给其他Eureka-Server,Eureka-Client会每间接一段时间(默认30秒)向Eureka-Server发送一次心跳来表明自己还活着,如果客户端不能正常续约,他会在一定时间内(默认90秒)从服务器注册列表中剔除,然后服务器会复制更新到其他Eureka-Server集群,当ApplicationClient想要调用ApplicationService的方法时,会向EurekaServer拉取注册列表,通过serviceId找到ip和端口进行远程调用,所以保证每个区域内的Eureka-Server集群至少有一个Eureka-Server能够正常运行,防止服务器瘫痪。
4、快速入门
4.1、编写EurekaServer
新建一个Module作为EurekaServer:
我们同样选择Maven工程,方法跟之前的一样
4.1.1、pom文件
<?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>cloud-demo</artifactId>
<groupId>com.bigfly</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<dependencies>
<!-- eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
4.1.2、文件结构
4.1.3、代码编写
EurekaServerApplication启动类:
package com.bigfly;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer//启动Eureka-Server
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class);
}
}
yml文件:
server:
port: 8761
4.1.4、测试
启动服务
控制台报错:
报错分析:
为了保证服务的可用性Eureka-Server以后将会集群搭建,所以Eureka-Server也作为一个客户端向其他的Eureka-Server注册,这里是因为Eureka-Server找不到注册中心地址所以报错,所以我们需要在yml文件中配置注册中心地址
解决:
yml文件增加
eureka:
instance:
hostname: 127.0.0.1
client:
#自己不注册自己
registerWithEureka: false
#不需要检索服务信息
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
如果页面出现如下错误,没有关系,这是因为Eureka-Server发现长时间没有客户端注册进来, Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现低于的情况(在单机调试的时候很容易满足,实际在
生产环境上通常是由于网络不稳定导致),Eureka Server会将当前的实例注册信息保护起来,同时提示这个警告。保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)
4.2、将客户端注册到Eureka-Server
4.2.1、user-service代码修改
pom文件增加:
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml文件增加:
#服务名称
spring:
application:
name: user-service
# 注册中心
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka
4.2.2、order-service代码修改
pom文件增加:
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml文件增加:
#服务名称
spring:
application:
name: order-service
# 注册中心
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka
4.2.3、测试
先启动eureka-server服务,启动完毕再启动user-service和order-service
访问http://127.0.0.1:8761 效果:
4.3、order-service 调用user-service
4.3.1、修改代码
OrderServiceImpl订单服务实现类:
package com.bigfly.service.impl;
import com.bigfly.entity.Order;
import com.bigfly.service.OrderService;
import com.bigfly.utils.JsonUtils;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.UUID;
@Service
public class OrderServcieImpl implements OrderService {
@Autowired
private RestTemplate restTemplate;
//↓↓↓↓↓↓↓↓↓↓↓↓修改部分↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
@Autowired
private DiscoveryClient discoveryClient;
//↑↑↑↑↑↑↑↑↑↑↑↑修改部分↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
/**
* 根据用户id 查询用户信息
* @param userId 用户id
* @return
*/
@Override
public Order findById(int userId) {
//↓↓↓↓↓↓↓↓↓↓↓↓修改部分↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
//通过serviceId 拉取服务列表
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
ServiceInstance instance = instances.get(0);
//getForObject 第一个参数url代表访问路径 第二个参数代表 返回值类型
String jsonStr = restTemplate.getForObject("http://"+instance.getHost()+":"+instance.getPort()+"/api/v1/user/2", String.class);
// String jsonStr = restTemplate.getForObject("http://127.0.0.1:8771/api/v1/user/2", String.class);
//↑↑↑↑↑↑↑↑↑↑↑↑修改部分↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
JsonNode jsonNode = JsonUtils.str2JsonNode(jsonStr);
Order order = new Order();
order.setOrderName("我是一个订单");
order.setSerialId(UUID.randomUUID().toString());
order.setUserName(jsonNode.get("data").get("name").textValue());
order.setPort(jsonNode.get("data").get("port").textValue());
return order;
}
}
4.3.2、断点调式
- 复制一个user-service模拟集群
-
启动服务
先启动eureka-server,启动完毕后启动其他服务,其中order-service断点模式启动
-
调试
访问:http://localhost:8781/api/v1/order/2
进入断点,F8下一步
放开断点访问结果
5、Eureka详解
5.1、高可用的Eureka
模拟多个eureka-server相互注册
修改eureka-server配置文件application.yml
server:
port: 8761
eureka:
instance:
hostname: 127.0.0.1
client:
#自己不注册自己
# registerWithEureka: false
#不需要检索服务信息
# fetchRegistry: false
serviceUrl:
defaultZone: http://127.0.0.1:8762/eureka
spring:
application:
name: eureka-server
复制一个eureka-server,方法同上
此配置表示,端口号为8761的eureka会注册到8762上,端口号为8762的eureka会注册到8761上,因为eureka会把自己的注册列表复制更新到其他的注册中心上,所以我们最后访问 http://127.0.0.1:8761/eureka 和http://127.0.0.1:8762/eureka我们将看到这样的结果
5.2、服务的调用方和提供方配置详解
一个服务既可以作为服务的提供方,也可以作为服务的调用方,这里方便理解我们分开说
-
一个服务的提供方(比如:user-service),在服务一启动时会检查
eureka.client.register-with-erueka=true
属性是否为true(不配置默认是true),如果是true就会把自己注册到注册中心(eureka-server)去,反之,如果为false就不会注册自己。 -
在服务注册完毕过后,服务的提供方会跟注册中心维持一个
心跳
(没间隔一段时间向注册中心发送一次rest请求)来告诉注册中心“我还活着”,这里我们叫服务的续约(renew),如果超过一段时间(默认是90秒)注册中心还没有收到心跳注册中心就会认为此服务已经宕机,然后把这个服务从注册列表中剔除。eureka: instance: #服务的续约时间间隔 lease-renewal-interval-in-seconds: 30 #服务的失效时间间隔 lease-expiration-duration-in-seconds: 90
如果在开发阶段我们可以适当把这个值跳小一点,在生产环境中这个值尽量不用修改
-
服务的调用方(比如:order-service),在启动时候会检测
eureka.client.fetch-registry=true
属性是否为true(不配置同样是true),如果是true,就会把注册中心(比如:eureka-server)的服务列表拉到本地缓存,并且没间隔一段时间(默认30秒)会重新拉取更新一次,每次调用都是从自己缓存中获取注册列表,后期如果注册中心挂了,服务依旧可以调用,但是不会更新注册列表了,所以注册中心尽量集群搭建,确保服务的可用性。eureka: client: #拉去列表时间间隔 registry-fetch-interval-seconds: 30
同样,生成环境我们不用修改,开发环境我们可以修改小一点。
5.3、失效剔除和自我保护
- 注册中心(eureka-server)会每间隔一段时间(默认60秒)把失效的服务(90秒没有收到心跳)从注册列表中剔除,也就是说一个服务,如果注册中心90秒没有收到心跳回复,不会立即剔除,而是每间隔一段时间统一剔除。
- 注册中心(eureka-server)会统计最近15分钟每分钟续约量是否超过85%,如果低于85%,eureka就会认为你没有宕机,可能是因为网络延迟等其他原因,eureka就会开启保护机制,这些实例就会被eureka保护起来,就算没有续约也不会剔除。我们在开发过程中很容易就满足续约量低于85%,所以一般开发时候会关闭自我保护(true:开启,false:关闭)
eureka:
server:
# 扫描失效服务的间隔时间(缺省为60*1000ms)
eviction-interval-timer-in-ms: 60000
# 关闭自我保护模式(缺省为打开)
enable-self-preservation: true
5.4、小知识
强制关闭某个服务:put:{eureka_ip:eureka_port}/eureka/apps/{appname}/{service_id}/status?value={UP/DOWN}