SpringCloud集群服务-2
大约 29 分钟
Feign 接口转换
远程接口映射
- 在进行远程接口调用时,原始的做法是直接依据微服务的名称找到与之相关的 REST 地址,随后通过 RestTemplate 对象实例实现服务调用,这样在每次进行服务调用时就必须明确的知道服务的地址,但是这种做法并不符合于传统的 RPC 实现形式,最佳的形式是客户端通过业务接口实现微服务的调用
1、
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '3.0.3'
2、
project(":common-api") { // 进行子模块的配置
dependencies { // 配置模块所需要的依赖库
compile("org.springframework.boot:spring-boot-starter-web") // SpringBoot依赖
compile('org.springframework.cloud:spring-cloud-starter-openfeign')
}
}
3、
package com.yootk.service;
import com.yootk.common.dto.DeptDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Map;
@FeignClient("dept.provider") // 定义要访问的微服务实例名称
public interface IDeptService { // 业务接口
/**
* 根据部门的编号获取部门的完整信息
* @param id 要查询的部门编号
* @return 编号存在则以DTO对象的形式返回部门数据,如果不存在返回null
*/
@GetMapping("/provider/dept/get/{deptno}") // 远程REST接口
public DeptDTO get(@PathVariable("deptno") long id);
/**
* 增加部门对象
* @param dto 保存要增加部门的详细数据
* @return 增加成功返回true,否则返回false
*/
@PostMapping("/provider/dept/add")
public boolean add(DeptDTO dto);
/**
* 列出所有的部门数据信息
* @return 全部数据的集合, 如果没有任何的部门数据则集合为空(size() == 0)
*/
@GetMapping("/provider/dept/list")
public List<DeptDTO> list();
/**
* 进行部门的分页数据加载操作
* @param currentPage 当前所在页
* @param lineSize 每页加载的数据行数
* @param column 模糊查询的数据列
* @param keyword 模糊查询关键字
* @return 部门集合数据以及统计数据,返回的数据项包括:
* 1、key = allDepts、value = List集合(部门的全部数据对象)
* 2、key = allRecorders、value = 总记录数;
* 3、key = allPages、value = 页数。
*/
@GetMapping("/provider/dept/split")
public Map<String, Object> split(
@RequestParam("cp") int currentPage,
@RequestParam("ls") int lineSize,
@RequestParam("col") String column,
@RequestParam("kw") String keyword);
}
4、
package com.yootk.consumer;
import muyan.yootk.config.ribbon.DeptProviderRibbonConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
// 如果此时要有多个配置项,可以使用@RibbonClients注解,该注解可以配置多个@RibbonClient
@RibbonClient(name = "dept.provider", configuration = DeptProviderRibbonConfig.class) // 自定义Ribbon配置
@EnableFeignClients("com.yootk.service") // Feign扫描包
public class StartConsumerApplication { // 沐言科技:www.yootk.com
public static void main(String[] args) {
SpringApplication.run(StartConsumerApplication.class, args);
}
}
Feign 转换日志
Feign 日志级别
- REST 服务是以 HTTP 为基础进行搭建的,而在使用 Feign 进行转换时,实际上是封装了用户对 REST 接口的调用过程,使之可以通过 RPC 的方式实现服务调用,如果开发者需要关注到这一操作的过程,则可以开启 Feign 转换日志,在 Feign 中对于转换日志的级别定义有四种

1、
https://docs.spring.io/spring-cloud/docs/Hoxton.SR12/reference/htmlsingle/#spring-cloud-openfeign
2、
package com.yootk.service.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class FeignConfig { // 定义Feign配置类
@Bean
public Logger.Level level() {
return Logger.Level.FULL; // 输出完全的日志信息
}
}
3、
package com.yootk.service;
import com.yootk.common.dto.DeptDTO;
import com.yootk.service.config.FeignConfig;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Map;
@FeignClient(value = "dept.provider", configuration = FeignConfig.class) // 定义要访问的微服务实例名称
public interface IDeptService { // 业务接口
/**
* 根据部门的编号获取部门的完整信息
* @param id 要查询的部门编号
* @return 编号存在则以DTO对象的形式返回部门数据,如果不存在返回null
*/
@GetMapping("/provider/dept/get/{deptno}") // 远程REST接口
public DeptDTO get(@PathVariable("deptno") long id);
/**
* 增加部门对象
* @param dto 保存要增加部门的详细数据
* @return 增加成功返回true,否则返回false
*/
@PostMapping("/provider/dept/add")
public boolean add(DeptDTO dto);
/**
* 列出所有的部门数据信息
* @return 全部数据的集合, 如果没有任何的部门数据则集合为空(size() == 0)
*/
@GetMapping("/provider/dept/list")
public List<DeptDTO> list();
/**
* 进行部门的分页数据加载操作
* @param currentPage 当前所在页
* @param lineSize 每页加载的数据行数
* @param column 模糊查询的数据列
* @param keyword 模糊查询关键字
* @return 部门集合数据以及统计数据,返回的数据项包括:
* 1、key = allDepts、value = List集合(部门的全部数据对象)
* 2、key = allRecorders、value = 总记录数;
* 3、key = allPages、value = 页数。
*/
@GetMapping("/provider/dept/split")
public Map<String, Object> split(
@RequestParam("cp") int currentPage,
@RequestParam("ls") int lineSize,
@RequestParam("col") String column,
@RequestParam("kw") String keyword);
}
4、
logging:
level:
com.yootk.service.IDeptService: DEBUG
5、
[IDeptService#get] ---> GET http://dept.provider/provider/dept/get/1 HTTP/1.1
[IDeptService#get] ---> END HTTP (0-byte body)
[IDeptService#get] <--- HTTP/1.1 200 (61ms)
[IDeptService#get] cache-control: no-cache, no-store, max-age=0, must-revalidate
[IDeptService#get] connection: keep-alive
[IDeptService#get] content-type: application/json
[IDeptService#get] date: Sun, 15 Aug 2021 07:43:52 GMT
[IDeptService#get] expires: 0
[IDeptService#get] keep-alive: timeout=60
[IDeptService#get] pragma: no-cache
[IDeptService#get] transfer-encoding: chunked
[IDeptService#get] x-content-type-options: nosniff
[IDeptService#get] x-frame-options: DENY
[IDeptService#get] x-xss-protection: 1; mode=block
[IDeptService#get]
[IDeptService#get] {"deptno":1,"dname":"开发部","loc":"yootk8002"}
[IDeptService#get] <--- END HTTP (50-byte body)
Feign 连接池
HTTP 的重复连接与关闭
- 在使用 Feign 组件实现远程微服务调用时,全部的处理操作都是基于 HTTP 协议完成的而 HTTP 协议是基于 TCP 协议开发。由于 HTTP 协议采用了无状态的处理形式,这样一来用户每一次发出请求都需要进行重复的连接打开与关闭操作,而这样的处理形式最终-:导致严重的性能损耗,

HTTP 持久化连接
- 在 HTTP/1.0 协议采用的是全双工协议,所以为了创建可靠的连接,就需要在连接建立”与“四次挥手”的处理机制,这样在每次进行请求和响与连接断开时采用“三次握手“应时都会造成大量的资源消耗,,所以为了解决这样的缺陷,最佳的做法是采用持久化连接的形式来实现 Socket 连接复用,这样用户只需要创建一次连接,就可以实现多次的请求与响应处理
HttpClient 连接池
- 这样可以在 HTTP/1.1 协议中,为了提高 Socket 复用性采用了多路复用的设计原则让一个连接来为不同的用户提供服务,从而实现了服务端的服务处理性能,而在客户端如果要想实现持久化连接,则可以基于 HttpClient 组件提供的连接池来进行管理,以实现同一条 TCP 链路上的连接复用
1、
// https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient
implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.13'
// https://mvnrepository.com/artifact/io.github.openfeign/feign-httpclient
implementation group: 'io.github.openfeign', name: 'feign-httpclient', version: '11.6'
// https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5
implementation group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.3.1'
// https://mvnrepository.com/artifact/io.github.openfeign/feign-hc5
implementation group: 'io.github.openfeign', name: 'feign-hc5', version: '13.1'
2、
ext.versions = [ // 定义全部的依赖库版本号
httpclient : '4.5.13', // HttpClient版本号
feignHttpclient : '11.6', // FeignHttpClient版本号
]
ext.libraries = [ // 依赖库引入配置
// 以下的配置为Feign与HttpClient有关的依赖库
'httpclient' : "org.apache.httpcomponents:httpclient:${versions.httpclient}",
'feign-httpclient' : "io.github.openfeign:feign-httpclient:${versions.feignHttpclient}"
]
3、
project(":consumer-springboot-80") { // 消费端模块
dependencies {
implementation(project(":common-api")) // 导入公共的子模块
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
implementation(libraries.'httpclient') // 引入httpclient组件
implementation(libraries.'feign-httpclient') // 引入feign-httpclient组件
}
}
4、
feign:
httpclient:
enabled: true # 启用httpclient连接池
max-connections: 200 # httpclient处理的最大连接数量
max-connections-per-route: 50 # 单个路径连接的最大数量
connection-timeout: 2000 # 超时等待
数据压缩传输
数据压缩传输
- 为了进一步提升数据传输的性能,在 Feiqn 组件中提供了请求与响应数据的 GZIP 压缩处理操作,这样当传输的数据量较大时就可以自动实现请求与响应数据的压缩与解压缩处理。在 Feign 中对于数据的压缩可以直接通过 application.yml 配置实现,而在配置时就需要同时启用请求和响应数据的压缩处理。

- GZIP(GUN Zip)主要是用来描述文件的压缩格式,最早是由 Jean-loup Gailly 和 Mark Adler 创建,主要在 UNIX 系统中使用,在 HTTP 协议上的 GZIP 编码是一种用来改进 WEB 应用程序性能的技术,可以基于压缩结构高效的实现网络传输(文本数据内容可以压缩到原始文本大小的 40%),现在大部分的服务器都有 GZIP 支持。
1、
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] ---> GET http://dept.provider/provider/dept/get/1 HTTP/1.1
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] ---> END HTTP (0-byte body)
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] <--- HTTP/1.1 200 (90ms)
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] cache-control: no-cache, no-store, max-age=0, must-revalidate
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] connection: keep-alive
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] content-type: application/json
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] date: Sun, 15 Aug 2021 08:01:06 GMT
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] expires: 0
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] keep-alive: timeout=60
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] pragma: no-cache
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] transfer-encoding: chunked
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] x-content-type-options: nosniff
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] x-frame-options: DENY
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] x-xss-protection: 1; mode=block
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get]
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] {"deptno":1,"dname":"开发部","loc":"yootk8001"}
DEBUG 18404 --- [p-nio-80-exec-1] com.yootk.service.IDeptService : [IDeptService#get] <--- END HTTP (50-byte body)
3、
server: # 服务端配置
port: 80 # 这个接口可以随意,反正最终都是由前端提供服务
compression: # 启用压缩配置
enabled: true # 配置启用
mime-types: application/json,application/xml,text/html,text/xml,text/plain # 压缩类型
4、
feign:
httpclient:
enabled: true # 启用httpclient连接池
max-connections: 200 # httpclient处理的最大连接数量
max-connections-per-route: 50 # 单个路径连接的最大数量
connection-timeout: 2000 # 超时等待
compression: # 压缩处理
request: # 请求压缩配置
enabled: true # 启用请求压缩
mime-types: application/json,application/xml,text/html,text/xml,text/plain # 压缩类型
min-request-size: 512 # 达到此阈值的时候启用压缩
response: # 响应处理
enabled: true # 响应压缩
Feign 工作原理
Feign 工作原理
- Feiqn 组件本质上是封装了 HTTP 请求与响应处理过程,使得整个的处理机制更加符合面向对象的设计形式,但是最终的接口实现子类必定是在远程服务端,所以消费端在进行接口执行远程方法时,就需要基于动态代理机制来生成接口实现子类,最终实现服务调用

1、
package feign;
public abstract class Feign {
public static class Builder {
private final List<RequestInterceptor> requestInterceptors =
new ArrayList<RequestInterceptor>(); // 请求拦截器
private Logger.Level logLevel = Logger.Level.NONE; // 日志记录级别
private Contract contract = new Contract.Default(); // 规则解析
private Client client = new Client.Default(null, null); // 客户端
private Retryer retryer = new Retryer.Default(); // 重试机制
private Logger logger = new NoOpLogger(); // 日志
private Encoder encoder = new Encoder.Default(); // 编码器
private Decoder decoder = new Decoder.Default(); // 解码器
private QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder();
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
private Options options = new Options();
private InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default(); // 动态代理工厂类
private boolean decode404;
private boolean closeAfterDecode = true;
private ExceptionPropagationPolicy propagationPolicy = NONE;
}
2、
package feign;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public interface InvocationHandlerFactory {
InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch);
interface MethodHandler {
Object invoke(Object[] argv) throws Throwable;
}
static final class Default implements InvocationHandlerFactory {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);
}
}
}
3、
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\
org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration
FeignAutoConfiguration
1、
package org.springframework.cloud.openfeign;
import feign.okhttp.OkHttpClient; // 源代码之中此处飘红,缺少相关的依赖
import okhttp3.ConnectionPool; // 源代码之中此处飘红,缺少相关的依赖
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class, // Feign配置项
FeignHttpClientProperties.class }) // Feign.HTTP配置项(HttpClient依赖库的使用)
@Import(DefaultGzipDecoderConfiguration.class) // 压缩解码的配置
public class FeignAutoConfiguration {
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@Bean
public HasFeatures feignFeature() {
return HasFeatures.namedFeature("Feign", Feign.class);
}
@Bean
public FeignContext feignContext() {
FeignContext context = new FeignContext();// Feign上下文
context.setConfigurations(this.configurations); // 接收当前的Feign配置项
return context;
}
@Configuration(proxyBeanMethods = false)
// Hystrix是其内部实现的一个熔断保护的组件,但是在SpringCloudAlibaba套件里面不推荐使用
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign") // Hystrix整合的配置
protected static class HystrixFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new HystrixTargeter();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration { // 默认情况下使用的是HttpClient组件
private final Timer connectionManagerTimer = new Timer(
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
@Autowired(required = false)
private RegistryBuilder registryBuilder; // 注册构建器
private CloseableHttpClient httpClient; // 在JavaWEB基础课程里面讲解过了此组件
@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = connectionManagerFactory
.newConnectionManager(httpClientProperties.isDisableSslValidation(),
httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(),
httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(),
this.registryBuilder); // HTTP连接池配置
this.connectionManagerTimer.schedule(new TimerTask() {
@Override
public void run() {
connectionManager.closeExpiredConnections();// 定期销毁已经失效的连接
}
}, 30000, httpClientProperties.getConnectionTimerRepeat());
return connectionManager;
}
@Bean
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
HttpClientConnectionManager httpClientConnectionManager,
FeignHttpClientProperties httpClientProperties) { // 创建HttpClient的处理Bean对象
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.build();
this.httpClient = httpClientFactory.createBuilder()
.setConnectionManager(httpClientConnectionManager)
.setDefaultRequestConfig(defaultRequestConfig).build();
return this.httpClient;
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(HttpClient httpClient) { // 提供了一个Client对象实例
return new ApacheHttpClient(httpClient); // Feign组件与HttpClient组件整合在一起了
}
@PreDestroy
public void destroy() throws Exception {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
this.httpClient.close();
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled") // 没有启用默认状态
protected static class OkHttpFeignConfiguration { // OKHttp组件的应用
private okhttp3.OkHttpClient okHttpClient;
@Bean
@ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool(
FeignHttpClientProperties httpClientProperties,
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
Integer maxTotalConnections = httpClientProperties.getMaxConnections();
Long timeToLive = httpClientProperties.getTimeToLive();
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);
}
@Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
ConnectionPool connectionPool,
FeignHttpClientProperties httpClientProperties) {
Boolean followRedirects = httpClientProperties.isFollowRedirects();
Integer connectTimeout = httpClientProperties.getConnectionTimeout();
Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
.followRedirects(followRedirects).connectionPool(connectionPool)
.build();
return this.okHttpClient;
}
@PreDestroy
public void destroy() {
if (this.okHttpClient != null) {
this.okHttpClient.dispatcher().executorService().shutdown();
this.okHttpClient.connectionPool().evictAll();
}
}
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient(okhttp3.OkHttpClient client) {
return new OkHttpClient(client);
}
}
}
2、
@ConfigurationProperties("feign.client")
public class FeignClientProperties {
public static class FeignClientConfiguration {
private Logger.Level loggerLevel;
private Integer connectTimeout;
private Integer readTimeout;
private Class<Retryer> retryer;
private Class<ErrorDecoder> errorDecoder;
private List<Class<RequestInterceptor>> requestInterceptors;
private Boolean decode404;
private Class<Decoder> decoder;
private Class<Encoder> encoder;
private Class<Contract> contract;
private ExceptionPropagationPolicy exceptionPropagationPolicy;
}
3、
@ConfigurationProperties(prefix = "feign.httpclient")
public class FeignHttpClientProperties {
public static final boolean DEFAULT_DISABLE_SSL_VALIDATION = false;
public static final int DEFAULT_MAX_CONNECTIONS = 200;
public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 50;
public static final long DEFAULT_TIME_TO_LIVE = 900L;
public static final TimeUnit DEFAULT_TIME_TO_LIVE_UNIT =
TimeUnit.SECONDS;
public static final boolean DEFAULT_FOLLOW_REDIRECTS = true;
public static final int DEFAULT_CONNECTION_TIMEOUT = 2000;
public static final int DEFAULT_CONNECTION_TIMER_REPEAT = 3000;
private boolean disableSslValidation =
DEFAULT_DISABLE_SSL_VALIDATION;
private int maxConnections = DEFAULT_MAX_CONNECTIONS;
private int maxConnectionsPerRoute =
DEFAULT_MAX_CONNECTIONS_PER_ROUTE;
private long timeToLive = DEFAULT_TIME_TO_LIVE;
private TimeUnit timeToLiveUnit = DEFAULT_TIME_TO_LIVE_UNIT;
private boolean followRedirects = DEFAULT_FOLLOW_REDIRECTS;
private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
private int connectionTimerRepeat = DEFAULT_CONNECTION_TIMER_REPEAT;
}
FeignRibbonClientAutoConfiguration
1、
package muyan.yootk.config.ribbon; // 该包不在应用程序启动类的扫描包路径下
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 这个类必须使用该注解定义
public class DeptProviderRibbonConfig {
@Bean // Bean注册
public IRule ribbonRule() { // 自定义负载均衡规则
return new RandomRule(); // 随机读取
}
}
2、
package org.springframework.cloud.openfeign.ribbon;
// Ribbon组件里面所提供的ILoaderBalancer就描述了负载均衡的公共标准
@ConditionalOnClass({ ILoadBalancer.class, Feign.class }) // 必须有指定的类存在才可以配置
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
matchIfMissing = true) // 需要有一个配置项存在,不存在就表示默认启用
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(FeignAutoConfiguration.class) // 应该有一个配置类在之前完成
@EnableConfigurationProperties({ FeignHttpClientProperties.class }) // 接收配置属性
@Import({ HttpClientFeignLoadBalancedConfiguration.class, // 使用HttpClient实现访问
OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {
@Bean
@Primary // 首要配置
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory cachingLBClientFactory(
SpringClientFactory factory) { // 所有的节点应该在本地做缓存
return new CachingSpringLoadBalancerFactory(factory); // 缓冲Spring负载均衡加载工厂
}
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryFactory);
}
@Bean
@ConditionalOnMissingBean
public Request.Options feignRequestOptions() { // 请求的处理参数
return LoadBalancerFeignClient.DEFAULT_OPTIONS;
}
}
FeignLoadBalancerAutoconfiguration
1、
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-commons
implementation group: 'org.springframework.cloud', name: 'spring-cloud-commons', version: '3.0.3'
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-loadbalancer
implementation group: 'org.springframework.cloud', name: 'spring-cloud-loadbalancer', version: '3.0.3'
2、
project(":consumer-springboot-80") { // 消费端模块
dependencies {
implementation(project(":common-api")) // 导入公共的子模块
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
implementation(libraries.'httpclient') // 引入httpclient组件
implementation(libraries.'feign-httpclient') // 引入feign-httpclient组件
implementation group: 'org.springframework.cloud', name: 'spring-cloud-commons', version: '3.0.3'
implementation group: 'org.springframework.cloud', name: 'spring-cloud-loadbalancer', version: '3.0.3'
}
}
3、
package org.springframework.cloud.loadbalancer.blocking.client;
@SuppressWarnings({ "unchecked", "rawtypes" })
// 整体的设计的形式和Ribbon的结构很相似,原因就在于:所有的加载都需要有一个列表先提供,而后再进行选择
public class BlockingLoadBalancerClient implements LoadBalancerClient { // 负载均衡客户端
private final LoadBalancerClientFactory loadBalancerClientFactory; // 负载均衡客户端工厂类
private final LoadBalancerProperties properties; // 负载均衡属性
public BlockingLoadBalancerClient(LoadBalancerClientFactory loadBalancerClientFactory,
LoadBalancerProperties properties) {
this.loadBalancerClientFactory = loadBalancerClientFactory;
this.properties = properties;
}
@Override // 服务的调用执行处理
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
String hint = getHint(serviceId); //
LoadBalancerRequestAdapter<T, DefaultRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,
new DefaultRequestContext(request, hint)); // 创建一个负载均衡请求
// 把每一个请求不再单独进行简单的请求处理,而是将其封装在一个处理的周期调用结构里面
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId); // 设置处理周期
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
ServiceInstance serviceInstance = choose(serviceId, lbRequest); // 获取一个主机信息
if (serviceInstance == null) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));// 迭代处理,配置处理完成后的上下文
throw new IllegalStateException("No instances available for " + serviceId);
}
return execute(serviceId, serviceInstance, lbRequest); // 执行调用
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException { // 真正的进行请求的发出了
DefaultResponse defaultResponse = new DefaultResponse(serviceInstance); // 接收响应
Set<LoadBalancerLifecycle> supportedLifecycleProcessors =
getSupportedLifecycleProcessors(serviceId);
Request lbRequest = request instanceof Request ? (Request) request : new DefaultRequest<>();
supportedLifecycleProcessors
.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, new DefaultResponse(serviceInstance))); // 进行所有的请求处理
try {
T response = request.apply(serviceInstance); // 请求的发送
Object clientResponse = getClientResponse(response); // 请求的接收
supportedLifecycleProcessors
.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,
lbRequest, defaultResponse, clientResponse)));
return response;
}
catch (IOException iOException) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.FAILED, iOException, lbRequest, defaultResponse)));
throw iOException;
}
catch (Exception exception) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.FAILED, exception, lbRequest, defaultResponse)));
ReflectionUtils.rethrowRuntimeException(exception);
}
return null;
}
private <T> Object getClientResponse(T response) { // 接收请求响应了
ClientHttpResponse clientHttpResponse = null;
if (response instanceof ClientHttpResponse) {
clientHttpResponse = (ClientHttpResponse) response;
}
if (clientHttpResponse != null) {
try {
return new ResponseData(clientHttpResponse, null);
} catch (IOException ignored) { }
}
return response;
}
private Set<LoadBalancerLifecycle> getSupportedLifecycleProcessors(String serviceId) {
return LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
DefaultRequestContext.class, Object.class, ServiceInstance.class);
}
@Override
public URI reconstructURI(ServiceInstance serviceInstance, URI original) {
return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
}
@Override
public ServiceInstance choose(String serviceId) {
return choose(serviceId, REQUEST);
}
@Override
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
}
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
if (loadBalancerResponse == null) {
return null;
}
return loadBalancerResponse.getServer();
}
private String getHint(String serviceId) { // 根据服务的名称获取属性信息
String defaultHint = properties.getHint().getOrDefault("default", "default");
String hintPropertyValue = properties.getHint().get(serviceId);
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
}
}
FeignClientsRegistrar
1、
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AbstractClassTestingTypeFilter;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* @author Spencer Gibb
* @author Jakub Narloch
* @author Venil Noronha
* @author Gang Li
*/
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
private ResourceLoader resourceLoader;
private Environment environment;
FeignClientsRegistrar() {
}
static void validateFallback(final Class clazz) {
Assert.isTrue(!clazz.isInterface(),
"Fallback class must implement the interface annotated by @FeignClient");
}
static void validateFallbackFactory(final Class clazz) {
Assert.isTrue(!clazz.isInterface(), "Fallback factory must produce instances "
+ "of fallback classes that implement the interface annotated by @FeignClient");
}
static String getName(String name) {
if (!StringUtils.hasText(name)) {
return "";
}
String host = null;
try {
String url;
if (!name.startsWith("http://") && !name.startsWith("https://")) {
url = "http://" + name;
}
else {
url = name;
}
host = new URI(url).getHost();
}
catch (URISyntaxException e) {
}
Assert.state(host != null, "Service id not legal hostname (" + name + ")");
return name;
}
static String getUrl(String url) {
if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
if (!url.contains("://")) {
url = "http://" + url;
}
try {
new URL(url);
}
catch (MalformedURLException e) {
throw new IllegalArgumentException(url + " is malformed", e);
}
}
return url;
}
static String getPath(String path) {
if (StringUtils.hasText(path)) {
path = path.trim();
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
}
return path;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}
private void registerDefaultConfiguration(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> defaultAttrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
String name;
if (metadata.hasEnclosingClass()) {
name = "default." + metadata.getEnclosingClassName();
}
else {
name = "default." + metadata.getClassName();
}
registerClientConfiguration(registry, name,
defaultAttrs.get("defaultConfiguration"));
}
}
public void registerFeignClients(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
Set<String> basePackages;
Map<String, Object> attrs = metadata
.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
scanner.addIncludeFilter(annotationTypeFilter);
basePackages = getBasePackages(metadata);
}
else {
final Set<String> clientClasses = new HashSet<>();
basePackages = new HashSet<>();
for (Class<?> clazz : clients) {
basePackages.add(ClassUtils.getPackageName(clazz));
clientClasses.add(clazz.getCanonicalName());
}
AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
@Override
protected boolean match(ClassMetadata metadata) {
String cleaned = metadata.getClassName().replaceAll("\\$", ".");
return clientClasses.contains(cleaned);
}
};
scanner.addIncludeFilter(
new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
}
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner
.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
// verify annotated class is an interface
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(),
"@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata
.getAnnotationAttributes(
FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name,
attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be
// null
beanDefinition.setPrimary(primary);
String qualifier = getQualifier(attributes);
if (StringUtils.hasText(qualifier)) {
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
private void validate(Map<String, Object> attributes) {
AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
// This blows up if an aliased property is overspecified
// FIXME annotation.getAliasedString("name", FeignClient.class, null);
validateFallback(annotation.getClass("fallback"));
validateFallbackFactory(annotation.getClass("fallbackFactory"));
}
/* for testing */ String getName(Map<String, Object> attributes) {
String name = (String) attributes.get("serviceId");
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("name");
}
if (!StringUtils.hasText(name)) {
name = (String) attributes.get("value");
}
name = resolve(name);
return getName(name);
}
private String getContextId(Map<String, Object> attributes) {
String contextId = (String) attributes.get("contextId");
if (!StringUtils.hasText(contextId)) {
return getName(attributes);
}
contextId = resolve(contextId);
return getName(contextId);
}
private String resolve(String value) {
if (StringUtils.hasText(value)) {
return this.environment.resolvePlaceholders(value);
}
return value;
}
private String getUrl(Map<String, Object> attributes) {
String url = resolve((String) attributes.get("url"));
return getUrl(url);
}
private String getPath(Map<String, Object> attributes) {
String path = resolve((String) attributes.get("path"));
return getPath(path);
}
protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override
protected boolean isCandidateComponent(
AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent()) {
if (!beanDefinition.getMetadata().isAnnotation()) {
isCandidate = true;
}
}
return isCandidate;
}
};
}
protected Set<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> attributes = importingClassMetadata
.getAnnotationAttributes(EnableFeignClients.class.getCanonicalName());
Set<String> basePackages = new HashSet<>();
for (String pkg : (String[]) attributes.get("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : (String[]) attributes.get("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : (Class[]) attributes.get("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(
ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
private String getQualifier(Map<String, Object> client) {
if (client == null) {
return null;
}
String qualifier = (String) client.get("qualifier");
if (StringUtils.hasText(qualifier)) {
return qualifier;
}
return null;
}
private String getClientName(Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("contextId");
if (!StringUtils.hasText(value)) {
value = (String) client.get("value");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("serviceId");
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
+ FeignClient.class.getSimpleName());
}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
/**
* Helper class to create a {@link TypeFilter} that matches if all the delegates
* match.
*
* @author Oliver Gierke
*/
private static class AllTypeFilter implements TypeFilter {
private final List<TypeFilter> delegates;
/**
* Creates a new {@link AllTypeFilter} to match if all the given delegates match.
* @param delegates must not be {@literal null}.
*/
AllTypeFilter(List<TypeFilter> delegates) {
Assert.notNull(delegates, "This argument is required, it must not be null");
this.delegates = delegates;
}
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
for (TypeFilter filter : this.delegates) {
if (!filter.match(metadataReader, metadataReaderFactory)) {
return false;
}
}
return true;
}
}
}
Hystrix 熔断机制
微服务调用链
- 在微服务的设计之中,会不断的根据业务的需要进行服务的扩充,同时这些扩充的微服务又有可能要调用其他的微服务,这样在整个微服务的调用过程中就会形成如图所示的调用链。
微服务雪崩效应
- 在实际的生产运行过程中,有可能会出现机房断电、网络不稳、并发访问量过大而出现响应速度过慢而出现服务不稳定,或者由于内部程序设计导致产生了异常等,都有可能导致微服务不可用的情况出现,现在假设“微服务-C”出现了问题,那么就有可能连带着“微服务-B”出现问题,从而再导致“微服务-A”出现问题,最终影响到“消费端'而此类问题在微服务设计中称为“雪崩效应正常执行
Hystrix
- 为了防止微服务出现雪崩效应,可以在微服务之中设置熔断机制,这样当某一个微服务出现问题之后,可以直接直接通过服务的降级机制进行保护,这样就不会影响到其他的微服务执行,同时也便于开发者进行错误的排查,而这样的熔断机制在 SpringCloud 中就可以通过 Hystrix 来实现,该组件是由 Netflix 公司开源的一款容错框架,具有多种功能:
- 断路器:当服务访问量过大,并且超过了预先配置的访问阈值时,Hystrix 会直接开启断路器
- 资源隔离:在 Hystrix 之中采用了舱壁隔离模式对资源进行隔离,使多个资源彼此之间不会产生影响
- 降级回退:在某些服务节点出现故障后依然可以正常的返回处理结果
- 请求结果缓存:Hvstrix 实现了一个内部缓存机制,可以将请求结果进行缓存,那么对于相同的请求则会直接走缓存而不用请求后端服务。
- 请求合并:可以实现将一段时间内的请求合并,然后只对后端服务发送一次请求。
断路器
- 当服务访问量过大,并且超过了预先配置的访问阈值时,Hystrix 会直接开启断路器同时会拒绝后续所有的请求,在一段时间之后断路器会进入到半打开状态,此时会允许如果尝试依然存在问题,则继续保持打开状态,如果部分请求通过并进行调用尝试,尝试没有问题,则会进入关闭状态
资源隔离
- 在 Hystrix 之中采用了舱壁隔离模式对资源进行隔离,,使多个资源彼此之间不会产生影响,而在 Hystrix 之中资源隔离形式分为线程池隔离与信号量隔离。线程池隔离(ThreadPoolRejection)在 Hystrix 中会为每一个资源创建一个线程池,这若干个线程池彼此之间相互隔离,即便某一个服务资源出现问题,导致线程池资源耗尽,也不会影响到其它线程池的正常工作,所以该模式适合耗时较长的接口(接口处理逻辑负载)调用场景。同时为了进步提升处理性能,线程池之中的所有的线程并不参与实际的服务调用,真正的服务调用由转发线程处理,这样就保证容器有足够的线程来处理新的请求。使用线程池隔离模式虽然会有一定的线程调度开销,但是在并发访问量大的情况下可以基于延迟队列实现调度处理(例如:等待超时、异步带来较好的并发处理性能。调用)
Hystrix 基本使用
1、
include 'provider-dept-hystrix'
2、
project(":provider-dept-hystrix") { // 部门微服务
dependencies {
implementation(project(":common-api")) // 导入公共的子模块
implementation(libraries.'mybatis-plus-boot-starter')
implementation(libraries.'mysql-connector-java')
implementation(libraries.'druid')
implementation(libraries.'springfox-boot-starter')
implementation('org.springframework.boot:spring-boot-starter-security')
// 以下的依赖库为Nacos注册中心所需要的依赖配置
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
}
}
3、
http://consumer-springboot-80/consumer/dept/get?deptno=99
4、
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-hystrix', version: '2.2.9.RELEASE'
5、
package com.yootk.provider.action;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.yootk.common.dto.DeptDTO;
import com.yootk.provider.vo.Dept;
import com.yootk.service.IDeptService;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@RestController
@RequestMapping("/provider/dept/*") // 微服务提供者父路径
@Slf4j // 使用一个注解
public class DeptAction {
@Autowired
private IDeptService deptService;
@ApiOperation(value="部门查询", notes = "根据部门编号查询部门详细信息")
@GetMapping("get/{id}")
@HystrixCommand(fallbackMethod = "getFallback") // 服务降级
public Object get(@PathVariable("id") long id) {
this.printRequestHeaders("get");
return this.deptService.get(id);
}
public Object getFallback(long id) { // 服务降级处理方法
DeptDTO dto = new DeptDTO();
dto.setDeptno(id); // id为用户传递到该REST接口之中的参数
dto.setDname("【Fallback】部门名称");
dto.setLoc("【Fallback】部门位置");
return dto;
}
@ApiOperation(value="部门增加", notes = "增加新的部门信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "deptDTO", required = true,
dataType = "DeptDTO", value = "部门传输对象实例")
})
@PostMapping("add")
public Object add(@RequestBody DeptDTO deptDTO) { // 后面会修改参数模式为JSON
this.printRequestHeaders("add");
return this.deptService.add(deptDTO);
}
@ApiOperation(value="部门列表", notes = "查询部门的完整信息")
@GetMapping("list")
public Object list() {
this.printRequestHeaders("list");
return this.deptService.list();
}
@ApiOperation(value="部门分页查询", notes = "根据指定的数据库参数实现部门数据的分页加载")
@ApiImplicitParams({
@ApiImplicitParam(name="cp", value = "当前所在页", required = true, dataType = "int"),
@ApiImplicitParam(name="ls", value = "每页显示的数据行数", required = true, dataType = "int"),
@ApiImplicitParam(name="col", value = "模糊查询列", required = true, dataType = "String"),
@ApiImplicitParam(name="kw", value = "模糊查询关键字", required = true, dataType = "String")
})
@GetMapping("split")
public Object split(int cp, int ls, String col, String kw) {
this.printRequestHeaders("split");
return this.deptService.split(cp, ls, col, kw);
}
private void printRequestHeaders(String restName) { // 实现所有请求头信息的输出
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Enumeration<String> headerEnums = request.getHeaderNames();
while (headerEnums.hasMoreElements()) {
String headerName = headerEnums.nextElement();
log.info("【{}】头信息:{} = {}", restName, headerName, request.getHeader(headerName));
}
}
}
6、
package com.yootk.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker // 启用熔断机制
public class StartProviderDeptHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(StartProviderDeptHystrixApplication.class, args);
}
}
Feign 失败回退
1、
package com.yootk.service.fallback;
import com.yootk.common.dto.DeptDTO;
import com.yootk.service.IDeptService;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component // 进行Bean注册
public class DeptServiceFallbackFactory implements FallbackFactory<IDeptService> {
@Override
public IDeptService create(Throwable cause) { // 定义失败回退处理
return new IDeptService() {
@Override
public DeptDTO get(long id) {
DeptDTO dto = new DeptDTO();
dto.setDeptno(id);
dto.setDname("【部门名称】" + cause.getMessage()); // 设置异常信息
dto.setLoc("【部门位置】" + cause.getMessage());
return dto;
}
@Override
public boolean add(DeptDTO dto) {
return false;
}
@Override
public List<DeptDTO> list() {
return new ArrayList<DeptDTO>();
}
@Override
public Map<String, Object> split(int currentPage, int lineSize, String column, String keyword) {
return new HashMap<>();
}
};
}
}
2、
package com.yootk.service;
import com.yootk.common.dto.DeptDTO;
import com.yootk.service.config.FeignConfig;
import com.yootk.service.fallback.DeptServiceFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import java.util.Map;
@FeignClient(value = "dept.provider",
configuration = FeignConfig.class,// 定义要访问的微服务实例名称
fallbackFactory = DeptServiceFallbackFactory.class) // 部门降级配置
public interface IDeptService { // 业务接口
/**
* 根据部门的编号获取部门的完整信息
* @param id 要查询的部门编号
* @return 编号存在则以DTO对象的形式返回部门数据,如果不存在返回null
*/
@GetMapping("/provider/dept/get/{deptno}") // 远程REST接口
public DeptDTO get(@PathVariable("deptno") long id);
/**
* 增加部门对象
* @param dto 保存要增加部门的详细数据
* @return 增加成功返回true,否则返回false
*/
@PostMapping("/provider/dept/add")
public boolean add(DeptDTO dto);
/**
* 列出所有的部门数据信息
* @return 全部数据的集合, 如果没有任何的部门数据则集合为空(size() == 0)
*/
@GetMapping("/provider/dept/list")
public List<DeptDTO> list();
/**
* 进行部门的分页数据加载操作
* @param currentPage 当前所在页
* @param lineSize 每页加载的数据行数
* @param column 模糊查询的数据列
* @param keyword 模糊查询关键字
* @return 部门集合数据以及统计数据,返回的数据项包括:
* 1、key = allDepts、value = List集合(部门的全部数据对象)
* 2、key = allRecorders、value = 总记录数;
* 3、key = allPages、value = 页数。
*/
@GetMapping("/provider/dept/split")
public Map<String, Object> split(
@RequestParam("cp") int currentPage,
@RequestParam("ls") int lineSize,
@RequestParam("col") String column,
@RequestParam("kw") String keyword);
}
3、
feign:
hystrix: # Hystrix配置
enabled: true # 启用熔断与失败回退
4、
package com.yootk.consumer;
import muyan.yootk.config.ribbon.DeptProviderRibbonConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableDiscoveryClient
// 如果此时要有多个配置项,可以使用@RibbonClients注解,该注解可以配置多个@RibbonClient
@RibbonClient(name = "dept.provider", configuration = DeptProviderRibbonConfig.class) // 自定义Ribbon配置
@ComponentScan({"com.yootk.service", "com.yootk.consumer"})
@EnableFeignClients("com.yootk.service") // Feign扫描包
public class StartConsumerApplication { // 沐言科技:www.yootk.com
public static void main(String[] args) {
SpringApplication.run(StartConsumerApplication.class, args);
}
}
Hystrix 监控处理
Hystrix 除了实现微服务的容错处理支持之外,,还提供了实时监控数据,可以根据微服务中提供的 HystrixCommand 方法提供执行数据(例如:每秒的请求数、请求成功数莞)同时这些数据可以通过 HystrixDashboard 提供的面板进行可视化监控,这样开发者就可以实时的掌握微服务的执行情况
1、
project(":hystrix-dashboard-8101") {
dependencies { // 依赖的配置
implementation("org.springframework.boot:spring-boot-starter-web")
implementation('org.springframework.boot:spring-boot-starter-actuator')
implementation('org.springframework.cloud:spring-cloud-starter-netflix-hystrix')
implementation('org.springframework.cloud:spring-cloud-starter-netflix-hystrix-dashboard')
}
}
2、
server:
port: 8101 # 监听端口
3、
package com.yookt.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard // 启动面板
public class StartHystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(StartHystrixDashboardApplication.class, args);
}
}
4、
127.0.0.1 hystrix-dashboard-8101
5、
http://hystrix-dashboard-8101:8101/hystrix
6、
project(":provider-dept-hystrix") { // 部门微服务
dependencies {
implementation(project(":common-api")) // 导入公共的子模块
implementation(libraries.'mybatis-plus-boot-starter')
implementation(libraries.'mysql-connector-java')
implementation(libraries.'druid')
implementation(libraries.'springfox-boot-starter')
implementation('org.springframework.boot:spring-boot-starter-security')
// 以下的依赖库为Nacos注册中心所需要的依赖配置
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
implementation('org.springframework.boot:spring-boot-starter-actuator')
implementation('org.springframework.cloud:spring-cloud-starter-netflix-hystrix')
}
}
7、
package com.yootk.provider.action;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.yootk.common.dto.DeptDTO;
import com.yootk.provider.vo.Dept;
import com.yootk.service.IDeptService;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@RestController
@RequestMapping("/provider/dept/*") // 微服务提供者父路径
@Slf4j // 使用一个注解
public class DeptAction {
@Autowired
private IDeptService deptService;
@ApiOperation(value = "部门查询", notes = "根据部门编号查询部门详细信息")
@GetMapping("get/{id}")
@HystrixCommand
public Object get(@PathVariable("id") long id) {
this.printRequestHeaders("get");
return this.deptService.get(id);
}
@ApiOperation(value = "部门增加", notes = "增加新的部门信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "deptDTO", required = true,
dataType = "DeptDTO", value = "部门传输对象实例")
})
@PostMapping("add")
@HystrixCommand
public Object add(@RequestBody DeptDTO deptDTO) { // 后面会修改参数模式为JSON
this.printRequestHeaders("add");
return this.deptService.add(deptDTO);
}
@ApiOperation(value = "部门列表", notes = "查询部门的完整信息")
@GetMapping("list")
@HystrixCommand
public Object list() {
this.printRequestHeaders("list");
return this.deptService.list();
}
@ApiOperation(value = "部门分页查询", notes = "根据指定的数据库参数实现部门数据的分页加载")
@ApiImplicitParams({
@ApiImplicitParam(name = "cp", value = "当前所在页", required = true, dataType = "int"),
@ApiImplicitParam(name = "ls", value = "每页显示的数据行数", required = true, dataType = "int"),
@ApiImplicitParam(name = "col", value = "模糊查询列", required = true, dataType = "String"),
@ApiImplicitParam(name = "kw", value = "模糊查询关键字", required = true, dataType = "String")
})
@GetMapping("split")
@HystrixCommand
public Object split(int cp, int ls, String col, String kw) {
this.printRequestHeaders("split");
return this.deptService.split(cp, ls, col, kw);
}
private void printRequestHeaders(String restName) { // 实现所有请求头信息的输出
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Enumeration<String> headerEnums = request.getHeaderNames();
while (headerEnums.hasMoreElements()) {
String headerName = headerEnums.nextElement();
log.info("【{}】头信息:{} = {}", restName, headerName, request.getHeader(headerName));
}
}
}
8、
management:
endpoints:
web:
exposure:
include: *
9、
package com.yootk.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix // 启用Hystrix监控
//@EnableCircuitBreaker // 启用熔断机制
public class StartProviderDeptHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(StartProviderDeptHystrixApplication.class, args);
}
}
10、
provider-dept-8001:8001/actuator/hystrix.stream
Turbine 聚合监控
1、
// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-turbine
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-turbine', version: '2.2.9.RELEASE'
2、
+--- org.springframework.cloud:spring-cloud-starter-netflix-turbine -> 2.2.2.RELEASE
| +--- org.springframework.cloud:spring-cloud-starter:2.2.2.RELEASE (*)
| +--- org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:2.2.2.RELEASE
3、
project(":trubine-8201") {
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
// 以下的依赖库为Nacos注册中心所需要的依赖配置
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery') {
exclude group: 'com.alibaba.nacos', module: 'nacos-client' // 移除旧版本的Nacos依赖
}
implementation(libraries.'nacos-client') // 引入与当前的Nacos匹配的依赖库
implementation('org.springframework.cloud:spring-cloud-starter-netflix-turbine') {
// 默认情况下Turbine属于Netflix套件之中的组成,所以必须要移除Eureka相关依赖
exclude group: 'org.springframework.cloud', module: 'spring-cloud-starter-netflix-eureka-client'
}
}
}
4、
server: # 服务端配置
port: 8201 # 8201端口
spring:
application: # 配置应用信息
name: turbine # 是微服务的名称
cloud: # Cloud配置
nacos: # Nacos注册中心配置
discovery: # 发现服务
weight: 10
service: ${spring.application.name} # 使用微服务的名称作为注册的服务名称
server-addr: nacos-server:8848 # Nacos地址
namespace: 96c23d77-8d08-4648-b750-1217845607ee # 命名空间ID
group: MICROCLOUD_GROUP # 一般建议大写
cluster-name: YootkCluster # 配置集群名称
username: muyan # 用户名
password: yootk # 密码
metadata: # 根据自身的需要配置元数据
version: 2.0 # 自定义元数据项
company: 沐言科技 # 自定义元数据项
url: www.yootk.com # 自定义元数据项
author: 李兴华(爆可爱的小李老师) # 自定义元数据项
turbine:
app-config: dept.provider
cluster-name-expression: new String('default')
5、
package com.yootk.turbine;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.turbine.EnableTurbine;
@SpringBootApplication
@EnableDiscoveryClient
@EnableTurbine
public class StartTurbineApplication {
public static void main(String[] args) {
SpringApplication.run(StartTurbineApplication.class, args);
}
}
6、
127.0.0.1 trubine-8201
7、
trubine-8201:8201/turbine.stream
demo
