Build zuul module

pom

		<! --eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <! - zuul gateway - >
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
Copy the code
  • Continue to add zuul module in the previous project. Zuul module inheritanceframework-rootAnd then configure the above coordinates in Zuul’s POM

The configuration file

Server: port: 7070 spring: Application: name: cloud-zuul #eureka: client: regist-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka instance: prefer-ip-address: trueCopy the code
  • The configuration file requires only simple project configuration. Because our order and Payment services are registered on Eureka. In order for Zuul to automatically obtain instances, the Zuul module also needs to be registered with Eureka to easily obtain instance collection.

Start the class

  • Then add zuul annotations to the startup class. Eureka annotations are needed because of Eureka
@EnableZuulProxy
@EnableEurekaClient
Copy the code

conclusion

  • It’s that simple. The above steps are just three steps, add coordinates, modify configuration, and add startup class. Zuul gateway is implemented.

  • The zuul after start-up, http://localhost:7070/cloud-payment-service/payment/get/1 will be acting on http://localhost:8001/payment/get/1. This could be 8002

  • http://localhost:7070/cloud-order-service/order/get?id=123 will be acting on http://localhost/order/get?id=123

  • But we don’t configure request forwarding like nginx does. Because zuul gateway has configured us with the default forwarding rules.

  • Zuul will configure default interception for all services registered on Eureka. Localhost :7070/[serviceId]/** Will be forwarded to the following interface on one of the serviceId machines to access the corresponding service.

Dynamic routing

Custom Route 1

zuul:
  routes:
    payment:
      path: /cloud-payment-service2/**
      serviceId: cloud-payment-service
Copy the code
  • We are incloud-zuulAdded module configuration. Then it will happenhttp://localhost:7070/cloud-payment-service2/payment/get/1It will be represented tohttp://localhost:8001/payment/get/1On. This could be 8002.
  • This configuration can be configured when the default configuration for interception does not meet our needs

  • There are two ways to customize routes. The one on the left relies on eureka services to discover forwarding routes, and the one internally relies on the ribbon for load balancing.
  • The one on the right is our traditional item forwarding. Similar to Nginx. Forwards the specified prefix to the specified server. The drawbacks are also obvious. No load balancing

Non-eureka multi-service forwarding

  • If your project does not integrate service discovery like Eureka, I can only say that your project is not suitable for distribution. But Zuul also has a way.

  • Of note is the writing of PATH. There are three main types of wildcard writing
The wildcard instructions
? Matches any single character /a, /b, /c
* Matches any number of characters/ABC
支那 Matches any character /ab/c at any level

Custom Route 2

  • Above we mentioned configuring our routing and forwarding in the configuration file. This one is available for our customization. But it’s too cumbersome to configure. If there are too many services, we will have trouble configuring them. And normally our interface forwarding is regular. Let’s take a look at how to customize rule routing through code.
  • We have that in microservices nowcloud-payment-service.cloud-order-serviceTwo microservices. We want to remove cloud and service from zuul routing. This can be done in configuration files. But if you have 200 services, that’s a little low in the configuration file.
  • This kind of customization is regular customization. That’s when we’re neededPatternServiceRouteMapperCome on stage.

Matcher

  • PatternServiceRouteMapperBefore we go on stage, I think we need to clean upjava.util.regex.MatcherThis class. becausePatternServiceRouteMapperBy looking at the name, we know that the interface is matched by the re. The inside is the use ofMatcherTo achieve.
  • Let’s first describe our routing customization requirements above. We need to transfercloud-payment-service.cloud-order-serviceMicroservice interface to similarpaymentorderThe name of the service.
  • Based on this we can write a regular like this(\w+)-(\w+)-(\w+)

  • From the above re match, we can also see that what we need is group(2). We just need to return the third match as the access interface. The above is also how the author usually obtains the matching content. A began to watchPatternServiceRouteMapperI didn’t understand the re he wrote. Suddenly I felt like I had learned my regular expressions for nothing.

  • The above isPatternServiceRouteMapperClass. Among them<name>This didn’t make sense at first. And with this, it’s going to match. That’s a dead letter. Look up the information to know that this is the group alias.

  • Then we improved on the original regular code. Found through the alias is still very convenient. The way to alias? <name>In this case, the name is the alias of the group in parentheses.

PatternServiceRouteMapper

  • Now we begin to introduce dynamic routing.PatternServiceRouteMapperClasses are also simple. You need two parametersservicePattern ,routePattern; The former is the re, the latter is the collated format. So our configuration is as follows
	@Bean
    public PatternServiceRouteMapper patternServiceRouteMapper(a) {
        return new PatternServiceRouteMapper(
                "(? 
      
       ^.+)-(? 
       
        .+)-(? 
        
         .+$)"
        ."${name}");
    }
Copy the code

Authentication Authentication (filter)

  • The gateway is our facade. It’s just that the facade here is the facade of our back-end microservices. Besides unifying us, facade also serves as an important filtering interface. A website released to the public will inevitably encounter malicious attacks. Normal website we all have permission to say, in the micro service architecture we can not be in each micro service to permission verification, so maintenance up quite troublesome.
  • In this case, we would rather separate authentication into one module and then invoke the authentication module in other modules. At first glance, it seems all right. My current project does exactly that. But although the scale continues to grow, the author also realized the drawbacks of this way. Remember we made some changes to the authentication module earlier. Some new information has been added to the method signature. This is a really innocuous change for us as developers. However, this small change caused a big stir and all of our modules need to update the authentication package and add new parameters.
  • The problem is that we need to re-send each module online. The schedule of each module was thus disrupted.
  • In view of the above withdrawal scheme, our protagonist Zuul can solve it perfectly today. Because Zuul doesn’t need other modules to be introduced. It’s about making other modules into an ecosystem. Zuul is the caretaker of the ecology.

Need to sort out

  • Suppose we now require that a token must be added to each interface parameter. We’re not going to validate value for the sake of the demonstration. In normal development this value should also be granted by the server.

  • If there is a token, the device is allowed. Otherwise, an error message is returned

Implement authentication

  • Implementing validation is all about implementing a filter. We just need to inheritcom.netflix.zuul.ZuulFilterCan. There are four methods in this class that we need to implement.
methods role
filterType Filter type
filterOrder The smaller the execution order, the higher the execution order
shouldFilter Whether to implement
run The specific logic
  • There are four filter types: Pre, Route, POST, and ERROR. The following code should be clear about the order in which they are executed.

  • Pre: Before routing. If an exception occurs, error and route are directly executed

  • Route: Execute after pre

  • Post: If everything is normal, it will be executed after the route route

  • Error: Abnormal execution

  • The following filters default in Netflix-Zuul

Between the filter we can via com.net flix. Zuul. Context. The RequestContext for context. We can also rely on him for data transfer.

The validation logic

	@Override
    public Object run(a) throws ZuulException {
        System.out.println("I'm a pre filter and I've been implemented...");
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        String token = request.getParameter("token");
        if (StringUtils.isEmpty(token)) {
            currentContext.setSendZuulResponse(false);
            currentContext.setResponseStatusCode(401);
            return null;
        }
        return null;
    }
Copy the code
  • We’re just going to do that in our filter. Notifies the client of the set response status.

Dynamic filter

  • Above, we realize how to forward dynamic routes in two ways. But we sometimes want to dynamically add or subtract filters when operating. This zuul is also possible, relying mainly on Groovy to dynamically load filters

pom

	<dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>3.0.0</version>
        </dependency>
Copy the code

Registered bean

  • I’m just going to write it down here to show you how to configure dynamic loading.
  • If we use it properly, inFilterFileManager.initReceive two parameters: an interval and an array of files
  • We can dynamically configure these configurations or directly configure them in the database. In combination with the page can let the operation personnel directly on the page can dynamically increase or decrease the filter.
	@Bean
    public FilterLoader filterLoader(a) {
        FilterLoader instance = FilterLoader.getInstance();
        instance.setCompiler(new GroovyCompiler());
        try {
            FilterFileManager.setFilenameFilter(new GroovyFileFilter());
            FilterFileManager.init(5."D:\\cloud\\filter\\pre");
        } catch (Exception e) {
            throw new RuntimeException("Something went wrong.");
        }
        return instance;
    }
Copy the code

Write a filter

  • Filters are described in detail in the authentication and Authentication section. It’s also easy to write a filter.
  • But we don’t have Java type filters anymore. It’s groovy type filters. What’s the difference between them. Just post the code
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

public class PreFilter extends ZuulFilter {
    @Override
    public String filterType(a) {
        return "pre";
    }

    @Override
    public int filterOrder(a) {
        return 0;
    }

    @Override
    public boolean shouldFilter(a) {
        return true;
    }

    @Override
    public Object run(a) {
        System.out.println("I'm a pre filter loaded dynamically...");
        return null; }}Copy the code

The verification results

  • Without rebooting Zuul, let’s put the prefilter. groovy file in the local directoryD:\cloud\filter\preJust drop it. Then we wait for 5S to see our new filter on the access route interface.

conclusion

  • Loading dynamic filters should not add too much logic in principle. It is not possible to use other package support. Because there’s only Java stuff inside Groovy.
  • Another important point is that we cannot interact with Spring in dynamically injected filters.

View the routes

  • Generally, we have two endpoints in Zuul, **/routes** and /filters. If we need to see these monitoring messages, we need to configure them in the ACTUATOR
management:
  endpoints:
    web:
      exposure:
        include: 'routes,filters'
Copy the code
  • Then start the project after we go through the accesshttp://localhost:7070/actuator

  • In addition to the ACTUATOR, we see routes and filters, the two monitors we need. /routes/{format} also belongs to routes. Format is the format that our routes display

routes

routes/{format}

filters

Gray released

  • When it comes to grayscale publishing we have to mention several other ways
  • Blue and green release: A blue and green release is two environments. Both are release environments but only one is always exposed to external use. The environment that is not exposed is our pre-release environment. We can test in this environment. Switch traffic after the test passes. After switching, the roles of the two sets of environments change.
  • Rolling publishing: Replacing online services one by one. We will replace A,B and C respectively. The problem with this is that if something goes wrong in the middle it’s a disaster.

So what is grayscale publishing? Gray release is our online resources remain unchanged. We just need to launch the new service. The old service and the new service are running at the same time. At this point we will be testing traffic to the new service. We tested this service. After the test passes, we will release a small amount of traffic to the new service for a trial period and then collect the data usage of this part. Once the data is satisfied, all traffic can be transferred to the new service. Of course, this type of release is certainly not satisfactory for our traditional projects. Grayscale publishing is suitable for distributed projects. For grayscale publishing our project must support distribution. Take, for example, the scheduled tasks for our backend services. Multiple services online at the same time will be repeated if distribution is not supported.

  • Finally, we upgraded the service without stopping the machine. This is basically the model for Internet companies these days. The upgrade cannot affect customers. Let’s use Zuul to achieve a simple gray release

The source code to achieve

The introduction of pom

  • The following JAR package is used to filter the list of servers. At the end of the day, zuul relies on our ribbon for load balancing. And we’re combined with Eureka. The ribbon and Eureka integrate seamlessly. So ultimately we are filtering the list of Eureka services. As we mentioned in the Eureka section, eureka-client provides operations such as retrieving a list of services. The next step is to specify the rule selection when getting the list of services.io.jmnarloch.spring.cloud.ribbon.predicate.MetadataAwarePredicateService filtering is implemented through the Metadata property of Eureka.
		<dependency>
            <groupId>io.jmnarloch</groupId>
            <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
Copy the code

The configuration file

  • We started two payments, port 8001,8002. The lancher property of the two payments is 1,2. Equivalent to specifying the payment service
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka
  instance:
    prefer-ip-address: true
    metadata-map:
      lancher: 1
Copy the code

Implementation filter

  • Combined with the above mentionedio.jmnarloch.spring.cloud.ribbon.predicate.MetadataAwarePredicateWe simply specify the metadata properties of the server we want to access in the Zuul filter. For example, we determine whether the request parameter containsnewParameter to determine the requested server
@Component
public class GreenFiler extends ZuulFilter {
    @Override
    public String filterType(a) {
        return "pre";
    }

    @Override
    public int filterOrder(a) {
        return 0;
    }

    @Override
    public boolean shouldFilter(a) {
        return true;
    }

    @Override
    public Object run(a) throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        if (request.getParameter("new") != null) {
            // put the serviceId in `RequestContext`
            RibbonFilterContextHolder.getCurrentContext().add("lancher"."1");
        } else {
            RibbonFilterContextHolder.getCurrentContext().add("lancher"."2");
        }
        return null; }}Copy the code

test

  • Combined with our custom route above, cloud-payment-service will eventually be converted to the payment request by us. As for the above token verification, we need to add a token to our Zuul request in order to pass the authentication
  • We visithttp://localhost:7070/payment/payment/get/1?token=123&newThe token is first verified to pass the final access on the service that is routed to Lancher =1 according to the new parameterhttp://localhost:8001/payment/get/1?token=123&new
  • We’re visitinghttp://localhost:7070/payment/payment/get/1?token=123The token is verified and routed to 8002.

Gray scale extension

  • Above, we realized the forwarding of the route through the parameters specified in the request. The implementation principle is based on the parameter attribute of Eureka metadata.
  • However, we should have encountered some software to treat different regions differently.
    • For example, alipay ant forest has different strategies in different cities
    • Let’s say an app invites you to participate in the inner version
  • The above is called grayscale publishing, and the principle is very simple. In fact, there are multiple instances, and Zuul forwards them to different instances according to the characteristics of the request. Different cities are routed according to the region, and the inside of the invitation is routed through individual user information. Their implementation depends on what we described aboveRibbon.Predicate. Want to scale the release of gray we can not do withoutPredicate

Predicate

  • The purpose of this class is to make assertions, an idea that Google came up with. Tap me specifically about Google
  • In the ribbon project, we took a brief look at how the Ribbon performs load balancing and its internal load balancing strategies through source code reading. You can click on the home page to find.
  • Today we’ll look at how the ribbon filters after retrieving a list of services before load balancing.
// Returns the assertion true or false based on the input
@GwtCompatible
public interface Predicate<T> {
  // This method has the following requirements: (1) this method does not cause any data contamination; (2) this method has the same effect in T equals; (3) this method has the same effect in APPLY
  boolean apply(@Nullable T input);
  Returns whether the two predicates are the same. In general, Predicate implementations don't need to override equals. If the implementation can indicate whether the predicates are the same or not, depending on its needs. What's called the same when two predicate objects apply are the same that means the objects are the same
  @Override
  boolean equals(@Nullable Object object);
}
Copy the code
  • Let’s use Predicate to implement simple data filtering. Of course some people will say why not use java8 stream filtering. This is just to pave the way for service filtering in the Ribbon. Why doesn’t the Ribbon use flow operations? Google’s Predicate for individual roles makes decoupling easier.
	@Test
    public void pt(a) {
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            userList.add(new User(Long.valueOf(i+1), "Zhang"+(i+1)));
        }

        Predicate<User> predicate = new Predicate<User>() {
            @Override
            public boolean apply(User user) {
                return user.getId() % 2= =0; }}; ArrayList<User> users = Lists.newArrayList(Iterables.filter(userList, predicate)); System.out.println(users); }Copy the code

AbstractServerPredicate

The premise to back

  • You can see this in our class structure diagram aboveAbstractServerPredicatePredicateImplementation class of. This class is also the key role the Ribbon plays in retrieving the list of services. Because all the functionality is extended based on this class.

Jmnarloch met

  • This is what I have in the RIbbon. We know that the Ribbon ends up inBaseLoadBalancerFor load balancing. Its internal rule defaults tonew RoundRobinRule()Because we introducedio-jmnarloch. First look at the inner class structure

  • io-jmnarlochThe interior is not very complicated, at least compared to Ribbon, Feign, etc. There are four packages inside
package role
api Provide context for external use
predicate Provides a filter to get the service list
rule Implementation of load balancing policies in the ribbon
support On the above auxiliary package

Registering load Rule

  • We can see that in the Support packageRibbonDiscoveryRuleAutoConfigurationIs configured with the Ribbon load balancing rule defined in the rule package.

  • Power toBaseLoadBalancerWe can see that rule is usrule.MetadataAwareRuleThis class. This seems to be different from what the ribbon says. In the ribbon, we say that when we need to customize rules, we need to@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)This way.
  • It’s actually configuringDiscoveryEnabledRuleAt the time of registration@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)Representation scope

How does MetadataAwareRule filter services

  • throughMetadataAwareRuleCombined with the code we can see that PredicateBaseRule#choose is ultimately in the list of select services

  • The predicate is what we get from the choose method getPredicate(). So the ribbon filters services through MetadataAwarePredicate before selecting them.

  • After the access to the filter object, we will carry out chooseRoundRibbinAfterFiltering.

Back to AbstractServerPredicate

  • Said on May end to perform chooseRoundRobinAfterFiltering Predicate. Remember the structure of the predicate from the beginning.MetadataAwarePredicateEventually inheritAbstractServerPredicate. whileAbstractServerPredicate # chooseRoundRobinAfterFilteringIs dependent on getEligibleServers’ to get a list of appropriate services.
  • inAbstractServerPredicateImplement a lot of chooseXXX methods. Because the ribbon is polling by default, BaseLoadBalance selects Round. We can change the way we want. I won’t repeat it here
  • EligibleAppropriate. GetEligibleServers translates to get a list of appropriate services.

  • We can clearly see that the final filtering logic falls on the apply method.

  • This is where we configure our service information through metadata-map: Lancher.

  • Here’s a condensed version of AbstractServerPredicate. The main one is the getEligibleServers method.

A subclass

  • In the aboveAbstractServerPredicateIn the structure diagram we can see that in addition toDiscoveryEnabledPredicateIn addition to this subclass, there are four subclasses.
A subclass role
AvailabilityPredicate Filter unavailable servers
CompositePredicate Composite pattern to ensure a certain number of services. In other words, too few services will fallback one by one until the number of services is sufficient
ZoneAffinityPredicate Select the Server in the specified zone
ZoneAvoidancePredicate Avoid using qualified servers. The opposite of ZoneAffinityPredicate

Other features

Rule out the routing

  • Some services we may not want zuul proxy routing for some time due to confidentiality. Let’s say we don’t let Zuul route the ORDER interface
zuul.ignored-patterns : /**/order/**
Copy the code

Local jump

  • I think this feature is a little bit lame. Its function is to get Zuul to route to its service. Why not just access your own interface service? I don’t get it here.
  • But his existence has its value. Let’s say we have a user module and our position is to handle the user’s actions. But he didn’t develop a login interface. At this point, we can implement the login interface in Zuul and route the user’s login interface to the getTest interface in Zuul.
zuul:
  routes:
    user:
      path: /cloud-user-service/**
      url: forward:/zuul
Copy the code
  • http://localhost:7070/cloud-user-service/getTest?token=123At this point the interface we’re accessing is finally routed to/zuul/getTestOn.

Request header carrying

  • Above is the information in the request object that we zuul routes to payment. In the request header we add zxhTom = HelloWorld. Set the cookie object to Cookie_1=value

  • But when we print the next two values in the payment, the cookie is not brought.

  • That’s because zuul filters out sensitive word request headers for security reasons when routing. Default Cookie, set-cookie, and Authorization attributes

  • But why do we treat the route separately. We often

zuul.routes.<router>.customSensitiveHeaders=ture
zuul.router.<router>.sensitiveHeaders=
Copy the code

Hystrix and zuul

  • Last week we just wrapped up our hystrix feature, where we talked about service downgrades, circuit breakers, and current limiting. By default, Zuul is hystrix integrated. We route interfaces in Zuul based on Hystrix
  • However, Zuul’s Hystrix catches the timeout exception, and other exceptions are normal return information in Zuul’s opinion, which needs to be returned to the client in the original mode
  • We can implement a uniform timeout fallback in Zuul. In this way we will return a uniform format when the service times out.
  • As a review of hystrix, when our service Zuul routes time out, they will be recorded by Hystrix, and when a certain error ratio is reached, the circuit breaker will be triggered. After a certain period of time, the half-open state of fusing will be broken, and the default time is 5000ms. That is to say, after our background service breaks down and restarts, our Zuul will have a fallback period of at least 5S. If there is no new route during this period, theoretically, it will be restored after 5S.

Unified fallback

@Component
public class ServerFallback implements FallbackProvider {
    @Override
    public String getRoute(a) {
        return "cloud-payment-service";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        if (cause instanceof HystrixTimeoutException) {
            return response(HttpStatus.GATEWAY_TIMEOUT);
        } else {
            returnresponse(HttpStatus.INTERNAL_SERVER_ERROR); }}private ClientHttpResponse response(HttpStatus status) {
        return new ClientHttpResponse() {
            @Override
            public HttpHeaders getHeaders(a) {
                HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
             return headers;
            }

            @Override
            public InputStream getBody(a) throws IOException {
                return new ByteArrayInputStream("fallback".getBytes());
            }

            @Override
            public HttpStatus getStatusCode(a) throws IOException {
                return status;
            }

            @Override
            public int getRawStatusCode(a) throws IOException {
                return status.value();
            }

            @Override
            public String getStatusText(a) throws IOException {
                return status.getReasonPhrase();
            }

            @Override
            public void close(a) {}}; }}Copy the code
  • It is worth noting that we will dynamically route above whencloud-payment-serviceConvert to Payment, but our getRoute still needs to specify the name of the service registered with eurekacloud-payment-serviceIf I change it topaymentIs not effective. Here the reader takes the test to understand.

Deficiency in

  • Zuul is currently not very friendly with UDP and WS support. The author here also does not carry on the relevant attempt
  • The Websocket will be polling back when it passes zuul, and we need to deal with timeouts.

The source code

Have time to prepare ZuulProxyAutoConfiguration under study

Related source code above