With microservices architecture, each microservice exposes a set of fine-grained services to the outside. The client’s request may involve a series of service calls. If all these microservices are exposed to the client, the client needs to request different microservices multiple times to complete a business process, increasing the code complexity at the client side. In addition, for microservices, we may also need unified authentication and verification of service calls and so on. Although the microservice architecture can divide our development unit into smaller pieces and reduce the development difficulty, if we cannot effectively deal with the problems mentioned above, it may cause the implementation of the microservice architecture to fail.
Zuul
refers to the Facade pattern
in the GOF design pattern, and combines fine-grained services to provide a coarse-grained service. All requests are imported into a unified entrance. Then the entire service only needs to expose an API, which shields the server-side implementation details. It also reduces the number of client-server network calls. This is the API Gateway
service. We can think of the API Gateway
as an intermediate layer between the client and the server. All external requests will first pass through the API Gateway
. Therefore, the API Gateway
has almost become a must choice when implementing a microservice architecture.
The Zuul
component of Spring Cloud Netflix
can be used as a reverse proxy
, forwarding requests to coarse-grained services on the back end through routing addressing
, and doing some general logic processing.
With Zuul
we can complete the following functions:
- Dynamic routing
- Monitoring and review
- Authentication and security
- Stress test: gradually increase the traffic of a service cluster to understand the service performance;
- Canary test
- Service migration
- Load tailoring: Allocate the corresponding capacity for each load type and discard requests that exceed the limit;
- Static response processing
Why we need API Gateway
Simplify client call complexity
Under microservice architecture, the number of instances of the backend service is generally dynamic, and it is difficult for the client to find the address information of the dynamically changed service instance. Therefore, in order to simplify the front-end call logic in microservice-based projects, API Gateway
is usually introduced as a lightweight gateway. At the same time, API Gateway
will also implement related authentication logic to simplify the complexity of mutual calls between internal services.
Data clipping and aggregation
Generally speaking, different clients have inconsistent requirements for data during display, such as mobile phones or Web terminals, or in low-latency network environments or high-latency network environments.
Therefore, in order to optimize the client’s experience, API Gateway
can tailor the general response data to meet the needs of different clients. At the same time, multiple API calls can be aggregated to reduce the number of client requests and optimize the client user experience.
Multi-channel support
Of course, we can also provide different API Gateways
for different channels and clients. The use of this mode is another well-known method called Backend for front-end
. In Backend for front-end
mode, we can target different client to create its BFF
separately. For more information about BFF
, please refer to this article: Pattern: Backends For Frontends
Example Project
Zuul-Server
1. pom.xml
1 | <dependency> |
2. Main class
1 |
|
Added @EnableZuulProxy
annotation to the main application class to start Zuul’s routing service.
3. application.properties
1 | server.port=8280 |
User-Service
We add another service: User-Service
here.
1. pom.xml
1 | <dependency> |
2. Main class
1 |
|
This is the same as Product-Service
.
3. User entity
1 | public class User { |
4. User service
1 |
|
The interface is very simple, just query a user’s information based on the given login name.
5. application.properties
1 | server.port=2200 |
Testing
1. Launch services
Start each server in the following order:
- Service-discovery
- User-Service(2200)
- User-Service(2300):
java -jar user-service-1.0.0-SNAPSHOT.jar --server.port=2300
- Zuul-Server
We can see the eureka admin page:
2. Test routing
Visit: http://localhost:8280/user-service/users/admin
Note that here we used service name to retrieve the relevant servicie.
3. Load balance testing
Let’s test if load balancing works. Earlier we have started two User-Service
microservices, the ports are: 2200 and 2300. Try visit: http://localhost:8280/user-service/users/admin to make a request, we will see that the following information will be output alternately on the screen:
4. Hystrix fault tolerance and monitoring test
We have integrated Hystrix
monitoring in Zuul-Server
. Visit: http://localhost:8280/hystrix/ and enter: http://localhost:8280/actuator/hystrix.stream to start monitor.
This means that Zuul
has integrated Hystrix
.
Spring-cloud-starter-netflix-zuul
itself has integratedhystrix
andribbon
, soZuul
is inherently equipped withthread isolation
andcircuit breaker
self-protection capabilities, as well asclient load balancing
for service calls. However, we need to note that when using the mapping relationship betweenpath
andurl
to configure routing rules, requests for routing will not be wrapped withHystrixCommand
, so this type of routing request has no thread isolation and circuit breaker protection functions, and also there will be no load balancing capabilities. Therefore, when usingZuul
, we try to use a combination ofpath
andserviceId
to configure. This not only can ensure the robustness and stability of the API gateway, but also can useRibbon
‘s client load balancing function.
Zuul Configuration
Routing configuration
Maybe you think it’s strange that we didn’t configure anything, but can access service through http://localhost:8280/user-service/users/admin. This is the default route mapping function of Zuul
, so let’s take a look at how to configure routing in Zuul
.
1. Service routing default rules
When we used Eureka
to built the API gateway
, Zuul
will automatically create a default routing rule for each service: the access path is prefixed with the service name configured by serviceId
, which is why we were able to use:
1 | http://localhost:8280/user-service/users |
to access the users
endpoint provided in User-Service
.
2. Customized microservice access path
The configuration format is: zuul.routes.Microservice Id = specified path
, such as:
1 | zuul.routes.user-service = /user/** |
In this way, we can access the services provided by user-service
through /user/
. For example, the previous access can be changed to: http://localhost:8280/user/users/admin.
The path to be configured can specify a regular expression to match the path. Therefore, /user/*
can only match the first-level path, but /user/**
can match all paths starting with /user/
.
3. Ignore specified microservices
Format: zuul.ignored-services = Micro service Id1, Micro service Id2 ...
, multiple micro services are separated by commas. Such as:
1 | zuul.ignored-services=user-service,product-service |
4. Specify the microservice Id and the corresponding path at the same time
1 | zuul.routes.api-a.path=/api-a/** |
5. Specify microservice URL and corresponding path at the same time
1 | zuul.routes.api-a.path=/api-a/** |
As mentioned before, the routing configured through the URL will not be executed by HystrixCommand
. As a result, Ribbon
‘s load balancing
, downgrading
, and circuit breaker
functions will not be obtained. Therefore, try to use serviceId
for configuration, you can also use the following configuration.
6. Specify multiple service instances and load balancing
If multiple service instances need to be configured, the configuration is as follows:
1 | zuul.routes.user.path: /user/** |
7. Forward to local url
1 | zuul.routes.user.path=/user/** |
8. Route prefix
You can add a uniform prefix to all mappings through zuul.prefix
. For example: /api. By default, the proxy will automatically strip this prefix before forwarding. If you need to prefix with forwarding, you can configure: zuul.stripPrefix = false
to turn off this default behavior. E.g:
1 | zuul.routes.users.path=/myusers/** |
Note:
zuul.stripPrefix
only works on the prefix ofzuul.prefix
. Does not work for prefix specified by path.
9. Routing configuration order
If you want to control the routing rules according to the configured order, you need to use YAML
. If you use the property file, the order will be lost. E.g:
1 | zuul: |
If the above example is configured using a properties file, the legacy
may be effective, so the users have no effect.
10. Custom transformation
We can also have a converter that uses a regular expression between the serviceId
and the route
to automatically match. E.g:
1 |
|
In this way, the service whose serviceId is users-v1
will be mapped to the route of /v1/users/
. Any regular expression is fine, but all named groups must include servicePattern
and routePattern
. If servicePattern
does not match a serviceId
, then the default is used. In the above example, a service with serviceId users
will be mapped to the route /users/
(without version information). This feature is off by default and only applies to services that have been discovered.
Zuul Header Settings
Sensitive header settings
It is not a problem to share information through headers between services in the same system, but if you don’t want some sensitive information in headers to leak out with HTTP forwarding, you need to specify a list of headers to be ignored in the routing configuration.
By default, when Zuul
requests routing, it will filter some sensitive information in the HTTP request header information. The default sensitive header information is defined through zuul.sensitiveHeaders
, including Cookie
, Set-Cookie
, and Authorization
. The configured sensitiveHeaders can be separated by commas.
The specified routes can be configured with:
1 | zuul.routes.[route].customSensitiveHeaders=true |
Set global:
1 | zuul.sensitiveHeaders=[set headers here] |
Ignore Header settings
If you need to configure some additional sensitive headers for each route, you can use zuul.ignoredHeaders
to uniformly set the headers to be ignored. Such as:
1 | zuul.ignoredHeaders=[set Header to ignore] |
There is no such configuration by default. If Spring Security
is introduced in the project, then Spring Security
will automatically add this configuration. The default values are: Pragma
, Cache-Control
, X-Frame-Options
, X-Content-Type- Options
, X-XSS-Protection
, Expries
.
If you also need to use the Spring Security Header
of the downstream microservice, you can add the following settings:
1 | zuul.ignoreSecurityHeaders=false |
Zuul Http Client
Zuul
‘s Http client supports Apache Http
, Ribbon’s RestClient
, and OkHttpClient
. By default, the Apache HTTP
client is used. The corresponding clients can be enabled in the following ways:
1 | # Enable Ribbon'RestClient |
If you need to use
OkHttpClient
, please note thatcom.squareup.okhttp3
related packages are already included in your project.
Zuul fault tolerance and fallback
Let’s take a look at the previous monitoring interface of Hystrix:
Please note that the granularity of Zuul
‘s Hystrix monitoring
is microservices, not an API, that is, all requests passing Zuul
will be protected by Hystrix
. If we shut down the User-Service
service now, what will happen when we visit it again?
So how do we implement fault tolerance and fallback for Zuul
? Zuul
provides a ZuulFallbackProvider
interface. By implementing this interface, we can implement fallback functions for Zuul
. So let’s transform Zuul-Server
before.
1 |
Note that:
- The
getRoute
method returns that we want to provide a fallback for that microservice. Note that the returned value is the name of the route, not the name of the service, and cannot be written as:USER-SERVICE
, otherwise the fallback will not work; - The
fallbackResponse
method returns aClientHttpResponse
object as our fallback response.
Now do a test, stop User-Service
and visit:http://localhost:8280/user-service/users/admin
Note that the fallback method has worked. If it doesn’t work, double check that getRoute
returns correctly.
Check out the source code here: zuul part 1 demo