[百度百科]Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
Spring Cloud是一系列框架的有序集合(Spring Cloud是一个规范)
开发服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等
利用Spring Boot的开发便利性简化了微服务架构的开发(自动装配)
这里,我们需要注意,Spring Cloud其实是一套规范,是一套用于构建微服务架构的规范,而不是一个可以拿来即用的框架(所谓规范就是应该有哪些功能组件,然后组件之间怎么配合,共同完成什么事情)。在这个规范之下第三方的Netflix公司开发了一些组件、Spring官方开发了一些框架/组件,包括第三方的阿里巴巴开发了一套框架/组件集合Spring Cloud Alibaba,这些才是Spring Cloud规范的实现。
Netflix搞了一套 ,简称SCN
Spring Cloud 吸收了Netflix公司的产品基础之上自己也搞了几个组件
阿里巴巴在之前的基础上搞出了一堆微服务组件,Spring Cloud Alibaba(SCA)
Spring Cloud 规范及实现意图要解决的问题其实就是微服务架构实施过程中存在的一些问题,比如微服务架构中的服务注册发现问题、网络问题(比如熔断场景)、统一认证安全授权问题、负载均衡问题、链路追踪等问题。
Distributed/versioned configuration (分布式/版本化配置)
Service registration and discovery (服务注册和发现)
Routing (智能路由)
Service-to-service calls (服务调用)
Load balancing (负载均衡)
Circuit Breakers (熔断器)
Global locks (全局锁)
Leadership election and cluster state ( 选举与集群状态管理)
Distributed messaging (分布式消息传递平台)
如前所述,Spring Cloud是一个微服务相关规范,这个规范意图为搭建微服务架构提供一站式服务,采用组件(框架)化机制定义一系列组件,各类组件针对性的处理微服务中的特定问题,这些组件共同来构成Spring Cloud微服务技术栈
3.1 Spring Cloud 核心组件
Spring Cloud 生态圈中的组件,按照发展可以分为第一代 Spring Cloud组件和第二代 Spring Cloud组件。
第一代 Spring Cloud(Netflix,SCN) | 第二代 Spring Cloud(主要就是Spring Cloud Alibaba,SCA) | |
---|---|---|
注册中心 | Netflix Eureka | 阿里巴巴 Nacos |
客户端负载均衡 | Netflix Ribbon | 阿里巴巴 Dubbo LB、Spring Cloud Loadbalancer |
熔断器 | Netflix Hystrix | 阿里巴巴 Sentinel |
网关 | Netflix Zuul:性能一般,未来将退出Spring Cloud 生态圈 | 官方 Spring Cloud Gateway |
配置中心 | 官方 Spring Cloud Config | 阿里巴巴 Nacos、携程 Apollo |
服务调用 | Netflix Feign | 阿里巴巴 Dubbo RPC |
消息驱动 | 官方 Spring Cloud Stream | |
链路追踪 | 官方 Spring Cloud Sleuth/Zipkin | |
阿里巴巴 seata 分布式事务方案 |
3.2 Spring Cloud 体系结构(组件协同工作机制)
Spring Cloud中的各组件协同工作,才能够支持一个完整的微服务架构。比如
注册中心负责服务的注册与发现,很好将各服务连接起来
API网关负责转发所有外来的请求
断路器负责监控服务之间的调用情况,连续多次失败进行熔断保护。
配置中心提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息
Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,基于RPC调用,对于目前使用率较高的Spring Cloud Netflix来说,它是基于HTTP的,所以效率上没有Dubbo高,但问题在于Dubbo体系的组件不全,不能够提供一站式解决方案,比如服务注册与发现需要借助于Zookeeper等实现,而Spring Cloud Netflix则是真正的提供了一站式服务化解决方案,且有Spring大家族背景。
前些年,Dubbo使用率高于SpringCloud,但目前Spring Cloud在服务化/微服务解决方案中已经有了非常好的发展趋势。
Spring Cloud 只是利用了Spring Boot 的特点,让我们能够快速的实现微服务组件开发,否则不使用Spring Boot的话,我们在使用Spring Cloud时,每一个组件的相关Jar包都需要我们自己导入配置以及需要开发人员考虑兼容性等各种情况。所以Spring Boot是我们快速把Spring Cloud微服务技术应用起来的一种方式。
本部分我们按照普通方式模拟一个微服务之间的调用,后续我们将一步步使用Spring Cloud的组件对案例进行改造。
本次课程数据库使用Mysql 5.7.x
商品信息表:
CREATE TABLE products(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50), --商品名称
price DOUBLE,
flag VARCHAR(2), --上架状态
goods_desc VARCHAR(100), --商品描述
images VARCHAR(400), --商品图片
goods_stock INT, --商品库存
goods_type VARCHAR(20) --商品类型
);
我们基于SpringBoot来构造工程环境,我们的工程模块关系如下所示:
3.1 父工程 demo-parent
在Idea中新建module,命名为demo-parent
pom.xml
pom
org.springframework.boot
spring-boot-starter-parent
2.1.6.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
1.18.4
provided
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-devtools
true
org.apache.maven.plugins
maven-compiler-plugin
11
utf-8
org.springframework.boot
spring-boot-maven-plugin
repackage
3.2 公共组件微服务
1) 在公共组件微服务中引入数据库驱动及mybatis-plus
demo-parent
com.demo
1.0-SNAPSHOT
4.0.0
demo-service-common
com.baomidou
mybatis-plus-boot-starter
3.3.2
javax.persistence
javax.persistence-api
2.2
mysql
mysql-connector-java
runtime
) 生成数据库实体类:com.demo.common.pojo.Products
package com.demo.common.pojo;
import lombok.Data;
import javax.persistence.Id;
import javax.persistence.Table;
@Data
@Table(name = "products")
public class Products {
@Id
private Integer id;
private String name;
private double price;
private String flag;
private String goodsDesc;
private String images;
private long goodsStock;
private String goodsType;
}
3.3 商品微服务
商品微服务是服务提供者,页面静态化微服务是服务的消费者
创建商品微服务demo-service-product,继承demo-parent
1)在商品微服务的pom文件中,引入公共组件坐标
com.demo
demo-service-common
1.0-SNAPSHOT
2)在yml文件中配置端口、应用名、数据库连接等信息
server: port: 9000 # 后期该微服务多实例,9000(10个以内) Spring: application: name: demo-service-product datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/demodb?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username: root password: 123456
3) Mapper接口开发
package com.demo.product.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.mapper.Mapper;
import com.demo.common.pojo.Products;
/**
* 现在使用的Mybatis-plus组件,该组件是Mybatis的加强版
* 能够与SpringBoot进行非常友好的整合,对比Mybatis框架只有使用便捷的改变
* 没有具体功能的改变
* 具体使用:让具体的Mapper接口继承BaseMapper即可
*/
public interface ProductMapper extends BaseMapper {
}
4) serive层开发
package com.demo.product.service.impl;
import com.demo.common.pojo.Products;
import com.demo.product.mapper.ProductMapper;
import com.demo.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
/**
* 根据商品ID查询商品对象
*/
@Override
public Products findById(Integer productId) {
return productMapper.selectById(productId);
}
}
5) controller层开发
package com.demo.product.controller;
import com.demo.common.pojo.Products;
import com.demo.product.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/query/{id}")
public Products query(@PathVariable Integer id){
return productService.findById(id);
}
}
6) 启动类
package com.demo.product;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.demo.product.mapper")
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class,args);
}
}
3.4 页面静态化微服务
1) 在pom文件中,引入公共组件依赖
demo-parent
com.demo
1.0-SNAPSHOT
4.0.0
demo-service-page
com.demo
demo-service-common
1.0-SNAPSHOT
2)在yml文件中配置端口、应用名、数据库连接等信息
server: port: 9100 # 后期该微服务多实例,端口从9100递增(10个以内) Spring: application: name: demo-service-page datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/demodb?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username: root password: 123456
3) 编写PageController,在PageController中调用商品微服务对应的URL
package com.demo.page.controller;
import com.demo.common.pojo.Products;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
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;
@RestController
@RequestMapping("/page")
public class PageController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getData/{id}")
public Products findDataById(@PathVariable Integer id){
Products products = restTemplate.getForObject("http://localhost:9000/product/query/"+id, Products.class);
System.out.println("从demo-service-product获得product对象:"+products);
return products;
}
}
4) 编写启动类,注入RestTemplate
package com.demo.page;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class PageApplication {
public static void main(String[] args) {
SpringApplication.run(PageApplication.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
我们在页面静态化微服务中使用RestTemplate调用商品微服务的商品状态接口时(Restful API 接口)。在微服务分布式集群环境下会存在什么问题呢?怎么解决?
存在的问题:
1)在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护。
2)服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡。
3)在服务消费者中,不清楚服务提供者的状态。
4)服务消费者调用服务提供者时候,如果出现故障能否及时发现不向用户抛出异常页面?
5)RestTemplate这种请求调用方式是否还有优化空间?能不能类似于Dubbo那样玩?
6)这么多的微服务统一认证如何实现?
7)配置文件每次都修改好多个很麻烦!?
8)....
上述分析出的问题,其实就是微服务架构中必然面临的一些问题:
1)服务管理:自动注册与发现、状态监管
2)服务负载均衡
3)熔断
4)远程过程调用
5)网关拦截、路由转发
6)统一认证
7)集中式配置管理,配置信息实时自动更新
这些问题,Spring Cloud 体系都有解决方案,后续我们会逐个学习。
说明:上面提到网关组件Zuul性能一般,未来将退出Spring Cloud 生态圈,所以我们直接讲解GateWay,在课程章节规划时,我们就把GateWay划分到第一代Spring Cloud 核心组件这一部分了。
各组件整体结构如下:
常用的服务注册中心:Eureka、Nacos、Zookeeper、Consul
1.1 关于服务注册中心
注意:服务注册中心本质上是为了解耦服务提供者和服务消费者。
服务消费者 --> 服务提供者
服务消费者 --> 服务注册中心 --> 服务提供者
对于任何一个微服务,原则上都应存在或者支持多个提供者(比如商品微服务部署多个实例),这是由微服务的分布式属性决定的。
更进一步,为了支持弹性扩、缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的。因此,原本在单体应用阶段常用的静态LB机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现,而这个组件就是服务注册中心。
1.1.1注册中心实现原理
分布式微服务架构中,服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的方式获取服务提供者的地址信息,而不再需要通过硬编码方式得到提供者的地址信息。消费者只需要知道当前系统发布了那些服务,而不需要知道服务具体存在于什么位置,这就是透明化路由。
1)服务提供者启动
2)服务提供者将相关服务信息主动注册到注册中心
3)服务消费者获取服务注册信息:
pull模式:服务消费者可以主动拉取可用的服务提供者清单
push模式:服务消费者订阅服务(当服务提供者有变化时,注册中心也会主动推送更新后的服务清单给消费者
4)服务消费者直接调用服务提供者
另外,注册中心也需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除;
1.1.2主流服务中心对比
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
1.2 服务注册中心组件 Eureka
服务注册中心的一般原理、对比了主流的服务注册中心方案,目光聚焦Eureka。
Eureka 基础架构
Eureka 包含两个组件:Eureka Server 和 Eureka Client,Eureka Client是一个Java客户端,用于简化与Eureka Server的交互;Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka Client向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作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信息,进行服务调用。
4)微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒,默认Eureka Server 90S会将还没有续约的给剔除)以续约自己的信息
5)Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒)
6)每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册列表的同步
7)Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者
Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和高可用性。
1.3 搭建单例Eureka Server服务注册中心
实现过程:
单实例Eureka Server—>访问管理界面
服务提供者(商品微服务注册到集群)
服务消费者(页面静态化微服务注册到Eureka/从Eureka Server获取服务信息)
完成调用
1、搭建Eureka Server服务 demo-cloud-eureka
demo-parent中引入Spring Cloud 依赖
Spring Cloud 是一个综合的项目,下面有很多子项目,比如eureka子项目
org.springframework.cloud
spring-cloud-dependencies
Greenwich.RELEASE
pom
import
2、demo-cloud-eureka工程pom.xml中引入依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
注意:在父工程的pom文件中手动引入jaxb的jar,因为Jdk9之后默认没有加载该模块,Eureka Server使用到,所以需要手动导入,否则Eureka Server服务无法启动
父工程:
com.sun.xml.bind
jaxb-core
2.2.11
javax.xml.bind
jaxb-api
com.sun.xml.bind
jaxb-impl
2.2.11
org.glassfish.jaxb
jaxb-runtime
2.2.10-b140310.1920
javax.activation
activation
1.1.1
3、在yml文件中配置Eureka server服务端口,服务名等信息
#Eureka server服务端口 server: port: 9200 spring: application: name: demo-cloud-eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId) eureka: instance: hostname: localhost client: service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ register-with-eureka: false # 自己就是服务不需要注册自己 fetch-registry: false #自己就是服务不需要从Eureka Server获取服务信息,默认为true,置为false
4、编写启动类,声明当前服务为Eureka注册中心
package com.demo.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
声明本项目是一个Eureka服务
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
5、访问http://127.0.0.1:9200,如果看到如下页面(Eureka注册中心后台),则表明Eureka Server发布成功
6、商品微服务和页面静态化微服务注册到Eureka
pom文件中添加Eureka Client依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
yml配置Eureka服务端信息
eureka:
client:
serviceUrl: # eureka server的路径
defaultZone: http://localhost:9200/eureka/
instance:
#使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
prefer-ip-address: true
#自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
修改启动类
@SpringBootApplication
@EnableDiscoveryClient //@EnableEurekaClient
public class PageApplication {
public static void main(String[] args) {
SpringApplication.run(PageApplication.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
1.4 搭建Eureka Server 高可用集群
在互联网应用中,服务实例很少有单个的。
如果EurekaServer只有一个实例,该实例挂掉,正好微服务消费者本地缓存列表中的服务实例也不可用,那么这个时候整个系统都受影响。
在生产环境中,我们会配置Eureka Server集群实现高可用。Eureka Server集群之中的节点通过点对点(P2P)通信的方式共享服务注册表。我们开启两台 Eureka Server 以搭建集群。
由于是在个人计算机中进行测试很难模拟多主机的情况,Eureka配置server集群时需要执行host地址。 所以需要修改个人电脑中host地址:
win10操作系统下:C:\Windows\System32\drivers\etc\host
127.0.0.1 demoCloudEurekaServerA 127.0.0.1 demoCloudEurekaServerB
将demo-cloud-eureka复制一份为demo-cloud-eureka9201
1、修改 demo-cloud-eureka-server 工程中的yml配置文件
9200:
#Eureka server服务端口 server: port: 9200 spring: application: name: demo-cloud-eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId) eureka: instance: hostname: demoCloudEurekaServerA client: register-with-eureka: true fetch-registry: true serviceUrl: defaultZone: http://demoCloudEurekaServerB:9201/eureka
9201:
#Eureka server服务端口 server: port: 9201 spring: application: name: demo-cloud-eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId) eureka: instance: hostname: demoCloudEurekaServerB client: register-with-eureka: true fetch-registry: true serviceUrl: defaultZone: http://demoCloudEurekaServerA:9200/eureka
商品微服务:
server: port: 9000 # 后期该微服务多实例,9000(10个以内) Spring: application: name: demo-service-product datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/demodb?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username: root password: 123456 eureka: client: serviceUrl: # eureka server的路径 defaultZone: http://democloudeurekaservera:9200/eureka/,http://democloudeurekaserverb:9201/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表 instance: #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip) prefer-ip-address: true #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
页面静态化微服务:
server: port: 9100 # 后期该微服务多实例,端口从9100递增(10个以内) Spring: application: name: demo-service-page datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/demodb?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username: root password: 123456 eureka: client: serviceUrl: # eureka server的路径 defaultZone: http://democloudeurekaservera:9200/eureka/,http://democloudeurekaserverb:9201/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表 instance: #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip) prefer-ip-address: true #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
服务消费者调用服务提供者
改造页面静态化微服务:之前是直接通过RestTemplate写死URL进行调用,现在通过Eureka方式进行调用。
@RestController
@RequestMapping("/page")
public class PageController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("/getData/{id}")
public Products findDataById(@PathVariable Integer id){
//1.获得Eureka中注册的demo-service-product实例集合
List instances = discoveryClient.getInstances("demo-service-product");
//2.获得实例集合中的第一个
ServiceInstance instance = instances.get(0);
//3.根据实例信息拼接IP地址
String host = instance.getHost();
int port = instance.getPort();
String url = "http://"+host+":"+port+"/product/query/"+id;
//4.调用
Products products = restTemplate.getForObject(url, Products.class);
System.out.println("从demo-service-product获得product对象:"+products);
return products;
}
}
1.5 Eureka细节详解
1.5.1 Eureka元数据详解
Eureka的元数据有两种:标准元数据和自定义元数据。
标准元数据:主机名、IP地址、端口号等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。
自定义元数据:可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式。这些元数据可以在远程客户端中访问。
类似于
instance: #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip) prefer-ip-address: true #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@ metadata-map: ip: 192.168.200.128 port: 10000 user: demo pwd: 123456
我们可以在程序中可以使用DiscoveryClient 获取指定微服务的所有元数据信息,在任何想注册中心注册的微服务都可以获取。
package com.demo.page.controller;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RestController
@RequestMapping("/metadata")
public class MetadataController {
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("show")
public String showMetadata(){
String result = "";
List instances = discoveryClient.getInstances("demo-service-page");
for (ServiceInstance instance:instances) {
//获取服务元数据
Map metadata = instance.getMetadata();
Set> entries = metadata.entrySet();
for (Map.Entry entry : entries){
String key = entry.getKey();
String value = entry.getValue();
result+="key:"+key+",value:"+value;
}
}
return result;
}
}
1.5.2 Eureka客户端详解
服务提供者(也是Eureka客户端)要向Eureka Server注册服务,并完成服务续约等工作
服务注册详解(服务提供者)
1)当我们导入了eureka-client依赖坐标,配置Eureka服务注册中心地址
2)服务在启动时会向注册中心发起注册请求,携带服务元数据信息
3)Eureka注册中心会把服务的信息保存在Map中。
服务续约详解(服务提供者)
服务每隔30秒会向注册中心续约(心跳)一次(也称为报活),如果没有续约,租约在90秒后到期,然后服务会被失效。每隔30秒的续约操作我们称之为心跳检测
Eureka Client :30S续约一次,在Eureka Server更新自己的状态 (Client端进行配置)
Eureka Server: 90S还没有进行续约,将该微服务实例从服务注册表(Map)剔除 (Client端进行配置)
Eureka Client: 30S拉取服务最新的注册表并缓存到本地 (Client端进行配置)
往往不需要我们调整这两个配置
#向Eureka服务中心集群注册服务 eureka: instance: # 租约续约间隔时间,默认30秒 lease-renewal-interval-in-seconds: 30 # 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列表移除 lease-expiration-duration-in-seconds: 90
获取服务列表(服务注册表)详解(服务消费者)
每隔30秒服务会从注册中心中拉取一份服务列表,这个时间可以通过配置修改。往往不需要我们调整
#向Eureka服务中心集群注册服务 eureka: client: # 每隔多久拉取一次服务列表 registry-fetch-interval-seconds: 30
1)服务消费者启动时,从 EurekaServer服务列表获取只读备份,缓存到本地
2)每隔30秒,会重新获取并更新数据
3)每隔30秒的时间可以通过配置eureka.client.registry-fetch-interval-seconds修改
1.5.3 Eureka服务端详解
服务下线:
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不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪。
为什么会有自我保护机制?
默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。
我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka 的保护机制,一旦开启了保护机制(默认开启),则服务注册中心维护的服务实例就不是那么准确了,此时我们通过修改Eureka Server的配置文件来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除(不推荐)。
eureka: server: enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
经验:建议生产环境打开自我保护机制