Springboot+Redis實(shí)現(xiàn)API接口防刷限流的項(xiàng)目實(shí)踐
前言
在開發(fā)分布式高并發(fā)系統(tǒng)時(shí)有三把利器用來保護(hù)系統(tǒng):緩存、降級(jí)、限流。
緩存:緩存的目的是提升系統(tǒng)訪問速度和增大系統(tǒng)處理容量
降級(jí):降級(jí)是當(dāng)服務(wù)出現(xiàn)問題或者影響到核心流程時(shí),需要暫時(shí)屏蔽掉,待高峰或者問題解決后再打開
限流:限流的目的是通過對(duì)并發(fā)訪問/請(qǐng)求進(jìn)行限速,或者對(duì)一個(gè)時(shí)間窗口內(nèi)的請(qǐng)求進(jìn)行限速來保護(hù)系統(tǒng),一旦達(dá)到限制速率則可以拒絕服務(wù)、排隊(duì)或等待、降級(jí)等處理
本文主要講的是api接口限流相關(guān)內(nèi)容,雖然不是論述高并發(fā)概念中的限流, 不過道理都差不多。通過限流可以讓系統(tǒng)維持在一個(gè)相對(duì)穩(wěn)定的狀態(tài),為更多的客戶提供服務(wù)。
api接口的限流主要應(yīng)用場景有:
- 電商系統(tǒng)(特別是6.18、雙11等)中的秒殺活動(dòng),使用限流防止使用軟件惡意刷 單;
- 各種基礎(chǔ)api接口限流:例如天氣信息獲取,IP對(duì)應(yīng)城市接口,百度、騰訊等對(duì)外提供的基礎(chǔ)接口,都是通過限流來實(shí)現(xiàn)免費(fèi)與付費(fèi)直接的轉(zhuǎn)換。
- 被各種系統(tǒng)廣泛調(diào)用的api接口,嚴(yán)重消耗網(wǎng)絡(luò)、內(nèi)存等資源,需要合理限流。
api限流實(shí)戰(zhàn)
一、SpringBoot中集成Redis
SpringBoot中集成Redis相對(duì)比較簡單,步驟如下:
1.1 引入Redis依賴
<!--springboot redis依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>1.2 在application.yml中配置Redis
spring:
redis:
database: 3 # Redis數(shù)據(jù)庫索引(默認(rèn)為0)
host: 127.0.0.1 # Redis服務(wù)器地址
port: 6379 # Redis服務(wù)器連接端口
password: 123456 # Redis服務(wù)器連接密碼(默認(rèn)為空)
timeout: 2000 # 連接超時(shí)時(shí)間(毫秒)
jedis:
pool:
max-active: 200 # 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
max-idle: 20 # 連接池中的最大空閑連接
min-idle: 0 # 連接池中的最小空閑連接
max-wait: -1 # 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)1.3 配置RedisTemplate
/**
* @Description: redis配置類
* @Author oyc
* @Date 2020/4/22 11:50 下午
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
/**
* RedisTemplate相關(guān)配置
* 使redis支持插入對(duì)象
*
* @param factory
* @return 方法緩存 Methods the cache
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置連接工廠
template.setConnectionFactory(factory);
// 設(shè)置key的序列化器
template.setKeySerializer(new StringRedisSerializer());
// 設(shè)置value的序列化器
//使用Jackson 2,將對(duì)象序列化為JSON
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//json轉(zhuǎn)對(duì)象類,不設(shè)置默認(rèn)的會(huì)將json轉(zhuǎn)成hashmap
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
return template;
}
}
以上,已經(jīng)完成Redis的集成,后續(xù)使用可以直接注入RedisTemplate(具體可參考另一篇博客:https://blog.csdn.net/u014553029/article/details/106087846),如下所示:
@Autowired private RedisTemplate<String, Object> redisTemplate;
二、實(shí)現(xiàn)限流
2.1 添加自定義AccessLimit注解
使用注解方式實(shí)現(xiàn)接口的限流操作,方便而優(yōu)雅。
/**
* @Description:
* @Author oyc
* @Date 2020/10/22 11:16 下午
*/
@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
/**
* 指定second 時(shí)間內(nèi) API請(qǐng)求次數(shù)
*/
int maxCount() default 5;
/**
* 請(qǐng)求次數(shù)的指定時(shí)間范圍 秒數(shù)(redis數(shù)據(jù)過期時(shí)間)
*/
int second() default 60;
}
2.2 編寫攔截器
限流的思路
- 通過路徑:ip的作為key,訪問次數(shù)為value的方式對(duì)某一用戶的某一請(qǐng)求進(jìn)行唯一標(biāo)識(shí)
- 每次訪問的時(shí)候判斷
key是否存在,是否count超過了限制的訪問次數(shù) - 若訪問超出限制,則應(yīng)
response返回msg:請(qǐng)求過于頻繁給前端予以展示
/**
* @Description: 訪問攔截器
* @Author oyc
* @Date 2020/10/22 11:20 下午
*/
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {// Handler 是否為 HandlerMethod 實(shí)例
if (handler instanceof HandlerMethod) {
// 強(qiáng)轉(zhuǎn)
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 獲取方法
Method method = handlerMethod.getMethod();
// 是否有AccessLimit注解
if (!method.isAnnotationPresent(AccessLimit.class)) {
return true;
}
// 獲取注解內(nèi)容信息
AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
if (accessLimit == null) {
return true;
}
int seconds = accessLimit.second();
int maxCount = accessLimit.maxCount();
// 存儲(chǔ)key
String key = request.getRemoteAddr() + ":" + request.getContextPath() + ":" + request.getServletPath();
// 已經(jīng)訪問的次數(shù)
Integer count = (Integer) redisTemplate.opsForValue().get(key);
System.out.println("已經(jīng)訪問的次數(shù):" + count);
if (null == count || -1 == count) {
redisTemplate.opsForValue().set(key, 1, seconds, TimeUnit.SECONDS);
return true;
}
if (count < maxCount) {
redisTemplate.opsForValue().increment(key);
return true;
}
if (count >= maxCount) {
logger.warn("請(qǐng)求過于頻繁請(qǐng)稍后再試");
return false;
}
}
return true;
} catch (Exception e) {
logger.warn("請(qǐng)求過于頻繁請(qǐng)稍后再試");
e.printStackTrace();
}
return true;
}
}
2.3 注冊攔截器并配置攔截路徑和不攔截路徑
/**
* @Description: 訪問攔截器配置
* @Author oyc
* @Date 2020/10/22 11:34 下午
*/
@Configuration
public class IntercepterConfig implements WebMvcConfigurer {
@Autowired
private AccessLimitInterceptor accessLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(accessLimitInterceptor)
.addPathPatterns("/**").excludePathPatterns("/static/**","/login.html","/user/login");
}
}
2.4 使用AccessLimit
/**
* @Description:
* @Author oyc
* @Date 2020/10/22 11:36 下午
*/
@RestController
@RequestMapping("access")
public class AccessLimitController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 限流測試
*/
@GetMapping
@AccessLimit(maxCount = 3,second = 60)
public String limit(HttpServletRequest request) {
logger.error("Access Limit Test");
return "限流測試";
}
}
2.4 測試

源碼傳送門:https://github.com/oycyqr/springboot-learning-demo/tree/master/springboot-validated
到此這篇關(guān)于Springboot+Redis實(shí)現(xiàn)API接口防刷限流的項(xiàng)目實(shí)踐的文章就介紹到這了,更多相關(guān)Springboot Redis 接口防刷限流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot整合Redis并且用Redis實(shí)現(xiàn)限流的方法 附Redis解壓包
- 基于SpringBoot+Redis實(shí)現(xiàn)一個(gè)簡單的限流器
- SpringBoot使用Redis對(duì)用戶IP進(jìn)行接口限流的項(xiàng)目實(shí)踐
- SpringBoot使用Redis對(duì)用戶IP進(jìn)行接口限流的示例詳解
- SpringBoot Redis用注釋實(shí)現(xiàn)接口限流詳解
- 使用SpringBoot?+?Redis?實(shí)現(xiàn)接口限流的方式
- SpringBoot中使用Redis對(duì)接口進(jìn)行限流的實(shí)現(xiàn)
- springboot+redis 實(shí)現(xiàn)分布式限流令牌桶的示例代碼
- SpringBoot整合redis實(shí)現(xiàn)計(jì)數(shù)器限流的示例
相關(guān)文章
Spring?Boot整合持久層之JdbcTemplate多數(shù)據(jù)源
持久層是JavaEE中訪問數(shù)據(jù)庫的核心操作,SpringBoot中對(duì)常見的持久層框架都提供了自動(dòng)化配置,例如JdbcTemplate、JPA 等,MyBatis 的自動(dòng)化配置則是MyBatis官方提供的。接下來分別向讀者介紹Spring Boot整合這持久層技術(shù)中的整合JdbcTemplate2022-08-08
Java HttpURLConnection超時(shí)和IO異常處理
這篇文章主要介紹了Java HttpURLConnection超時(shí)和IO異常處理的相關(guān)資料,需要的朋友可以參考下2016-09-09
Fluent Mybatis實(shí)現(xiàn)環(huán)境隔離和租戶隔離
我們在實(shí)際的業(yè)務(wù)開發(fā)中,經(jīng)常會(huì)碰到環(huán)境邏輯隔離和租戶數(shù)據(jù)邏輯隔離的問題。本文就詳細(xì)的來介紹一下,感興趣的小伙伴們可以參考一下2021-08-08
springboot2 生產(chǎn)部署注意事項(xiàng)及示例代碼
這篇文章主要介紹了springboot2 生產(chǎn)部署注意事項(xiàng)及示例代碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
鄰接表無向圖的Java語言實(shí)現(xiàn)完整源碼
這篇文章主要介紹了鄰接表無向圖的Java語言實(shí)現(xiàn)完整源碼,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12
Spring Boot2中如何優(yōu)雅地個(gè)性化定制Jackson實(shí)現(xiàn)示例
這篇文章主要為大家介紹了Spring Boot2中如何優(yōu)雅地個(gè)性化定制Jackson實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05

