Spring Cloud之Hystrix服务容错

Spring Cloud之Hystrix服务容错

  • Hystrix的概述
  • Hystrix的使用
    • 相关依赖
    • Eureka注册中心
    • 服务提供者
    • 服务消费者
    • 执行测试
  • @HystrixCommand详解
    • 服务降级
    • 异常处理
    • 命名与分组
    • Hystrix缓存
      • 缓存清除
      • 请求合并
  • 在Ribbon与Feign中使用Hystrix
    • 在ribbon中使用断路器
    • 在Feign中使用断路器
  • Hystrix Dashboard
    • 监控单个Hystrix实例

Hystrix的概述

由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。

Spring Cloud Hystrix在Hystrix(由Netflix开发的开源软件)的基础上进行封装,提供了服务熔断,服务降级,线程隔离等功能,通过这些功能可以提供服务的容错率。

Hystrix组件,实现了断路器模式。较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值(Hystric 是5秒20次) 断路器将会被打开。断路打开后,可用避免连锁故障,fallback方法可以直接返回一个固定值。

Hystrix的使用

相关依赖

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                 <version>Hoxton.SR9</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </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-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>

Eureka注册中心

@EnableEurekaServer
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
server:
  port: 8080

spring:
  application:
    name: Eureka-Server

  security:
    user:
      name: admin
      password: admin123

eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
    serviceUrl:
      defaultZone: http://admin:admin123@localhost:8080/eureka/

Security安全配置,放行

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 方案一
        http.csrf().disable(); // 关闭csrf
        http.authorizeRequests().anyRequest().authenticated().and().httpBasic(); // 开启认证

        // 方案二
        //	http.csrf().ignoringAntMatchers("/eureka/**");
        //	super.configure(http);
    }
}

服务提供者

server:
  port: ${PORT:8081}
  
spring:
  application:
    name: Server-Produce
    
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: true
    fetch-registry: true
    serviceUrl:
      defaultZone: http://admin:admin123@localhost:8080/eureka/
@RestController
public class TestController {
    
    @GetMapping("/test")
    public String hello() {
        return "hello world";
    }
}
@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

服务消费者

server:
  port: 8083
  
spring:
  application:
    name: Server-Consumer
    
eureka:
  client:
    serviceUrl:
      defaultZone: http://admin:admin123@localhost:8080/eureka/
@SpringCloudApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
@Service
public class TestService {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand
    public String test() {
        return restTemplate.getForObject("http://Server-Produce/test/", String.class);
    }

    public String testError() {
        return "服务暂时不可用,请稍后再试";
    }
}
@RestController
public class TestController {
    
    @Autowired
    private TestService testService;

    @RequestMapping("test/{id}")
    public String test(){
        return testService.test();
    }
}

添加@EnableHystrix或者@EnableCircuitBreaker注解。这两个注解是等价的

@EnableDiscoveryClient
@SpringBootApplication
@EnableCircuitBreaker
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

@EnableHystrix源码

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableCircuitBreaker
public @interface EnableHystrix {
}

@EnableCircuitBreaker、@EnableDiscoveryClient和@SpringBootApplication三个注解的组合可以使用@SpringCloudApplication来代替

@SpringCloudApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

@EnableHystrix注解源码

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}

执行测试

在这里插入图片描述
在这里插入图片描述

@HystrixCommand详解

@HystrixCommand注解还包含许多别的属性功能

服务降级

如果方法抛出异常,使用@HystrixCommand注解指定服务降级方法

@Service
public class TestService {
    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "testError")
    public String test() {
        return restTemplate.getForObject("http://Server-Produce/test/", String.class);
    }

    public String testError() {
        return "服务暂时不可用,请稍后再试";
    }
}

在这里插入图片描述
在这里插入图片描述

异常处理

使用@HystrixCommand注解标注的方法,除了HystrixBadRequestException异常外,其他异常都会触发服务降级。如果想指定某个异常不触发服务降级,可以使用@HystrixCommand注解的ignoreExceptions属性进行忽略。

@Service
public class TestService {
    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "testError",ignoreExceptions = {ArrayIndexOutOfBoundsException.class})
    public String test() {
        String[] strArray=new  String[]{"1","2"};
        System.out.println(strArray[2]);
        return restTemplate.getForObject("http://Server-Produce/test/", String.class);
    }

    public String testError() {
        return "服务暂时不可用,请稍后再试";
    }
}
java.lang.ArrayIndexOutOfBoundsException: 2
	at com.example.demo.service.TestService.test(TestService.java:21) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_201]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]

对于方法抛出的异常信息,可以在服务降级的方法中使用Throwable对象获取

@Service
public class TestService {
    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "testError")
    public String test(){
        String[] strArray=new  String[]{"1","2"};
        try {
            System.out.println(strArray[2]);
        } catch (Exception e) {
            throw new ArrayIndexOutOfBoundsException("数组越界异常");
        }
        return restTemplate.getForObject("http://Server-Produce/test/", String.class);
    }

    public String testError(Throwable e) {
        System.out.println(e.toString());
        return "服务暂时不可用,请稍后再试";
    }
}

java.lang.ArrayIndexOutOfBoundsException: 数组越界异常

命名与分组

指定@HystrixCommand注解的commandKey、groupKey以及threadPoolKey属性可以设置命令名称、分组以及线程池划分

@Service
public class TestService {
    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "testError", commandKey = "test", groupKey = "testGroup", threadPoolKey = "testThread")
    public String test() {
        return restTemplate.getForObject("http://Server-Produce/test/", String.class);
    }

    public String testError(Throwable e) {
        return "服务暂时不可用,请稍后再试";
    }
}

通过设置命令组,Hystrix会根据组来组织和统计命令的告警、仪表盘等信息。默认情况下,Hystrix命令通过组名来划分线程池,即组名相同的命令放到同一个线程池里,如果通过threadPoolKey设置了线程池名称,则按照线程池名称划分。

INFO 23288 --- [ix-testThread-2] cn.ybzy.demo.service.TestService     : 执行test()....

INFO 23288 --- [rix-testGroup-1] cn.ybzy.demo.service.TestService     : 执行test()....

Hystrix缓存

多次调用同一个方法

@RestController
public class TestController {

    @Autowired
    private TestService testService;

    @RequestMapping("test")
    public String test() {
        for (int i = 0; i < 5; i++) {
            testService.test();
        }
        return testService.test();
    }
}
    INFO 23288 --- [ix-testThread-9] cn.ybzy.demo.service.TestService     : 执行test()....
    INFO 23288 --- [x-testThread-10] cn.ybzy.demo.service.TestService     : 执行test()....
    INFO 23288 --- [x-testThread-10] cn.ybzy.demo.service.TestService     : 执行test()....
    INFO 23288 --- [x-testThread-10] cn.ybzy.demo.service.TestService     : 执行test()....
    INFO 23288 --- [x-testThread-10] cn.ybzy.demo.service.TestService     : 执行test()....
    INFO 23288 --- [x-testThread-10] cn.ybzy.demo.service.TestService     : 执行test()....
开启缓存可以让方法只被调用一次,其余调用直接从缓存里获取。

使用@CacheResult注解即可在Hystrix中开启缓存

Hystrix会将返回的对象进行缓存,缓存的key默认为方法的所有参数,这里只有一个id参数,所以缓存的key为用户id。
@Service
public class TestService {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private RestTemplate restTemplate;

    @CacheResult
    @HystrixCommand(fallbackMethod = "testError", commandKey = "test", groupKey = "testGroup", threadPoolKey = "testThread")
    public String test(Long id) {
        log.info("执行test({})....",id);
        return restTemplate.getForObject("http://Server-Produce/test/", String.class);
    }

    public String testError(Long id,Throwable e) {
        log.info("请求参数ID值:{}",id);
        return "服务暂时不可用,请稍后再试";
    }
}

请求缓存不可用。需要初始化 HystrixRequestContext

java.lang.IllegalStateException: Request caching is not available. Maybe you need to initialize the HystrixRequestContext?
	at com.netflix.hystrix.HystrixRequestCache.get(HystrixRequestCache.java:104) ~[hystrix-core-1.5.18.jar:1.5.18]
	at com.netflix.hystrix.AbstractCommand$7.call(AbstractCommand.java:478) ~[hystrix-core-1.5.18.jar:1.5.18]
	at com.netflix.hystrix.AbstractCommand$7.call(AbstractCommand.java:454) ~[hystrix-core-1.5.18.jar:1.5.18]
	at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46) ~[rxjava-1.3.8.jar:1.3.8]
	at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35) ~[rxjava-1.3.8.jar:1.3.8]
@Component
@WebFilter(filterName = "hystrixRequestContextServletFilter", urlPatterns = "/*", asyncSupported = true)
public class HystrixRequestContextServletFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        filterChain.doFilter(servletRequest, servletResponse);
        context.close();
    }

    @Override
    public void destroy() {
    }
}
INFO 22740 --- [ix-testThread-1] cn.ybzy.demo.service.TestService     : 执行test(1)....

断点发现每次请求都需要为HystrixRequestContext进行初始化,在同一次请求中的第一次调用后会对结果进行缓存,缓存的生命周期在当前请求之内!

设定key值

可以明确的指定缓存的key值

指定key的值有两种方式

通过@CacheKey注解指定
    @CacheResult
    @HystrixCommand(fallbackMethod = "testError", commandKey = "test", groupKey = "testGroup", threadPoolKey = "testThread")
    public String test(@CacheKey("id") Long id) {
        log.info("执行test({})....",id);
        return restTemplate.getForObject("http://Server-Produce/test/", String.class);
    }

指定参数对象内部属性为key值

    @CacheResult
    @HystrixCommand(fallbackMethod = "testError", commandKey = "test", groupKey = "testGroup", threadPoolKey = "testThread")
    public String test(@CacheKey("id") User user) {
        return restTemplate.getForObject("http://Server-Produce/test/", String.class);
    }

通过方法来指定,方法的返回值必须是String类型

    public String getCacheKey(Long id) {
        return String.valueOf(id);
    }

    @CacheResult(cacheKeyMethod = "getCacheKey")
    @HystrixCommand(fallbackMethod = "testError", commandKey = "test", groupKey = "testGroup", threadPoolKey = "testThread")
    public String test(Long id) {
        return restTemplate.getForObject("http://Server-Produce/test/", String.class);
    }

缓存清除

@CacheRemove的commandKey属性和getUser里定义的一致

    @CacheResult
    @HystrixCommand(fallbackMethod = "testError", commandKey = "updateUser", groupKey = "testGroup", threadPoolKey = "testThread")
    public String test(@CacheKey("id") Long id) {
        return restTemplate.getForObject("http://Server-Produce/test/", String.class);
    }

    @CacheRemove(commandKey = "updateUser")
    @HystrixCommand
    public void updateUser(@CacheKey("id") User user) {
        this.restTemplate.put("http://Server-Produce/updateUser/", user);
    }

请求合并

请求合并就是将多个单个请求合并成一个请求,去调用服务提供者,从而降低服务提供者负载的,一种应对高并发的解决办法。

Hystrix中提供了一个@HystrixCollapser注解,该注解可以将处于一个很短的时间段(默认10 毫秒)内对同一依赖服务的多个请求进行整合并以批量方式发起请求。

服务提供者

    @GetMapping("testRequestMerge/{id}")
    public User get(@PathVariable long id) {
        return new User(Long.valueOf(id), "user" + id, "123456");
    }

    @GetMapping("testRequestMerge")
    public List<User> testRequestMerge(String ids) {
        log.info("批量获取用户信息");
        List<User> list = new ArrayList<>();
        for (String id : ids.split(",")) {
            list.add(new User(Long.valueOf(id), "user" + id, "123456"));
        }
        return list;
    }

服务消费者

    @HystrixCollapser(batchMethod = "findUserBatch", scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,collapserProperties = {
            @HystrixProperty(name = "maxRequestsInBatch",value = "5"),
            @HystrixProperty(name = "timerDelayInMilliseconds", value = "100")
    })
    public Future<User> findUser(Long id) {
        log.info("获取单个用户信息");
        return new AsyncResult<User>() {
            @Override
            public User invoke() {
                return restTemplate.getForObject("http://Server-Produce/testRequestMerge/{id}", User.class, id);
            }
        };
    }

    @HystrixCommand
    public List<User> findUserBatch(List<Long> ids) {
        log.info("批量获取用户信息,ids: " + ids);
        User[] users = restTemplate.getForObject("http://Server-Produce/testRequestMerge?ids={1}", User[].class, StringUtils.join(ids, ","));
        return Arrays.asList(users);
    }
batchMethod属性指定批量处理的方法

timerDelayInMilliseconds: 设置多长时间之内算一次批处理,默认为10ms

maxRequestsInBatch 设置批量处理的最大请求数量,默认值为Integer.MAX_VALUE

测试方法

    @GetMapping("testRequestMerge")
    public void testRequerstMerge() throws ExecutionException, InterruptedException {
        Future<User> f1 = testService.findUser(1L);
        Future<User> f2 = testService.findUser(2L);
        Future<User> f3 = testService.findUser(3L);
        f1.get();
        f2.get();
        f3.get();
        Thread.sleep(200);
        Future<User> f4 = testService.findUser(4L);
        f4.get();
    }

测试方法中对findUser方法进行了4次的调用,最后一次调用(f4)之前先让线程等待200毫秒(大于timerDelayInMilliseconds中定义的100毫秒),所以预期是前三次调用会被合并,而最后一次调用不会被合并进去。

  INFO 23312 --- [-TestService-10] com.example.demo.service.TestService     : 批量获取用户信息,ids: [1, 3, 2]
  INFO 23312 --- [-TestService-10] com.example.demo.service.TestService     : 批量获取用户信息,ids: [4]

控制台并没有打印出findUser方法中的获取单个用户信息的日志,实际上findUser方法并不会被调用,所以上面的代码可以简化为:

    @HystrixCollapser(batchMethod = "findUserBatch", scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,collapserProperties = {
            @HystrixProperty(name = "maxRequestsInBatch",value = "5"),
            @HystrixProperty(name = "timerDelayInMilliseconds", value = "100")
    })
    public Future<User> findUser(Long id) {
        return null;
    }

    @HystrixCommand
    public List<User> findUserBatch(List<Long> ids) {
        log.info("批量获取用户信息,ids: " + ids);
        User[] users = restTemplate.getForObject("http://Server-Produce/testRequestMerge?ids={1}", User[].class, StringUtils.join(ids, ","));
        return Arrays.asList(users);
    }

在Ribbon与Feign中使用Hystrix

在ribbon中使用断路器

在方法上加上@HystrixCommand注解。该注解对该方法创建了熔断器的功能,并指定了fallbackMethod熔断方法,熔断方法直接返回了一个字符串。

@Service
public class HelloService {

	@Autowired
	private RestTemplate restTemplate;

	//服务id
	String serviceId = "eureka-client1";

    @HystrixCommand(fallbackMethod = "error")
    public String test(String name) {

		String forObject = restTemplate.getForObject("http://" + serviceId + "/test", String.class);
		System.out.println(forObject);
		return  forObject ;
	}

    public String error(String name) {
        return "调用:"+serviceId +"服务失败!";
    }
}

在Feign中使用断路器

Feign是自带断路器的,需要在配置文件中配置打开

feign:
  hystrix:
    enabled: true

在接口的注解中加上fallback的指定类

@FeignClient(value = "service-hi",fallback=TestControllerError .class)

@Component
@FeignClient(value = "eureka-client1") //指定远程调用的服务名
public interface TestController {
    //远程调用test接口
    @GetMapping("/test")//用GetMapping标识远程调用的http的方法类型
    public String test();
}

TestControllerError需要实现TestController接口,并注入到Ioc容器中

public class TestControllerError implements TestController{

	public String test() {
		return "sorry";
	}
}

Hystrix Dashboard

Hystrix提供了Hystrix Dashboard来实时监控Hystrix的运行情况,通过Hystrix Dashboard反馈的实时信息,可以帮助快速发现系统中存在的问题,从而及时地采取应对措施。

监控单个Hystrix实例

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                 <version>Hoxton.SR8</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
spring:
  application:
    name: Hystrix-Dashboard
server:
  port: 9000

在入口类上加入注解@EnableHystrixDashboard来启用Hystrix Dashboard的功能。

@SpringBootApplication
@EnableHystrixDashboard
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

启动项目访问http://localhost:9002/hystrix
Spring Cloud之Hystrix服务容错_第1张图片

你可能感兴趣的:(#,Spring,Cloud,spring,java,spring,boot,spring,cloud)