Hystrix

Hystrix(嘿死缺死)叫做断路器/熔断器,微服务系统中,整个系统出错的概率非常高,因为在微服务系统中,涉及到的模块太多了,每一个模块出错,都有可能导致整个服务出错,当所有模块都稳定运行时,整个服务才算是稳定运行
我们希望当整个系统中,某一个模块无法正常工作时,能够通过我们提前配置一些东西,来使得整个系统正常运行,即单个模块出问题,不影响整个系统。

基本用法

首先创建一个新的SpringBoot模块,然后添加依赖:

项目创建成功后,添加如下配置,将Hystrix注册到Eureka上:

spring.application.name=hystrix
server.port=3000
eureka.client.service-url.defaultZone=http://localhost:1111/eureka

然后,在项目启动类上添加如下注解,开启断路器,同时提供一个RestTemplate实例

@SpringBootApplication
@EnableCircuitBreaker
public class HystrixApplication {

    public static void main(String[] args) {
        SpringApplication.run(HystrixApplication.class, args);
    }
    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

启动类上的注解,也可以使用@SpringCloudApplication代替:

这样,提供Hystric的接口。

@Service
public class HelloService {
    @Autowired
    RestTemplate restTemplate;

    /**
     * 在这个方法中,我们将发起一个远程调用,去调用provider中提供的/hello接口
     *
     * 但是,这个调用可能会失败
     * 我们在这个方法上添加@HystrixCommand注解,配置fallbackMethod属性,这个属性表示该方法调用失败时的临时替代方法
     * @return
     */
    @HystrixCommand(fallbackMethod = "error")
    public String hello(){
        return restTemplate.getForObject("http://provider/hello", String.class);
    }

    /**
     * 注意,这个方法名字要和fallbackMethod 一致
     * 方法返回值也要和对应的方法一致
     * @return
     */
    public String error(){
        return "error";
    }

    public class HelloController{
        @Autowired
        HelloService helloService;

        @GetMapping("/hello")
        public String hello(){
            return helloService.hello();
        }
    }

}

新建一个Controller

@RestController
public class HelloController {
    @Autowired
    HelloService helloService;

    @GetMapping("/hello")
    public String hello(){
        return helloService.hello();
    }
}

演示:
开启Eureka和provider

请求命令

请求命令就是以继承类的方式来替代前面的注解方式。
我们来自定义一个HelloCommand:

public class HelloCommand extends HystrixCommand<String> {
    RestTemplate restTemplate;

    public HelloCommand(Setter setter, RestTemplate restTemplate) {
        super(setter);
        this.restTemplate = restTemplate;
    }

    @Override
    protected String run() throws Exception {
        return restTemplate.getForObject("http://provider/hello", String.class);
    }
}

调用方法:

@RestController
public class HelloController {
    @Autowired
    HelloService helloService;
    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/hello")
    public String hello(){
        return helloService.hello();
    }

    @GetMapping("/hello2")
    public void hello2() {
        HelloCommand helloCommand = new
                HelloCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("javaboy")), restTemplate);
        String execute = helloCommand.execute();//直接执行
        System.out.println(execute);
        //第二种调用方式
        HelloCommand helloCommand2 = new
                HelloCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("javaboy")), restTemplate);
        try {
            Future<String> queue = helloCommand2.queue();
            String s = queue.get();
            System.out.println(s);//先入队,后执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

注意:

  1. 一个实例只能执行一次
  2. 可以直接执行,也可以先入队,后执行

通过注解实现异步调用

首先,定义如下方法,返回Future

@HystrixCommand(fallbackMethod = "error")
public Future<String> hello2(){
    return new AsyncResult<String>() {
        @Override
        public String invoke() {
            return restTemplate.getForObject("http://provider/hello",String.class);
        }
    };
}

然后,调用该方法:

@GetMapping("/hello3")
public void hello3(){
    Future<String> hello2 = helloService.hello2();
    try {
        String s = hello2.get();
        System.out.println(s);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

[容错]请求命令方式

通过继承方式使用Hystrix,如何实现服务器容错/降级?重写继承类的getFallback方法即可:

public class HelloCommand extends HystrixCommand<String> {
    RestTemplate restTemplate;

    public HelloCommand(Setter setter, RestTemplate restTemplate) {
        super(setter);
        this.restTemplate = restTemplate;
    }

    @Override
    protected String run() throws Exception {
        return restTemplate.getForObject("http://provider/hello", String.class);
    }

    /**
     * 这个方法就是请求失败的回调
     * @return
     */
    @Override
    protected String getFallback() {
        return "error-extends";
    }
}

异常处理

就是当发起服务调用时,如果不是provider的原因导致请求调用失败,而是consumer中本身代码有问题导致的请求失败,即consumer中抛出了异常,这个时候,也会自动进行服务降级,只不过这个时候降级,我们还需要知道到底是哪里出异常了。
如下示例代码,如果 hello 方法中,执行时抛出异常,那么一样也会进行服务降级,进入到 error 方法中,在 error 方法中,我们可以获取到异常的详细信息:

public class HelloCommand extends HystrixCommand<String> {
    RestTemplate restTemplate;

    public HelloCommand(Setter setter, RestTemplate restTemplate) {
        super(setter);
        this.restTemplate = restTemplate;
    }

    @Override
    protected String run() throws Exception {
        return restTemplate.getForObject("http://provider/hello", String.class);
    }

    /**
     * 这个方法就是请求失败的回调
     * @return
     */
    @Override
    protected String getFallback() {
        return "error-extends";
    }
}

这是注解的方式,也可以通过继承的方式:

public class HelloCommand extends HystrixCommand<String> {
    RestTemplate restTemplate;

    public HelloCommand(Setter setter, RestTemplate restTemplate) {
        super(setter);
        this.restTemplate = restTemplate;
    }

    @Override
    protected String run() throws Exception {
        int i = 1 / 0;
        return restTemplate.getForObject("http://provider/hello", String.class);
    }

    /**
     * 这个方法就是请求失败的回调
     *
     * @return
     */
    @Override
    protected String getFallback() {
        return "error-extends:"+getExecutionException().getMessage();
    }
}

如果是通过继承的方式来做 Hystrix,在 getFallback 方法中,我们可以通过 getExecutionException 方法来获取执行的异常信息。
另一种可能性(作为了解)。如果抛异常了,我们希望异常直接抛出,不要服务降级,那么只需要配置忽略某一个异常即可

@HystrixCommand(fallbackMethod = "error",ignoreExceptions = ArithmeticException.class)
public String hello(){
    int i = 1 / 0;
    return restTemplate.getForObject("http://provider/hello", String.class);
}

这个配置表示当hello方法抛出ArithmeticException异常时,不要进行服务降级,直接将错误抛出。

请求缓存

请求缓存就是是consumer中调用同一个接口,如果参数相同,则可以使用之前缓存下来的数据。
首先修改provider中的hello2接口,一会用来检测缓存配置是否生效:

@GetMapping("/hello2")
public String hello2(String name){
    System.out.println(new Date()+">>>"+name);
    return "hello "+ name;
}

然后,在hystrix的请求中,添加如下注解:

@HystrixCommand(fallbackMethod = "error2")
@CacheResult //这个注解表示该方法的请求结果会被缓存起来,默认情况下,缓存的key就是方法的参数,缓存的value就是方法的返回值
public String hello3(String name){
    return restTemplate.getForObject("http://provider/hello2?name={1}",String.class,name);
}
public String error2(String name){
    return "error2"+name;
}

这个配置完成后,缓存并不会生效,一般来说,我们使用缓存,都有一个缓存声明周期这样一个概念。这里也一样,我们需要初始化HystrixRequestContext,初始化完成后,缓存开始生效,HystrixRequestContext close之后,缓存失效。

@GetMapping("/hello4")
public void hello4(){
    HystrixRequestContext ctx = HystrixRequestContext.initializeContext();
    String javaboy = helloService.hello3("javaboy");
    javaboy=helloService.hello3("javaboy");
    ctx.close();
}

在 ctx close 之前,缓存是有效的,close 之后,缓存就失效了。也就是说,访问一次 hello4 接口,provider 只会被调用一次(第二次使用的缓存),如果再次调用 hello4 接口,之前缓存的数据是失效的。

默认情况下,缓存的key就是所调用方法的参数,如果参数有多个,就是多个参数组合起来作为缓存的key。
例如如下方法:

@HystrixCommand(fallbackMethod = "error2")
@CacheResult //这个注解表示该方法的请求结果会被缓存起来,默认情况下,缓存的key就是方法的参数,缓存的value就是方法的返回值
public String hello3(String name){
    return restTemplate.getForObject("http://provider/hello2?name={1}",String.class,name);
}
public String error2(String name,String age){
    return "error2"+name;
}

此时缓存的 key 就是 name+age,但是,如果有多个参数,但是又只想使用其中一个作为缓存的 key,那么我们可以通过 @CacheKey 注解来解决。

@HystrixCommand(fallbackMethod = "error2")
@CacheResult //这个注解表示该方法的请求结果会被缓存起来,默认情况下,缓存的key就是方法的参数,缓存的value就是方法的返回值
public String hello3(@CacheKey String name,Integer age){
    return restTemplate.getForObject("http://provider/hello2?name={1}",String.class,name);
}

上面这个配置,虽然有两个参数,但是缓存时以name为准,也就是说,两次请求中,只要name一样,即使age不一样,第二次请求也可以使用第一次请求缓存的结果。
另外还有一个注解叫做@CacheRemove()。在做数据缓存时,如果有一个数据删除的方法,我们一般除了删除数据库中的数据,还希望能够顺带删除缓存中的数据,这个时候@CacheRemove()就派上用场了。
@CacheRemove()在使用时,必须指定commandKey属性,commanKey其实就是缓存方法的名字,指定了commandKey,@CacheRemove才能找到数据缓存在哪里了,进而才能成功删除掉数据。
例如如下方法定义缓存与删除缓存:

@HystrixCommand(fallbackMethod = "error2")
@CacheResult //这个注解表示该方法的请求结果会被缓存起来,默认情况下,缓存的key就是方法的参数,缓存的value就是方法的返回值
public String hello3(String name){
    return restTemplate.getForObject("http://provider/hello2?name={1}",String.class,name);
}

@HystrixCommand
@CacheRemove(commandKey = "hello3")
public String deleteUserByName(String name) {
    return null;
}

再去调用

@GetMapping("/hello4")
public void hello4(){
    HystrixRequestContext ctx = HystrixRequestContext.initializeContext();
    //第一请求完了,数据已经缓存下来了
    String javaboy = helloService.hello3("javaboy");
    //删除数据,同时缓存中的数据也会被删除
    helloService.deleteUserByName("jvaboy");
    //第二次请求时,虽然参数还是javaboy,但是缓存数据已经没了,所以这一次,provider还是会收到请求
    javaboy=helloService.hello3("javaboy");
    ctx.close();
}

运行效果

如果是继承的方式使用Hystrix,只需要重写getCacheKey方法即可

public class HelloCommand extends HystrixCommand<String> {
    RestTemplate restTemplate;
    String name;

    public HelloCommand(Setter setter, RestTemplate restTemplate,String name) {
        super(setter);
        this.name=name;
        this.restTemplate = restTemplate;
    }

    @Override
    protected String run() throws Exception {
        return restTemplate.getForObject("http://provider/hello2?name={1}", String.class,name);
    }

    @Override
    protected String getCacheKey() {
        return name;
    }

    @Override
    protected String getFallback() {
        return "error-extends:"+getExecutionException().getMessage();
    }
}

调用时候,一定记得初始化 HystrixRequestContext:

@GetMapping("/hello2")
public void hello2() {
    HystrixRequestContext ctx = HystrixRequestContext.initializeContext();
    HelloCommand helloCommand = new
            HelloCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("javaboy")), restTemplate,"javaboy");
    String execute = helloCommand.execute();//直接执行
    System.out.println(execute);
    //第二种调用方式
    HelloCommand helloCommand2 = new
            HelloCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("javaboy")), restTemplate,"javaboy");
    try {
        Future<String> queue = helloCommand2.queue();
        String s = queue.get();
        System.out.println(s);//先入队,后执行
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

请求合并

如果consumer中,频繁的调用provider中的同一个接口,在调用的时候,只是参数不一样,那么这样情况下,我们就可以将多个请求合并成一个,这样可以有效提高请求的发送效率。
首先,我们在provider中提供一个请求合并的接口:

@Service
public class UserService {
    @Autowired
    RestTemplate restTemplate;
    @GetMapping("/user/{ids}")
    public List<User> getUsersByIds(List<Integer> ids) {
        User[] users = restTemplate.getForObject("http://provider/user/{1}", User[].class, StringUtils.join(ids,
                ","));
        return Arrays.asList(users);

    }
}

接下来定义 UserBatchCommand ,相当于我们之前的 HelloCommand:

public class UserBatchCommand extends HystrixCommand<List<User>> {
    private List<Integer> ids;
    private UserService userService;
    public UserBatchCommand(List<Integer> ids, UserService userService) {
        super(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("batchCmd")).andCommandKey(HystrixCommandKey.Factory.asKey("batchKey")));
        this.ids = ids;
        this.userService = userService;
    }
    @Override
    protected List<User> run() throws Exception {
        return userService.getUsersByIds(ids);
    }

最后,定义最最关键的请求合并方法:

public class UserCollapseCommand extends HystrixCollapser<List<User>,User,Integer> {

    private UserService userService;
    private Integer id;
    public UserCollapseCommand(UserService userService, Integer id) {
        super(HystrixCollapser.Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("UserCollapseCommand")).andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(200)));
        this.userService = userService;
        this.id = id;
    }

    /**
     * 请求参数
     * @return
     */
    @Override
    public Integer getRequestArgument() {
        return id;
    }

    /**
     * 请求合并的方法
     *
     * @param collection
     * @return
     */
    @Override
    protected HystrixCommand<List<User>>
    createCommand(Collection<CollapsedRequest<User, Integer>> collection) {
        List<Integer> ids = new ArrayList<>(collection.size());
        for (CollapsedRequest<User, Integer> userIntegerCollapsedRequest :
                collection) {
            ids.add(userIntegerCollapsedRequest.getArgument());
        }
        return new UserBatchCommand(ids, userService);
    }

    /**
     * 请求结果分发
     *
     * @param users
     * @param collection
     */
    @Override
    protected void mapResponseToRequests(List<User> users,
                                         Collection<CollapsedRequest<User, Integer>> collection) {
        int count = 0;
        for (CollapsedRequest<User, Integer> request : collection) {
            request.setResponse(users.get(count++));
        }
    }
}

最后就是测试调用:

@GetMapping("/hello5")
public void hello5() throws ExecutionException, InterruptedException {
    HystrixRequestContext ctx = HystrixRequestContext.initializeContext();
    UserCollapseCommand cmd1 = new UserCollapseCommand(userService, 99);
    UserCollapseCommand cmd2 = new UserCollapseCommand(userService, 98);
    UserCollapseCommand cmd3 = new UserCollapseCommand(userService, 97);
    UserCollapseCommand cmd4 = new UserCollapseCommand(userService, 96);
    Future<User> q1 = cmd1.queue();
    Future<User> q2 = cmd2.queue();
    Future<User> q3 = cmd3.queue();
    Future<User> q4 = cmd4.queue();
    User u1 = q1.get();
    User u2 = q2.get();
    User u3 = q3.get();
    User u4 = q4.get();
    System.out.println(u1);
    System.out.println(u2);
    System.out.println(u3);
    System.out.println(u4);
    ctx.close();
}

效果图

通过注解实现请求合并

@Service
public class UserService {
    @Autowired
    RestTemplate restTemplate;

    @HystrixCollapser(batchMethod = "getUsersByIds",collapserProperties = {@HystrixProperty(name
            = "timerDelayInMilliseconds",value = "200")})
    public Future<User> getUserById(Integer id) {
        return null;
    }

    @HystrixCommand
    public List<User> getUsersByIds(List<Integer> ids) {
        User[] users = restTemplate.getForObject("http://provider/user/{1}", User[].class,
                StringUtils.join(ids,
                ","));
        return Arrays.asList(users);

    }
}

这里的核心是 @HystrixCollapser 注解。在这个注解中,指定批处理的方法即可。
测试代码如下

    @GetMapping("/hello6")
    public void hello6() throws ExecutionException, InterruptedException {
        HystrixRequestContext ctx = HystrixRequestContext.initializeContext();
        Future<User> q1 = userService.getUserById(99);
        Future<User> q2 = userService.getUserById(98);
        Future<User> q3 = userService.getUserById(97);
        User user1 = q1.get();
        User user2 = q2.get();
        User user3 = q3.get();
        System.out.println(user1);
        System.out.println(user2);
        System.out.println(user3);
        Thread.sleep(2000);
        Future<User> q4 = userService.getUserById(96);
        User user4 = q4.get();
        System.out.println(user4);
        ctx.close();
    }

posted @ 2020-08-14 00:10  柒丶月  阅读(164)  评论(0编辑  收藏  举报