当前位置:网站首页>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 .
边栏推荐
- [diy] how to make a personalized radio
- Entity alignment two of knowledge map
- Recyclerview not call any Adapter method :onCreateViewHolder,onBindViewHolder,
- PHP online examination system version 4.0 source code computer + mobile terminal
- The mail command is used in combination with the pipeline command statement
- 电子游戏的核心原理
- 02 基础入门-数据包拓展
- Comment faire une radio personnalisée
- Use of OLED screen
- 报错分析~csdn反弹shell报错
猜你喜欢
Detailed explanation of knowledge map construction process steps
OLED屏幕的使用
JMeter server resource indicator monitoring (CPU, memory, etc.)
Learn to punch in Web
[network planning] Chapter 3 data link layer (4) LAN, Ethernet, WLAN, VLAN
持续测试(CT)实战经验分享
Number of schemes from the upper left corner to the lower right corner of the chessboard (2)
数字三角形模型 AcWing 1018. 最低通行费
[Yann Lecun likes the red stone neural network made by minecraft]
BeagleBoneBlack 上手记
随机推荐
【计网】第三章 数据链路层(3)信道划分介质访问控制
解剖生理学复习题·VIII血液系统
In unity space, an object moves around a fixed point on the sphere at a fixed speed
【计网】第三章 数据链路层(4)局域网、以太网、无线局域网、VLAN
Tencent byte Alibaba Xiaomi jd.com offer got a soft hand, and the teacher said it was great
Rhcsa Road
2022 Guangdong Provincial Safety Officer C certificate third batch (full-time safety production management personnel) simulation examination and Guangdong Provincial Safety Officer C certificate third
HMS core machine learning service creates a new "sound" state of simultaneous interpreting translation, and AI makes international exchanges smoother
Activiti global process monitors activitieventlistener to monitor different types of events, which is very convenient without configuring task monitoring in acitivit
【每周一坑】输出三角形
设计你的安全架构OKR
【每周一坑】正整数分解质因数 +【解答】计算100以内质数之和
Rhcsa Road
5. 无线体内纳米网:十大“可行吗?”问题
Anaconda安装后Jupyter launch 没反应&网页打开运行没执行
[cloud native and 5g] micro services support 5g core network
recyclerview gridlayout 平分中间空白区域
"Penalty kick" games
Is it difficult for small and micro enterprises to make accounts? Smart accounting gadget quick to use
Build your own application based on Google's open source tensorflow object detection API video object recognition system (IV)