我们假如一个项目拆分为四个微服务,四个微服务各建一个模块,分别是用户注册模块、用户登录模块、用户详情浏览模块和数据库操作模块,这四个模块通过内部服务治理互相调用。如果一个模块出现故障会导致依赖它的模块也发生故障从而发生故障蔓延,进而导致整个服务的瘫痪。比如登录模块依赖于数据库模块,如果数据库模块发生故障,那么当登录模块去调用数据库模块的时候可能得不到响应,这个调用的线程被挂起,如果处于高并发的环境下,就会导致登录模块也崩溃。
当一个系统划分的模块越多,这种故障发生的频率就会越高,对于这个问题,Spring Cloud中最重要的解决方案就是断路器Hystrix,那么本文我们就来看看什么是断路器Hystrix:
场景实现
在之前的文章中我们已经成功的搭建出服务注册中心、服务提供者和服务消费者三个微服务,本文依然用前面三个案例来进行。
首先我们分别启动服务注册中心,端口号为1000,然后再启动两个服务提供者,端口号分别是2000和3000,然后再启动一个服务消费者,服务消费者的端口号为4000,这几个都启动成功之后,访问http://localhost:1000/可以看到查看注册中心的服务者和消费者:
然后我们访问http://localhost:4000/hi?name=riboon这个地址,可以看到如下效果:
再一次 访问http://localhost:4000/hi?name=riboon这个地址,会看到因为负载均衡的轮训机制,调用另外一个服务,端口号发生改变:
此时我们关闭掉任意一个服务提供者,再去访问这个地址,会看到如下效果:
通过前面的学习,知道Spring Cloud中采取的默认负载均衡策略就是轮询,所以当一个服务提供者关掉之后,刷新的时候服务请求成功和请求失败是成对出现的:当服务消费者去请求那个被关掉的服务提供者的时候就会请求失败,当服务消费者去请求正常的服务提供者时就能获得期望的结果。请求失败时不能给用户展示这样一个ErrorPage,而应该是一个可控的页面。现在我们就看一下熔断器是怎么做处理的:
服务消费者中加入断路器
首先我们需要在服务消费者中引入hystrix,如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
修改服务消费者启动入口类
引入hystrix之后,我们需要在入口类上通过@EnableCircuitBreaker
开启断路器功能,如下:
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class ServiceRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRibbonApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
我们也可以将这三个注解整合到@SpringBootApplication注解中,
使用一个名为@SpringBootApplication
的注解代替这三个注解,@SpringBootApplication
注解的定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
修改Controller
然后我们创建一个HelloService类,如下:
@Service
public class HelloService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "hiError")
public String hiService(String name) {
return restTemplate.getForObject("http://SERVICE-HI/hi?name=" + name, String.class);
}
public String hiError(String name) {
return "hi," + name + ",sorry,error!";
}
}
关于HelloService类:
1.RestTemplate执行网络请求的操作我们放在HelloService中来完成。
2.error方法是一个请求失败时回调的方法。
3.在hello方法上通过@HystrixCommand注解来指定请求失败时回调的方法。
OK,最后我们将ConsumerController的逻辑修改成下面这样:
@RestController
public class HelloControler {
@Autowired
HelloService helloService;
@RequestMapping(value = "/hi")
public String hi(@RequestParam String name) {
return helloService.hiService(name);
}
}
此时我们就开启了断路器功能。
测试
我们先确认服务注册中心,两个服务提供者的实例,端口号分别是2000和3000,一个服务消费者,端口号为4000,一共四个实例都启动成功,启动成功之后,访问两次http://localhost:4000/hi?name=Hystrix,分别如下:
然后,我们像前面那样关掉一个服务提供者,此时访问http://localhost:4000/hi?name=Hystrix,结果如下
会发现,此时如果服务调用失败,就会调用我们自己指定的那个失败的回调方法。
在开发使用过程中,不仅仅是服务提供者被关闭时我们需要断路器,如果请求超时也会触发熔断请求,调用指定回调方法返回数据。