关键词:
文章目录
概述
限流 简言之就是当请求达到一定的并发数或速率,就对服务进行等待、排队、降级、拒绝服务等操作。
限流算法
我们先简单捋一捋限流算法
并发编程-25 高并发处理手段之消息队列思路 + 应用拆分思路 + 应用限流思路
计数器限流
漏桶算法
把水比作是请求,漏桶比作是系统处理能力极限,水先进入到漏桶里,漏桶里的水按一定速率流出,当流出的速率小于流入的速率时,由于漏桶容量有限,后续进入的水直接溢出(拒绝请求),以此实现限流
令牌桶算法
可以简单地理解为医去银行办理业务,只有拿到号以后才可以进行业务办理。
系统会维护一个令牌(token)桶,以一个恒定的速度往桶里放入令牌(token),这时如果有请求进来想要被处理,则需要先从桶里获取一个令牌(token),当桶里没有令牌(token)可取时,则该请求将被拒绝服务。令牌桶算法通过控制桶的容量、发放令牌的速率,来达到对请求的限制。
V1.0
上 guava
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
package com.artisan.controller;
import com.artisan.annos.ArtisanLimit;
import com.google.common.util.concurrent.RateLimiter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@Slf4j
@RestController
@RequestMapping("/rateLimit")
public class RateLimitController
/**
* 限流策略 : 1秒钟1个请求
*/
private final RateLimiter limiter = RateLimiter.create(1);
private DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@SneakyThrows
@GetMapping("/test")
public String testLimiter()
//500毫秒内,没拿到令牌,就直接进入服务降级
boolean tryAcquire = limiter.tryAcquire(500, TimeUnit.MILLISECONDS);
if (!tryAcquire)
log.warn("BOOM 服务降级,时间", LocalDateTime.now().format(dtf));
return "系统繁忙,请稍后再试!";
log.info("获取令牌成功,时间", LocalDateTime.now().format(dtf));
return "业务处理成功";
我们可以看到RateLimiter的2个核心方法:create()、tryAcquire()
- acquire() 获取一个令牌, 改方法会阻塞直到获取到这一个令牌, 返回值为获取到这个令牌花费的时间
- acquire(int permits) 获取指定数量的令牌, 该方法也会阻塞, 返回值为获取到这 N 个令牌花费的时间
- tryAcquire() 判断时候能获取到令牌, 如果不能获取立即返回 false
- tryAcquire(int permits) 获取指定数量的令牌, 如果不能获取立即返回 false
- tryAcquire(long timeout, TimeUnit unit) 判断能否在指定时间内获取到令牌, 如果不能获取立即返回 false
- tryAcquire(int permits, long timeout, TimeUnit unit) 同上
测试一下
V2.0 自定义注解+AOP实现接口限流
1.0的功能实现了,但是业务代码和限流代码混在一起,非常的不美观。
搞依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
搞自定义限流注解
package com.artisan.annos;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface ArtisanLimit
/**
* 资源的key,唯一
* 作用:不同的接口,不同的流量控制
*/
String key() default "";
/**
* 最多的访问限制次数
*/
double permitsPerSecond();
/**
* 获取令牌最大等待时间
*/
long timeout();
/**
* 获取令牌最大等待时间,单位(例:分钟/秒/毫秒) 默认:毫秒
*/
TimeUnit timeunit() default TimeUnit.MILLISECONDS;
/**
* 得不到令牌的提示语
*/
String message() default "系统繁忙,请稍后再试.";
搞AOP
使用AOP切面拦截限流注解
package com.artisan.aop;
import com.artisan.annos.ArtisanLimit;
import com.artisan.resp.ResponseCode;
import com.artisan.resp.ResponseData;
import com.artisan.utils.WebUtils;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@Slf4j
@Aspect
@Component
public class ArtisanLimitAop
/**
* 不同的接口,不同的流量控制
* map的key为 ArtisanLimit.key
*/
private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();
@Around("@annotation(com.artisan.annos.ArtisanLimit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//拿ArtisanLimit的注解
ArtisanLimit limit = method.getAnnotation(ArtisanLimit.class);
if (limit != null)
//key作用:不同的接口,不同的流量控制
String key = limit.key();
RateLimiter rateLimiter = null;
//验证缓存是否有命中key
if (!limitMap.containsKey(key))
// 创建令牌桶
rateLimiter = RateLimiter.create(limit.permitsPerSecond());
limitMap.put(key, rateLimiter);
log.info("新建了令牌桶=,容量=", key, limit.permitsPerSecond());
rateLimiter = limitMap.get(key);
// 拿令牌
boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeunit());
// 拿不到命令,直接返回异常提示
if (!acquire)
log.warn("令牌桶=,获取令牌失败", key);
this.responseFail(limit.message());
return null;
return joinPoint.proceed();
/**
* 直接向前端抛出异常
*
* @param msg 提示信息
*/
private void responseFail(String msg)
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
ResponseData<Object> resultData = ResponseData.fail(ResponseCode.LIMIT_ERROR.getCode(), msg);
WebUtils.writeJson(response, resultData);
用上验证
@GetMapping("/test2")
@ArtisanLimit(key = "testLimit2", permitsPerSecond = 1, timeout = 500, timeunit = TimeUnit.MILLISECONDS, message = "test2 当前排队人数较多,请稍后再试!")
public String test2()
log.info("令牌桶test2获取令牌成功");
return "test2 ok";
源码
https://github.com/yangshangwei/boot2
springboot优雅的实现图片返回
废话少说,直接干货:前提:添加图片转换器@ConfigurationpublicclassWebMvcConfigimplementsWebMvcConfigurer{/***增加图片转换器*@paramconverters*/@OverridepublicvoidextendMessageConverters(List<HttpMessageConverter<?>>co 查看详情
教你优雅的实现springboot并行任务
SpringBoot的定时任务:第一种:把参数配置到.properties文件中:代码:packagecom.accord.task;importjava.text.SimpleDateFormat;importjava.util.Date;importorg.springframework.scheduling.annotation.Scheduled;importorg.springframe 查看详情
springboot在k8s下实现优雅停机
...,否则滚动升级时,还是会影响到业务。所以,我们希望SpringBoot应用实现优雅停机。此次教程基于SpringBoot2.5.0。默认情况下,SpringBoot是直接关机的,所以,需要将优雅停机配置打开。在applicatoin.yaml中配置:这时我们只需要在发... 查看详情
教你优雅的实现springboot并行任务(代码片段)
点击关注公众号,实用技术文章及时了解SpringBoot的定时任务:第一种:把参数配置到.properties文件中:代码:package com.accord.task; import java.text.SimpleDateFormat;import java.util.Date; import org.springfram 查看详情
springboot项目优雅的实现多配置文件切换以及获取配置信息(代码片段)
SpringBoot项目优雅的实现多配置文件切换以及获取配置信息一、构建项目二、创建工具类进行解析yml获取对应的配置数据三、测试在我们平时的生产中肯定不会单纯的的只有一个配置文件,通产会分为测试、开发、生产三个版... 查看详情
springboot-优雅的实现应用启动参数校验
文章目录需求三部曲Step1Properties类搞上Validation相关配置Step2启动测试Step3配上试试自定义校验规则Step1搞接口实现Step2搞属性文件Step3搞自定义校验规则Step4验证一把源码需求有个参数非常非常非常非常非常重要,如果未配置或... 查看详情
springboot并行任务,这才是优雅的实现方式!(代码片段)
SpringBoot的定时任务:第一种:把参数配置到.properties文件中:代码:packagecom.accord.task;importjava.text.SimpleDateFormat;importjava.util.Date;importorg.springframework.scheduling.annotation.Sch 查看详情
教你优雅的实现springboot并行任务(代码片段)
👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇SpringBoot的定时任务:第一种:把参数配置到.properties文件中:代码:package com.accord.task; import java.text.SimpleDateFormat;impor 查看详情
springboot优雅的配置拦截器方式
其实springboot拦截器的配置方式和springMVC差不多,只有一些小的改变需要注意下就ok了。下面主要介绍两种常用的拦截器:一、基于URL实现的拦截器:publicclassLoginInterceptorextendsHandlerInterceptorAdapter{/***在请求处理之前进行调用(Contr... 查看详情
springboot优雅的配置拦截器方式
步骤:1.实现WebMvcConfigurer配置类2.实现拦截器3.把拦截器添加到配置中4.添加需要拦截的请求5.添加需要排除的请求1packagecom.zp.springbootdemo.interceptor;23importorg.springframework.context.annotation.Bean;4importorg.springframework.context.anno 查看详情
springboot优雅的实现重处理功能(代码片段)
点击上方关注“终端研发部”设为“星标”,和你一起掌握更多数据库知识在实际工作中,重处理是一个非常常见的场景,比如:发送消息失败。调用远程服务失败。争抢锁失败。这些错误可能是因为网络波动造成的... 查看详情
springboot实战:在requestbody中优雅的使用枚举参数(代码片段)
该图片由Christian_Crowd在Pixabay上发布你好,我是看山。前文说到优雅的使用枚举参数和实现原理,本文继续说一下如何在RequestBody中优雅使用枚举。本文先上实战,说一下如何实现。在优雅的使用枚举参数代码的基础上... 查看详情
springboot实现业务校验,这种方式才叫优雅!
Hollis的新书限时折扣中,一本深入讲解Java基础的干货笔记!在日常的接口开发中,为了保证接口的稳定安全,我们一般需要在接口逻辑中处理两种校验:参数校验业务规则校验首先我们先看看参数校验。参数... 查看详情
springboot应用优雅重启-actuator
参考技术ASpringBoot最大特点便是简化配置,提升开发效率,实现简单部署就是通过内嵌一个Web容器,如果Tomcat、Jettty等。对于SpringBoot应用,只需打包成一个简单的Jar包,然后执行java-jar就可以启动,是一种非常优雅的方式,但同... 查看详情
springboot项目优雅的实现多配置文件切换以及获取配置信息(代码片段)
SpringBoot项目优雅的实现多配置文件切换以及获取配置信息一、构建项目二、创建工具类进行解析yml获取对应的配置数据三、测试在我们平时的生产中肯定不会单纯的的只有一个配置文件,通产会分为测试、开发、生产三个版... 查看详情
springboot项目优雅的实现多配置文件切换以及获取配置信息(代码片段)
SpringBoot项目优雅的实现多配置文件切换以及获取配置信息一、构建项目二、创建工具类进行解析yml获取对应的配置数据三、测试在我们平时的生产中肯定不会单纯的的只有一个配置文件,通产会分为测试、开发、生产三个版... 查看详情
springboot一个注解实现重试机制,不能太优雅了。。。(代码片段)
来源:blog.csdn.net/h254931252/article/details/109257998前言在实际工作中,重处理是一个非常常见的场景,比如:发送消息失败。调用远程服务失败。争抢锁失败。这些错误可能是因为网络波动造成的,等待过后重处理就能... 查看详情
springboot中如何实现业务校验,这种方式才叫优雅!(代码片段)
大家好,我是飘渺。在日常的接口开发中,为了保证接口的稳定安全,我们一般需要在接口逻辑中处理两种校验:参数校验业务规则校验首先我们先看看参数校验。参数校验参数校验很好理解,比如登录的时... 查看详情