With the introduction of several core components from the previous spring cloud, we have been able to build a short (imperfect) microservices architecture. As shown in the following example:
Alt
We implemented service registries and service registration and discovery using the Eureka in spring cloud netflix, while service consumption and balanced workloads across the ribbon or feign through the spring cloud Config enables the application of multi-environment external configuration and version Management. To make the service cluster more robust, use the hystrix-break mechanism to avoid the spread of failures caused by individual service anomalies in the MicroServices architecture.
In this architecture, our service cluster includes: internal Services service A and service B, they are registered with the subscription service to Eureka Server, and the Open service is an external service that is exposed to the service callers through a balanced load. In this paper, we focus on the external services of this piece, the implementation is reasonable, or whether there is a better way to achieve it?
Let's start with some of the things that this architecture needs to do and the shortcomings that exist:
- first, It destroys the service stateless Feature. In order to ensure the security of the external services, we need to implement the access control of the service, and the Open service control mechanism will run through and pollute the entire open services business logic, This will bring the most direct problem is that the service cluster of the rest API stateless Features. From the point of view of specific development and testing, in addition to considering the actual business logic in the work, additional control processing of interface access is Required.
- second, the existing interfaces cannot be reused directly. When we need to access the interface in a certain cluster, we have to implement the permission control by adding the check logic on the original interface, or adding a proxy call, and cannot reuse the original interface directly.
In the face of similar problems, how can we solve them? Here is the Topic: service gateway!
In order to solve these problems, we need to take permission control such things out of our service unit, and the most suitable for these logic is in the forefront of external access to the place, we need a more powerful load balancer, which is the future of this article: service gateway.
A service gateway is an integral part of the microservices Architecture. In addition to the service Routing and load Balancing function, the service gateway provides the rest API to the external system, and it also has the functions of privilege Control. The Zuul in Spring Cloud Netflix is a role that provides Front-door protection for microservices architectures, while migrating permissions control of these heavier non-business logic content to the service routing plane, enabling service cluster principals to be more reusable and testability-oriented.
Let's take a look at examples to use Zuul to serve as a way to Function.
Preparatory work
Before using zuul, we built a service registry, and two simple services, such as: I built a service-a, a service-b. Then start Eureka-server and the two Services. By visiting eureka-server, we can see that service-a and Service-b are already registered with the service Center.
Alt
Getting Started with Zuul
- The introduction of dependency spring-cloud-starter-zuul, spring-cloud-starter-eureka, if not by specifying Serviceid way, Eureka dependence is not needed, but in order to transparency of the service cluster details, Or use Serviceid to avoid the direct reference Url.
<dependency><groupId>org.springframework.cloud</groupId><artifactId> Spring-cloud-starter-zuul</artifactid></dependency> <dependency><groupId> org.springframework.cloud</groupid><artifactid>spring-cloud-starter-eureka</artifactid></ Dependency>
- Apply main class using
@EnableZuulProxy
annotations to open Zuul
@EnableZuulProxy @springcloudapplication public class publicstaticvoid main (string[] Args) {new Springapplicationbuilder (application. Class). web (true). run (args);}}
Here with the @SpringCloudApplication
annotations, not mentioned before, through the source we see, it integrates @SpringBootApplication
,, @EnableDiscoveryClient
@EnableCircuitBreaker
, The main purpose or simplify the Configuration. The specific role of these notes is not detailed here, the previous article has been Introduced.
application.properties
The basic information for the Zuul app is configured in, such as: app name, service port, and so On.
spring.application.name=api-gatewayserver.port=5555
Zuul Configuration
After the completion of the above work, Zuul can already run, but how to make it for our microservices cluster service, but also need our separate configuration, the following detailed introduction of some common configuration Content.
Service Routing
Through the function of service routing, when we provide service externally, we only need to expose the caller to our service by exposing the call address configured in zuul, without needing to know the host information of the specific service Provided.
Two mapping methods are available in Zuul:
The configuration, defined, all the accesses to Zuul are /api-a-url/**
mapped to the above, that is, http://localhost:2222/
when we visit http://localhost:5555/api-a-url/add?a=1&b=2
, Zuul will route the request To: http://localhost:2222/add?a=1&b=2
up.
Where the Api-a-url part of the configuration attribute Zuul.routes.api-a-url.path is the name of the route and can be arbitrarily defined, but the path and URL of a set of mappings should be the same, as is the case with SERVICEID.
- URL mapping is not particularly friendly for zuul, Zuul needs to know all of our service addresses in order to complete all the mapping configurations. In fact, when we implement the MicroServices architecture, the relationship between the service name and the service instance address already exists in Eureka server, so we just need to register Zuul to Eureka Server to discover other services, we can implement the mapping of the SERVICEID. For example, We can configure the Following:
Zuul.routes.api-a.path=/api-a/**zuul.routes.api-a.serviceid=service-a zuul.routes.api-b.path=/ Api-b/**zuul.routes.api-b.serviceid=service-b eureka.client.serviceurl.defaultzone=http://localhost : 1111/eureka/
For the two microservices service-a and service-b we implemented in the preparation, we defined two routes api-a and api-b to map separately. In addition, in order to let Zuul can find Service-a and service-b, also joined the Eureka Configuration.
next, We start the eureka-server, service-a, service-b, and the service gateways implemented here with zuul, and in the Eureka-server control page, we can see that we have registered service-a, Service-b and Api-gateway
Alt
Try to access service-a and service-b through the service network interfacing, and access the following URL according to the configured mapping relationship
http://localhost:5555/api-a/add?a=1&b=2
: Access to the Add service in service-a via Serviceid mapping
http://localhost:5555/api-b/add?a=1&b=2
: Access to the Add service in service-b via Serviceid mapping
http://localhost:5555/api-a-url/add?a=1&b=2
: Access to the Add service in service-a via URL mapping
It is recommended to use Serviceid mapping method, in addition to Zuul maintenance more friendly, Serviceid mapping mode also supports the circuit breaker, in the case of service failure, can effectively prevent the failure spread to the service gateway and affect the entire system of external services
Service filtering
After the service routing is completed, We also need some security measures to protect the client from accessing only the resources it should access. So we need to use Zuul filter to realize the security control of our external service.
Defining a filter in a service gateway requires only ZuulFilter
four abstract functions that inherit the abstract class to implement its definition to intercept and filter requests.
As the following example, a Zuul filter is defined, which implements whether the request is checked for parameters before the request is routed, and if so, if there is accessToken
no access denied, An error is returned 401 Unauthorized
.
public classAccessfilterextendsZuulfilter {Private StaticLogger log = Loggerfactory.getlogger (accessfilter.class); @Override publicString filtertype () {return"pre";} @Override public intFilterorder () {return0;} @Override public Booleanshouldfilter () {return true;} @Override publicObject Run () {requestcontext ctx=Requestcontext.getcurrentcontext (); HttpServletRequest Request=ctx.getrequest (); log.info (string.format ('%s Request to%s ', Request.getmethod (), request.getrequesturl (). toString ()); Object Accesstoken= Request.getparameter ("accesstoken");if(accesstoken = =NULL) {log.warn ("access token is empty"); Ctx.setsendzuulresponse (false); Ctx.setresponsestatuscode (60s);return NULL;} Log.info ("access token ok");return NULL;} }
The implementation of a custom filter requires inheritance ZuulFilter
, and the following four methods need to be overridden:
filterType
: Returns a String representing the type of filter, with four different life cycle filter types defined in zuul, as Follows:
pre
: Can be called before the request is routed
routing
: Called when a request is routed
post
: Called after the Routing and Error filters
error
: Called when an error occurs while processing a request
filterOrder
: Defines the order of execution of the filter by int value
shouldFilter
: Returns a Boolean type to determine if the filter is to be executed, so the filter can be switched by this Function. In the example above, we return true directly, so the filter is always in Effect.
run
: The specific logic of the Filter. It is important to note that here we ctx.setSendZuulResponse(false)
can further optimize our return by making Zuul filter the request, not routing it, and then by ctx.setResponseStatusCode(401)
setting its return error code, for example, by ctx.setResponseBody(body)
editing the returned body Content.
After implementing a custom filter, you also need to instantiate the filter to take effect, we only need to add the following in the main class of the Application:
@EnableZuulProxy @springcloudapplication public class publicstaticvoid main (string[] Args) {new Springapplicationbuilder (application. Class). web (true). run (args), @Bean public accessfilter accessfilter () { returnnew accessfilter ();}}
After you start the service gateway, access:
http://localhost:5555/api-a/add?a=1&b=2
: 401 Error returned
http://localhost:5555/api-a/add?a=1&b=2&accessToken=token
: correctly routed to server-a and returns the calculated content
For some other types of filtering, this is not a one-off, according to the previous filterType
life cycle introduction, You can refer to understand, and according to their own needs in different life cycle to implement different types of Filters.
Alt
finally, to summarize why the service gateway is an important part of the MicroServices architecture, is why we have to do it:
- It not only realizes the routing function to block many service details, but also realizes the routing of Service level and balanced Load.
- The decoupling of interface privilege check and microservices business logic is Realized. Through the service Gateway filter, in each life cycle to verify the content of the request, the original service layer to do the check forward, to ensure the non-state of microservices, while reducing the micro-service testing difficulty, so that the service itself more focused on business logic Processing.
- Realize the circuit breaker, not because of the failure of the specific micro-service caused the blocking of the service gateway, can still serve the Outside.
Spring Cloud builds a microservices architecture (v) service Gateway