微服务框架之Spring Cloud简介
在了解 Spring Cloud 之前先了解一下微服务架构需要考量的核心关键点,如下图:
对于以上等核心关键点的处理,不需要我们重复造车轮, Spring Cloud 已经帮我们集成了,它使用 Spring Boot 风格将一些比较成熟的微服务框架组合起来,屏蔽掉了复杂的配置和实现原理,为快速构建微服务架构的应用提供了一套基础设施工具和开发支持。
Spring Cloud 所提供的核心功能包含:
Spring Cloud架构图
Spring Cloud子项目
Spring Cloud 旗下的子项目大致可以分为两类:
如下:
1. Spring Cloud 与 Spring Boot
Spring Boot 可以说是微服务架构的核心技术之一。通过在 Spring Boot 应用中添加 Spring MVC 依赖,就可以快速实现基于 REST 架构的服务接口,并且可以提供对 HTTP 标准动作的支持。而且 Spring Boot 默认提供 JackJson 序列化支持,可以让服务接口输入、输出支持 JSON 等。因此,当使用 Spring Cloud 进行微服务架构开发时,使用 Spring Boot 是一条必经之路。
2. Spring Cloud 与服务治理( Eureka )
服务治理是 Spring Cloud 的核心,在实现上其提供了两个选择,即 Consul 和 Netflix 的 Eureka 。
Eureka 提供了服务注册中心、服务发现客户端,以及注册服务的 UI 界面应用。
在 Eureka 的实现中,节点之间相互平等,有部分注册中心“挂掉”也不会对整个应用造成影响,即使集群只剩一个节点存活,也可以正常地治理服务。即使所有服务注册节点都宕机, Eureka 客户端中所缓存的服务实例列表信息,也可让服务消费者能够正常工作,从而保障微服务之间互相调用的健壮性和应用的弹性。
3. Spring Cloud 与客户端负载均衡( Ribbon )
Ribbon 默认与 Eureak 进行无缝整合,当客户端启动的时候,从 Eureka 服务器中获取一份服务注册列表并维护在本地,当服务消费者需要调用服务时, Ribbon 就会根据负载均衡策略选择一个合适的服务提供者实例并进行访问。
Spring Cloud 通过集成 Netflix 的 Feign 项目,为开发者提供了声明式服务调用,从而简化了微服务之间的调用处理方式。并且默认 Feign 项目集成了 Ribbon ,使得声明式调用也支持客户端负载均衡功能。
4. Spring Cloud 与微服务容错、降级( Hystrix )
为了给微服务架构提供更大的弹性,在 Spring Cloud 中,通过集成 Netflix 下子项目 Hystrix ,通过所提供的 @HystrixCommand 注解可以轻松为我们所开发的微服务提供容错、回退、降级等功能。此外, Hystrix 也默认集成到 Feign 子项目中。
Hystrix 是根据“断路器”模式而创建。当 Hystrix 监控到某服务单元发生故障之后,就会进入服务熔断处理,并向调用方返回一个符合预期的服务降级处理( fallback ),而不是长时间的等待或者抛出调用异常,从而保障服务调用方的线程不会被长时间、不必要地占用,避免故障在应用中的蔓延造成的雪崩效应。
而 Hystrix 的仪表盘项目( Dashboard )可以监控各个服务调用所消耗的时间、请求数、成功率等,通过这种近乎实时的监控和告警,可以及时发现系统中潜在问题并进行处理。
5. Spring Cloud 与服务网关( Zuul )
Spring Cloud 通过集成 Netflix 中的 Zuul 实现 API 服务网关功能,提供对请求的路由和过滤两个功能
路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。
过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
通过 Zuul ,可以将细粒度的服务组合起来提供一个粗粒度的服务,所有请求都导入一个统一的入口,对外整个服务只需要暴露一个 API 接口,屏蔽了服务端的实现细节。通过 Zuul 的反向代理功能,可以实现路由寻址,将请求转发到后端的粗粒度服务上,并做一些通用的逻辑处理。此外, Zuul 默认会与 Eureka 服务器进行整合,自动从 Eureka 服务器中获取所有注册的服务并进行路由映射,实现 API 服务网关自动配置。
6. Spring Cloud 与消息中间件( Stream )
Spring Cloud 为简化基于消息的开发,提供了 Stream 子项目,通过建立消息应用抽象层,构建了消息收发、分组消费和消息分片等功能处理,将业务应用中的消息收发与具体消息中间件进行解耦,使微服务应用开发中可以非常方便地与 Kafka 和 RabbitMQ 等消息中间件进行集成。
Spring Cloud Bus 基于 Stream 进行扩展,可以作为微服务之间的事件、消息总线,用于服务集群中状态变化的传播。
比如 Spring Cloud Config 借助 Bus ,可以实现配置的动态刷新处理。
7. Spring Cloud 与分布式配置中心( Config )
针对微服务架构下的配置文件管理需求, Spring Cloud 提供了一个 Config 子项目。 Spring Cloud Config 具有中心化、版本控制、支持动态更新和语言独立等特性。
在 Config 子项目中将微服务应用分为两种角色:配置服务器( Config Server )和配置客户端( Config Client )。使用配置服务器集中地管理所有配置属性文件,配置服务中心可以将配置属性文件存储到 Git 、 SVN 等具有版本管理仓库中,也可以存放在文件系统中。默认采用 Git 的方式进行存储,因此可以很容易地对配置文件进行修改,并实现版本控制。
8. Spring Cloud 与微服务链路追踪( Sleuth )
Spring Cloud 中的 Sleuth 子项目为开发者提供了微服务之间调用的链路追踪。
Sleuth 核心思想就是通过一个全局的 ID 将分布在各微服务服务节点上的请求处理串联起来,还原了调用关系,并借助数据埋点,实现对微服务调用链路上的性能数据的采集。
因此,通过 Sleuth 可以很清楚地了解到一个用户请求经过了哪些服务、每个服务处理花费了多长时间,从而可以对用户的请求进行分析。此外,通过将采集的数据发送给 Zipkin 进行存储、统计和分析,从而可以实现可视化的分析和展示,帮助开发者对微服务实施优化处理。
9. Spring Cloud 与微服务安全( Security )
Spring Cloud Security 为我们提供了一个认证和鉴权的安全框架,实现了资源授权、令牌管理等功能,同时结合 Zuul 可以将认证信息在微服务调用过程中直接传递,简化了我们进行安全管控的开发。
Spring Cloud Security 默认支持 OAuth 2.0 认证协议,因此单点登录也可以非常容易实现,并且 OAuth2.0 所生成的令牌可以使用 JWT 的方式,进一步简化了微服务中的安全管理。
10. Spring Cloud 的其他子项目
自定义springcloud-gateway熔断处理
一、场景
使用spring cloud gateway后,有了熔断,问题也就随之而来,服务间调用有了hystrix可以及时的排除坏接口、坏服务的问题,对系统很有帮助。但是!不是所有的接口都是极短时间内完成的,不是所有的接口都可以设置一样的超时时间的!
那么我们面临一个问题,那就是百分之99的接口都可以在1s内完美完成,但是就是那几个特殊接口,需要十几秒,几十秒的等待时间,而默认熔断的时间又只有一个。
二、分析
在前面 springcloudgateway源码解析之请求篇 中我们知道请求会经过一些列的过滤器(GatewayFilter),而springcloudgateway的降级熔断处理就是由一个特殊的过滤器来处理的,通过源码分析我们关注到HystrixGatewayFilterFactory这个类,这个类的作用就是生产GatewayFilter用的,我们看下它的实现
可以看到红框处最后构建了一个匿名的GatewayFilter对象返回,这个对象在接口请求过程中会被加载到过滤器链条中,仔细看到 这里是创建了一个RouteHystrixCommand这个命令对象,最终调用command.toObservable()方法处理请求,如果超时熔断调用resumeWithFallback方法
通过源码分析 gateway在路由时可以指定HystrixCommandKey,并且对HystrixCommandKey设置超时时间
三、方案
知道网关熔断的原理就好办了,自定义熔断的过滤器配置到接口请求过程中,由过滤器来读取接口熔断配置并构建HystrixObservableCommand处理请求。
自定义一个类XXXGatewayFilterFactory继承AbstractGatewayFilterFactory,将api和对应的timeout配置化,来实现细化到具体接口的熔断配置,具体实现如下:
package org.unicorn.framework.gateway.filter;
import cn.hutool.core.collection.CollectionUtil;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixObservableCommand;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.gateway.support.TimeoutException;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import rx.Observable;
import rx.RxReactiveStreams;
import rx.Subscription;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
/**
* @Author: xiebin
* @Description:
* @Date:Create:in 2022-07-06 9:17
*/
@Component
public class UnicornHystrixGatewayFilterFactoryextends AbstractGatewayFilterFactory {
private static final StringNAME ="unicornHystrix";
private ObjectProviderdispatcherHandlerProvider;
private AntPathMatcherantPathMatcher;
public UnicornHystrixGatewayFilterFactory(ObjectProvider dispatcherHandlerProvider) {
super(Config.class);
this.dispatcherHandlerProvider = dispatcherHandlerProvider;
this.antPathMatcher =new AntPathMatcher();
}
@Override
public ListshortcutFieldOrder() {
return Collections.singletonList(NAME_KEY);
}
/**
* 获取服务ID
*
* @param exchange
* @return
*/
public StringserviceId(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().pathWithinApplication().value();
try {
path = path.split("/")[1];
return path;
}catch (Exception e) {
return "default";
}
}
/**
* @param key
* @param timeout
* @return
*/
private HystrixObservableCommand.SetterinitSetter(String key, Integer timeout) {
HystrixObservableCommand.Setter setter = HystrixObservableCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(key)).andCommandKey(HystrixCommandKey.Factory.asKey(key));
if (timeout !=null) {
setter.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(timeout));
}
return setter;
}
/**
* @param exchange
* @param chain
* @param config
* @return
*/
private UnicornRouteHystrixCommandinitUnicornRouteHystrixCommand(ServerWebExchange exchange, GatewayFilterChain chain, Config config) {
//路由配置的超时设置
List apiTimeoutList = config.getTimeouts();
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().pathWithinApplication().value();
UnicornRouteHystrixCommand command;
if (CollectionUtil.isNotEmpty(apiTimeoutList)) {
//request匹配属于那种模式
ApiHystrixTimeout apiHystrixTimeout = getApiHystrixTimeout(apiTimeoutList, path);
command =new UnicornRouteHystrixCommand(config.getFallbackUri(), exchange, chain, initSetter(apiHystrixTimeout.getApiPattern(), apiHystrixTimeout.getTimeout()));
}else {
command =new UnicornRouteHystrixCommand(config.getFallbackUri(), exchange, chain, initSetter(serviceId(exchange), null));
}
return command;
}
/**
* @param apiTimeoutList
* @param path
* @return
*/
private ApiHystrixTimeoutgetApiHystrixTimeout(List apiTimeoutList, String path) {
for (ApiHystrixTimeout apiTimeoutPattern : apiTimeoutList) {
if (this.antPathMatcher.match(apiTimeoutPattern.getApiPattern(), path)) {
return apiTimeoutPattern;
}
}
ApiHystrixTimeout apiHystrixTimeout =new ApiHystrixTimeout();
apiHystrixTimeout.setApiPattern("default");
apiHystrixTimeout.timeout =null;
return apiHystrixTimeout;
}
@Override
public GatewayFilterapply(Config config) {
return (exchange, chain) - {
UnicornRouteHystrixCommand command = initUnicornRouteHystrixCommand(exchange, chain, config);
return Mono.create(s - {
Subscription sub =command.toObservable().subscribe(s::success, s::error, s::success);
s.onCancel(sub::unsubscribe);
}).onErrorResume((Function) throwable - {
if (throwableinstanceof HystrixRuntimeException) {
HystrixRuntimeException e = (HystrixRuntimeException) throwable;
HystrixRuntimeException.FailureType failureType = e.getFailureType();
switch (failureType) {
case TIMEOUT:
return Mono.error(new TimeoutException());
case COMMAND_EXCEPTION: {
Throwable cause = e.getCause();
if (causeinstanceof ResponseStatusException || AnnotatedElementUtils
.findMergedAnnotation(cause.getClass(), ResponseStatus.class) !=null) {
return Mono.error(cause);
}
}
default:
break;
}
}
return Mono.error(throwable);
}).then();
};
}
@Override
public Stringname() {
return NAME;
}
private class UnicornRouteHystrixCommandextends HystrixObservableCommand {
private final URIfallbackUri;
private final ServerWebExchangeexchange;
private final GatewayFilterChainchain;
/**
* @param fallbackUri
* @param exchange
* @param chain
*/
public UnicornRouteHystrixCommand(URI fallbackUri, ServerWebExchange exchange, GatewayFilterChain chain, HystrixObservableCommand.Setter setter) {
super(setter);
this.fallbackUri = fallbackUri;
this.exchange = exchange;
this.chain = chain;
}
@Override
protected Observableconstruct() {
return RxReactiveStreams.toObservable(this.chain.filter(exchange));
}
@Override
protected ObservableresumeWithFallback() {
if (null ==fallbackUri) {
return super.resumeWithFallback();
}
URI uri =exchange.getRequest().getURI();
boolean encoded = ServerWebExchangeUtils.containsEncodedParts(uri);
URI requestUrl = UriComponentsBuilder.fromUri(uri)
.host(null)
.port(null)
.uri(this.fallbackUri)
.build(encoded)
.toUri();
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
ServerHttpRequest request =this.exchange.getRequest().mutate().uri(requestUrl).build();
ServerWebExchange mutated =exchange.mutate().request(request).build();
DispatcherHandler dispatcherHandler = UnicornHystrixGatewayFilterFactory.this.dispatcherHandlerProvider.getIfAvailable();
return RxReactiveStreams.toObservable(dispatcherHandler.handle(mutated));
}
}
public static class ApiHystrixTimeout {
public StringgetApiPattern() {
return apiPattern;
}
public void setApiPattern(String apiPattern) {
this.apiPattern = apiPattern;
}
public IntegergetTimeout() {
return timeout;
}
public void setTimeout(Integer timeout) {
this.timeout = timeout;
}
private StringapiPattern;
private Integertimeout;
}
public static class Config {
private Stringid;
private URIfallbackUri;
/**
* url - timeout ms
*/
private Listtimeouts;
public StringgetId() {
return id;
}
public ConfigsetId(String id) {
this.id = id;
return this;
}
public URIgetFallbackUri() {
return fallbackUri;
}
public ConfigsetFallbackUri(URI fallbackUri) {
if (fallbackUri !=null !"forward".equals(fallbackUri.getScheme())) {
throw new IllegalArgumentException("Hystrix Filter currently only supports 'forward' URIs, found " + fallbackUri);
}
this.fallbackUri = fallbackUri;
return this;
}
public ListgetTimeouts() {
return timeouts;
}
public ConfigsetTimeouts(List timeouts) {
this.timeouts = timeouts;
return this;
}
}
}
配置示例
spring.cloud.gateway.default-filters[0].name=unicornHystrix
spring.cloud.gateway.default-filters[0].args.fallbackUri=forward:/defaultFallback
spring.cloud.gateway.default-filters[0].args.timeouts[0].apiPattern=/gf-oss-service//oss/part/upload
spring.cloud.gateway.default-filters[0].args.timeouts[0].timeout=100000
Spring Cloud入门系列-前期准备
在写这一系列的文章之前,觉得很有必要阐述一下什么是Spring Cloud。不像Spring(Spring Framework),大体上能够理解为它是一个管理bean的容器。也不想SpringBoot,可以理解为它是加强版的Spring,集成了SSM和其它一些框架,并且大量支持和推荐注解开发。
但是对于Spring Cloud,它是一个微服务架构的框架, 它不是单独的某个项目,是多个项目的集成 。也就是说如果想学习Spring Cloud,实际上是在 学习多个有关微服务的项目。
所谓微服务呢,就是把原本一站式解决的业务拆分成具体的某个模块,每个模块只做一个事情,然后还顺便衍生出了统一管理这些服务的模块,以及服务的保护措施等模块。归结起来就是5个核心, 服务发现(注册)、负载均衡、断路器、服务网关和分布式配置。
在几个星期前,当我想建一个模块的时候,可能会选择采用 Spring Initializer 来创建,但是最近觉得还是直接建立一个新模块比较舒服。每个人的习惯都不一样,自己怎么舒服怎么来。
下面就演示一下如何利用maven创建一个 module
修改模块名就可以创建想要的模块,这样的好处是能够集成父模块中导入的依赖,相比于 Spring Initializer 会简单多了,因为后者需要手动配置模块的父子关系才可以(或者懂怎么搞的小伙伴也可以留言一手)。
为了更好的学习,首先建立了一个总的工程,同样是用了maven来建立一个项目,建立完后结构如下所示
接下来要做的就是把整个src目录给删掉,因为后续也用不到它;其次就是修改pom文件
各位小伙伴需要修改的第7和第8行的 groupId 和 artifactId 。这样对于必须用的依赖,可以在根模块中,也就是该pom文件声明即可。比如上面的 spring-boot-starter-web 在所有的子模块中都有整个依赖。
甚至如果足够懒,那你完全可以把所有的依赖都写在父模块中,这样后续建立子模块的过程中就可以不管pom文件了。