为了避免微服务中因为启动某一个服务宕机,而导致“雪崩”,使整个应用阻塞;
熔断器Hystrix使用了线程隔离和服务降级的方式,提高整体应用的容错能力。
我使用的SpringCloud版本是Hoxton.SR3
线程隔离:Hystrix使用自己的线程池,和主应用服务器线程隔离开来。每个服务都使用独立的线程池。
服务降级:优先保证核心服务可用,非核心服务不可用或弱可用。
当某个服务的线程池已满或者响应时间过长,就会响应一个友好提示,而不是无限阻塞或者直接报错。
Hystrix实现了弹性容错,当情况好转之后可以自动重连。
服务消费方:
Eureka-client包已经依赖导入了Hystrix,可以直接使用,无需再导包才怪咧
第一步:导包
上面那个是Eureka-client包中的,不一样。必须导入以下依赖才可以使用@HystrixCommand注解
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
第二步:启动类加注解
package com.company;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
//@EnableDiscoveryClient //启用Eureka客户端
//@SpringBootApplication
//@EnableCircuitBreaker //启用Hystrix熔断功能
@SpringCloudApplication //替代以上三个注解
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
第三步:
@GetMapping("/{id}")
//@HystrixCommand(defaultFallback = "getUserByIdFallBack")这里千万不要写错了,如果写成了defaultFallback则会出现异常说找不到降级方法,其实是默认降级不能有参数,单个方法使用的应该是fallbackMethod@HystrixCommand(fallbackMethod= "getUserByIdFallBack")public String getUserById(@PathVariable("id")Long id){
String url="http://user-service/user/"+id;
long start=System.currentTimeMillis();
String user = template.getForObject(url, String.class);
long end=System.currentTimeMillis();
log.debug("调用时长:{}",end-start);
return user;
}
public String getUserByIdFallBack(Long id){
return "很抱歉,服务器正忙,请稍后再试。";
}
使用配置当前类降级函数,配置成功
package com.company.controller;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/consumer")
@Slf4j
@DefaultProperties(defaultFallback = "getUserByIdFallBack")//指定默认降级函数
public class ConsumerController {
@Autowired
private RestTemplate template;
@GetMapping("/{id}")
@HystrixCommand //启用熔断降级
public String getUserById(@PathVariable("id")Long id){
String url="http://user-service/user/"+id;
long start=System.currentTimeMillis();
String user = template.getForObject(url, String.class);
long end=System.currentTimeMillis();
log.debug("调用时长:{}",end-start);
return user;
}
//注意此时的返回值没有限制,不能写参数,因为这是当前类通用的降级方法
public String getUserByIdFallBack(){
return "很抱歉,服务器正忙,请稍后再试。";
}
}
默认调用超时时间为1秒,超过一秒就会触发熔断
服务提供方:
添加休眠模仿超时
package com.company.service.impl;
import com.company.mapper.UserMapper;
import com.company.pojo.User;
import com.company.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@Service("userService")
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public User findOne(Long id) {
try {
TimeUnit.MILLISECONDS.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return userMapper.selectByPrimaryKey(id);
}
@Override
@Transactional
public void insert(User user) {
user.setUserName("test5");
user.setPassword("test5");
user.setName("ceshi5");
userMapper.insert(user);
// int i=10/0;
}
}
测试结果,调用时长超过1秒的都触发了,低于1秒的都响应成功。
Hystrix默认超时时长为1秒。
配置单个方法的超时时间
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000") //设置3秒后超时,value的单位是毫秒
})
/** * Copyright 2012 Netflix, Inc. * * 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 * * http://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 com.netflix.hystrix; import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forBoolean; import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forInteger; import static com.netflix.hystrix.strategy.properties.HystrixPropertiesChainedProperty.forString; import java.util.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.netflix.hystrix.strategy.properties.HystrixDynamicProperty; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; import com.netflix.hystrix.strategy.properties.HystrixProperty; import com.netflix.hystrix.util.HystrixRollingNumber; import com.netflix.hystrix.util.HystrixRollingPercentile; /** * Properties for instances of {@link HystrixCommand}. * <p> * Default implementation of methods uses Archaius (https://github.com/Netflix/archaius) */ public abstract class HystrixCommandProperties { private static final Logger logger = LoggerFactory.getLogger(HystrixCommandProperties.class); /* defaults */ /* package */ static final Integer default_metricsRollingStatisticalWindow = 10000;// default => statisticalWindow: 10000 = 10 seconds (and default of 10 buckets so each bucket is 1 second) private static final Integer default_metricsRollingStatisticalWindowBuckets = 10;// default => statisticalWindowBuckets: 10 = 10 buckets in a 10 second window so each bucket is 1 second private static final Integer default_circuitBreakerRequestVolumeThreshold = 20;// default => statisticalWindowVolumeThreshold: 20 requests in 10 seconds must occur before statistics matter private static final Integer default_circuitBreakerSleepWindowInMilliseconds = 5000;// default => sleepWindow: 5000 = 5 seconds that we will sleep before trying again after tripping the circuit private static final Integer default_circuitBreakerErrorThresholdPercentage = 50;// default => errorThresholdPercentage = 50 = if 50%+ of requests in 10 seconds are failures or latent then we will trip the circuit private static final Boolean default_circuitBreakerForceOpen = false;// default => forceCircuitOpen = false (we want to allow traffic) /* package */ static final Boolean default_circuitBreakerForceClosed = false;// default => ignoreErrors = false private static final Integer default_executionTimeoutInMilliseconds = 1000; // default => executionTimeoutInMilliseconds: 1000 = 1 second
在HystrixCommandProperties定义了很多属性,按住Ctrl再点击要配置的属性。就可以找到要配置的key,例:
this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);
也可以全局配置修改Hystrix的默认超时时间
在消费方的application.yaml文件中配置
前缀+key
hystrix:
command:
default:
execution.isolation.thread.timeoutInMilliseconds
如下:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
也可以在yaml文件中配置单个方法
单个服务hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
user-service://配置单个服务id,实测无效
execution:
isolation:
thread:
timeoutInMilliseconds: 1000配置单个方法
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
getUserById://配置单个方法,实测有效;并且如果有多个Controller中有重复的方法名,则多个同名方法均有效。
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
另外有一点需要注意,Ribbon重试超时时长设置需要小于Hystrix降级超时时长,否则还没来得及重试就已经服务降级了。
org.springframework.cloud.netflix.zuul.filters.route.support.AbstractRibbonCommand
protected static int getHystrixTimeout(IClientConfig config, String commandKey) { int ribbonTimeout = getRibbonTimeout(config, commandKey); DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance(); int defaultHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 0).get(); int commandHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command." + commandKey + ".execution.isolation.thread.timeoutInMilliseconds", 0).get(); int hystrixTimeout; if (commandHystrixTimeout > 0) { hystrixTimeout = commandHystrixTimeout; } else if (defaultHystrixTimeout > 0) { hystrixTimeout = defaultHystrixTimeout; } else { hystrixTimeout = ribbonTimeout; } if (hystrixTimeout < ribbonTimeout) { LOGGER.warn("The Hystrix timeout of " + hystrixTimeout + "ms for the command " + commandKey + " is set lower than the combination of the Ribbon read and connect timeout, " + ribbonTimeout + "ms."); } return hystrixTimeout; } protected static int getRibbonTimeout(IClientConfig config, String commandKey) { int ribbonTimeout; if (config == null) { ribbonTimeout = 2000; } else { int ribbonReadTimeout = getTimeout(config, commandKey, "ReadTimeout", Keys.ReadTimeout, 1000); int ribbonConnectTimeout = getTimeout(config, commandKey, "ConnectTimeout", Keys.ConnectTimeout, 1000); int maxAutoRetries = getTimeout(config, commandKey, "MaxAutoRetries", Keys.MaxAutoRetries, 0); int maxAutoRetriesNextServer = getTimeout(config, commandKey, "MaxAutoRetriesNextServer", Keys.MaxAutoRetriesNextServer, 1); ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1); } return ribbonTimeout; }
Ribbon超时计算公式:(连接时长+读取时长)*(当前实例重试次数[默认是0]+1)*(切换实例重试次数[默认是1]+1)