关于Feign的超时记录:

在Spring Cloud微服务架构中,大部分公司都是利用Open Feign进行服务间的调用,而比较简单的业务使用默认配置是不会有多大问题的,但是如果是业务比较复杂,服务要进行比较繁杂的业务计算,那后台很有可能会出现Read Timeout这个异常。

1、关于hystrix的熔断超时

如果Feign开启了熔断,必须要重新设置熔断超时的时间,因为默认的熔断超时时间太短了,只有1秒,这容易导致业务服务的调用还没完成然后超时就被熔断了。

如何配置熔断超时:

#Feign如何开启熔断
feign.hystrix.enabled=true
#是否开始超时熔断,如果为false,则熔断机制只在服务不可用时开启(spring-cloud-starter-openfeign中的HystrixCommandProperties默认为true)
hystrix.command.default.execution.timeout.enabled=true
#设置超时熔断时间(spring-cloud-starter-openfeign中的HystrixCommandProperties默认为1000毫秒)
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000

注意:关于hystrix在application.properties配置是没提示的,但是HystrixCommandProperties是会获取的。
/

/ 构造函数
protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix) {
        
    // .... 省略很多其他配置
  
    // propertyPrefix:hystrix,key:default
    this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);
}
 
// 具体获取属性的方法
private static HystrixProperty<String> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, String builderOverrideValue, String defaultValue) {
   return HystrixPropertiesChainedProperty.forString().add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue).add(propertyPrefix + ".command.default." + instanceProperty, defaultValue).build();
}

2、Feign局部设置超时间

spring-cloud-dependencies Dalston版本之后,默认Feign对Hystrix的支持默认是关闭的,需要手动开启。

feign.hystrix.enabled=true

开启hystrix,可以选择关闭熔断或超时。
2.1关闭熔断:

全局关闭熔断:

hystrix.command.default.circuitBreaker.enabled: false

局部关闭熔断:

hystrix.command..circuitBreaker.enabled: false
2.2设置超时:

全局设置超时:

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 1000

局部设置超时:

hystrix.command..execution.isolation.thread.timeoutInMilliseconds: 1000
2.3设置局部方法超时成功案例:

需要对下图两个方法局部设置超时时间为10秒,

方法一、配置如下:

禁用Hystrix超时

hystrix.threadpool.default.coreSize = 10
 hystrix.command.default.fallback.enabled = true
 hystrix.command.default.execution.timeout.enabled= true
 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 3000
 hystrix.command.MemberStaffFeign#getExcelDataByDoctor(String,Integer,Integer).execution.isolation.thread.timeoutInMilliseconds=10000
 hystrix.command.MemberStaffFeign#getExcelDataByTeam(String,Integer,Integer).execution.isolation.thread.timeoutInMilliseconds=10000

@SpringBootApplication启动类中添加@EnableCircuitBreaker注解

@EnableFeignClients
 @EnableEurekaClient
 @EnableDiscoveryClient
 @EnableApolloConfig
 @EnableCircuitBreaker
 @SpringBootApplication
 public class BusinessSystemApplication {
 public static void main(String[] args) {
 ConfigurableApplicationContext app = new SpringApplicationBuilder(BusinessSystemApplication.class).run(args);
 System.out.println(app.getEnvironment().getProperty(“spring.application.name”) + “服务启动完毕…”);
 }
 }


2.4关闭超时

全局关闭:

hystrix.command.default.execution.timeout.enabled: false

局部关闭:

hystrix.command..execution.timeout.enabled: false


3、关于Ribbon超时。
Feign调用默认是使用Ribbon进行负载均衡的,所以我们还需要了解关于Ribbon的超时。

①、Feign的调用链路
看一下Feign的请求是否有使用Ribbon的超时时间,而且是如何读取Ribbon的超时时间的?

(1)、org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute

(2)、com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)

(3)、org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory#create

创建Client,这里会判断对应ClientName的链接Client是否创建过,如果创建过复用之前的Client;
如果不存在则创建一个并且放入cache缓存。
public FeignLoadBalancer create(String clientName) {
 FeignLoadBalancer client = this.cache.get(clientName);
 if(client != null) {
 return client;
 }
 IClientConfig config = this.factory.getClientConfig(clientName);
 ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
 ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
 // 判断是否有重试
 client = loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
 loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
 this.cache.put(clientName, client);
 return client;
 }


(4)、com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)

负载均衡器抽象类

(5)、org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute

Feign的负载均衡器实现类。到这里我们可以看到,连接超时和读超时的配置都在这里:
如果application.properties配置文件中的超时时间不为空,则使用配置的超时时间。
如果为空则使用默认值,而从FeignLoadBalancer的构造函数可以看到,默认值也是取的RibbonProperties的默认超时时间。

public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
 throws IOException {
 Request.Options options;
 // 设置超时时间。,如果orride的配置为空,则用默认值
 if (configOverride != null) {
 RibbonProperties override = RibbonProperties.from(configOverride);
 options = new Request.Options(
 override.connectTimeout(this.connectTimeout),
 override.readTimeout(this.readTimeout));
 }
 else {
 options = new Request.Options(this.connectTimeout, this.readTimeout);
 }
 // 发起请求
 Response response = request.client().execute(request.toRequest(), options);
 return new RibbonResponse(request.getUri(), response);
 }// 构造函数
 public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector) {
 super(lb, clientConfig);
 this.setRetryHandler(RetryHandler.DEFAULT);
 this.clientConfig = clientConfig;
 this.ribbon = RibbonProperties.from(clientConfig);
 RibbonProperties ribbon = this.ribbon;
 this.connectTimeout = ribbon.getConnectTimeout();
 this.readTimeout = ribbon.getReadTimeout();
 this.serverIntrospector = serverIntrospector;
 }


②、Ribbon的默认超时时间
在RibbonClientConfiguration中:

public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
 public static final int DEFAULT_READ_TIMEOUT = 1000;


③、如何自定义Ribbon超时时间
首先,RibbonProperties的超时时间的读取的源码如下:

public Integer getConnectTimeout() {
 return (Integer)this.get(CommonClientConfigKey.ConnectTimeout);
 }public Integer getReadTimeout() {
 return (Integer)this.get(CommonClientConfigKey.ReadTimeout);
 }


然后,可以在CommonClientConfigKey中可以看到两个超时时间的名称:

// ConnectTimeout:
 public static final IClientConfigKey ConnectTimeout = new CommonClientConfigKey(“ConnectTimeout”) {};// ReadTimeout:
 public static final IClientConfigKey ReadTimeout = new CommonClientConfigKey(“ReadTimeout”) {};


然后,在IClientConfig的默认实现类:DefaultClientConfigImpl中,可以发现Ribbon配置的前缀

public static final String DEFAULT_PROPERTY_NAME_SPACE = “ribbon”;


所以,最后Ribbon该这么配置超时时间:

ribbon.ConnectTimeout=5000
 ribbon.ReadTimeout=5000


总结
1.如何配置好Hystrix和Ribbon的超时时间呢?

其实是有套路的,因为Feign的请求:其实是Hystrix+Ribbon。Hystrix在最外层,然后再到Ribbon,最后里面的是http请求。所以说。Hystrix的熔断时间必须大于Ribbon的 ( ConnectTimeout + ReadTimeout )。而如果Ribbon开启了重试机制,还需要乘以对应的重试次数,保证在Ribbon里的请求还没结束时,Hystrix的熔断时间不会超时。

我的其他文章

亲身分享 一次 字节跳动 真实面试经历和面试题

第一种

直接生成base64为编码,代码简洁

引入pom

<dependency>
	<groupId>gui.ava</groupId>
	<artifactId>html2image</artifactId>
	<version>0.9</version>
</dependency>

完整示例

public static void main(String[] args) {
	HtmlImageGenerator imageGenerator = new HtmlImageGenerator();
 
	String htmlstr = "<span>test</span>";
 
	/*html代码元素会有限制,项input标签元素不能用,img没测试,应该可以*/
	imageGenerator.loadHtml(htmlstr);
	BufferedImage bufferedImage = getGrayPicture(imageGenerator.getBufferedImage());
	ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
	try {
		ImageIO.write(bufferedImage, "jpg", outputStream);
		String base64Img = Base64.encodeBase64String(outputStream.toByteArray());
		String res = "data:image/jpg;base64," + base64Img.toString();
		System.out.println(res);
	} catch (IOException e) {
		e.printStackTrace();
	}finally {
		if(outputStream != null){
			try {
				outputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
 
 
public static BufferedImage getGrayPicture(BufferedImage originalImage)
{
	BufferedImage grayPicture;
	int imageWidth = originalImage.getWidth();
	int imageHeight = originalImage.getHeight();
 
	grayPicture = new BufferedImage(imageWidth, imageHeight,
			BufferedImage.TYPE_INT_RGB);
	ColorConvertOp cco = new ColorConvertOp(ColorSpace
			.getInstance(ColorSpace.CS_GRAY), null);
	cco.filter(originalImage, grayPicture);
	return grayPicture;
}

第二种

第二种可以读取本地html文件,并且把jpg写到指定位置

引入

<dependency>
	<groupId>com.github.xuwei-k</groupId>
	<artifactId>html2image</artifactId>
	<version>0.1.0</version>
</dependency>
public static void main(String[] args) {
	System.out.println(html2Img(getHtmlContent("C:/aaa.html","utf-8"),"C:/123.jpg"));
}
 
 
/**
 *
 * @Description 读取HTML文件,获取字符内容
 * @param filePath
 * @param charset
 * @return
 */
public static String getHtmlContent(String filePath, String charset){
 
	String line = null;
	StringBuilder sb = new StringBuilder();
	BufferedReader reader = null;
 
	try {
		reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(filePath)),charset));
		while ((line = reader.readLine()) != null) {
			sb.append(line + "\n");
		}
	} catch (IOException e) {
		e.printStackTrace();
		throw new RuntimeException("读取HTML文件,获取字符内容异常");
	} finally {
		try {
			reader.close();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("关闭流异常");
		}
	}
	return sb.toString();
}
 
 
/**
 *
 * @Description HTML转Image
 * @param htmText HTML文本字符串
 * @return 希望生成的Image Location
 */
public static String html2Img(String htmText, String saveImageLocation){
 
	HtmlImageGenerator imageGenerator = new HtmlImageGenerator();
	try {
		imageGenerator.loadHtml(htmText);
		Thread.sleep(100);
		imageGenerator.getBufferedImage();
		Thread.sleep(200);
		imageGenerator.saveAsImage(saveImageLocation);
		//imageGenerator.saveAsHtmlWithMap("hello-world.html", saveImageLocation);
		//不需要转换位图的,下面三行可以不要
		BufferedImage sourceImg = ImageIO.read(new File(saveImageLocation));
		sourceImg = transform_Gray24BitMap(sourceImg);
		ImageIO.write(sourceImg, "BMP", new File(saveImageLocation));
	} catch (Exception e) {
		e.printStackTrace();
		throw new RuntimeException("将HTML文件转换成图片异常");
	}
	return saveImageLocation;
}
 
/**
 *
 * @Description 转换成24位图的BMP
 * @param image
 * @return
 */
public static BufferedImage transform_Gray24BitMap(BufferedImage image){
	int h = image.getHeight();
	int w = image.getWidth();
	int[] pixels = new int[w * h]; // 定义数组,用来存储图片的像素
	int gray;
	PixelGrabber pg = new PixelGrabber(image, 0, 0, w, h, pixels, 0, w);
	try {
		pg.grabPixels(); // 读取像素值
	} catch (InterruptedException e) {
		throw new RuntimeException("转换成24位图的BMP时,处理像素值异常");
	}
 
	for (int j = 0; j < h; j++){ // 扫描列
		for (int i = 0; i < w; i++) { // 扫描行
			// 由红,绿,蓝值得到灰度值
			gray = (int) (((pixels[w * j + i] >> 16) & 0xff) * 0.8);
			gray += (int) (((pixels[w * j + i] >> 8) & 0xff) * 0.1);
			gray += (int) (((pixels[w * j + i]) & 0xff) * 0.1);
			pixels[w * j + i] = (255 << 24) | (gray << 16) | (gray << 8) | gray;
		}
	}
 
	MemoryImageSource s= new MemoryImageSource(w,h,pixels,0,w);
	Image img =Toolkit.getDefaultToolkit().createImage(s);
	BufferedImage buf = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);//如果要转换成别的位图,改这个常量即可
	buf.createGraphics().drawImage(img, 0, 0, null);
	return buf;
}

其他

传送门:https://store.ityao.cn/

自己做的小产品,开发技术 SpringCloud + Nacos + 支付宝支付 + Vue

感兴趣的可以相互讨论技术!

顺便说一下,国内又一款开源软件Wall,搭建特别简单,可以搭建个人照片墙和视频墙,有兴趣可以看下教程: