Spring Cloud Gateway combines configuration center current limit

Source: Internet
Author: User

Preface

Let's say you have a task assigned to you by your leader, as follows:

    • Limiting current for specific interfaces
    • Different interface limits can vary in intensity
    • Current limit configuration can be dynamically adjusted to take effect in real time

If you receive the above task, how will you design + implement it?

Each person's view of the problem is different, the natural thinking out of the plan is different, is so-called all roads through Rome, can reach the destination of the road that is a good way.

How to analyze requirements

Below I give my realization way, for your reference only, Daniel please ignore.

Specific problems specific analysis, according to demand points, respectively to do analysis.

Demand One "How to limit the flow of specific interfaces" This is also mentioned in the previous article, just let Keyresolver return is the interface URI, so that the dimension of the current limit is to this interface to limit the flow.

Demand Two "Different interfaces limit the intensity of the flow can be different" this through the configuration of the way obviously not, the configuration of the Replenishrate and burstcapacity are configured to die, if you want to make dynamic so must be self-expanding redisratelimiter to achieve.

The premise is that there must be a configuration list, which is the current limit value for each interface. With this configuration we can get the current limit value for this interface via the requested interface.

requirements Three "can dynamically adjust the current limit configuration, real-time effective" This is also relatively easy, whether you are stored files, storage database, memory cache as long as every time to read, it must be real-time effective, but performance problems we have to consider AH.

Save files, read files, consume Io, mainly inconvenient to modify
Database, can be modified through the Web interface, you can also directly change the database, every time to query, performance is not
Storage Distributed Cache (REDIS) with improved performance over database

The comparison is sure that the cache is the best solution, and there is a better solution?
There, with the configuration Center to do, my side with their own configuration center (https://github.com/yinjihuan/smconf) to explain, replaced by other configuration center is the same idea.

The advantage of the configuration center is that it is used to store the configuration, the configuration is loaded when the project starts, the update is pushed when there is a modification, and each read is in the local object, and the performance is good.

We can start the code after the specific scheme, but have you ever thought about how to initialize the current limit value for so many interfaces? Manual to add?

Different service maintenance groups, of course, may also be a group maintenance, from the perspective of the designer to think, should set the right to the user, to our interface developers, each interface can withstand how much concurrency let the user to decide, your duty is to limit the flow in the gateway. Of course, the specific limits in the company will not necessarily be determined by the developer, this has to be based on the results of the test, the best adjustment.

Words don't say much-start the code
First of all, we define our own redisratelimiter, copy the source of a little transformation can be, this side only paste the core code.

public class Customredisratelimiter extends abstractratelimiter<customredisratelimiter.config>
Implements Applicationcontextaware {

 public static final String configuration_property_name = "Custom-redis-rate-limiter";p ublic static final string Redis_script_name = "Redisrequestratelimiterscript";p ublic static final String Remaining_header = " X-ratelimit-remaining ";p ublic static final String Replenish_rate_header =" X-ratelimit-replenish-rate ";p ublic static Final String Burst_capacity_header = "x-ratelimit-burst-capacity";p ublic customredisratelimiter ( Reactiveredistemplate<string, string> redistemplate, redisscript<list<long>> script, Validator VA Lidator) {super (Config.class, Configuration_property_name, validator); This.redistemplate = redistemplate; this.script = script; Initialized.compareandset (False, True);} Public customredisratelimiter (int defaultreplenishrate, int defaultburstcapacity) {super (Config.class, CONFIGURATION _property_name, NULL); This.defaultconfig = new Config (). Setreplenishrate (Defaultreplenishrate). setburstcapacity (defaultburstcapacity);}  

//Current limit configuration
Private ratelimitconf ratelimitconf;

@Override @suppresswarnings ("unchecked") public void Setapplicationcontext (ApplicationContext context) throws  beansexception {* *//Load config * * this.ratelimitconf = Context.getbean (Ratelimitconf.class); }/** * This uses a basic token buckets algorithm and relies on the fact that * Redis scripts execute atomically. No Other operations can run between * fetching the count and writing the new count. */@Override @suppresswarnings ("unchecked") Public mono<response> isallowed (string routeid, string id) {if (!this.    Initialized.get ()) {throw new IllegalStateException ("Redisratelimiter is not initialized");    }//config Routeconfig = GetConfig (). Getordefault (Routeid, defaultconfig);    if (ratelimitconf = = null) {throw new IllegalArgumentException ("No Configuration found for route" + Routeid);    } map<string,integer> Routeconfig = Ratelimitconf.getlimitmap ();    Format of key: Service name. Interface URI. Type String Replenishratekey = Routeid + "." + ID + ". Replenishrate"; InchT replenishrate = routeconfig.get (replenishratekey) = = null?    Routeconfig.get ("Default.replenishrate"): Routeconfig.get (Replenishratekey);    String Burstcapacitykey = Routeid + "." + ID + ". burstcapacity"; int burstcapacity = Routeconfig.get (burstcapacitykey) = = null?    Routeconfig.get ("Default.burstcapacity"): Routeconfig.get (Burstcapacitykey);        try {list<string> keys = Getkeys (ID); The arguments to the LUA script.        Time () returns unixtime in//seconds. list<string> Scriptargs = arrays.aslist (Replenishrate + "", Burstcapacity + "", Instant.now (). GetEpoc        Hsecond () + "", "1"); Allowed, Tokens_left = Redis.eval (SCRIPT, keys, args) flux<list<long>> Flux = This.redisTemplate.exe        Cute (this.script, keys, Scriptargs);        . log ("Redisratelimiter", Level.finer); Return Flux.onerrorresume (Throwable-Flux.just (Arrays.aslist (1L, -1l)). Reduce (New arraylist<long& gt; (), (Longs, L), {longs.addall (L);                return longs;                    }). Map (results, {Boolean allowed = results.get (0) = = 1L;                    Long tokensleft = results.get (1);                    Response Response = new Response (Allowed, getheaders (replenishrate, burstcapacity, Tokensleft));                    if (log.isdebugenabled ()) {Log.debug ("Response:" + response);                } return response;    }); } catch (Exception e) {/* * * We don ' t want a hard dependency on Redis to allow traffic.         Make * sure to set a alert so you know if the is happening too much.         * Stripe ' s observed failure rate is 0.01%.    */Log.error ("Error determining if user allowed from Redis", e); } return Mono.just (New Response (True, Getheaders (Replenishrate, burstcapacity, -1l)));} Public hashmap<string, string> getheaders (IntegerReplenishrate, Integer burstcapacity, Long tokensleft) {hashmap<string, string> headers = new hashmap<> ();    Headers.put (This.remainingheader, tokensleft.tostring ());    Headers.put (This.replenishrateheader, string.valueof (replenishrate));    Headers.put (This.burstcapacityheader, string.valueof (burstcapacity)); return headers;}

}
To load our configuration class in Setapplicationcontext, the configuration class is defined as follows:

@CxytianDiConf (system= "Fangjia-gateway")
public class Ratelimitconf {
Current Limit Configuration
@ConfField (value = "Limitmap")
Private map<string, integer> limitmap = new hashmap<string, integer> () {{
Put ("Default.replenishrate", 100);
Put ("Default.burstcapacity", 1000);
}};
public void Setlimitmap (map<string, integer> limitmap) {
This.limitmap = Limitmap;
}
Public map<string, Integer> Getlimitmap () {
return limitmap;
}
}
All the interfaces corresponding to the current limit information in the map, there is a default value, if there is no corresponding configuration of the interface with the default value of the current limit.

The Isallowed method is passed through the ' service name. Interface URI. Type ' form a key, and the key is used to get the corresponding value in the map.

The role of the type is primarily used to differentiate between replenishrate and burstcapacity two values.

The next step is to configure Customredisratelimiter:

@Beanbr/> @Primary

Reactiveredistemplate<string, String> Redistemplate,
@Qualifier (customredisratelimiter.redis_script_name) redisscript<list<long>> RedisScript,
Validator Validator) {
return new Customredisratelimiter (Redistemplate, Redisscript, validator);
}
The logic on this side of the gateway has been implemented, and the next step is to customize the annotations in the specific service, and then initialize the current limit parameters to our configuration center.

Defining annotations

@Target (Elementtype.method) br/> @Retention (retentionpolicy.runtime)

Public @interface Apiratelimit {

/** * 速率 * @return */int replenishRate() default 100;/** * 容积 * @return */int burstCapacity() default 1000;

}
Start the listener, read the annotations, initialize the configuration

/**

  • APIs that initialize API gateways that require concurrency restrictions
  • @author Yinjihuan
  • */
    public class Initgatewayapilimitratelistener implements applicationlistener<applicationreadyevent> {

    Controller package Path
    Private String Controllerpath;

    Private ratelimitconf ratelimitconf;

    Private Confinit Confinit;

    Private String ApplicationName;

    Public Initgatewayapilimitratelistener (String Controllerpath) {
    This.controllerpath = Controllerpath;
    }

    @Override
    public void Onapplicationevent (Applicationreadyevent event) {
    this.ratelimitconf = Event.getapplicationcontext (). Getbean (Ratelimitconf.class);
    This.confinit = Event.getapplicationcontext (). Getbean (Confinit.class);
    This.applicationname = Event.getapplicationcontext (). Getenvironment (). GetProperty ("Spring.application.name");
    try {
    Initlimitrateapi ();
    } catch (Exception e) {
    throw new RuntimeException ("Initialization of API exceptions requiring concurrency restrictions", e);
    }
    }

    /**

    • Initializing APIs that require concurrency restrictions
    • @throws IOException
    • @throws ClassNotFoundException
      */
      private void Initlimitrateapi () throws IOException, ClassNotFoundException {
      map<string, integer> limitmap = Ratelimitconf.getlimitmap ();
      Classpathpackagescannerutils scan = new Classpathpackagescannerutils (This.controllerpath);
      list<string> classlist = Scan.getfullyqualifiedclassnamelist ();
      for (String clazz:classlist) {
      class<?> CLZ = Class.forName (clazz);
      if (!clz.isannotationpresent (Restcontroller.class)) {
      Continue
      }
      Method[] methods = Clz.getdeclaredmethods ();
      for (Method method:methods) {
      if (Method.isannotationpresent (Apiratelimit.class)) {
      Apiratelimit apiratelimit = method.getannotation (Apiratelimit.class);
      String Replenishratekey = ApplicationName + "." + Getapiuri (Clz, method) + ". Replenishrate";
      String Burstcapacitykey = ApplicationName + "." + Getapiuri (Clz, method) + ". Burstcapacity";
      Limitmap.put (Replenishratekey, Apiratelimit.replenishrate ());
      Limitmap.put (Burstcapacitykey, apiratelimit.burstcapacity ());
      }
      }
      }
      Ratelimitconf.setlimitmap (LIMITMAP);
      Initialize values to Configuration center
      Confinit.init (ratelimitconf);
      }

      Private String Getapiuri (class<?> Clz, method) {
      StringBuilder uri = new StringBuilder ();
      Uri.append (Clz.getannotation (Requestmapping.class). Value () [0]);
      if (Method.isannotationpresent (Getmapping.class)) {
      Uri.append (Method.getannotation (Getmapping.class). Value () [0]);
      } else if (Method.isannotationpresent (Postmapping.class)) {
      Uri.append (Method.getannotation (Postmapping.class). Value () [0]);
      } else if (Method.isannotationpresent (Requestmapping.class)) {
      Uri.append (Method.getannotation (Requestmapping.class). Value () [0]);
      }
      return uri.tostring ();
      }
      }
      Configuring listeners

Springapplication application = new Springapplication (fshhouseserviceapplication.class);
Application.addlisteners (New Initgatewayapilimitratelistener ("Com.fangjia.fsh.house.controller"));
context = Application.Run (args);
The last use is very simple, just need to add a note to

@ApiRateLimit (replenishrate=10, burstcapacity=100) br/> @GetMapping ("/data")

return new Houseinfo (1L, "Shanghai", "Hongkou", "East Body Area");
}

Summary

My side is just to provide you with a way to achieve the idea, perhaps we have a better plan.
I think as long as I don't let every development care about this kind of non-business function, that's it, it's all handled at the framework level. Of course, the realization of the principle can be shared with you, will use very well, both will use and understand the principle that is better.

Spring Cloud Gateway combines configuration center current limit

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.