Introduction
The full name of OpenFeign is Spring Cloud OpenFeign. It is a declarative service call and load balancing component officially launched by Spring. It appears to replace Feign that has entered the maintenance state. Spring Cloud openfeign enhances Feign to support Spring MVC annotations, and also integrates Ribbon and Nacos, making Feign more convenient to use. Feign’s use of http to call methods remotely is like calling local methods, and it doesn’t feel like remote methods. Its use is the same as writing the control class directly, exposing the interface to provide calls, we only need to write the call interface + @FeignClient annotation, when using this API, we only need to define the method, and call this method at that time. This kind of call between services is very convenient to use, and the experience is better.
How to realize the interface call?
In the usual springboot project, how is this kind of rest service called? Usually, Httpclient, Okhttp, HttpURLConnection, RestTemplate are used, among which RestTemplate is the most common. RestTemplate was used in the nacos configuration center before.
SpringCloud integrates OpenFeign
Just use an example to simply use OpenFeign to call between services, and learn about the functions of Feign components through examples.
Introducing dependencies
Using OpenFeign components requires the introduction of client dependencies
<!--OpenFeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> Copy Code
Write call interface
When calling services remotely through OpenFeign, it is more convenient than RestTemplate, just like writing a controller interface. You need to write the @FeignClient annotation, which configures the microservice name and @RequestMapping(“/api/store”) of rest, or you can write the complete path when declaring and calling pai. The simple correspondence is shown in the figure below
code show as below:
package com.lyd.demo.feign; import com.lyd.demo.feign.config.FeignOkhttpConfig; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import java.util.Map; /** * @author: lyd * @description: Remotely call the service-store service * @Date: 2022/9/24 * introduce: * name / value : the name of the microservice to call * path: the path above the control class --- @RequestMapping("/api/store") */ @FeignClient(name = "service-store", path = "/api/store") public interface StoreFeignService { // Declare the rest to call @GetMapping("/{id}") Map<String, Object> getStoreNum(@PathVariable String id); } /** * @RestController * @RequestMapping("/api/store") * public class StoreController { * @Value("${server.port}") * private String currentPort; * @GetMapping("/{id}") * public Map<String, Object> getStoreNum(@PathVariable String id) throws InterruptedException { * Map<String, Object> map = new HashMap<>(); * map.put("port", currentPort); * map.put("num", 10); * return map; * } * } */ Copy Code
Annotation @EnableFeignClients needs to be written in the startup class
@Autowired private StoreFeignService storeFeignService; // call directly in the business storeFeignService.getStoreNum(uid); Copy Code
OpenFeign custom configuration
Feign provides a lot of extension mechanisms, allowing users to use it more flexibly. feign.Logger.Level: modify the log level, including four different levels: NONE, BASIC, HEADERS, FULL feign.codec.Decoder: the parser for the response result, Parse the results of http remote calls, for example, parse json strings into java objects feign.codec.Encoder: Request parameter encoding, encode request parameters, so that it is convenient to send through http request feign.Contract< /strong>: Supported annotation format, the default is SpringMVC annotation feign. Retryer: Failure retry mechanism, retry mechanism for request failure, default is not, but Ribbon retry will be used
Log Configuration
You can display the required logs by configuring Feign’s log level.
1), define the configuration class
Define a feign configuration file and hand it over to spring management. Feign’s log level defaults to NONE at the beginning, and does not display any logs. You can return the log level by defining a bean
package com.lyd.demo.feign.config; import feign. Logger; import feign. Retryer; import org.springframework.context.annotation.Bean; import java.util.concurrent.TimeUnit; /** * @author: lyd * @description: feign configuration file - log * @Date: 2022/9/24 */ @Configuration public class FeignConfig { @Bean public Logger.Level feignLoggerLevel() { return Logger.Level.BASIC; } } Copy Code
There are four log levels:
- NONE [best performance, suitable for production]: Do not record any logs (default value).
- BASIC [Suitable for production environment tracking issues]: Only record the request method, URL, response status code, and execution time.
- HEADERS: On the basis of recording the BASIC level, record the headers of the request and response.
- FULL [Comparatively suitable for positioning problems in development and test environments]: Record the header, body and metadata of requests and responses.
2), configuration file setting level
The default level of springboot is info, which is relatively high and needs to be configured in the configuration file. If the level is only configured under loggin.level, it is a global configuration, so we can specify the package and specify the log level under which package.
logging: level: com.lyd.demo.feign:debug Copy Code
3), configuration domain
Global configuration: Add the @Configuration annotation to the feign configuration class and throw it directly to spring for management to achieve global configuration. Partial configuration: ①. Partial configuration can be specified by specifying the configuration file in the feign client, just add the specified configuration class after the annotation
@FeignClient(name = "service-store", path = "/api/store", configuration = FeignConfig.class) Copy Code
②. Local configuration can also be specified directly through the yml configuration file.
feign: client: config: service-goods: FULL # Specify which service and give it a type. Copy Code
Configuration timeout
Configure the timeout period directly through yml
feign: client: config: default: # Here default is the global configuration, if it is to write the service name, it is the configuration for a microservice connectTimeout: 2000 readTimeout: 2000 Copy Code
Add a Thread.sleep(5000) to the store service, and you can see the timeout exception SocketTimeoutException.
Retry mechanism configuration
Create a retryer by adding beans (retry period (50 milliseconds), maximum retry period (2000 milliseconds), maximum number of attempts 3 times), feign does not use a linear retry mechanism but uses an index level (multiplication) retry mechanism each retry time current retry time *= 1.5
@Bean public Retryer retryer() { return new Retryer. Default(50, TimeUnit. SECONDS. toMillis(2), 3); } Copy Code
Let’s take a look at the default constructor to understand the meaning of the parameters more clearly.
public Default(long period, long maxPeriod, int maxAttempts) { this. period = period; this.maxPeriod = maxPeriod; this.maxAttempts = maxAttempts; this.attempt = 1; } Copy Code
As shown in the figure, it will retry until an exception is reported at the end. Not only that, but you can also configure contract settings, add interceptors, and more. . .
Feign usage optimization
The bottom layer of Feign initiates http requests and relies on other frameworks. Its underlying client implementation includes:
- URLConnection: default implementation, does not support connection pooling
- Apache HttpClient: support connection pool
- OKHttp: support connection pool
This time using OkHttp
Import dependencies
<!--okHttp--> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency> Copy Code
Set configuration class
package com.lyd.demo.feign.config; import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; /** * @author: lyd * @description: Configuration of OkHttpFeign * @Date: 2022/9/24 */ @Configuration @ConditionalOnClass({OkHttpClient. class}) @ConditionalOnProperty({"feign.okhttp.enabled"}) public class FeignOkhttpConfig { @Bean public okhttp3. OkHttpClient okHttpClient(OkhttpProperties okhttpProperties) { return new okhttp3.OkHttpClient.Builder() //Set connection timeout .connectTimeout(okhttpProperties.getConnectTimeout(), TimeUnit.MILLISECONDS) //Set read timeout .readTimeout(okhttpProperties.getReadTimeout(), TimeUnit.MILLISECONDS) // Whether to automatically reconnect .retryOnConnectionFailure(true) .connectionPool(new ConnectionPool()) .addInterceptor(new OkHttpLogInterceptor()) //Construct the OkHttpClient object .build(); } } Copy Code
yml configuration
feign: client: config: default: connectTimeout: 2000 readTimeout: 2000 httpclient: enabled: false okhttp: enabled: true connectTimeout: 4000 readTimeout: 3000 Copy Code
Get timeout by class
package com.lyd.demo.feign.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * @author: lyd * @description: configuration parameters * @Date: 2022/9/24 */ @Data @Component @ConfigurationProperties(prefix = "feign.okhttp") public class OkhttpProperties { private Long connectTimeout; private Long readTimeout; } Copy Code
Interceptor
The code of business requirements can be configured in the interceptor.
package com.lyd.demo.feign.config; import lombok.extern.slf4j.Slf4j; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; import java.io.IOException; /** * @author: lyd * @description: Interceptor * @Date: 2022/9/24 */ @Slf4j public class OkHttpLogInterceptor implements Interceptor { @Override public Response intercept(Interceptor. Chain chain) throws IOException { //This chain contains request and response, so you can get anything you want from here Request request = chain. request(); long t1 = System.nanoTime();//The time when the request was initiated log.info(String.format("Send request %s on %s%n%s", request.url(), chain.connection(), request.headers())); Response response = chain. proceed(request); long t2 = System.nanoTime();//Time to receive the response //Note that you cannot directly use response.body().string() to output logs here //Because after response.body().string(), the stream in the response will be closed and the program will report an error. We need to create a new response for the application layer to process ResponseBody responseBody = response. peekBody(1024 * 1024); log.info(String.format("received response: [%s] %n returns json:【%s】%.1fms%n%s", response.request().url(), responseBody. string(), (t2 - t1) / 1e6d, response. headers())); return response; } } Copy Code
Import configuration
@FeignClient(name = "service-store", path = "/api/store", configuration = FeignOkhttpConfig.class) Copy Code
operation result: