常见的限流算法:
- 漏桶算法:能够强行限制数据的传输速率,但是由于流速是恒定的,对突发特性的流量是无法处理的 (nginx限流使用的是漏桶算法,借助limit_conn_zone模块进行限流)
- 令牌桶算法 :能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。
- 滑动窗口算法:它本质上是计数算法,每个时间片段都有独立的计数器,通过这些计数器来限制请求的涌入。
总的来说:
- 令牌桶算法:能够保证自身系统的流量均匀;
- 漏桶算法:保证被调用系统(目标系统)流量均匀。
实际的生产中通常采用 漏桶算法+令牌桶算法的方式来对网络流量进行高效地控制。详细的限流算法介绍可参考我的博客:限流算法
Redis使用令牌桶方式进行限流
自定义接口:AccessLimit
@Inherited
@Documented
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
int limit() default 5; //限制的次数
int sec() default 5;//限制的时间段(秒)
}
配置拦截器:AccessLimitConfig
public class AccessLimitConfig implements WebMvcConfigurer {
@Bean
public AccessLimitInterceptor accessLimitInterceptor(){
return new AccessLimitInterceptor();
}
/**
* 配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(accessLimitInterceptor()).addPathPatterns("/**").excludePathPatterns("/attendance/userinfo/loginValidateCode","/swagger-ui.html","/swagger-resources/configuration/security",
"/swagger-resources/configuration/ui","/swagger*//**","/api/v1/login","/v2/api-docs",
"/configuration/ui", "/swagger-resources", "/configuration/security", "/swagger-ui.html", "/webjars/**");
}
限流拦截器:AccessLimitInterceptor
public class AccessLimitInterceptor implements HandlerInterceptor {
@Autowired
RedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
if (!method.isAnnotationPresent(AccessLimit.class)) {
return true;
}
AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
if (accessLimit == null) {
return true;
}
int limit = accessLimit.limit();
int sec = accessLimit.sec();
String key = CommonUtil.getIpAddr(request) + request.getRequestURI();
Integer maxLimit = null;
if(redisTemplate.opsForValue().get(key)!=null){
maxLimit =Integer.valueOf(redisTemplate.opsForValue().get(key).toString()) ;
}
if (maxLimit == null) {
redisTemplate.opsForValue().set(key, "1", sec, TimeUnit.SECONDS); //set时一定要加过期时间
} else if (maxLimit < limit) {
redisTemplate.opsForValue().set(key, (maxLimit + 1)+"", sec, TimeUnit.SECONDS);
} else {
output(response, "请求太频繁!");
return false;
}
}
return true;
}
public void output(HttpServletResponse response, String msg) throws IOException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
outputStream.write(msg.getBytes("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
} finally {
outputStream.flush();
outputStream.close();
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
接口使用
/**
* 这是用户模块的Controller
*/
@RestController
@RequestMapping("/attendance/commodity")
@Api(value = "商品模块")
public class CommodityController {
@Autowired
CommodityService commodityService;
@Autowired
RabbitTemplate rabbitTemplate;
@Autowired
RedisTemplate redisTemplate;
@Autowired
private Environment env;
//日志记录器
private static Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
/**
* 进行商品秒杀
*/
@PostMapping("/seckill")
@ApiOperation(value = "进行商品秒杀")
@AccessLimit(limit = 4,sec = 10) //加上自定义注解即可
public ResponseResult seckill(HttpServletRequest request) {
logger.info("start UserController.seckill");
String productId ="43486796d2871dfbec0f62951750ea4c";
String user ="admin";
OrderPojo orderPojo =new OrderPojo();
orderPojo.setUserId(user);
orderPojo.setProductId(productId);
orderPojo.setOrderPrice(100L);
orderPojo.setTempId(CommonUtil.getUUID());
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
rabbitTemplate.setExchange(env.getProperty("order.exchange.name"));
rabbitTemplate.setRoutingKey(env.getProperty("order.routing.key.name"));
try {
Message message = MessageBuilder.withBody(JacksonUtils.toJson(orderPojo).getBytes("UTF-8")).build();
//设置请求编码格式
message.getMessageProperties().setHeader(AbstractJavaTypeMapper.DEFAULT_CONTENT_CLASSID_FIELD_NAME, MessageProperties.CONTENT_TYPE_JSON);
rabbitTemplate.convertAndSend(message);
}catch (Exception e){
logger.error("订单消息生产者发送消息失败"+e.getMessage(),e);
return ResponseResult.error(ConstantsUtil.OPERATE_ERROR);
}
logger.info("end UserController.seckill");
return ResponseResult.success(orderPojo.getTempId(),ConstantsUtil.OPERATE_SUCCESS);
}
}
验证