Hystrix是什么、设计目标是什么
◆Hystrix 是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互
◆Hystrix 通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现服务之间的容错
◆Hystrix 有四大设计目标
1.对客户端访问的延迟和故障进行保护和控制
2.在复杂的分布式系统中阻止级联故障
3.快速失败,快速恢复
4.兜底回退,尽可能优雅的降级
Hystrix是如何实现它的目标的
1.对依赖项(服务)进行包装代理,不直接与依赖项交互
2.调用超时时间允许自行设定,超时之后立刻熔断报错
3.每一个依赖项都在自己的空间内(线程池或信号量隔离),依赖项之间不存在干扰
4.请求依赖项失败后,可以选择出错或者是兜底回退
SpringCloud Netflix Hystrix的三种模式
◆断路器模式 :设置超时或者失败等熔断策略
◆后备策略模式:断路器模式触发之后,如果存在后备策略,则执行后备
◆Hystrix 舱壁模式的启发:货船为了进行防止漏水和火灾的扩散,会将货仓分隔为多个,当发生灾害时,将所在货仓进行隔离就可以降低整艘船的风险
舱壁模式功能就是:我们可以通过为各个服务分别指定线程池
使用Hystrix
1.添加依赖
<!-- 集成hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.在启动入口添加注解
@EnableCircuitBreaker //启用hystrix
3.使用注解的方式,使用Hystrix
/**
* <h1>使用 HystrixCommand 注解</h1>
* */
@Slf4j
@Service
public class UseHystrixCommandAnnotation {
private final NacosClientService nacosClientService;
public UseHystrixCommandAnnotation(NacosClientService nacosClientService) {
this.nacosClientService = nacosClientService;
}
@HystrixCommand(
// 用于对 Hystrix 命令进行分组, 分组之后便于统计展示于仪表盘、上传报告和预警等等
// 内部进行度量统计时候的分组标识, 数据上报和统计的最小维度就是 groupKey
groupKey = "NacosClientService",
// HystrixCommand 的名字, 默认是当前类的名字, 主要方便 Hystrix 进行监控、报警等
commandKey = "NacosClientService",
// 舱壁模式
threadPoolKey = "NacosClientService",
// 后备模式
fallbackMethod = "getNacosClientInfoFallback",
// 断路器模式
commandProperties = {
// 超时时间, 单位毫秒, 超时进 fallback
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500"),
// 判断熔断的最少请求数, 默认是10; 只有在一定时间内请求数量达到该值, 才会进行成功率的计算
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
// 熔断的阈值默认值 50, 表示在一定时间内有50%的请求处理失败, 会触发熔断
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "10"),
},
// 舱壁模式
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),
@HystrixProperty(name = "maxQueueSize", value = "101"),
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
// 在时间窗口中, 收集统计信息的次数; 在 1440ms 的窗口中一共统计 12 次
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
// 时间窗口, 从监听到第一次失败开始计时
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440")
}
)
public List<ServiceInstance> getNacosClientInfo(String serviceId) {
log.info("use hystrix command annotation to get nacos client info: [{}], [{}]",
serviceId, Thread.currentThread().getName());
return nacosClientService.getNacosClientInfo(serviceId);
}
/**
* <h2>getNacosClientInfo 的兜底策略 - Hystrix 后备模式</h2>
* */
public List<ServiceInstance> getNacosClientInfoFallback(String serviceId) {
log.warn("can not get nacos client, trigger hystrix fallback: [{}], [{}]",
serviceId, Thread.currentThread().getName());
return Collections.emptyList();
}
}
使用编码的形式,使用Hystrix,默认使用线程池
/**
* <h1>给 NacosClientService 实现包装</h1>
* Hystrix 舱壁模式:
* 1. 线程池
* 2. 信号量: 算法 + 数据结构, 有限状态机
* */
@Slf4j
public class NacosClientHystrixCommand extends HystrixCommand<List<ServiceInstance>> {
/** 需要保护的服务 */
private final NacosClientService nacosClientService;
/** 方法需要传递的参数 */
private final String serviceId;
public NacosClientHystrixCommand(NacosClientService nacosClientService, String serviceId) {
super(
Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey("NacosClientService"))
.andCommandKey(HystrixCommandKey.Factory.asKey("NacosClientHystrixCommand"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("NacosClientPool"))
// 线程池 key 配置
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(THREAD) // 线程池隔离策略
.withFallbackEnabled(true) // 开启降级
.withCircuitBreakerEnabled(true) // 开启熔断器
)
);
// 可以配置信号量隔离策略
// Setter semaphore =
// Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("NacosClientService"))
// .andCommandKey(HystrixCommandKey.Factory.asKey("NacosClientHystrixCommand"))
// .andCommandPropertiesDefaults(
// HystrixCommandProperties.Setter()
// .withCircuitBreakerRequestVolumeThreshold(10)
// .withCircuitBreakerSleepWindowInMilliseconds(5000)
// .withCircuitBreakerErrorThresholdPercentage(50)
// .withExecutionIsolationStrategy(SEMAPHORE) // 指定使用信号量隔离
// //.....
// );
this.nacosClientService = nacosClientService;
this.serviceId = serviceId;
}
/**
* <h2>要保护的方法调用写在 run 方法中</h2>
* */
@Override
protected List<ServiceInstance> run() throws Exception {
log.info("NacosClientService In Hystrix Command to Get Service Instance: [{}], [{}]",
this.serviceId, Thread.currentThread().getName());
return this.nacosClientService.getNacosClientInfo(this.serviceId);
}
/**
* <h2>降级处理策略</h2>
* */
@Override
protected List<ServiceInstance> getFallback() {
log.warn("NacosClientService run error: [{}], [{}]",
this.serviceId, Thread.currentThread().getName());
return Collections.emptyList();
}
}
Controller的调用方式
private final NacosClientService nacosClientService;
public HystrixController(NacosClientService nacosClientService) {
this.nacosClientService = nacosClientService;
}
@GetMapping("/simple-hystrix-command")
public List<ServiceInstance> getServiceInstanceByServiceId(
@RequestParam String serviceId) throws Exception {
// 第一种方式
List<ServiceInstance> serviceInstances01 = new NacosClientHystrixCommand(
nacosClientService, serviceId
).execute(); // 同步阻塞
log.info("use execute to get service instances: [{}], [{}]",
JSON.toJSONString(serviceInstances01), Thread.currentThread().getName());
// 第二种方式
List<ServiceInstance> serviceInstances02;
Future<List<ServiceInstance>> future = new NacosClientHystrixCommand(
nacosClientService, serviceId
).queue(); // 异步非阻塞
// 这里可以做一些别的事, 需要的时候再去拿结果
serviceInstances02 = future.get();
log.info("use queue to get service instances: [{}], [{}]",
JSON.toJSONString(serviceInstances02), Thread.currentThread().getName());
// 第三种方式
Observable<List<ServiceInstance>> observable = new NacosClientHystrixCommand(
nacosClientService, serviceId
).observe(); // 热响应调用
List<ServiceInstance> serviceInstances03 = observable.toBlocking().single();
log.info("use observe to get service instances: [{}], [{}]",
JSON.toJSONString(serviceInstances03), Thread.currentThread().getName());
// 第四种方式
Observable<List<ServiceInstance>> toObservable = new NacosClientHystrixCommand(
nacosClientService, serviceId
).toObservable(); // 异步冷响应调用
List<ServiceInstance> serviceInstances04 = toObservable.toBlocking().single();
log.info("use toObservable to get service instances: [{}], [{}]",
JSON.toJSONString(serviceInstances04), Thread.currentThread().getName());
// execute = queue + get
return serviceInstances01;
}
使用编码的形式,使用Hystrix,默认使用信号量
/**
* <h1>HystrixCommand, 隔离策略是基于信号量实现的</h1>
* */
@Slf4j
public class NacosClientHystrixObservableCommand extends
HystrixObservableCommand<List<ServiceInstance>> {
/** 要保护的服务 */
private final NacosClientService nacosClientService;
/** 方法需要传递的参数 */
private final List<String> serviceIds;
public NacosClientHystrixObservableCommand(NacosClientService nacosClientService,
List<String> serviceIds) {
super(
HystrixObservableCommand.Setter
.withGroupKey(HystrixCommandGroupKey
.Factory.asKey("NacosClientService"))
.andCommandKey(HystrixCommandKey
.Factory.asKey("NacosClientHystrixObservableCommand"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withFallbackEnabled(true) // 开启降级
.withCircuitBreakerEnabled(true) // 开启熔断器
)
);
this.nacosClientService = nacosClientService;
this.serviceIds = serviceIds;
}
/**
* <h2>要保护的方法调用写在这里</h2>
* */
@Override
protected Observable<List<ServiceInstance>> construct() {
return Observable.create(new Observable.OnSubscribe<List<ServiceInstance>>() {
// Observable 有三个关键的事件方法, 分别是 onNext、onCompleted、onError
@Override
public void call(Subscriber<? super List<ServiceInstance>> subscriber) {
try {
if (!subscriber.isUnsubscribed()) {
log.info("subscriber command task: [{}], [{}]",
JSON.toJSONString(serviceIds),
Thread.currentThread().getName());
serviceIds.forEach(
s -> subscriber
.onNext(nacosClientService.getNacosClientInfo(s))
);
subscriber.onCompleted();
log.info("command task completed: [{}], [{}]",
JSON.toJSONString(serviceIds),
Thread.currentThread().getName());
}
} catch (Exception ex) {
subscriber.onError(ex);
}
}
});
}
/**
* <h2>服务降级</h2>
* */
@Override
protected Observable<List<ServiceInstance>> resumeWithFallback() {
return Observable.create(new Observable.OnSubscribe<List<ServiceInstance>>() {
@Override
public void call(Subscriber<? super List<ServiceInstance>> subscriber) {
try {
if (!subscriber.isUnsubscribed()) {
log.info("(fallback) subscriber command task: [{}], [{}]",
JSON.toJSONString(serviceIds),
Thread.currentThread().getName());
subscriber.onNext(Collections.emptyList());
subscriber.onCompleted();
log.info("(fallback) command task completed: [{}], [{}]",
JSON.toJSONString(serviceIds),
Thread.currentThread().getName());
}
} catch (Exception ex) {
subscriber.onError(ex);
}
}
});
}
}
Controller的调用方式
@GetMapping("/hystrix-observable-command")
public List<ServiceInstance> getServiceInstancesByServiceIdObservable(
@RequestParam String serviceId) {
List<String> serviceIds = Arrays.asList(serviceId, serviceId, serviceId);
List<List<ServiceInstance>> result = new ArrayList<>(serviceIds.size());
NacosClientHystrixObservableCommand observableCommand =
new NacosClientHystrixObservableCommand(nacosClientService, serviceIds);
// 异步执行命令
Observable<List<ServiceInstance>> observe = observableCommand.observe();
// 注册获取结果
observe.subscribe(
new Observer<List<ServiceInstance>>() {
// 执行 onNext 之后再去执行 onCompleted
@Override
public void onCompleted() {
log.info("all tasks is complete: [{}], [{}]",
serviceId, Thread.currentThread().getName());
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(List<ServiceInstance> instances) {
result.add(instances);
}
}
);
log.info("observable command result is : [{}], [{}]",
JSON.toJSONString(result), Thread.currentThread().getName());
return result.get(0);
}
SpringCloud Netflix Hystrix请求缓存
Hystrix请求缓存的两种实现方式
◆编程(继承HystrixCommand ) 方式,重写getCacheKey方法即可
◆注解方式
编程(继承HystrixCommand ) 方式,重写getCacheKey方法即可
首先需要添加一个过滤器(这个过滤器都是需要的,注解开发也需要)
/**
* <h1>初始化 Hystrix 请求上下文环境</h1>
* */
@Slf4j
@Component
@WebFilter(
filterName = "HystrixRequestContextServletFilter",
urlPatterns = "/*",
asyncSupported = true
)
public class HystrixRequestContextServletFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
// 初始化 Hystrix 请求上下文
// 在不同的 context 中缓存是不共享的
// 这个初始化是必须的
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
// 配置
hystrixConcurrencyStrategyConfig();
// 请求正常通过
filterChain.doFilter(servletRequest, servletResponse);
} finally {
// 关闭 Hystrix 请求上下文
context.shutdown();
}
}
/**
* <h2>配置 Hystrix 的并发策略</h2>
* */
public void hystrixConcurrencyStrategyConfig() {
try {
HystrixConcurrencyStrategy target =
HystrixConcurrencyStrategyDefault.getInstance();
HystrixConcurrencyStrategy strategy =
HystrixPlugins.getInstance().getConcurrencyStrategy();
if (strategy instanceof HystrixConcurrencyStrategyDefault) {
// 如果已经就是我们想要配置的
return;
}
// 将原来其他的配置保存下来
HystrixCommandExecutionHook commandExecutionHook =
HystrixPlugins.getInstance().getCommandExecutionHook();
HystrixEventNotifier eventNotifier =
HystrixPlugins.getInstance().getEventNotifier();
HystrixMetricsPublisher metricsPublisher =
HystrixPlugins.getInstance().getMetricsPublisher();
HystrixPropertiesStrategy propertiesStrategy =
HystrixPlugins.getInstance().getPropertiesStrategy();
// 先重置, 再把我们自定义的配置与原来的配置写回去
HystrixPlugins.reset();
HystrixPlugins.getInstance().registerConcurrencyStrategy(target);
HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
log.info("config hystrix concurrency strategy success");
} catch (Exception ex) {
log.error("Failed to register Hystrix Concurrency Strategy: [{}]",
ex.getMessage(), ex);
}
}
}
在启动入口需要添加注解:@ServletComponentScan,这样过滤器才会生效。
/**
* <h1>带有缓存功能的 Hystrix</h1>
* */
@Slf4j
public class CacheHystrixCommand extends HystrixCommand<List<ServiceInstance>> {
/** 需要保护的服务 */
private final NacosClientService nacosClientService;
/** 方法需要传递的参数 */
private final String serviceId;
private static final HystrixCommandKey CACHED_KEY =
HystrixCommandKey.Factory.asKey("CacheHystrixCommand");
public CacheHystrixCommand(NacosClientService nacosClientService, String serviceId) {
super(
HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey
.Factory.asKey("CacheHystrixCommandGroup"))
.andCommandKey(CACHED_KEY)
);
this.nacosClientService = nacosClientService;
this.serviceId = serviceId;
}
@Override
protected List<ServiceInstance> run() throws Exception {
log.info("CacheHystrixCommand In Hystrix Command to get service instance:" +
" [{}], [{}]", this.serviceId, Thread.currentThread().getName());
return this.nacosClientService.getNacosClientInfo(this.serviceId);
}
@Override
protected String getCacheKey() {
return serviceId;
}
@Override
protected List<ServiceInstance> getFallback() {
return Collections.emptyList();
}
/**
* <h2>根据缓存 key 清理在一次 Hystrix 请求上下文中的缓存</h2>
* */
public static void flushRequestCache(String serviceId) {
HystrixRequestCache.getInstance(
CACHED_KEY,
HystrixConcurrencyStrategyDefault.getInstance()
).clear(serviceId);
log.info("flush request cache in hystrix command: [{}], [{}]",
serviceId, Thread.currentThread().getName());
}
}
Controller
@GetMapping("/cache-hystrix-command")
public void cacheHystrixCommand(@RequestParam String serviceId) {
// 使用缓存 Command, 发起两次请求
CacheHystrixCommand command1 = new CacheHystrixCommand(
nacosClientService, serviceId
);
CacheHystrixCommand command2 = new CacheHystrixCommand(
nacosClientService, serviceId
);
List<ServiceInstance> result01 = command1.execute();
List<ServiceInstance> result02 = command2.execute();
log.info("result01, result02: [{}], [{}]",
JSON.toJSONString(result01), JSON.toJSONString(result02));
// 清除缓存
CacheHystrixCommand.flushRequestCache(serviceId);
// 使用缓存 Command, 发起两次请求
CacheHystrixCommand command3 = new CacheHystrixCommand(
nacosClientService, serviceId
);
CacheHystrixCommand command4 = new CacheHystrixCommand(
nacosClientService, serviceId
);
List<ServiceInstance> result03 = command3.execute();
List<ServiceInstance> result04 = command4.execute();
log.info("result03, result04: [{}], [{}]",
JSON.toJSONString(result03), JSON.toJSONString(result04));
}
注解方式
@CacheResult:该注解用来标记请求命令返回的结果应该被缓存,它必须与@HystrixCommand注解结合使用(属性:cacheKeyMethod)
@CacheRemove:该注解用来让请求命令的綬存失效,失效的缓存根据commandKey进行查找。(属性:commandKey, cacheKeyMethod)
@CacheKey:该注解用来在请求命令的参数上标记,使其作为cacheKey,如果没有使用此注解则会使用所有参数列表中的参数作为cacheKey(属性:value)
/**
* <h1>使用注解方式开启 Hystrix 请求缓存</h1>
* */
@Slf4j
@Service
public class CacheHystrixCommandAnnotation {
private final NacosClientService nacosClientService;
public CacheHystrixCommandAnnotation(NacosClientService nacosClientService) {
this.nacosClientService = nacosClientService;
}
// 第一种 Hystrix Cache 注解的使用方法
@CacheResult(cacheKeyMethod = "getCacheKey")
@HystrixCommand(commandKey = "CacheHystrixCommandAnnotation")
public List<ServiceInstance> useCacheByAnnotation01(String serviceId) {
log.info("use cache01 to get nacos client info: [{}]", serviceId);
return nacosClientService.getNacosClientInfo(serviceId);
}
@CacheRemove(commandKey = "CacheHystrixCommandAnnotation",
cacheKeyMethod = "getCacheKey")
@HystrixCommand
public void flushCacheByAnnotation01(String cacheId) {
log.info("flush hystrix cache key: [{}]", cacheId);
}
public String getCacheKey(String cacheId) {
return cacheId;
}
// 第二种 Hystrix Cache 注解的使用方法(常用)
@CacheResult
@HystrixCommand(commandKey = "CacheHystrixCommandAnnotation")
public List<ServiceInstance> useCacheByAnnotation02(@CacheKey String serviceId) {
log.info("use cache02 to get nacos client info: [{}]", serviceId);
return nacosClientService.getNacosClientInfo(serviceId);
}
@CacheRemove(commandKey = "CacheHystrixCommandAnnotation")
@HystrixCommand
public void flushCacheByAnnotation02(@CacheKey String cacheId) {
log.info("flush hystrix cache key: [{}]", cacheId);
}
// 第三种 Hystrix Cache 注解的使用方法
@CacheResult
@HystrixCommand(commandKey = "CacheHystrixCommandAnnotation")
public List<ServiceInstance> useCacheByAnnotation03(String serviceId) {
log.info("use cache03 to get nacos client info: [{}]", serviceId);
return nacosClientService.getNacosClientInfo(serviceId);
}
@CacheRemove(commandKey = "CacheHystrixCommandAnnotation")
@HystrixCommand
public void flushCacheByAnnotation03(String cacheId) {
log.info("flush hystrix cache key: [{}]", cacheId);
}
}
SpringCloud Netflix Hystrix 请求合并
使用Hystrix的请求合并,将多个请求merge为一个,提高服务的并发能力
◆适用场景:单个对象的查询并发数很高,服务提供方负载较高,就可以考虑使用请求合并
◆注意事项:
1.请求在代码中人为的设置了延迟时间,会降低请求的响应速度
2.可能会提高服务提供方的负载,因为返回List 结果数据量偏大
3.实现请求合并比较复杂
编程方式实现请求合并
service中编写批量请求的方法
/**
* <h2>提供给编程方式的 Hystrix 请求合并</h2>
* */
public List<List<ServiceInstance>> getNacosClientInfos(List<String> serviceIds) {
log.info("request nacos client to get service instance infos: [{}]",
JSON.toJSONString(serviceIds));
List<List<ServiceInstance>> result = new ArrayList<>(serviceIds.size());
serviceIds.forEach(s -> result.add(discoveryClient.getInstances(s)));
return result;
}
为批量请求编写降级策略等
/**
* <h1>批量请求 Hystrix Command</h1>
* */
@Slf4j
public class NacosClientBatchCommand extends HystrixCommand<List<List<ServiceInstance>>> {
private final NacosClientService nacosClientService;
private final List<String> serviceIds;
protected NacosClientBatchCommand(
NacosClientService nacosClientService, List<String> serviceIds
) {
super(
HystrixCommand.Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey("NacosClientBatchCommand")
)
);
this.nacosClientService = nacosClientService;
this.serviceIds = serviceIds;
}
@Override
protected List<List<ServiceInstance>> run() throws Exception {
log.info("use nacos client batch command to get result: [{}]",
JSON.toJSONString(serviceIds));
return nacosClientService.getNacosClientInfos(serviceIds);
}
@Override
protected List<List<ServiceInstance>> getFallback() {
log.warn("nacos client batch command failure, use fallback");
return Collections.emptyList();
}
}
请求合并器
/**
* <h1>请求合并器</h1>
* */
@Slf4j
public class NacosClientCollapseCommand extends HystrixCollapser<List<List<ServiceInstance>>, List<ServiceInstance>, String> {
private final NacosClientService nacosClientService;
private final String serviceId;
public NacosClientCollapseCommand(NacosClientService nacosClientService, String serviceId) {
super(
HystrixCollapser.Setter.withCollapserKey(
HystrixCollapserKey.Factory.asKey("NacosClientCollapseCommand")
).andCollapserPropertiesDefaults(
HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(300)
)
);
this.nacosClientService = nacosClientService;
this.serviceId = serviceId;
}
/**
* <h2>获取请求中的参数</h2>
* */
@Override
public String getRequestArgument() {
return this.serviceId;
}
/**
* <h2>创建批量请求 Hystrix Command</h2>
* */
@Override
protected HystrixCommand<List<List<ServiceInstance>>> createCommand(
Collection<CollapsedRequest<List<ServiceInstance>, String>> collection) {
List<String> serviceIds = new ArrayList<>(collection.size());
serviceIds.addAll(
collection.stream()
.map(CollapsedRequest::getArgument)
.collect(Collectors.toList())
);
return new NacosClientBatchCommand(nacosClientService, serviceIds);
}
/**
* <h2>响应分发给单独的请求</h2>
* */
@Override
protected void mapResponseToRequests(List<List<ServiceInstance>> lists,
Collection<CollapsedRequest<List<ServiceInstance>, String>> collection) {
int count = 0;
for (CollapsedRequest<List<ServiceInstance>, String> collapsedRequest : collection) {
// 从批量响应集合中按顺序取出结果
List<ServiceInstance> instances = lists.get(count++);
// 将结果返回原 Response 中
collapsedRequest.setResponse(instances);
}
}
}
controller
/**
* <h2>编程方式实现请求合并</h2>
* */
@GetMapping("/request-merge")
public void requestMerge() throws Exception {
// 前三个请求会被合并
NacosClientCollapseCommand collapseCommand01 = new NacosClientCollapseCommand(
nacosClientService, "e-commerce-nacos-client1");
NacosClientCollapseCommand collapseCommand02 = new NacosClientCollapseCommand(
nacosClientService, "e-commerce-nacos-client2");
NacosClientCollapseCommand collapseCommand03 = new NacosClientCollapseCommand(
nacosClientService, "e-commerce-nacos-client3");
Future<List<ServiceInstance>> future01 = collapseCommand01.queue();
Future<List<ServiceInstance>> future02 = collapseCommand02.queue();
Future<List<ServiceInstance>> future03 = collapseCommand03.queue();
future01.get();
future02.get();
future03.get();
Thread.sleep(2000);
// 过了合并的时间窗口, 第四个请求单独发起
NacosClientCollapseCommand collapseCommand04 = new NacosClientCollapseCommand(
nacosClientService, "e-commerce-nacos-client4");
Future<List<ServiceInstance>> future04 = collapseCommand04.queue();
future04.get();
}
注解的方式实现请
service
// 使用注解实现 Hystrix 请求合并
@HystrixCollapser(
batchMethod = "findNacosClientInfos",
scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
collapserProperties = {
@HystrixProperty(name = "timerDelayInMilliseconds", value = "300")
}
)
public Future<List<ServiceInstance>> findNacosClientInfo(String serviceId) {
// 系统运行正常, 不会走这个方法
throw new RuntimeException("This method body should not be executed!");
}
@HystrixCommand
public List<List<ServiceInstance>> findNacosClientInfos(List<String> serviceIds) {
log.info("coming in find nacos client infos: [{}]", JSON.toJSONString(serviceIds));
return getNacosClientInfos(serviceIds);
}
controller
/**
* <h2>注解的方式实现请求合并</h2>
* */
@GetMapping("/request-merge-annotation")
public void requestMergeAnnotation() throws Exception {
Future<List<ServiceInstance>> future01 = nacosClientService.findNacosClientInfo(
"e-commerce-nacos-client1"
);
Future<List<ServiceInstance>> future02 = nacosClientService.findNacosClientInfo(
"e-commerce-nacos-client2"
);
Future<List<ServiceInstance>> future03 = nacosClientService.findNacosClientInfo(
"e-commerce-nacos-client3"
);
future01.get();
future02.get();
future03.get();
Thread.sleep(2000);
Future<List<ServiceInstance>> future04 = nacosClientService.findNacosClientInfo(
"e-commerce-nacos-client4"
);
future04.get();
}
OpenFeign集成Hystrix开启后备模式
◆在配置文件中开启Hystrix的熔断功能: feign.hystrix.enabled: true
◆@FeignClient 注解的fallback和fallbackFactory属性
1. fallback和fallbackFactory都是用于配置响应回退,但是不可以同时使用
2. fallbackFactory能够获取到OpenFeign调用抛出的异常
首先要在@FeignClient注解里面添加fallback属性,如图
/**
* <h1>与 Authority 服务通信的 Feign Client 接口定义</h1>
* */
@FeignClient(
contextId = "AuthorityFeignClient", value = "e-commerce-authority-center",
fallback = AuthorityFeignClientFallback.class)
public interface AuthorityFeignClient {
/**
* <h2>通过 OpenFeign 访问 Authority 获取 Token</h2>
* */
@RequestMapping(value = "/ecommerce-authority-center/authority/token",
method = RequestMethod.POST,
consumes = "application/json", produces = "application/json")
JwtToken getTokenByFeign(@RequestBody UsernameAndPassword usernameAndPassword);
}
fallback 的使用
添加后备实现
/**
* <h1>AuthorityFeignClient 后备 fallback</h1>
* */
@Slf4j
@Component
public class AuthorityFeignClientFallback implements AuthorityFeignClient {
@Override
public JwtToken getTokenByFeign(UsernameAndPassword usernameAndPassword) {
log.info("authority feign client get token by feign request error " +
"(Hystrix Fallback): [{}]", JSON.toJSONString(usernameAndPassword));
return new JwtToken("qinyi");
}
}
在接口/ecommerce-authority-center/authority/token访问不到的时候,就会返回后备结果
fallbackFactory的使用
/**
* <h1>OpenFeign 集成 Hystrix 的另一种模式</h1>
* */
@Slf4j
@Component
public class AuthorityFeignClientFallbackFactory
implements FallbackFactory<AuthorityFeignClient> {
@Override
public AuthorityFeignClient create(Throwable throwable) {
log.warn("authority feign client get token by feign request error " +
"(Hystrix FallbackFactory): [{}]", throwable.getMessage(), throwable);
return new AuthorityFeignClient() {
@Override
public JwtToken getTokenByFeign(UsernameAndPassword usernameAndPassword) {
return new JwtToken("qinyi-factory");
}
};
}
}
使用Hystrix监控面板监测客户端容错
◆Hystrix Dashboard是一个单独的应用,用来实时的对Hystrix的[使用情况」进行实时的监控
创建微服务e-commerce-hystrix-dashboard
添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>e-commerce-springcloud</artifactId>
<groupId>com.taluohui.ecommerce</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>e-commerce-hystrix-dashboard</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 模块名及描述信息 -->
<name>e-commerce-hystrix-dashboard</name>
<description>Hystrix Dashboard</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- spring cloud alibaba nacos discovery 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
<!--
SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
-->
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
添加配置文件
server:
port: 9999
servlet:
context-path: /ecommerce-hystrix-dashboard
spring:
application:
name: e-commerce-hystrix-dashboard
cloud:
nacos:
# 服务注册发现
discovery:
enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
server-addr: 127.0.0.1:8848
# server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850 # Nacos 服务器地址
namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6
metadata:
management:
context-path: ${server.servlet.context-path}/actuator
hystrix:
dashboard:
proxy-stream-allow-list: "127.0.0.1"
# 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
添加启动项
/**
* <h1>hystrix dashboard 入口</h1>
* 127.0.0.1:9999/ecommerce-hystrix-dashboard/hystrix/
* http://127.0.0.1:8000/ecommerce-nacos-client/actuator/hystrix.stream
* */
@EnableDiscoveryClient
@SpringBootApplication
@EnableHystrixDashboard // 开启 Hystrix Dashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
访问127.0.0.1:9999/ecommerce-hystrix-dashboard/hystrix/,就可以进入监控界面