从形式上来说,Feign一个顶三,Feign = RestTemplate + Ribbon + Hystrix
常用的服务注册中心:Eureka、Nacos、Zookeeper、Consul
注意:服务注册中心本质上是为了解耦服务提供者和服务消费者。
服务消费者 --> 服务提供者
服务消费者 --> 服务注册中心 --> 服务提供者
对于任何一个微服务,原则上都应存在或者支持多个提供者(比如商品微服务部署多个实例),这是由微服务的分布式属性决定的。
更进一步,为了支持弹性扩、缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的。因此,原本在单体应用阶段常用的静态LB机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现,而这个组件就是服务注册中心。
分布式微服务架构中,服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的方式获取服务提供者的地址信息,而不再需要通过硬编码方式得到提供者的地址信息。消费者只需要知道当前系统发布了那些服务,而不需要知道服务具体存在于什么位置,这就是透明化路由。
1)服务提供者启动
2)服务提供者将相关服务信息主动注册到注册中心
3)服务消费者获取服务注册信息:
4)服务消费者直接调用服务提供者
另外,注册中心也需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除;
Zookeeper
Dubbo + Zookeeper
Zookeeper它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。
简单来说zookeeper本质 = 存储 + 监听通知。
Zookeeper 用来做服务注册中心,主要是因为它具有节点变更通知功能,只要客户端监听相关服务节点,服务节点的所有变更,都能及时的通知到监听客户端,这样作为调用方只要使用Zookeeper 的客户端就能实现服务节点的订阅和变更通知功能了,非常方便。另外,Zookeeper可用性也可以,因为只要半数以上的选举节点存活,整个集群就是可用的,最少节点数为3。
Eureka
由Netflix开源,并被Pivatal集成到SpringCloud体系中,它是基于 RestfulAPI 风格开发的服务注册与发现组件。
Consul
Consul是由HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件, 采用Raft算法保证服务的一致性,且支持健康检查。
Nacos
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说Nacos 就是 注册中心 + 配置中心的组合,帮助我们解决微服务开发必会涉及到的服务注册 与发现,服务配置,服务管理等问题。Nacos 是 Spring Cloud Alibaba 核心组件之一,负责服务注册与发现,还有配置。
组件名 | 语言 | CAP | 对外暴露接口 |
---|---|---|---|
Eureka | Java | AP(自我保护机制,保证可用) | HTTP |
Consul | Go | CP | HTTP/DNS |
Zookeeper | Java | CP | 客户端 |
Nacos | Java | 支持AP/CP切换 | HTTP |
CAP定理又称CAP原则,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),最多只能同时三个特性中的两个,三者不可兼得。
P:分区容错性:分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务(一定的要满足的)
C:数据一致性:all nodes see the same data at the same time
A:高可用:Reads and writes always succeed
CAP不可能同时满足三个,要么是AP,要么是CP
Eureka 包含两个组件:Eureka Server 和 Eureka Client,Eureka Client是一个Java客户端,用于简化与Eureka Server的交互;Eureka Server提供服务发现的能力,各个微服务启动时,会通过EurekaClient向Eureka Server 进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;
1)图中us-east-1c、us-east-1d,us-east-1e代表不同的区也就是不同的机房
2)图中每一个Eureka Server都是一个集群。
3)图中Application Service作为服务提供者向Eureka Server中注册服务,Eureka Server接受到注册事件会在集群和分区中进行数据同步,Application Client作为消费端(服务消费者)可以从EurekaServer中获取到服务注册信息,进行服务调用。
4)微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒,默认Eureka Server90S会将还没有续约的给剔除)以续约自己的信息
5)Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒)
6)每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册列表的同步
7)Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者
Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和高可用性。
实现过程:
Spring Cloud 是一个综合的项目,下面有很多子项目,比如eureka子项目
szx-parent 父工程引入依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
szx-cloud-eureka工程pom.xml中引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
注意:在父工程的pom文件中手动引入jaxb的jar,因为Jdk9之后默认没有加载该模块,Eureka Server使用到,所以需要手动导入,否则EurekaServer服务无法启动
父工程:
<dependency>
<groupId>com.sun.xml.bindgroupId>
<artifactId>jaxb-coreartifactId>
<version>2.2.11version>
dependency>
<dependency>
<groupId>javax.xml.bindgroupId>
<artifactId>jaxb-apiartifactId>
dependency>
<dependency>
<groupId>com.sun.xml.bindgroupId>
<artifactId>jaxb-implartifactId>
<version>2.2.11version>
dependency>
<dependency>
<groupId>org.glassfish.jaxbgroupId>
<artifactId>jaxb-runtimeartifactId>
<version>2.2.10-b140310.1920version>
dependency>
<dependency>
<groupId>javax.activationgroupId>
<artifactId>activationartifactId>
<version>1.1.1version>
dependency>
在properties文件中配置Eureka server服务端口,服务名等信息
#配置端口号,在微服务的集群环境中,通常会为每一个微服务叠加
server.port=9200
#微服务中的唯一标识
spring.application.name=szx-cloud-eureka-server
#配置Eureka Server
#当前Eureka实例的主机名
#eureka.instance.hostname=localhost
#使用Ip注册,否则会使用主机名注册了
eureka.instance.prefer-ip-address=true
#自定义实例显示格式,加上版本号,便于多版本管理,注意ip-address,早期版本是ipAddress
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
#客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
eureka.client.service-url.defaultZone:http://localhost:${server.port}/eureka/
#表示是否向Eureka中注册自己的信息,因为自己就是服务不需要注册自己,默认为true,置为false
eureka.client.register-with-eureka=true
#表示是否查询/拉取Eureka Server服务注册列表,自己就是服务不需要从Eureka Server获取服务信息,默认为true,置为false
eureka.client.fetch-registry=true
编写启动类,声明当前服务为Eureka注册中心
@SpringBootApplication
@EnableEurekaServer //标识当前项目为Eureka Server
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
商品微服务和页面静态化微服务注册到Eureka
pom文件中添加Eureka Client依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
properties配置Eureka服务端信息
#配置Eureka Client
#客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
eureka.client.service-url.defaultZone:http://localhost:9200/eureka/
#使用Ip注册,否则会使用主机名注册了
eureka.instance.prefer-ip-address=true
#自定义实例显示格式,加上版本号,便于多版本管理,注意ip-address,早期版本是ipAddress
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
修改启动类
@SpringBootApplication
//@EnableEurekaClient //Eureka客户端,只能在Eureka环境中使用
@EnableDiscoveryClient //也是将当前项目表示为注册中心的客户端,像注册中心进行注册,可以在所有的服务注册中心环境中使用
在互联网应用中,服务实例很少有单个的。
如果EurekaServer只有一个实例,该实例挂掉,正好微服务消费者本地缓存列表中的服务实例也不可用,那么这个时候整个系统都受影响。
在生产环境中,我们会配置Eureka Server集群实现高可用。Eureka Server集群之中的节点通过点对点(P2P)通信的方式共享服务注册表。我们开启两台 Eureka Server 以搭建集群。
因为不想修改个人电脑中host地址,采用两个一样的szx-cloud-eureka开启不同端口的Eureka来实现集群
9201:
#配置端口号,在微服务的集群环境中,通常会为每一个微服务叠加
server.port=9201
#微服务中的唯一标识
spring.application.name=szx-cloud-eureka-server
#配置Eureka Server
#当前Eureka实例的主机名
#eureka.instance.hostname=localhost
#使用Ip注册,否则会使用主机名注册了
eureka.instance.prefer-ip-address=true
#自定义实例显示格式,加上版本号,便于多版本管理,注意ip-address,早期版本是ipAddress
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
#客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
eureka.client.service-url.defaultZone:http://127.0.0.1:9201/eureka/,http://127.0.0.1:9202/eureka/
#表示是否向Eureka中注册自己的信息,因为自己就是服务不需要注册自己,默认为true,置为false
eureka.client.register-with-eureka=true
#表示是否查询/拉取Eureka Server服务注册列表,自己就是服务不需要从Eureka Server获取服务信息,默认为true,置为false
eureka.client.fetch-registry=true
9202:
#配置端口号,在微服务的集群环境中,通常会为每一个微服务叠加
server.port=9202
#微服务中的唯一标识
spring.application.name=szx-cloud-eureka-server
#配置Eureka Server
#当前Eureka实例的主机名
#eureka.instance.hostname=localhost
#使用Ip注册,否则会使用主机名注册了
eureka.instance.prefer-ip-address=true
#自定义实例显示格式,加上版本号,便于多版本管理,注意ip-address,早期版本是ipAddress
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
#客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
eureka.client.service-url.defaultZone:http://127.0.0.1:9202/eureka/,http://127.0.0.1:9201/eureka/
#表示是否向Eureka中注册自己的信息,因为自己就是服务不需要注册自己,默认为true,置为false
eureka.client.register-with-eureka=true
#表示是否查询/拉取Eureka Server服务注册列表,自己就是服务不需要从Eureka Server获取服务信息,默认为true,置为false
eureka.client.fetch-registry=true
商品微服务:
#客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
eureka.client.service-url.defaultZone:eureka.client.service-url.defaultZone:http://127.0.0.1:9202/eureka/,http://127.0.0.1:9201/eureka/
页面静态化微服务:
#客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
eureka.client.service-url.defaultZone:eureka.client.service-url.defaultZone:http://127.0.0.1:9202/eureka/,http://127.0.0.1:9201/eureka/
服务消费者调用服务提供者
改造页面静态化微服务:之前是直接通过RestTemplate写死URL进行调用,现在通过Eureka方式进行调用。
@RestController
@RequestMapping("/page")
public class PageController {
// private static final String PRODUCTURL = "http://localhost:9000/product/selectProductById/";
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/getProduct/{id}")
public Products getProduct(@PathVariable Integer id){
//获得szx-service-product在服务中心注册的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("szx-service-product");
//因为没有部署集群,获得商品服务列表中的第一个
ServiceInstance serviceInstance = instances.get(0);
//获得商品微服务的主机地址
String host = serviceInstance.getHost();
//获得商品微服务的端口号
int port = serviceInstance.getPort();
//拼写URL地址
String url = "http://" + host + ":" + port + "/product/selectProductById/" + id;
System.out.println("******************************************************************************");
System.out.println("url = " + url);
System.out.println("******************************************************************************");
//发送http请求给商品微服务,将id传递过去,获取id所对应的Product对象
Products products = restTemplate.getForObject(url, Products.class);
return products;
}
}
Eureka的元数据有两种:标准元数据和自定义元数据。
标准元数据:主机名、IP地址、端口号等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。
自定义元数据:可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式。这些元数据可以在远程客户端中访问。
类似于(商品微服务配置文件):
#自定义元数据,会和标准元数据一起注册到服务注册中心,可以被注册中心所有的Client获取
eureka.instance.metadata-map.name=XiaoXing
eureka.instance.metadata-map.age=23
eureka.instance.metadata-map.master=szx
eureka.instance.metadata-map.password=123
@GetMapping("/showMetadata")
public List<String> showMetadata(){
List<String> result = new ArrayList<>();
//获得szx-service-product在服务中心注册的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("szx-service-product");
for (ServiceInstance instance : instances) {
//获取元数据
Map<String, String> metadata = instance.getMetadata();
Set<Map.Entry<String, String>> entries = metadata.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
String string = "key:" + key + ",value:" + value;
result.add(string);
}
}
return result;
}
debug下查看元数据(标准元数据):
debug下查看元数据(自定义元数据):
服务提供者(也是Eureka客户端)要向EurekaServer注册服务,并完成服务续约等工作
服务注册详解(服务提供者)
1)当我们导入了eureka-client依赖坐标,配置Eureka服务注册中心地址
2)服务在启动时会向注册中心发起注册请求,携带服务元数据信息
3)Eureka注册中心会把服务的信息保存在Map中。
服务续约详解(服务提供者)
服务每隔30秒会向注册中心续约(心跳)一次(也称为报活),如果没有续约,租约在90秒后到期,然后服务会被失效。每隔30秒的续约操作我们称之为心跳检测
往往不需要我们调整这两个配置
#租约续约间隔时间,默认30秒
eureka.instance.lease-renewal-interval-in-seconds=30
#租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列表移除
eureka.instance.lease-expiration-duration-in-seconds=90
获取服务列表(服务注册表)详解(服务消费者)
每隔30秒服务会从注册中心中拉取一份服务列表,这个时间可以通过配置修改。往往不需要我们调整
#每隔多久拉取一次服务列表
eureka.client.registry-fetch-interval-seconds=30
1)服务消费者启动时,从 EurekaServer服务列表获取只读备份,缓存到本地
2)每隔30秒,会重新获取并更新数据
3)每隔30秒的时间可以通过配置eureka.client.registry-fetch-interval-seconds修改
服务下线:
1)当服务正常关闭操作时,会发送服务下线的REST请求给EurekaServer。
2)服务中心接受到请求后,将该服务置为下线状态
失效剔除:
Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认60s)进行检查,如果发现实例在在一定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内没有收到心跳,则会注销此实例。
自我保护机制:
自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。
自我保护机制的工作机制是:如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:
因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪。
为什么会有自我保护机制?
默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。
我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka的保护机制,一旦开启了保护机制(默认开启),则服务注册中心维护的服务实例就不是那么准确了,此时我们通过修改Eureka Server的配置文件来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除(不推荐)。
#关闭自我保护模式(缺省为打开)
eureka.server.enable-self-preservation=false
建议生产环境打开自我保护机制