Gateway Series 2: comparison of common Java gateway implementation schemes

What is a service gateway

Previously, we have learned about the basic springboot for building micro services, and we can also use springboot to build services. Next, let's talk about springcloud based on springboot. This spring cloud is not a specific technology. It refers to an ecosystem in microservices. For example, it includes gateway, registration center, configuration center, etc. Today, let's first learn about microservice gateways. There are many kinds of microservice gateways. This time, we use the mainstream spring cloud gateway to explain. Each microservice module is composed of several independent microservices, and each microservice module runs independently. For example, the user service provides services and functions related to user information, and the payment module provides payment related functions. Various services communicate with each other through REST API or RPC (later), and generally our microservices should achieve stateless communication. After we implement the micro service, it will also bring inconvenience in some aspects. If the web page or app needs to request to modify the delivery address and pay after shopping, in this scenario:

As shown in the figure above, there will be some problems:

  • The client needs to initiate multiple requests and request services corresponding to different domain names, which increases the communication cost and the complexity of the maintenance of the client code.
  • Service verification will be done separately in each service. If the verification and authentication logic of each service is different, it will lead to repeated verification by the client.
  • In addition, if each service adopts different protocols, it will be disastrous for the client.

Based on the above, we need an intermediate layer for the client to request the intermediate layer. As for the service that needs to be requested, the middleware requests it, and finally summarizes and returns the results to the client. This intermediate layer is the gateway.

Why use a gateway

Using a gateway has several functions:

Unified authentication

Generally, there are two kinds of authentication on the gateway: 1. Authentication of the requested client identity. 2. Access control is to determine whether there is access to a resource after confirming the user's identity. Once, in a single application, the client requests authentication and the constraints on resource permissions are relatively simple. The corresponding user and permission information can be obtained through the requested session. However, in the microservice architecture, all services are divided into a single microservice and deployed in a cluster, which will become complex, Because if you still use session, in the distributed situation, each request does not necessarily fall on the same machine, which will lead to invalid session. We need to do extra work to ensure that the sessions in the cluster are consistent. Therefore, we conduct unified processing authentication at the gateway layer:

Logging

After the client request comes in, we need to record the time dependence, source address, ip and other information of the current request, so that we can intercept and obtain it at the gateway level, and then output it to the log file for output through ELK component. The recorded content can be recorded uniformly in multiple dimensions and multiple information without recording separately in each specific service.

Request distribution and filtering

For the gateway, this request matching distribution is the most important function. In fact, nginx has the function of request forwarding and filtering. For the gateway, it can filter the requests before and after.

  • Request distribution: receive the request from the client, map the request to each subsequent microservice and request microservices. Because the microservice granularity is relatively fine, this gateway can functionally integrate each microservice and finally give it back to the client.
  • Filtering: the gateway will intercept all requests, which is equivalent to a horizontal aspect of AOP in spring. On this aspect, operations such as authentication, current limiting and authentication are carried out.

Gray Publishing

Generally, the company's Internet products iterate very fast, basically in small steps. It's basically a release iteration a week. In this case, there will be risks, such as compatibility, functional integrity, short time, bug s and eventually accidents. In this way, when we release new functions, we will release them to the designated machine and divide a small part of the traffic to observe the specific situation. Therefore, the gateway, as the entrance of the request, can just complete this function.

Common Gateway Solutions

There are several gateways commonly used, such as OpenResty, zuul, Gateway, Kong, Tyk, etc. We mainly use the framework of spring system, so we will explain the Gateway in this paper, and do not focus on the implementation of other gateways. OpenResty is a web server with nginx+lua integration, which integrates many third-party libraries and modules. Zuul actually integrated spring cloud in the early stage. At that time, due to the performance problems that may be caused by his thread model strategy, spring finally chose its own spring cloud gateway.

Environmental preparation

In this article, we use a simple case to demonstrate the use of spring cloud gateway. First, we need to use two spring boot applications. For the specific creation method, please refer to the second article of this topic.

  • spring-cloud-gateway-service1 is a micro service
  • Spring cloud gateway Wanguan gateway microservice

We created two services based on the previous topics. For the first service, we added a controller

@RequestMapping(value = {"/getUser"}, method = RequestMethod.GET)
    public String getUser() {
        Map<String ,String> user = new HashMap<>();
        user.put("name", "Zhang San");
        user.put("age", "45");
        String s = JSONObject.toJSONString(user);
        return s;
    }
Copy code

For the second gateway service, we increase pom dependency

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            spring-cloud-starter-gateway
            <version>2.0.4.RELEASE</version>
        </dependency>
Copy code

In application Add gateway route in YML

spring:
  cloud:
    gateway:
      routes:
      - predicates:
            - Path =/gateway/**  #Matching rules
        uri: http://Localhost: access address of 8099 / getuser # service 1
        filters:
            - StripRrefix: 1 #Remove prefix
 server: 
    port: 8077
 Copy code

For the meaning of the above configuration:

  • uri: target service address, configurable uri and lb: / / application service name
  • Predictions: matching conditions, which match whether to request the route according to the rules
  • filters: filter rules. This filter includes pre filter and post filter,
  • StripPrefix=1, which means removing the prefix, that is, removing 'gateway' when forwarding the target url

At this time, after starting the service, we found the service startup log: Netty started on port(s): 8077 It indicates that our service is successful, and the gateway relies on nettyserver to start several services to listen. We visit:

curl http://localhost:8077/gateway/getUser

If the configuration is correct, the result returned by the service will be returned.

Principle of spring cloud gateway

The above figure is the schematic diagram officially given by gateway, which may not be easy to understand. Let's draw a diagram to help understand: as shown in the above figure, there are several concepts to explain first:

  • Route: it is one of the components of the gateway and consists of id, uri, predicate and filter.
  • Predicate: matches the content in the http request. If the returned result is true, it will be forwarded according to the current router.
  • Filter: provides pre - and post filtering for requests.

When the client sends a request to the gateway, the gateway will decide which route to access according to a series of Predicate matching results, and then process the request according to the filter, which can be executed before and after the request is sent to the back-end service.

Routing rules

The spring cloud gateway provides a route matching mechanism, such as Path=/gateway / * * This means to match the request whose URL prefix is / gateway / through the property of Path. In fact, spring cloud gateway provides us with many rules for us to use. The use of each Predicate can be understood as: it will be forwarded only when this condition is met. If there are more than one, it will be forwarded when all conditions are met. The source code of these predictions is at org springframework. cloud. gateway. handler. In the Predict package, let's take a brief look:

Dynamic routing

There are two main ways for gateway to configure Routing: 1 Use yml configuration file, 2 Write it in the code. In both yml and code configuration, the routing configuration cannot be modified after the gateway is started. If a new service needs to be launched, the gateway needs to be offline first, and then restart the gateway after modifying the yml configuration. In this way, if there is no graceful shutdown on the gateway, there will be service interruption, which is undoubtedly unacceptable. When the gateway is started, the routing information will be loaded into memory by default. The routing information is encapsulated in the RouteDefinition object. Multiple routedefinitions are configured to form the routing system of the gateway. The attributes in the RouteDefinition correspond to the attributes configured in the above code one by one:

Then we need our dynamic way to solve this problem. Spring Cloud Gateway provides Endpoint endpoints to expose routing information. It has methods to obtain all routes, refresh routes, view a single route, delete routes and so on. The specific implementation class is org springframework. cloud. gateway. actuate. Gatewaycontrollerendpoint. If you want to access the methods in the Endpoint, you need to add the spring boot starter actor annotation and expose all endpoints in the configuration file. To write a dynamic routing implementation class, you need to implement the ApplicationEventPublisherAware interface.

/**
 * Dynamic routing service
 */
@Service
public class GoRouteServiceImpl implements ApplicationEventPublisherAware {
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    private ApplicationEventPublisher publisher;
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
    //Add route
    public String add(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }
    //Update route
    public String update(RouteDefinition definition) {
        try {
            delete(definition.getId());
        } catch (Exception e) {
            return "update fail,not find route  routeId: "+definition.getId();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            return "update route  fail";
        }
    }
    //Delete route
    public Mono<ResponseEntity<Object>> delete(String id) {
        return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
            return Mono.just(ResponseEntity.ok().build());
        })).onErrorResume((t) -> {
            return t instanceof NotFoundException;
        }, (t) -> {
            return Mono.just(ResponseEntity.notFound().build());
        });
    }
}
Copy code

Write Rest interfaces to realize dynamic routing function through these interfaces

@RestController
@RequestMapping("/changeRoute")
public class ChangeRouteController {
    @Autowired
    private GoRouteServiceImpl goRouteServiceImpl;
    //Add route
    @PostMapping("/add")
    public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
        String flag = "fail";
        try {
            RouteDefinition definition = assembleRouteDefinition(gwdefinition);
            flag = this.goRouteService.add(definition);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return flag;
    }
    //Delete route
    @DeleteMapping("/routes/{id}")
    public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
        try {
            return this.goRouteService.delete(id);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
    //Update route
    @PostMapping("/update")
    public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
        RouteDefinition definition = assembleRouteDefinition(gwdefinition);
        return this.goRouteService.update(definition);
    }
    //Convert the passed parameters into routing objects
    private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
        RouteDefinition definition = new RouteDefinition();
        definition.setId(gwdefinition.getId());
        definition.setOrder(gwdefinition.getOrder());
        //Set assertion
        List<PredicateDefinition> pdList=new ArrayList<>();
        List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
        for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
            PredicateDefinition predicate = new PredicateDefinition();
            predicate.setArgs(gpDefinition.getArgs());
            predicate.setName(gpDefinition.getName());
            pdList.add(predicate);
        }
        definition.setPredicates(pdList);
        //catalog filter
        List<FilterDefinition> filters = new ArrayList();
        List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
        for(GatewayFilterDefinition filterDefinition : gatewayFilters){
            FilterDefinition filter = new FilterDefinition();
            filter.setName(filterDefinition.getName());
            filter.setArgs(filterDefinition.getArgs());
            filters.add(filter);
        }
        definition.setFilters(filters);
        URI uri = null;
        if(gwdefinition.getUri().startsWith("http")){
            uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
        }else{
            // The following method is used when the uri is LB: / / consumer service
            uri = URI.create(gwdefinition.getUri());
        }
        definition.setUri(uri);
        return definition;
    }
}
Copy code

In fact, we rarely call rest service through API to add or delete routing information. Generally, our mainstream is to dynamically add routing by integrating the config function of nacos. Integration with nacos will be discussed later.

filter

Gateway Filter is divided into Pre Filter and Post Filter, i.e. Pre Filter and Post Filter. They are executed before the specific request is forwarded to the back-end micro service and before the result is returned to the client. There are many built-in gatewayfilters, about 19 kinds, such as:

  • AddRequestHeader GatewayFilter Factory ,
  • AddRequestParameter GatewayFilter Factory ,
  • AddResponseHeader GatewayFilter Factory

Just a few examples. It's easy to use. Let's focus on how to customize the filter:

  1. Global filter: the global filter is effective for all routes and does not need to be configured in the configuration file. It mainly implements the GlobalFilter and Ordered interfaces and registers the filter with the spring container.
@Service
@Slf4j
public class AllDefineFilter implements GlobalFilter,Ordered{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("[pre]-Enter AllDefineFilter");
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            log.info("[post]-Return Result");
        }));
    }
    @Override
    public int getOrder() {
        return 0;
    }
}
Copy code
  1. Local filter: it needs to be configured in the configuration file. If configured, the filter will take effect. It mainly implements the gatewayfilter and ordered interfaces, and registers them in the spring container through the subclass of AbstractGatewayFilterFactory. Of course, it can also directly inherit AbstractGatewayFilterFactory, write filter logic in it, and read external data from the configuration file.
@Component
@Slf4j
public class UserDefineGatewayFilter extends AbstractGatewayFilterFactory<UserDefineGatewayFilter.GpConfig>{

    public UserDefineGatewayFilter(){
        super(GpConfig.class);
    }

    @Override
    public GatewayFilter apply(GpConfig config) {
        return ((exchange, chain) -> {
            log.info("[Pre] Filter Request,name:"+config.getName());
            return chain.filter(exchange).then(Mono.fromRunnable(()->{
                log.info("[Post] Response Filter");
            }));
        });
    }

    public static class UserConfig{
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}
Copy code

This area needs attention:

  • The class name must uniformly end with GatewayFiterFactory, because by default, the name of the filter will use the prefix of the custom class. Here, name=UserDefine, that is, the name value in filters in yml.
  • In the apply method, both Pre and Post filters are included. In the then method, it is the Post-processing after the execution of the request.
  • UserConfig is a configuration class with only one attribute name. This property can be used in the ym file.
  • This class needs to be loaded into the Spring IoC container, which is implemented here using the @ Component annotation.

In fact, the whole spring cloud gateway is well integrated with spring cloud alibaba. It can be integrated with nacos and sentinel for current limiting. We will explain it separately in this later stage.

Author: I'm brother Daming

Link: https://juejin.cn/post/6923100060913926157

Source: rare earth Nuggets

The copyright belongs to the author. For commercial reprint, please contact the author for authorization. For non-commercial reprint, please indicate the source.

Keywords: Java Spring Boot Spring Cloud

Added by Patrick on Sun, 13 Feb 2022 10:59:04 +0200