preface
At the beginning, we planned to implement the leaky bucket current limiting algorithm through the thread pool, but after the actual analysis, we found that it does not seem feasible. There are two difficulties. One is the resource problem. If each interface method creates a thread pool, it is unimaginable; Another problem is that if a thread pool is adopted globally, it will not be possible to achieve refined interface flow restriction, which seems not flexible enough, so I gave up. Here is my initial idea:
Define a thread pool. The leaky bucket is realized through the thread pool work queue. The leaky bucket exit rate is controlled by the thread sleep. Discarding requests exceeding the capacity is realized through the thread pool rejection policy.
Finally, I directly found an implementation algorithm that can be found on the network to complete today's example demo. Let's start directly.
Implementation of leaky bucket algorithm
First, let's review the leaky bucket current limiting algorithm. Its specific principle is as follows: we need to define a leaky bucket with fixed capacity. Because the number of external requests is uncertain, we need to control the number of requests through the capacity of the leaky bucket. At the same time, to determine the rate of leaky bucket release request (exit), we control the frequency at which the interface service is called through the exit rate. When the number of requests in the leaky bucket reaches the upper limit, all requests to join the leaky bucket will be discarded.
After studying the leaky bucket algorithm in detail, you will find that there are two ways to deal with request discarding. One is to directly discard the request and return error information. The other is to let the current request in and out of the blocking state. After the resources are released from the leaky bucket, put the request into the leaky bucket. Today, let's look at the first one. As for the second one, I'll wait until I have studied it clearly.
Create project
Like yesterday, we first created a web project for spring boot, but today's project is relatively simple. There is no need to introduce any external packages. Just for the convenience of testing, I introduced fastjason's dependency:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.72</version> </dependency>
Core business realization
Let's first look at the implementation of leaky bucket current limiting algorithm:
public final class LeakyBucket { // Barrel capacity private int capacity = 10; // Amount of water drops remaining in the bucket (empty bucket at initialization) private AtomicInteger water = new AtomicInteger(0); // The outflow rate of water droplets is 1 drop per 1000 milliseconds private int leakRate; // After the first request, the barrel began to leak at this point in time private long leakTimeStamp; public LeakyBucket(int capacity, int leakRate) { this.capacity = capacity; this.leakRate = leakRate; } public LeakyBucket(int leakRate) { this.leakRate = leakRate; } public boolean acquire() { // If it is an empty bucket, the current time is taken as the bucket opening time if (water.get() == 0) { leakTimeStamp = System.currentTimeMillis(); water.addAndGet(1); return capacity != 0; } // First perform water leakage and calculate the remaining water volume int waterLeft = water.get() - ((int) ((System.currentTimeMillis() - leakTimeStamp) / 1000)) * leakRate; water.set(Math.max(0, waterLeft)); // Update leakTimeStamp again leakTimeStamp = System.currentTimeMillis(); // Try adding water and the water is not full if ((water.get()) < capacity) { water.addAndGet(1); return true; } else { // Refuse to add water when the water is full return false; } } }
At present, the retrieval on the network is basically this implementation (I don't know who copied who, and I don't have the face to speak. After all, I'm also a code Porter).
As for the implementation of the leaky bucket algorithm, the core point is the acquire() method. This method will judge whether the leaky bucket is full. If it is full, it will directly return false. Calling this method for the first time will return true. From the second time, the remaining water in the leaky bucket will be calculated and the water volume will be updated. If the water volume does not reach the upper limit, the water volume will be + 1 and return true.
However, the implementation problem of this algorithm is also obvious: the leakRate process is used to calculate the remaining water level and does not participate in other operations, which leads to the uneven outlet of the leaky bucket. A more reasonable approach is to calculate the sleep time through the rate, and then control the uniformity of the rate through the sleep time. Today, due to the relationship of time, I will continue to go down. There will be time later, and I will share after optimization.
Interceptor implementation
Today's speed limit is still achieved through interceptors, and the implementation process is relatively simple:
@Component public class LeakyBucketLimiterInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; // Judge whether the method contains counter limit. With this annotation, speed limit operation is required if (handlerMethod.hasMethodAnnotation(LeakyBucketLimit.class)) { LeakyBucketLimit annotation = handlerMethod.getMethod().getAnnotation(LeakyBucketLimit.class); LeakyBucket leakyBucket = (LeakyBucket)BeanTool.getBean(annotation.limitClass()); boolean acquire = leakyBucket.acquire(); response.setContentType("text/json;charset=utf-8"); JSONObject result = new JSONObject(); if (acquire) { result.put("result", "Request succeeded"); } else { result.put("result", "Access limit reached, access prohibited"); response.getWriter().print(JSON.toJSONString(result)); } System.out.println(result); return acquire; } } return Boolean.TRUE; } }
First, I build the bean of the leaky bucket algorithm in the configuration class, then get the instance of the leaky bucket algorithm in the interceptor, and execute its acquire() to intercept. If the leaky bucket is successfully added, I will access the relevant interface, otherwise I will directly return the error information. The following is the configuration of leaky bucket algorithm:
@Configuration public class LeakyBucketConfig { @Bean("leakyBucket") public LeakyBucket leakyBucket() { return new LeakyBucket(10, 5); } }
Then the interceptor annotation:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LeakyBucketLimit { /** * Name of current limiter * @return */ String limitBeanName(); /** * Interceptor class * * @return */ Class<?> limitClass() default LeakyBucket.class; }
Add this annotation to our target interface to realize the current limiting operation:
@LeakyBucketLimit(limitBeanName = "leakyBucket") @GetMapping("/bucket") public Object bucketTest() { JSONObject result = new JSONObject(); result.put("result", "Request succeeded"); logger.info("timestamp: {}, result: {}", System.currentTimeMillis(), result); return result; }
test
The test here can be directly called in batch through postman (Baidu can be used for details):
Here, I create 20 threads and call the interface directly:
It can be seen from the call results that we initiated 20 requests at the same time, but the system only accepted 10 requests (that is, the upper limit of leaky bucket), and the other requests were directly discarded, indicating that the current limiting effect has been achieved. However, from the time stamp of system operation, the implementation exit of this current limiting algorithm is not uniform, Make complaints about the current limit of the counter that we shared yesterday, and of course, this is what I want to do. So when you buddy the online code, you must practice it in person, and you can't copy the homework blindly.
epilogue
To sum up, I have said earlier: I am not satisfied with this algorithm. Because its export rate is not uniform and needs to be further optimized, today's demo example is only half successful - the idea of web Implementation of leaky bucket algorithm has been shared, mainly the idea of business layer and current limit decoupling, but the core implementation of leaky bucket algorithm has not been solved. Later, I intend to refer to the sleep operation of ratelimit of guava to optimize the above algorithm, So let's stop here today. Good night, everyone!