说在前面
在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
解决或缓解服务雪崩的方案
一般情况对于服务依赖的保护主要有3中解决方案:
(1)熔断模式:这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
(2)隔离模式:这种模式就像对系统请求按类型划分成一个个小岛的一样,当某个小岛被火少光了,不会影响到其他的小岛。例如可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署,再或者公司最近推广的多中心。
(3)限流模式:上述的熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。
Netflix提供了一个叫Hystrix的类库,它实现了断路器模式。在微服务架构中,通常一个微服务会调用多个其他的微服务。一个相对低层级的服务失败可能造成上层应用的级联失败,服务访问量越大失败率越高。当断路打开的时候,这个调用就被终止了。打开的断路可以阻止级联失败。
启动EureKaserver工程(注册中心)和SpringCloud-Service工程(服务生产者)
可以参考:https://blog.csdn.net/u012081441/article/details/80708060
一、Feign中使用断路器
创建项目SpringCloud-Feign
pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fit</groupId>
<artifactId>SpringCloud-Feign</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</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-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RC1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
application.yml文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8888/eureka/
server:
port: 8765
spring:
application:
name: SpringCloud-Feign
feign:
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
service层
@FeignClient(value = "SpringCloud-Service",fallback = UserServiceErrorImpl.class)
public interface UserService {
@RequestMapping(value ="getUser")
public Map<String,Object> getUser();
}
UserServiceErrorImpl.java 实现UserService 接口,并注入到Ioc容器中,代码如下:
@Component
public class UserServiceErrorImpl implements UserService {
public Map<String, Object> getUser() {
// TODO Auto-generated method stub
Map<String, Object> errorMap = new HashMap<String, Object>();
errorMap.put("code", "500");
errorMap.put("message", "后台异常");
return errorMap;
}
}
FeignApp.java文件
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class FeignApp {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
SpringApplication.run(FeignApp.class, args);
}
}
验证断路器
启动SpringCloud-Feign项目,停掉SpringCloud-Service,访问http://localhost:8765/getUser,返回如下图,说明断路器起作用了:
二、在ribbon使用断路器
创建项目SpringCloud-Consumer
pom.xml文件如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fit</groupId>
<artifactId>SpringCloud-Consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot父类依赖引用 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath />
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</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-hystrix</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RC1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
application.yml
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8888/eureka/
server:
port: 8764
spring:
application:
name: SpringCloud-Consumer
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
service层
@Service
public class UserService {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "getServiceUserError")
public Map<String,Object> getServiceUser(){
@SuppressWarnings("unchecked")
Map<String,Object> user = restTemplate.getForObject("http://SpringCloud-Service/getUser", Map.class);
return user;
}
public Map<String,Object> getServiceUserError(){
// TODO Auto-generated method stub
Map<String, Object> errorMap = new HashMap<String, Object>();
errorMap.put("code", "500");
errorMap.put("message", "后台异常");
return errorMap;
}
}
controller层
@Controller
public class ConsumerUserController {
@Resource
private UserService userService;
@ResponseBody
@RequestMapping(value ="getServiceUser")
public Map<String,Object> getServiceUser(){
Map<String,Object> user = userService.getServiceUser();
return user;
}
}
SpringCloudConsumerApp.java
@EnableEurekaClient
@EnableHystrix
@SpringBootApplication
public class SpringCloudConsumerApp {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
SpringApplication.run(SpringCloudConsumerApp.class, args);
}
@Bean
@LoadBalanced // 添加负载均衡支持
RestTemplate restTemplate() {
return new RestTemplate();
}
}
验证断路器
启动SpringCloud-Consumer项目,停掉SpringCloud-Service,访问http://localhost:8764/getServiceUser,返回如下图,说明断路器起作用了: