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个请求,如下配置