当前位置:网站首页>User defined current limiting annotation
User defined current limiting annotation
2022-07-06 20:34:00 【Little happy】
Redis Except for caching , Can do a lot of things : Distributed lock 、 Current limiting 、 Handle request interface idempotency ... Too much too much ~
Code :https://github.com/1040580896/rate_limiter
1. preparation
First let's create one Spring Boot engineering , introduce Web and Redis rely on , At the same time, considering that the interface current limit is generally marked by annotation , And annotation is through AOP To analyze , So we need to add AOP Dependence , The final dependencies are as follows :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Configuration information
Then prepare one in advance Redis example , Here, after our project is configured , Just configure it Redis The basic information of , as follows :
spring.redis.port=6379
spring.redis.host=120.24.87.xxx
spring.redis.password=asd112211
2. Current limiting notes
Next, we create a current limiting annotation , We divide current limiting into two cases :
- Global current limit for the current interface , For example, the interface can be in 1 Visit... In minutes 100 Time .
- For a certain IP Current limiting of address , For example, a IP The address can be in 1 Visit... In minutes 100 Time .
In both cases , Let's create an enumeration class :
/** * Current limiting type */
public enum LimitType {
/** * The default current limiting side month , Limit current for a certain interface */
Default,
/** * For a certain IP Carry out current limiting */
IP
}
Next, let's create a current limiting annotation :
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD})
public @interface RateLimiter {
/** * Current limiting key, Mainly refers to prefix * @return */
String key() default "rate_limit";
/** * Current limiting time window * @return */
int time() default 60;
/** * Current limiting times in the time window * @return */
int count() default 100;
/** * Type of current limiting * @return */
LimitType limitType() default LimitType.Default;
}
The first parameter is current limiting key, This is just a prefix , The future is complete key Is this prefix plus the full path of the interface method , Together, they form current limiting key, This key Will be saved to Redis in .
The other three parameters are easy to understand , I won't say much more .
Okay , Which interface needs current limiting in the future , Just add... On which interface @RateLimiter
annotation , Then configure relevant parameters .
3. customized RedisTemplate
My friends know , stay Spring Boot in , We are actually more used to using Spring Data Redis To operate Redis, But by default RedisTemplate There's a little pit , Serialization uses JdkSerializationRedisSerializer, I don't know if my friends have noticed , Directly use this serialization tool to save to Redis Upper key and value Will be inexplicably more prefixes , This leads to errors when you read with commands .
For example, when storing ,key yes name,value yes javaboy, But when you operate on the command line ,get name
But you can't get the data you want , The reason is to save to redis after name There are some more characters in front , You can only continue to use RedisTemplate Read it out .
We use it Redis Current limiting will use Lua Script , Use Lua Script time , This will happen , So we need to change it RedisTemplate The serialization scheme of .
*
A little friend may say why not StringRedisTemplate Well ?StringRedisTemplate There really is no problem mentioned above , But the data types it can store are not rich enough , So... Is not considered here .
modify RedisTemplate Serialization scheme , The code is as follows :
@Configuration
public class RedisConfig {
@Bean
RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
template.setKeySerializer(serializer);
template.setHashKeySerializer(serializer);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
return template;
}
@Bean
DefaultRedisScript<Long> limitScript(){
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setResultType(Long.class);
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua")));
return script;
}
}
4. Development Lua Script
Redis For some atomic operations in, we can use Lua Script to achieve , Want to call Lua Script , We have two different ideas :
- stay Redis The server side is well defined Lua Script , Then calculate a hash value , stay Java In the code , Lock which... To execute with this hash value Lua Script .
- Directly in Java The code will Lua Well defined script , Then send it to Redis The server executes .
Spring Data Redis Operations are also provided in Lua Script interface , It's more convenient , So here's the second option .
We are resources New under the directory lua Folders are dedicated to storing lua Script , The script is as follows :
local key = KEYS[1]
local time = tonumber(ARGV[1])
local count = tonumber(ARGV[2])
local current = redis.call('get', key)
if current and tonumber(current) > count then
return tonumber(current)
end
current = redis.call('incr', key)
if tonumber(current) == 1 then
redis.call('expire',key,time)
end
return tonumber(current)
This script is actually not difficult , You probably know what to do at a glance .KEYS and ARGV They are all parameters passed in when calling later ,tonumber Is to turn a string into a number ,redis.call Is to implement specific redis Instructions , The specific process is as follows :
- First get the incoming key as well as Current limiting count And time time.
- adopt get Get this key Corresponding value , This value is how many times this interface can be accessed in the current time window .
- If it's a first visit , The result obtained at this time is nil, Otherwise, the result should be a number , So the next step is to judge , If the result is a number , And this number is greater than count, That means the flow limit has been exceeded , Then you can directly return the query result .
- If the result is nil, The description is the first visit , Now give the current key Self increasing 1, Then set an expiration time .
- Finally, self increment 1 The returned value is OK .
In fact, this paragraph Lua The script is easy to understand .
5. Annotation analysis
Next, we need to customize the section , To parse this annotation , Let's look at the definition of section :
@Aspect
@Component
public class RateLimitAspect {
private static final Logger log = LoggerFactory.getLogger(RateLimitAspect.class);
@Autowired
RedisTemplate<Object, Object> redisTemplate;
@Autowired
RedisScript<Long> redisScript;
@Before("@annotation(rateLimiter)")
public void before(JoinPoint jp, RateLimiter rateLimiter) throws RateLimitException {
int time = rateLimiter.time();
int count = rateLimiter.count();
// Combine key
String combineKey = getCombineKey(rateLimiter, jp);
// call
try {
Long number = redisTemplate.execute(redisScript, Collections.singletonList(combineKey), time, count);
if (number == null || number.intValue() > count) {
// Over current limit threshold
log.info(" Current interface to reach the maximum current limit times ");
throw new RateLimitException(" Too many visits , Please visit later ");
}
log.info(" Number of requests in a time window :{}, Current number of requests :{}, The cache key by {}", count, number, combineKey);
} catch (Exception e) {
throw e;
}
}
/** * This key The number of interface calls is cached in redis Medium key * rate_limit:11.11.11.11-com.th.ratelimit.comtroller.HelloController-hello * rate_limit:com.th.ratelimit.comtroller.HelloController-hello * * @param rateLimiter * @param jp * @return */
private String getCombineKey(RateLimiter rateLimiter, JoinPoint jp) {
StringBuffer key = new StringBuffer(rateLimiter.key());
if (rateLimiter.limitType() == LimitType.IP) {
key.append(IpUtils.getIpAddr(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()))
.append("-");
}
MethodSignature signature = (MethodSignature) jp.getSignature();
Method method = signature.getMethod();
key.append(method.getDeclaringClass().getName())
.append("-")
.append(method.getName());
return key.toString();
}
}
This section is to intercept all added @RateLimiter
Method of annotation , Processing annotations in pre notification .
- First get the... In the annotation key、time as well as count Three parameters .
- Get a combined key, The so-called combined key, It's in the annotation key attributively , Plus the full path of the method , If it is IP In terms of mode , Just add IP Address . With IP Model as an example , Finally generated key Like this :
rate_limit:127.0.0.1-org.javaboy.ratelimiter.controller.HelloController-hello
( If not IP Pattern , So the generated key Not included in IP Address ). - The generated key Put it in the set .
- adopt redisTemplate.execute Method takes and executes a Lua Script , The first parameter is the object encapsulated by the script , The second parameter is key, Corresponds to... In the script KEYS, Followed by variable length parameters , Corresponds to... In the script ARGV.
- take Lua The result of script execution is similar to count Compare , If it is greater than count, It means overload , Just throw an exception .
Okay , It's done .
6. The interface test
Next, let's do a simple test of the interface , as follows :
@RestController
public class HelloController {
@GetMapping("/hello")
@RateLimiter(time = 5,count = 3,limitType = LimitType.IP)
public String hello() {
return "hello>>>"+new Date();
}
}
every last IP Address , stay 5 Can only access... In seconds 3 Time .
This can be tested by manually refreshing the browser .
7. Global exception handling
Because the overload is thrown out of the exception , So we also need a global exception handler , as follows :
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler(ServiceException.class)
public Map<String,Object> serviceException(ServiceException e) {
HashMap<String, Object> map = new HashMap<>();
map.put("status", 500);
map.put("message", e.getMessage());
return map;
}
}
This is a little demo, I won't define entity classes , Direct use Map Come back to JSON 了 .
All right. , Be accomplished .
边栏推荐
- Application layer of tcp/ip protocol cluster
- Database - how to get familiar with hundreds of tables of the project -navicat these unique skills, have you got it? (exclusive experience)
- 知识图谱构建流程步骤详解
- 【每周一坑】输出三角形
- Notes on beagleboneblack
- String length limit?
- Minimum cut edge set of undirected graph
- Boder radius has four values, and boder radius exceeds four values
- Why do novices often fail to answer questions in the programming community, and even get ridiculed?
- 使用.Net分析.Net达人挑战赛参与情况
猜你喜欢
Comment faire une radio personnalisée
[DIY]如何制作一款个性的收音机
[diy] how to make a personalized radio
Cesium Click to draw a circle (dynamically draw a circle)
[weekly pit] information encryption + [answer] positive integer factorization prime factor
Rhcsa Road
SQL injection 2
OLED屏幕的使用
电子游戏的核心原理
Maximum likelihood estimation and cross entropy loss
随机推荐
Deep learning classification network -- zfnet
Unity making plug-ins
Why do novices often fail to answer questions in the programming community, and even get ridiculed?
Wechat applet common collection
2022 refrigeration and air conditioning equipment installation and repair examination contents and new version of refrigeration and air conditioning equipment installation and repair examination quest
In line elements are transformed into block level elements, and display transformation and implicit transformation
Node. Js: express + MySQL realizes registration, login and identity authentication
Extraction rules and test objectives of performance test points
逻辑是个好东西
Linear distance between two points of cesium
use. Net analysis Net talent challenge participation
Technology sharing | packet capturing analysis TCP protocol
SSO single sign on
[cloud lesson] EI lesson 47 Mrs offline data analysis - processing OBS data through Flink
[DIY]如何制作一款个性的收音机
[weekly pit] positive integer factorization prime factor + [solution] calculate the sum of prime numbers within 100
B-jiege's tree (pressed tree DP)
[DSP] [Part 1] start DSP learning
【每周一坑】输出三角形
数字三角形模型 AcWing 1018. 最低通行费