Feign

Feign is Netflix’s open source, load-balancing, declarative Http client that uses Feign to call apis as if they were native methods, eliminating the need to constantly parse/encapsulate JSON data when calling target services. Feign is working to make it easier to write HTTP clients in Java.

Why use Feign

In our microservice environment, service discovery is implemented using NACOS and load balancing is implemented using ribbon. However, the following problems exist in inter-service invocation under the existing technology system, which is why we need to use Feign:

  1. Poor code readability
  2. Complex urls are difficult to maintain
  3. It is difficult to respond to changes in requirements and is painful during rapid iterations
  4. Different programming experiences

Feign implements HTTP calls

  1. And rely on

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    Copy the code
  2. Write notes

    In starting the class add @ EnableFeignClients (basePackages = “com. Samir. Contentcenter. Feignclient”) annotation (basePackages feign interface package path).

  3. Write the configuration

  4. Rewrite the code

    1. Write the FeIGN interface

      package com.samir.contentcenter.feignclient;
      
      import com.samir.contentcenter.domian.dto.user.UserDTO;
      import org.springframework.cloud.openfeign.FeignClient;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      
      @FeignClient(name = "user-center", path = "/users")
      public interface UserCenterClient {
      
          /**
           * http://user-center/users/{id}
           * @param id
           * @return* /
          @GetMapping(value = "/{id}")
          UserDTO findById(@RequestParam("id") Integer id);
      }
      Copy the code
    2. Modify the original code call

      package com.samir.contentcenter.service.content.impl;
      
      import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import com.samir.contentcenter.domian.dto.content.ShareDTO;
      import com.samir.contentcenter.domian.dto.user.UserDTO;
      import com.samir.contentcenter.domian.entity.content.Share;
      import com.samir.contentcenter.dao.content.ShareDao;
      import com.samir.contentcenter.feignclient.UserCenterClient;
      import com.samir.contentcenter.service.content.ShareService;
      import org.springframework.beans.BeanUtils;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.cloud.client.ServiceInstance;
      import org.springframework.cloud.client.discovery.DiscoveryClient;
      import org.springframework.stereotype.Service;
      import org.springframework.web.client.RestTemplate;
      
      import java.util.List;
      import java.util.concurrent.ThreadLocalRandom;
      import java.util.stream.Collectors;
      
      /** * Auto created by codeAppend plugin */
      @Service
      public class ShareServiceImpl extends ServiceImpl<ShareDao.Share> implements ShareService {
      
          @Autowired
          private RestTemplate restTemplate;
      
          @Autowired
          private DiscoveryClient discoveryClient;
      
          @Autowired
          private UserCenterClient userCenterClient;
      
          @Override
          public ShareDTO findById(Integer id) {
              // Get share details
              Share share = baseMapper.selectById(id);
              // Get the publisher ID
              Integer userId = share.getUserId();
      
      // List
                
                  instances = discoveryClient.getInstances("user-center");
                
      // List
                
                  urls = instances.stream().map(instance -> instance.getUri().toString() + "/users/{id}").collect(Collectors.toList());
                
      //
      // // random algorithm
      // int i = ThreadLocalRandom.current().nextInt(urls.size());
      // // Remotely invoke the user center service interface
      // UserDTO userDTO = restTemplate.getForObject(urls.get(i), UserDTO.class, userId);
      
              // Call the user-centric interface with feign
              UserDTO userDTO = userCenterClient.findById(userId);
      
              // Message assembly
              ShareDTO shareDTO = ShareDTO.builder()
                      .wxNickname(userDTO.getWxNickname())
                      .build();
              BeanUtils.copyProperties(share, shareDTO);
              returnshareDTO; }}Copy the code

The composition of Feign

interface role The default value
Feign.Builder The entrance to Feign Feign.Builder
Client What does Feign use underneath to sample requests With Ribbo: LoadBalancerFeignClient; If not: feign.client.default
Contract Contract, annotation support SpringMvcContract
Encoder Encoder that converts an object into the body of an HTTP request message SpringEncoder
Decoder Decoder that converts the body of the response message into an object ResponseEntityDecoder
Logger Log manager Slf4jLogger
RequestInterceptor Used to add common logic for each request There is no

Custom Feign log level

level Print the content
NONE (default) No logs are recorded
BASIC Only the request method, URL, response status code, and execution time are recorded
HEADERS Record BASIC level based on request and response headers
FULL Document the header, body, and metadata of the request and response (Suitable for development environments)

Fine-grained Configuration (Log level)

Java code approach

  1. Write Feign configuration classes

    package com.samir.contentcenter.configuration;
    
    import feign.Logger;
    import org.springframework.context.annotation.Bean;
    // If the @configuration annotation is added, you need to avoid the problem of the parent and child above, otherwise it will be global; No writing is the best implementation
    public class UserCenterFeignConfigurantion {
        @Bean
        public Logger.Level level(a) {
            returnLogger.Level.FULL; }}Copy the code
  2. Introduce configuration classes in the Feign client

    package com.samir.contentcenter.feignclient;
    
    import com.samir.contentcenter.configuration.UserCenterFeignConfigurantion;
    import com.samir.contentcenter.domian.dto.user.UserDTO;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    @FeignClient(name = "user-center", path = "/users", configuration = UserCenterFeignConfigurantion.class)
    public interface UserCenterClient {
    
        /**
         * http://user-center/users/{id}
         * @param id
         * @return* /
        @GetMapping(value = "/{id}")
        UserDTO findById(@RequestParam("id") Integer id);
    
    }
    Copy the code
  3. Set the log level of the Feign client to DEBUG in the configuration file

    logging:
      level:
        com.samir.contentcenter.feignclient.UserCenterClient: debug
    Copy the code

Configuring Attribute Mode

feign.client.config.. LoggerLevel: indicates the log level

feign:
  client:
    config:
      user-center:
        loggerLevel: full
Copy the code

Global configuration

Java code approach

  • Method 1: Make parent-child context ComponentSacn overlap (strongly not recommended)

  • @enableFeignClients (defaultConfiguration= XXX.class)

    package com.samir.contentcenter;
    
    import com.samir.contentcenter.configuration.GlobalFeignConfiguration;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    @MapperScan("com.samir.contentcenter.dao")
    @EnableDiscoveryClient
    // Just set the feign configuration in the launcher class
    @EnableFeignClients(basePackages = "com.samir.contentcenter.feignclient", defaultConfiguration = GlobalFeignConfiguration.class)
    public class ContentCenterApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ContentCenterApplication.class, args);
        }
    
        @Bean
        @LoadBalanced
        RestTemplate restTemplate(a) {
            return newRestTemplate(); }}Copy the code

Configuring Attribute Mode

Feign. Client. Config. Default. LoggerLevel: log level

feign:
  client:
    config:
      default: Default is the global configuration
        loggerLevel: full
Copy the code

Supported configuration items

Code way

Configuration items role
Feign.Builder The entrance to Feign
Client What does the Feign layer use to request
Contract Contract, annotation support
Encoder Encoder for converting an object into the body of an HTTP request message
Decoder Decoder for converting the body of the response message into an object
Logger Log manager

Attribute way

Use the * * feign. Client. Config.. Property **. The following attributes:

  • ConnectTimeout: 5000
  • ReadTimeout: 5000
  • LoggerLevel: Full # Log level
  • ErrorDecoder: com. Example. SimpleErrorDecoder # error decoder
  • Retryer: com. Example. # SimpleRetryer retry strategy
  • RequestInterceptors: com. Example. FooRequestInterceptor # interceptors
  • Whether decode404: false # 404 error code decoding (see processing logic feign. SynchronousMethodHandler# executeAndDecode)
  • Encoder: com. Example. # SimpleEncoder encoder
  • Decoder: com. Example. SimpleDecoder # decoder
    • Contract: com. Example. # SimpleContract contract

Configuration best Practices

Ribbon configuration vs Feign configuration

way Particle size Ribbon Feign
Code way local @RibbonClient(name = “user-center”, configuration = xxx.class); XXX. Class must use @Configuration and cannot overlap parent and child contexts @feignClient (name = “user-center”, path = “/users”, configuration = xxx.class); xxx.classnon@Configuration must be used, if not overlapping parent and child contexts
Code way global @ RibbonClients (defaultConfiguration) @ EnableFeignClients (defaultConfiguration)
Attribute way local The full path. Ribbon. NFLoadBalancerRuleClassName = rules feign.client.config.. LoggerLevel: indicates the log level
Attribute way global Feign. Client. Config. Default. LoggerLevel: log level

Feign code vs. attribute

Configuration mode advantages disadvantages
Code configuration Code based, more flexible Pay attention to parent-child context issues; Online changes need to be repackaged for distribution
The configuration properties Easy-to-use; Simple and intuitive configuration; Online modification does not need to be repackaged and published (configuration center); Higher priority Extreme scenarios are less flexible than code configuration

Priority: global code configuration < global property configuration < fine-grained code configuration < fine-grained property configuration

The best implementation

  • Use attribute configuration as much as possible, and consider code configuration when attribute mode is not possible.
  • Try to keep the same microservice simple, do not mix the two ways, increase the complexity of the location code. Simple is beautiful.

The succession of Feign

In the case of the User-Center service, the interface provided by The User-Center service is basically the same as the Feign client interface used by Content-Center to call the User-Center service, so we can consider separating the interface and managing it in a separate MAVEN API module. However, it is not recommended to use it in this way, and many enterprises are using it now, so they need to decide whether to adapt to inheritance according to their own situation.

Multi-parameter request construction

  • GET

    • Methods a

      @GetMapping(value = "/find")
      UserDTO find(@RequestParam("id") Integer id, @RequestParam("name") String name);
      Copy the code
    • Way 2

      @GetMapping(value = "/find")
      UserDTO find(@StringQueryMap User user);
      Copy the code
    • Method 3 (Not recommended)

      @GetMapping(value = "/find")
      UserDTO find(@RequestParam Map<String, Object> map);
      Copy the code
  • POST

    • Methods a

      @PostMapping(value = "/find")
      UserDTO find(@RequestBody User user);
      Copy the code
    • Method 2 (Recommended)

      @PostMapping(value = "/find", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
      UserDTO find(User user);
      Copy the code

Feign is used outside the Ribbon

Use feign to invoke a service that is not registered in the registry, for example, www.baidu.com

package com.samir.contentcenter.feignclient;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "baidu", url = "http://www.baidu.com") // Do this with the URL attribute
public interface TestBaiduFeignClient {
    @GetMapping("")
    public String index(a);
}
Copy the code

RestTemplate vs Feign

Rule: Use Feign instead of RestTemplate; But when Feign really can’t solve a problem, consider RestTemplate.

The Angle RestTemplate Feign
Readability and maintainability general excellent
Development experience poor excellent
performance Very good Medium (about 50% of the RestTemplate)
flexibility excellent Medium (built-in features for most scenarios)

Feign performance optimization

Configuring connection pooling for FEign [improves performance by 15% or so]

The underlying feign uses UrlConnection requests by default and has no connection pooling. Feign supports Apache httpClient and OkHTTP. These two HTTP requests support connection pooling, so we need to integrate one of them into our project configuration.

  • httpclient

    1. And rely on

      <dependency>
          <groupId>io.github.openfeign</groupId>
          <artifactId>feign-httpclient</artifactId>
      </dependency>
      Copy the code
    2. Write the configuration

      feign:
        httpclient:
          enabled: true # let Feign use Apache HttpClient for requests instead of the default URlConnection
          Configure the optimal connection pool size from the results of the pressure test
          max-connections: 200 # feign maximum number of connections
          max-connections-per-route: 50 # feign Specifies the maximum number of connections for a single path
      Copy the code
  • okhttp

    1. And rely on

      <dependency>
          <groupId>io.github.openfeign</groupId>
          <artifactId>feign-okhttp</artifactId>
          <version>10.4.0</version>
      </dependency>
      Copy the code
    2. Write the configuration

      feign:
        okhttp:
          enabled: true Let Feign use okHTTP for requests instead of using the default URlConnection
        httpclient:
        	Configure the optimal connection pool size from the results of the pressure test
          max-connections: 200 # feign maximum number of connections
          max-connections-per-route: 50 # feign Specifies the maximum number of connections for a single path
      Copy the code

The level of logging

If the production environment requires logs, you are advised to set the log level to Basic