序文
分散型の高同時実行システムを開発する場合、システムを保護するための3つの強力なツールがあります。キャッシュ、ダウングレード、および電流制限です。
キャッシュ
キャッシングの目的は、システムアクセス速度を向上させ、システム処理能力を向上させることです。
ダウングレードとは、サービスに問題があるか、コアプロセスに影響を与える場合です。ピークまたは問題が解決した後、サービスを一時的にブロックして開く必要があります。
現在の制限の目的は、同時アクセス/リクエストのレートを制限するか、時間枠内のリクエストのレートを制限することによってシステムを保護することです。制限レートに達すると、システムを拒否、キューまたは待機、ダウングレードなどできます。
この記事では、主にapiインターフェイスの電流制限に関連するコンテンツについて説明します。高同時実行性の概念での電流制限については説明していませんが、真実は同じです。電流制限により、システムを比較的安定した状態に保ち、より多くのお客様にサービスを提供することができます。
apiインターフェイスの電流制限の主なアプリケーションシナリオは次のとおりです。
- eコマースシステム(特に6.18、Double 11など)では、現在の制限を使用して、ソフトウェアを使用した注文の悪意のある購入を防ぎます。
- さまざまな基本的なapiインターフェイスの電流制限:たとえば、気象情報の取得、都市インターフェイスに対応するIP、Baidu、Tencentなどが提供する基本インターフェイスはすべて、電流制限を使用して無料と有料の間の直接変換を実現します。
- さまざまなシステムで広く呼び出されているapiインターフェイスは、ネットワークやメモリなどのリソースを大量に消費し、妥当な電流制限を必要とします。
API電流制限戦闘
1つは、RedisをSpringBootに統合する
SpringBootへのRedisの統合は比較的簡単で、手順は次のとおりです。
1.1Redis依存関係の紹介
<!--springboot redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1.2application.ymlでRedisを構成します
spring:
redis:
database: 3 # Redis数据库索引(默认为0)
host: 127.0.0.1 # Redis服务器地址
port: 6379 # Redis服务器连接端口
password: 123456 # Redis服务器连接密码(默认为空)
timeout: 2000 # 连接超时时间(毫秒)
jedis:
pool:
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
max-idle: 20 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
1.3RedisTemplateを構成する
/**
* @Description: redis配置类
* @Author oyc
* @Date 2020/4/22 11:50 下午
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
/**
* RedisTemplate相关配置
* 使redis支持插入对象
*
* @param factory
* @return 方法缓存 Methods the cache
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
// 设置key的序列化器
template.setKeySerializer(new StringRedisSerializer());
// 设置value的序列化器
//使用Jackson 2,将对象序列化为JSON
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//json转对象类,不设置默认的会将json转成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;
}
}
上記では、Redisの統合が完了しており、その後の使用は、以下に示すように、RedisTemplateに直接挿入できます(詳細については、別のブログhttps://blog.csdn.net/u014553029/article/details/106087846を参照してください)。
@Autowired
private RedisTemplate<String, Object> redisTemplate;
2.現在の制限を実現する
2.1カスタムAccessLimit
注釈を追加する
注釈方式を使用してインターフェースの電流制限操作を実現すると、便利でエレガントです。
/**
* @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 时间内 API请求次数
*/
int maxCount() default 5;
/**
* 请求次数的指定时间范围 秒数(redis数据过期时间)
*/
int second() default 60;
}
2.2インターセプターの作成
現在の制限のアイデア
- 使用するIP:パスを一意にユーザの要求を識別するための値としてキーとアクセス時間として
- 訪問するたびに、
key
それが存在するかどうか、およびcount
制限された訪問数を超えているかどうかを判断します - アクセスが制限を超えた場合は、表示のためにフロントエンドに
response
戻す必要がありmsg:请求过于频繁
ます
/**
* @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 实例
if (handler instanceof HandlerMethod) {
// 强转
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取方法
Method method = handlerMethod.getMethod();
// 是否有AccessLimit注解
if (!method.isAnnotationPresent(AccessLimit.class)) {
return true;
}
// 获取注解内容信息
AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
if (accessLimit == null) {
return true;
}
int seconds = accessLimit.second();
int maxCount = accessLimit.maxCount();
// 存储key
String key = request.getRemoteAddr() + ":" + request.getContextPath() + ":" + request.getServletPath();
// 已经访问的次数
Integer count = (Integer) redisTemplate.opsForValue().get(key);
System.out.println("已经访问的次数:" + 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("请求过于频繁请稍后再试");
return false;
}
}
return true;
} catch (Exception e) {
logger.warn("请求过于频繁请稍后再试");
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.4AccessLimitを使用する
/**
* @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