阅读的书籍为《Spring Cloud 微服务实战》。
在微服务架构中,服务被拆分成了若干服务单元。各个服务单元应用间通过服务注册与订阅的方式相互依赖。
由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,可能会因为网络问题或者依赖服务自身的问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断的增加,最终就会因等待出现故障的依赖方响应形成任务积压,导致自身服务的瘫痪。
在微服务架构中,若一个单元出现故障,很容易因为依赖关系引发故障的蔓延,最终导致整个系统的瘫痪,故需要断路器等一系列的服务保护机制。
使用断路器模式,可以通过断路器的故障监控,向调用方返回一个错误响应,而不是长时间的等待,不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
采用《Spring Cloud学习(3)——服务发现与消费以及客户端负载均衡Ribbon》中的几个服务:
两个Eureka Server实例,两个HELLO-WORLD-SERVICE实例,一个RIBBON-DEMO实例。
未加入断路器,关闭一个HELLO-WORLD-SERVICE实例。访问http://localhost:9000/ribbon-test。会出现以下错误:
也可以使用@SpringCloudApplication注解,来代替下面三个注解。
@EnableDiscoveryClient
@SpringBootApplication
@EnableCircuitBreaker
package com.example.ribbondemo.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
private final Logger log = Logger.getLogger(getClass());
@RequestMapping(value = "/ribbon-test", method = RequestMethod.GET)
@HystrixCommand(fallbackMethod = "ribbonTestFallback")
public String ribbonTest() {
long start = System.currentTimeMillis();
String result = restTemplate.getForEntity("http://HELLO-WORLD-SERVICE/helloworld", String.class).getBody();
long end = System.currentTimeMillis();
log.info("Spend time : " + (end - start));
return result;
}
public String ribbonTestFallback() {
return "error";
}
}
修改HELLO-WORLD-SERVICE中的/helloworld接口,模拟服务阻塞的情况。 Hystrix默认的超时时间为2000毫秒,这里采用0-3000的随机数,阻塞线程,使有一定概率触发断路器。
package com.example.helloworld.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.apache.log4j.Logger;
import java.util.List;
import java.util.Random;
@RestController
public class HelloworldController {
private final Logger log = Logger.getLogger(getClass());
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping(value = "/helloworld", method = RequestMethod.GET)
public String helloworld() throws Exception {
int sleepTime = new Random().nextInt(3000);
log.info("sleepTime : " + sleepTime);
Thread.sleep(sleepTime);
log.info("discoveryClient.getServices().size() = " + discoveryClient.getServices().size());
for (String s : discoveryClient.getServices()) {
log.info("services " + s);
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(s);
for (ServiceInstance si : serviceInstances) {
log.info(" services:" + s + ":getHost()=" + si.getHost());
log.info(" services:" + s + ":getPort()=" + si.getPort());
log.info(" services:" + s + ":getServiceId()=" + si.getServiceId());
log.info(" services:" + s + ":getUri()=" + si.getUri());
log.info(" services:" + s + ":getMetadata()=" + si.getMetadata());
}
}
return "hello world";
}
}
当访问出现error的时候,观察HELLO-WORLD-SERVICE实例的控制台,发现:
当控制台输出的Spend time大于2000时,就会返回error。即服务消费者因调用的服务超时,从而触发熔断请求,并调用回调逻辑,返回结果。