SpringBoot 与 SpringCould 是什么?它们的出现是为了解决什么问题?
Spring Boot:是一种服务开发技术;是为了达到简化项目的目的。
Spring Cloud:分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶;是为了解决大型项目中,各种出现的问题,提高系统的整体性能。
在对 Boot 和 Cloud 版本选择注意事项:
建议严格按照官方版本采用 Json 串中的来选择。也可以在下图的链接里,找到当前 Cloud 推荐使用的 Boot 版本。
故,在本次的学习中,版本选择如下:
- Cloud:Hoxton.SR4
- Boot:2.2.5.RELEASE
- Java:1.8
- Cloud Alibaba:??
Cloud 中的模块及实例
Eureka: 服务治理、注册、发现
Eureka:类似ZK的功能,其服务架构与 dubbo 的对比如下:
Eureka 的两个组件:
- Eureka Server:微服务中的各个节点到 Server 中去注册自己的信息。
- Eureka Client:一个 Java 客户端,用于简化与 Server 的交互。Client 会以 30 秒为周期向 Server 端发送心跳,Server 在 90s 没有收到某一个 Client 的心跳时,将会移除该节点。
Eureka 集群
Q:为什么我们需要 Eurake 集群?
A:为保障服务注册、服务调用的高可用性
搭建 Eureka 注册中心集群,各节点间 互相注册,相互守望,实现 负载均衡 + 故障容错 的功能。
Dubbo 是一个 RPC 调用方式的分布式服务组件之一,Eureka 提供的是 REST API 的调用方式。
Eureka 在启动类使用 @EnableEurekaServer 标识该应用为注册中心,在启动类使用 @EnableEurekaClient 标识该应用为服务提供方或消费方
1. 注册中心配置文件
server:
port: 7001
spring:
application:
name: eureka-server7001 #eureka服务端的实例名称,当与 hostname 同时存在时,采用applicationName
#在消费、提供方的应用中,该名称为Eureka中Application的值,若provider为集群,则name值应当一致,才能让消费方通过一个name调用到多个提供方节点应用
eureka:
instance:
hostname: eurekaServer7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#集群指向其它eureka
defaultZone: http://localhost:7002/eureka/
#单机就是7001自己
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
2. 服务提供方配置文件
server:
port: 8001
spring:
application:
#在消费、提供方的应用中,该名称为Eureka中Application的值,若provider为集群,则name值应当一致,才能让消费方通过一个name调用到多个提供方节点应用
name: eureka-provider7002
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
serviceUrl:
#defaultZone: http://localhost:7001/eureka/
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
instance:
#在eurakeServer中采用IP形式的链接地址
preferIpAddress: true
#定义实例ID格式,即eureka中节点的显示名称
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
3. 服务消费方配置文件
server:
port: 9001
spring:
application:
name: eureka-consumer7003
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
serviceUrl:
#defaultZone: http://localhost:7001/eureka/
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka/
Eureka 自我保护机制
什么是自我保护机制?
当 Eurekaserver 在一定时间(默认90秒)内没有收到实例的心跳,便会把该实例从注册表中删除。但是,如果 Server 在 15 分钟内,心跳失败的比例低于 85%,那么 Server 便会将当前的这些实例保护起来,不让实例过期。
在自我保护机制下:
1. Server 不会注销任何服务实例,但如果保护期间内实例出现问题,那么消费方的调用就会失败,所以客户端必须要有容错机制。
2. Server 仍然能够接受新实例的注册和查询请求,但是不会同步新的实例到其它 Server 上。当网络稳定后,Server 实例便会与其他节点 Server 节点同步信息。
为什么需要自我保护机制?
为防止 EurekaServer 在网络不通的情况下,将正常的 EurekaClient 实例错误剔除的操作。自我保护模式是一种应对网络异常的安全保护措施。
关闭自我保护机制
在实际开发中,我们会不断的重启服务,所以需要 Eurake 服务端及时的删除失效的服务。在生产环境不推荐使用。
- 在服务注册端:
eureka:
server:
enable-self-preservation: false # 关闭自我保护机制 保证不可用服务及时清除
eviction-interval-timer-in-ms: 3000 # 驱逐计时器扫描失效服务的间隔时间,默认 60*1000 毫秒
eureka.server.eviction-interval-timer-in-ms 不建议修改,它只是驱逐计时器扫描的间隔时间,当手动关闭 Eureka Client 后,注册中心也并不会立马注销此节点,同样要等到约 90s 后收不到客户端心跳才会将其移除。
2. 在服务提供方:
eureka:
server:
lease-renewal-interval-in-seconds: 1 # eureka客户端租赁续约间隔 单位秒 默认30
lease-expiration-duration-in-seconds: 3 # eureka客户端租赁期满时间 默认90
- 在服务消方:
eureka:
client:
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
register-fetch-interval-seconds: 30 # 消费方缓存服务列表清单的更新时间,默认为30s
DiscoveryClient 源码阅读:
DiscoveryClient 需要做的事情(可在类的头部找到注释):
a:向server注册实例
b:向server服务续约
c:关闭服务时,取消租约
d:查询server中的服务列表
我们在配置属性名 eureka.client.serviceUrl.defaultZone 后,会使用到 EndpointUtils 中的函数 getServiceUrlsMapFromConfig
/**
* Get the list of all eureka service urls from properties file for the eureka client to talk to.
*
* @param clientConfig the clientConfig to use
* @param instanceZone The zone in which the client resides
* @param preferSameZone true if we have to prefer the same zone as the client, false otherwise
* @return an (ordered) map of zone -> list of urls mappings, with the preferred zone first in iteration order
*/
public static Map<String, List<String>> getServiceUrlsMapFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {
Map<String, List<String>> orderedUrls = new LinkedHashMap<>();
String region = getRegion(clientConfig);
String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());
if (availZones == null || availZones.length == 0) {
availZones = new String[1];
availZones[0] = DEFAULT_ZONE;
}
logger.debug("The availability zone for the given region {} are {}", region, availZones);
int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);
String zone = availZones[myZoneOffset];
List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);
if (serviceUrls != null) {
orderedUrls.put(zone, serviceUrls);
}
int currentOffset = myZoneOffset == (availZones.length - 1) ? 0 : (myZoneOffset + 1);
while (currentOffset != myZoneOffset) {
zone = availZones[currentOffset];
serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);
if (serviceUrls != null) {
orderedUrls.put(zone, serviceUrls);
}
if (currentOffset == (availZones.length - 1)) {
currentOffset = 0;
} else {
currentOffset++;
}
}
if (orderedUrls.size() < 1) {
throw new IllegalArgumentException("DiscoveryClient: invalid serviceUrl specified!");
}
return orderedUrls;
}
- 先执行了 getRegion(clientConfig) 函数获取了一个 Region,可以从函数中发现,每一个应用只属于一个 Region,默认为 default ,也可以通过 eureka.client.Region 来设置。
/**
* Get the region that this particular instance is in.
* @return - The region in which the particular instance belongs to.
*/
public static String getRegion(EurekaClientConfig clientConfig) {
String region = clientConfig.getRegion();
if (region == null) {
region = DEFAULT_REGION;
}
region = region.trim().toLowerCase();
return region;
}
- 再执行 getAvailabilityZones(String region) 函数,默认使用 defaultZone ,即配置的 eureka.client.serviceUrl.defaultZone 的值。也可以通过eureka.client.availability-zones 来设置,按,分隔。由此可以知道一个 Region 可以对应多个 Zone 。
public String[] getAvailabilityZones(String region) {
String value = this.availabilityZones.get(region);
if (value == null) {
value = DEFAULT_ZONE;
}
return value.split(",");
}
3.在获取了 Region 和 Zone 后,才开始加载 Eureka Server 的具体地址,函数 getServiceUrlsMapFromConfig 中通过如下的逻辑,加载位于哪一个 Zone 的
int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);
String zone = availZones[myZoneOffset];
List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone);
服务调用 Ribbon与OpenFeign
SpringCloud 服务调用: Ribbon与OpenFeign
服务熔断、降级组件:Hystrix与Sentinel
SpringCloud 服务熔断、降级组件:Hystrix与Sentinel
分布式配置中心 Config
是什么?有什么优势?
Config 为微服务架构中的微服务提供集中化的外部配置方式,通过客户端获取项目中,共用的配置中心资源,完成各个子项目的配置。
关于@RefreshScope在Spring中的使用
@Bean默认使用的单例饿汉式加载,即在 spirng 应用启动时,就会装载到 IOC 容器中。
@RefreshScope 标注在类上,完成对类成员变量的刷新;@RefreshScope 标注在返回 Bean 的方法上,完成对 Bean 的重新加载。但其前提是客户端本地已经感知到了配置的变化(即向客户端执行 curl -X POST http://ip:port/project-name/actuator/refresh)
k8s下的微服务组件