一、什么叫熔断
在微服务环境中,设想以下场景:某一个业务中需要调用S服务,而S服务需要调用A服务,以此类推,依次调用B服务和C服务。当系统环境产生异常,C服务无法正常运行导致宕机。进而会影响到调动链之上的服务执行。这样会造成服务链上的服务发生雪崩性的错误,进而导致整个系统不能使用。为了避免这样的情况发生,当某一个服务发生了异常状态后,架构内应该能够及时自动的终止服务调动,而当服务恢复后,再恢复对服务的访问。这样的容错机制就称为熔断。需要注意的是,熔断机制并是要保障一个报错的接口能够正常执行,而是要让有可能报错的接口,快速返回可预期的失败场景,并快速释放资源,避免调用链上的服务全面崩溃。
二、熔断器的运行机制
断路器很好理解, 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN).
三、如何创建熔断工程(使用restTemplate)
- 首先在中台(s工程,只负责调用其他服务,不进行其他操作)引入熔断机制:
<!-- 熔断机制 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.8.RELEASE</version>
</dependency>
- 在yml中引入:
hystrix:
command:
cslcp:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 1000
default:
execution:
timeout:
#开启熔断超时
enabled: true
isolation:
thread:
#熔断超时的秒数,方法调用总时长,熔断超时要比resttemplate超时大。低于r几倍,比如r重试三次每个100秒,
#则熔断需要设置小于300秒
timeoutInMilliseconds: 5000
- 中台(s工程)yml全如下图:
熔断的超时时间配置:
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 6000
5. 创建s工程的service层、实现类、controller层、config类
service层:
package com.zhisen.cslcps.service;
import java.util.Map;
import org.springframework.stereotype.Service;
@Service
public interface HystrixService {
Map<String, Object> cslcp1_test();
Map<String, Object> cslcp1_test1(Map<String, Object> param);
Map<String, Object> cslcp1_test2(Map<String, Object> param);
}
实现类:
package com.zhisen.cslcps.service.impl;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.zhisen.cslcps.service.HystrixService;
@Service
public class hystrixServiceImpl implements HystrixService {
@Autowired
RestTemplate restTemplate;
@Override
// 指定出现错误调用的fallback方法
@HystrixCommand(fallbackMethod="contextCslcp1_test")
public Map<String, Object> cslcp1_test() {
// TODO Auto-generated method stub
// 简化版本无参数传递方式
// 创建s工程返回值
Map<String, Object> result = new HashMap<String, Object>();
// 准备访问URL
String urlString = "http://CSLCP/test";
// 开始调用远程接口
Map<String, Object> cs1Map = restTemplate.getForObject(urlString, Map.class);
System.out.println("cslcp01:" + cs1Map);
result.put("s01", "success");
result.put("c01", cs1Map);
return result;
}
@Override
// 触发降维的是异常
//@HystrixCommand(fallbackMethod="contextCslcp1_test") //在新版本不要用重试
//指定此次操作最多能接受的响应时间,如果超出预期,直接进行熔断操作
@HystrixCommand(fallbackMethod="contextCslcp1_test",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000")
})
public Map<String, Object> cslcp1_test1(Map<String, Object> param) {
// 创建s工程返回值
Map<String, Object> result = new HashMap<String, Object>();
// 准备访问URL
String urlString = "http://CSLCP/test1?name={name}&age={age}";
// 开始调用远程接口
Map<String, Object> cs1Map = restTemplate.getForObject(urlString, Map.class, param);
result.put("s01", "success");
result.put("c01", cs1Map);
return result;
}
// 出错误代替被服务侧的方法,返回值必须相同
public Map<String, Object> contextCslcp1_test(Map<String, Object> param) {
Map<String, Object> result = new HashMap<String, Object>();
System.out.println("contextCslcp1_test执行中。。。。。。");
System.out.println(param);
result.put("error", param);
return result;
}
@Override
public Map<String, Object> cslcp1_test2(Map<String, Object> param) {
// TODO Auto-generated method stub
String urlString2 = "http://CSLCP/test2";
Map<String, Object> cs2Map = restTemplate.postForObject(urlString2, null, Map.class, param);
return cs2Map;
}
}
controller层:
package com.zhisen.cslcps.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.zhisen.cslcps.service.HystrixService;
@RestController
public class HystrixControlles {
@Autowired
private HystrixService hystrixService;
@GetMapping("/hystrix1")
public Map<String, Object> hystrixTest1(){
Map<String, Object> result = new HashMap<String, Object>();
try {
// Map<String, Object> ctx1 = this.hystrixService.cslcp1_test();
Map<String, Object> param = new HashMap<String, Object>();
param.put("name", "paramMap");
param.put("age", "paramAge");
Map<String, Object> ctx2 = this.hystrixService.cslcp1_test1(param);
// Map<String, Object> ctx3 = this.hystrixService.cslcp1_test2(param);
// result.put("cslcp1_test", ctx1);
result.put("cslcp1_test1", ctx2);
// result.put("cslcp1_test2", ctx3);
result.put("status", 1);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
result.put("status", 0);
}
return result;
}
}
config类:
需要微服务熔断监控的可以启用第二个方法,不需要的直接忽略。
springCloud微服务–启动网站监控工程(hystrix+actuator).
网络请求超时时间的配置
- 由于spring cloud 2020全面舍弃了ribbon,所以传统的ribbon配置已经在新版本中失效了。取而代之的是在构建restTemplate的时候来手工指定对应的超时时间,具体代码如下:
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(500);
factory.setReadTimeout(1000);
return new RestTemplate(factory);
- 其中connectTimeout是设置连接超时时间,实验可以采用向eureka中注册一个不存在的ip的服务并尝试连接该服务,就会发生超时。
- readTimout 可以将服务提供测的执行时间加长(sleep),然后调用接口就会爆发readtime异常
全部代码如下:
package com.zhisen.cslcps.config;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
@Configuration
public class AppConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
// 限定接口最长链接时间,内网设置一秒,互联网4秒数
factory.setConnectTimeout(1000);
// 限定接口最长读取时间,是均值,不等于超过几秒立即报错。调用接口一次最长多少秒。如果发生超时他会重试。单次抓取数据最大。经验告诉我们5秒是极限。
factory.setReadTimeout(19000);
return new RestTemplate(factory);
}
// 配置可监控
@Bean
public ServletRegistrationBean hystrixMetricsStremServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
}
- 在中台主类启动熔断机制
package com.zhisen.cslcps;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.retry.annotation.EnableRetry;
@SpringBootApplication
@EnableDiscoveryClient
//@EnableFeignClients
//启动熔断机制
@EnableHystrix
public class CslcpsApplication {
public static void main(String[] args) {
SpringApplication.run(CslcpsApplication.class, args);
}
}
- 在服务类yml中添加prefer-id 方便我们去手工制造一些bug(设置异常场景)
- 关闭A01服务 两个fallback函数都会调用
- 将第二接口设置为暂停5秒,当调用后发现超时,处罚熔断机制
- 设置远程接口发生异常,处罚熔断机制
- 工程类的接口正常构建,如果不会的可以直接点击下面的链接
查看cslcp-1就可以啦SpringCloud微服务–使用使用restTemplate实现服务间调用传参
.
- 用postmain调用中台controller中的接口,我们就可以直接观看熔断机制的整个流程了。
五、如何调用重试(当调用某个接口失败时,再次发送请求尝试)
中台引入依赖:
<!-- 重试 -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.1</version>
</dependency>
主类上面加上注解:@EnableRetry
-----与@EnableHystrix注解添加方式一样
实现类的方法上面加上注解:
// 如果发生异常。则可以尝试三次,发生固定类型错误。
@Retryable(value = RestClientException.class, maxAttempts = 3)
如果你先实现熔断可以直接使用:@Recover
放到fallback方法上面
因为@HystrixCommand(fallbackMethod="方法名称")
在新版本好像不太支持和Retryable
一起用。
除此之外你还可以为整个类设置默认熔断方法
@DefaultProperties(defaultFallback = "fallBack")
在类中添加fallBack方法,然后覆盖默认的超时时间
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
你还可以为每一个服务设置不同的超时时间
ystrix:
commend:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
user-service:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000
queryById:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
六、熔断机制开闭控制(断路器)
向开篇讲述熔断机制解决问题时候提起过,当服务重复失败,没有达到我们期待的阈值时,我们需要为了保护调用方和被调用方两个服务正常进行,我们需要让接口直接去调用fallback,这时熔断处于打开状态,所有的请求都会进入fallback函数,当过了窗口期后,我们则需要进行尝试,再次判断是否达到阈值,这时我们的程序处于半开状态,当发现达到了期待值,则进入关闭状态,程序正常执行,当发现依然没有达到期待值,则继续等待下一个窗口期,以此类推。
如果你是使用restTemplate则需要在调用服务的yml中引入如下:
hystrix:
command:
#cslcp:
#execution:
# timeout:
# enabled: true
#isolation:
# thread:
# timeoutInMilliseconds: 1000
default:
execution:
timeout:
#开启熔断超时
enabled: true
isolation:
thread:
#熔断超时的秒数,方法调用总时长,熔断超时要比resttemplate超时大。低于r几倍,比如r重试三次每个100秒,
#则熔断需要设置小于300秒
timeoutInMilliseconds: 1000
metrics:
rollingStats:
timeInMillliseconds: 15000 #统计窗口
circuitBreaker:
requestVolumeThreshold: 10 #窗口期内最低访问次数
errorThresholdPercentage: 20 #窗口期内最低容错比率
sleepWindowInMilliseconds: 15000 #窗口期时长
如果你使用的feign则需要在yml中添加:
feign:
#启用断路器
circuitbreaker:
enabled: true
client:
default:
connetTimeout: 5000
readTimeout: 3000
#设置指定业务时间
cslcp:
connetTimeout: 5000
readTimeout: 3000
相比之下还是fegin简单了许多。
重点是搞懂原理就简单了许多。
七、补充:幂等
在编程中.一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。
-
查询操作 查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作
-
删除操作 删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个)
-
插入动作使用唯一的uuid
制作整理不易,以上内容均为原创(参考了部分官方文档和老师整理的案例)。如要引用请附上本文链接,如有疑问可以在评论区畅所欲言,作者看到会第一时间回复,欢迎交流!