SpringColud - Feign (service call) & gateway (Zuul)

Table of contents

1、Feign

1.1. Import dependencies

1.2, open the Feign function

1.3, Feign client

1.4, Controller class

1.5. Load balancing

1.6, Hystrix support

1.7. Request compression

1.8, log level

2. Zuul (gateway)

2.1 Introduction to Zuul

 2.2. Zuul framework

2.3. Create a project

2.4, startup class

2.5, start the test 

2.6. Add Eureka client dependencies

2.7, routing prefix

2.8, filter

2.9, custom filter

2.10, load balancing and fuse


In the previous study, we used Ribbon's load balancing function, which greatly simplifies the code for remote calls (for service names)

String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);

Is there a more elegant way to optimize these codes again?

This is the function of Feign that we will learn next.

1、Feign

Feign can hide the Rest request and pretend to be a Controller similar to SpringMVC. You don't need to splicing urls, splicing parameters, etc. by yourself, and leave everything to Feign.

1.1. Import dependencies

Transform consumer-service project

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

1.2, open the Feign function

We add annotations to the startup class and enable the Feign function

@SpringCloudApplication //包含@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker
@EnableFeignClients //开启feign客户端
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

Delete RestTemplate : feign has automatically integrated the RestTemplate of Ribbon load balancing. Therefore, there is no need to register RestTemplate here.

1.3, Feign client

Create a package, create an interface

@Component
@FeignClient(value = "user-service") //集成了ribbon,直接面向服务去调用
public interface UserClient {
    @GetMapping("/user/{id}")
    //参数里的注解不能省,省略则报错
    User getUser(@PathVariable("id") String id);
}
  • First of all, this is an interface, and Feign will help us generate implementation classes through dynamic proxies. This is very similar to the mapper of mybatis

  • @FeignClient, declaring that this is a Feign client, similar to @Mapperannotations. valueAt the same time specify the service name through the attribute

  • The definition method in the interface fully adopts SpringMVC annotations, and Feign will help us generate URLs according to the annotations, and access the results

1.4, Controller class

@RestController
@RequestMapping("/consumer2")
public class ConsumerController2 {
    @Autowired
    private UserClient userClient;

    @GetMapping("/{id}")
    public User getUser(@PathVariable("id") String id) {
        return userClient.getUser(id);
    }
}

The result is obtained normally.

1.5. Load balancing

Feign itself has integrated Ribbon dependencies and automatic configuration:

So we don't need to introduce additional dependencies, and we don't need to register RestTemplateobjects.

1.6, Hystrix support

Feign also has Hystrix integration by default:

 However, it is turned off by default. We need to enable it with the following parameters: (add configuration content to the project)

feign:
  hystrix:
    enabled: true # 开启Feign的熔断功能

However, the Fallback configuration in Feign is not as simple as in hystrix.

First of all, we need to define a class UserClientFallback to implement the UserClient just written as the processing class of fallback

@Component
public class UserClientImpl implements UserClient {
    @Override
    public User getUser(String id) {
        User user=new User();
        user.setUsername("网络拥挤,请稍后再试!");
        return  user;
    }
}

Then in UserFeignClient, specify the implementation class just written

@RestController
@RequestMapping("/consumer2")
//内置hystrix降级服务
public class ConsumerController2 {
    @Autowired
    private UserClient userClient;

    @GetMapping("/{id}")
    @HystrixCommand //使用feign内置的hystrix
    public User getUser(@PathVariable("id") String id) {
        //如果在本地服务器中报错,内置hystrix的降级服务不会执行
        if("1".equals(id)){
            throw new RuntimeException("服务崩溃了!");
        }
        return userClient.getUser(id);
    }
    //内置hystrix不用在这里写降级方法,需要实现接口,在实现类里书写
}

It should be noted that: to define a downgrade class in the feign interface, to trigger service downgrade, an exception must occur on the service provider.

Remove client custom error 

@RestController
@RequestMapping("/consumer2")
//内置hystrix降级服务
public class ConsumerController2 {
    @Autowired
    private UserClient userClient;

    @GetMapping("/{id}")
    @HystrixCommand //使用feign内置的hystrix
    public User getUser(@PathVariable("id") String id) {
        //如果在本地服务器中报错,内置hystrix的降级服务不会执行
        return userClient.getUser(id);
    }
    //内置hystrix不用在这里写降级方法,需要实现接口,在实现类里书写
}

Report an error on the server side

Service downgraded successfully 

1.7. Request compression

Spring Cloud Feign supports GZIP compression for requests and responses to reduce performance loss during communication. The compression function of requests and responses can be enabled by the following parameters

feign:
  compression:
    request:
      enabled: true # 开启请求压缩
      min-request-size: 2048 # 设置触发压缩的大小下限
    response:
      enabled: true # 开启响应压缩

At the same time, we can also set the requested data type and the lower limit of the size that triggers compression:

feign:
  compression:
    request:
      enabled: true # 开启请求压缩
      mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
      min-request-size: 2048 # 设置触发压缩的大小下限

1.8, log level

As mentioned earlier, logging.level.xx=debugthe log level is set by . However this has no effect on the Fegin client. Because @FeignClientthe annotation-modified client will create a new Fegin.Logger instance when it is proxied. We need to additionally specify the level of this log.

logging:
  level:
    cn.itssl: debug

Write a configuration class to define the log level  

@Configuration
public class FeignLogConfiguration {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

The Level specified here is FULL, and Feign supports 4 levels:  

  • NONE: Do not record any log information, which is the default value.

  • BASIC: Only log the request method, URL, response status code and execution time

  • HEADERS: On the basis of BASIC, the header information of the request and response is additionally recorded

  • FULL: Record details of all requests and responses, including header information, request body, and metadata.

Specify the configuration class in FeignClient:

@Component
@FeignClient(value = "user-service",fallback = UserClientImpl.class,configuration = FeignLogConfiguration.class) //集成了ribbon,直接面向服务去调用
public interface UserClient {
    @GetMapping("/user/{id}")
    //参数里的注解不能省,省略则报错
    User getUser(@PathVariable("id") String id);
}

2. Zuul (gateway)

2.1 Introduction to Zuul

 2.2. Zuul framework

Whether it is a request from a client (PC or mobile) or an internal call to a service. All requests for services will go through the Zuul gateway, and then the gateway will implement authentication, dynamic routing and other operations. Zuul is the unified entrance to our services.

2.3. Create a project

create a project

Add Zuul dependency:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
server:
  port: 8085
spring:
  application:
    name: zuul-service
zuul:
  routes:
    consumer-api: # 这里是路由id,随意写
      path: /consumerApi/** # 这里是映射路径
      #url: http://127.0.0.1:8081 # 映射路径对应的实际url地址
      serviceId: consumer-service
    user-api:
      path: /userApi/**
      serviceId: user-service

2.4, startup class

@SpringBootApplication
@EnableZuulProxy //开启网关功能
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class,args);
    }
}

2.5, start the test 

It can be seen that the zuul service on port 8085 is accessed, the consumer service on port 8084 is accessed, and the producer service on port 8762 is called.

In the routing rules just now, we hard-coded the service address corresponding to the path! This is obviously unreasonable if there are multiple instances of the same service. We should go to the Eureka registry to find a list of all instances corresponding to the service according to the name of the service, and then perform dynamic routing!

2.6. Add Eureka client dependencies

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
eureka:
  client:
    registry-fetch-interval-seconds: 5 #获取服务列表的周期5秒
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka,http://127.0.0.1:8762/eureka,http://127.0.0.1:8763/eureka
@SpringBootApplication
@EnableZuulProxy //开启网关功能
@EnableDiscoveryClient //开启eureka
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class,args);
    }
}

Modify the mapping configuration and obtain it through the service name

Because there is already a Eureka client, we can obtain the address information of the service from Eureka, so there is no need to specify an IP address when mapping, but to access it through the service name, and Zuul has integrated Ribbon's load balancing function.

zuul:
  routes:
    consumer-service: /consumerApi/**
    user-service: /userApi/**

Start again, this time when Zuul is acting as a proxy, it will use Ribbon for load balancing access

2.7, routing prefix

zuul:
  routes:
    consumer-service: /consumerApi/**
    user-service: /userApi/**
  prefix: /api #路由前缀

2.8, filter

One of the important functions of Zuul as a gateway is to implement request authentication. And this action is often implemented through the filter provided by Zuul.

ZuulFilter is the top-level parent class of filters. Here we look at the 4 most important methods defined in it:

  • shouldFilter: Returns a Booleanvalue to determine whether the filter needs to be executed. Return true to execute, return false not to execute.

  • run: The specific business logic of the filter.

  • filterType: Returns a string representing the type of filter. Contains the following 4 types:

    • pre: the request is executed before being routed

    • route: Called when a request is routed

    • post: called after route and errror filters

    • error: Error calling while processing request

  • filterOrder: The execution order of the filter is defined by the returned int value, the smaller the number, the higher the priority.

filter life cycle

Normal Process: 

When the request arrives, it will first pass through the pre type filter, and then reach the route type for routing. The request will reach the real service provider, execute the request, and after returning the result, it will reach the post filter. Then return the response.  

Exception process:

  • During the whole process, if the pre or route filter is abnormal, it will directly enter the error filter. After the error is processed, the request will be handed over to the POST filter and finally returned to the user.

  • If the error filter itself has an exception, it will eventually enter the POST filter and return the final result to the requesting client.

  • If the POST filter is abnormal, it will jump to the error filter, but unlike pre and route, the request will no longer reach the POST filter.

List of all built-in filters:

scenes to be used

  • Request authentication: generally placed in the pre type, if it is found that there is no access right, it will be intercepted directly

  • Exception handling: Generally, it will be processed in combination of error type and post type filter.

  • Service call duration statistics: combined use of pre and post.

2.9, custom filter

Next, let's define a filter to simulate a login verification. Basic logic: If there is an access-token parameter in the request, the request is considered valid and allowed.

@Component
public class LoginFilter extends ZuulFilter {
    //过滤器的类型
    @Override
    public String filterType() {
        return "pre";
    }

    //过滤器的执行顺序
    @Override
    public int filterOrder() {
        return 1;
    }
    //该过滤器是否生效
    @Override
    public boolean shouldFilter() {
        return true;
    }

    //登录效验逻辑
    @Override
    public Object run() throws ZuulException {
        //获取Zuul提供的上下文对象
        RequestContext requestContext = RequestContext.getCurrentContext();
        // 从上下文对象中获取请求对象
        HttpServletRequest request = requestContext.getRequest();
        // 获取token信息
        String token = request.getParameter("token");
        //判断
        if(StringUtils.isEmpty(token)){
            // 过滤该请求,不对其进行路由
            requestContext.setSendZuulResponse(false);
            // 设置响应状态码,401
            requestContext.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
            // 设置响应信息
            requestContext.setResponseBody("{\"status\":\"401\", \"text\":\"request error!\"}");
        }
        // 校验通过,把登陆信息放入上下文信息,继续向后执行
        requestContext.set("token", token);
        return null;
    }

}

When we access without parameters, a 401 error will occur

Only when the token parameter is carried, can the data be accessed normally

2.10, load balancing and fuse

By default, Zuul has integrated Ribbon load balancing and Hystix fuse mechanism. But all timeout strategies are default values. For example, the fuse timeout time is only 1S, which is easy to trigger. Therefore, it is recommended that we manually configure:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为6000ms

Guess you like

Origin blog.csdn.net/select_myname/article/details/128323049