Springboot整合 RateLimiter实现限流
依赖
<!--RateLimiter的底层是基于令牌桶算法来实现的,来自谷歌的Guava包中-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
代码部分
package com.ip.config;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Description:
* @Author: Yourheart
* @Create: 2022/8/4 12:54
*/
@Component
public class LimitFilter implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//每秒只能接收10个请求
registry.addInterceptor(new LimitInterceptor(10,LimitInterceptor.LimitType.DROP))
.addPathPatterns("/**")
//忽略拦截
.excludePathPatterns("/login");
}
}
package com.ip.config;
import com.google.common.util.concurrent.RateLimiter;
import com.ip.exception.CustomizeErrorException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;
/**
* @Description:
* @Author: Yourheart
* @Create: 2022/8/4 12:55
*/
@Component
@Slf4j
public class LimitInterceptor extends HandlerInterceptorAdapter {
public enum LimitType {
/**
* 丢弃
*/
DROP,
/**
* 等待
*/
WAIT
}
/**
* Guava 开源工具限流工具类
* 限流器
*/
private RateLimiter limiter;
/**
* 限流方式
*/
private LimitType limitType = LimitType.DROP;
public LimitInterceptor() {
this.limiter = RateLimiter.create(1);
}
/**
* @param tps 限流(每秒处理量)
* @param limitType
*/
public LimitInterceptor(int tps, LimitInterceptor.LimitType limitType) {
this.limiter = RateLimiter.create(tps);
this.limitType = limitType;
}
/**
* @param permitsPerSecond 每秒新增的令牌数
* @param limitType 限流类型
*/
public LimitInterceptor(double permitsPerSecond, LimitInterceptor.LimitType limitType) {
this.limiter = RateLimiter.create(permitsPerSecond, 10, TimeUnit.MILLISECONDS);
this.limitType = limitType;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (limitType.equals(LimitType.DROP)) {
//尝试获取一个令牌,立即返回
if (limiter.tryAcquire()) {
return super.preHandle(request, response, handler);
}
}
/**
* 达到限流后,往页面提示的错误信息
*/
throw new CustomizeErrorException("亲爱的主人,您已经频繁使用我很多次了,请休息后稍后再试哟");
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
}
public RateLimiter getLimiter() {
return limiter;
}
public void setLimiter(RateLimiter limiter) {
this.limiter = limiter;
}
}
定制化异常
package com.ip.exception;
/**
* @Description:
* @Author: Yourheart
* @Create: 2022/8/4 13:51
*/
public class CustomizeErrorException extends Exception {
public CustomizeErrorException() {
}
public CustomizeErrorException(String message) {
super(message);
}
}
package com.ip.exception;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.javassist.NotFoundException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.util.NestedServletException;
import java.util.HashMap;
import java.util.Map;
/**
* @Description:
* @Author: Yourheart
* @Create: 2021/9/1 09:47
*/
@ControllerAdvice
@Slf4j
public class MyExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseBody
public Map<String,Object> handleException(Exception e){
log.error("全局异常打印:",e);
Map<String,Object> map=new HashMap<>(16);
map.put("code","-101");
map.put("msg",e.getMessage());
return map;
}
}
效果图

以下是完整代码部分
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ip</groupId>
<artifactId>ip-service</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<skipTests>true</skipTests>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--tomcat容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--添加fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7</version>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--springboot整合mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<!-- 热部署模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
</dependency>
<!--java爬虫需要的jar包-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.12.2</version>
</dependency>
<!--判断空的用法 -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.10.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>1.7.2</version>
</dependency>
<!--RateLimiter的底层是基于令牌桶算法来实现的,来自谷歌的Guava包中-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<finalName>server0807</finalName>
</build>
</project>
package com.ip.controller.front;
import com.alibaba.fastjson.JSONObject;
import com.ip.service.QueryIpService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author yourheart
* @Description
* @create 2021-06-06 10:59
*/
@Controller
@RequestMapping("/queryIp")
@Slf4j
public class QueryIpController {
@Autowired
private QueryIpService queryIpService;
@RequestMapping("/queryIpCity/{ip}")
@ResponseBody
public String queryIpCity(@PathVariable String ip){
long a = System.currentTimeMillis();
JSONObject ipCityByIp = queryIpService.getIpCityByIp(ip);
String jsonString = ipCityByIp.toJSONString();
long b = System.currentTimeMillis();
log.info("接口调用耗时:{}",(b-a)+"ms");
return jsonString;
}
}
package com.ip.service;
import com.alibaba.fastjson.JSONObject;
/**
* @author yourheart
* @Description
* @create 2021-06-06 10:44
*/
public interface QueryIpService {
/**
* 获取ip地址的信息
* @return
*/
JSONObject getIpCity();
/**
* 通过ip地址查询对应的ip信息
* @param ip
* @return
*/
JSONObject getIpCityByIp(String ip);
/**
* 通过ip查询对应的城市信息
* @param ip
* @return
*/
String queryCityByIp(String ip);
}
package com.ip.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.ip.bean.QueryIpBean;
import com.ip.constants.Constant;
import com.ip.mapper.QueryIpMapper;
import com.ip.service.QueryIpService;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClientException;
import java.io.IOException;
/**
* @author yourheart
* @Description
* @create 2021-06-06 10:46
*/
@Service
@Slf4j
public class QueryIpServiceImpl implements QueryIpService {
@Autowired
private QueryIpMapper queryIpMapper;
/**
* 获取ip地址的信息
* @return
*/
@Override
public JSONObject getIpCity() {
return null;
}
public String recordIp(String ip) {
String url = "http://ip.taobao.com/outGetIpInfo?ip=" + ip + "&accessKey=alibaba-inc";
String jsonStr = okGetArt(url);
JSONObject jsonObject = JSONObject.parseObject(jsonStr);
JSONObject result = (JSONObject) jsonObject.get("data");
return result.get("country") +""+ result.get("region")+""+ result.get("city")+""+result.get("isp");
}
public String okGetArt(String url){
String html=null;
OkHttpClient okHttpClient = new OkHttpClient();
Request build = new Request.Builder().url(url).addHeader("Content-type", "charset=utf-8").build();
try (Response responseObj = okHttpClient.newCall(build).execute()){
html = responseObj.body().string();
} catch (IOException e) {
e.printStackTrace();
}
return html;
}
public JSONObject judgeIp(String ip) {
long a = System.currentTimeMillis();
int queryIpNum = queryIpMapper.queryIpNum(ip);
long b = System.currentTimeMillis();
log.info("queryIpNum查询耗时:{}",(b-a)+"ms");
log.info("queryIpNum:{}", queryIpNum);
if (queryIpNum == 1) {
long a1 = System.currentTimeMillis();
QueryIpBean queryIpBean = queryIpMapper.queryIp(ip);
long b1 = System.currentTimeMillis();
log.info("queryIp查询耗时:{}",(b1-a1)+"ms");
log.info("queryIpBean:{}", queryIpBean);
JSONObject object1=new JSONObject();
object1.put("ip", ip);
object1.put("address",queryIpBean.getIpContent());
return object1;
} else if (queryIpNum == 0) {
//调用接口获取ip地址的信息
String addr = null;
try {
addr = recordIp(ip);
log.info("调用接口返回参数:{}",addr);
} catch (RestClientException e) {
String city="城市信息未能获取";
JSONObject object1=new JSONObject();
object1.put("ip", ip);
object1.put("address",city);
return object1;
}
JSONObject object=new JSONObject();
object.put("ip",ip);
object.put("address",addr);
QueryIpBean queryIpBean = new QueryIpBean();
queryIpBean.setIp(ip);
queryIpBean.setIpContent(addr);
int addIp = queryIpMapper.addIp(queryIpBean);
if (addIp == 1) {
log.info("ip数据库中新增一条ip信息");
return object;
} else {
log.info("新增ip信息添加失败");
return object;
}
}
return null;
}
/**
* 通过ip地址查询对应的ip信息
* @param ips
* @return
*/
@Override
public JSONObject getIpCityByIp(String ips) {
log.info("入参:"+ips);
if (Constant.NULL.equals(ips)){
JSONObject object=new JSONObject();
object.put("ip","0.0.0.0");
object.put("address","小坏蛋,你给我个null,就想骗我的人");
return object;
}
JSONObject object1 = new JSONObject();
String ipp=ips;
if (StringUtils.isEmpty(ipp)){
String ip="127.0.0.1";
String city="ip地址为null";
object1.put("ip", ip);
object1.put("city",city);
return object1;
}
return judgeIp(ips);
}
protected String getAddress(JSONObject jsonObject,String isp){
String address=null;
if (StringUtils.isNotEmpty(jsonObject.getString(Constant.COUNTRY))){
address=jsonObject.getString("Country");
}
if (StringUtils.isNotEmpty(jsonObject.getString(Constant.PROVINCE))){
address=address+jsonObject.getString("Province");
}
if (StringUtils.isNotEmpty(jsonObject.getString(Constant.CITY))){
address=address+jsonObject.getString("City");
}
if (StringUtils.isNotEmpty(isp)){
address=address+isp;
}
if (StringUtils.isEmpty(address)){
address="地址飘到火星了";
}
return address;
}
/**
* 通过ip查询对应的城市信息
* @param ip
* @return
*/
@Override
public String queryCityByIp(String ip) {
return null;
}
}
package com.ip.bean;
import lombok.Data;
/**
* @author yourheart
* @Description
* @create 2021-11-23 22:03
*/
@Data
public class QueryIpBean {
/**
* 主键
*/
private String id;
/**
* ip地址
*/
private String ip;
/**
* ip地址的详细信息
*/
private String ipContent;
/**
* ip信息存储时间
*/
private String ipTime;
}
建议使用压测,测试是否可以限流,压测工具使用教程地址
https://www.cnblogs.com/q202105271618/p/17214621.html
每秒只能接受10个请求,如下配置



浙公网安备 33010602011771号