org.springframework.boot
spring-boot-starter-aop
com.google.guava
guava
${guava.version}
org.apache.maven.plugins
maven-compiler-plugin
3.1
${java.version}${java.version}
可以看到,我在项目中除了引用了SpringBoot相关的Jar包外,还引用了guava框架,版本为28.2-jre。
创建核心类
这里,我主要是模拟一个支付接口的限流场景。首先,我们定义一个PayService接口和MessageService接口。PayService接口主要用于模拟后续的支付业务,MessageService接口模拟发送消息。接口的定义分别如下所示。
- PayService
package io.mykit.limiter.service;
import java.math.BigDecimal;
/**
-
@author binghe
-
@version 1.0.0
-
@description 模拟支付
*/
public interface PayService {
int pay(BigDecimal price);
}
- MessageService
package io.mykit.limiter.service;
/**
-
@author binghe
-
@version 1.0.0
-
@description 模拟发送消息服务
*/
public interface MessageService {
boolean sendMessage(String message);
}
接下来,创建二者的实现类,分别如下。
- MessageServiceImpl
package io.mykit.limiter.service.impl;
import io.mykit.limiter.service.MessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
-
@author binghe
-
@version 1.0.0
-
@description 模拟实现发送消息
*/
@Service
public class MessageServiceImpl implements MessageService {
private final Logger logger = LoggerFactory.getLogger(MessageServiceImpl.class);
@Override
public boolean sendMessage(String message) {
logger.info(“发送消息成功===>>” + message);
return true;
}
}
- PayServiceImpl
package io.mykit.limiter.service.impl;
import io.mykit.limiter.service.PayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
-
@author binghe
-
@version 1.0.0
-
@description 模拟支付
*/
@Service
public class PayServiceImpl implements PayService {
private final Logger logger = LoggerFactory.getLogger(PayServiceImpl.class);
@Override
public int pay(BigDecimal price) {
logger.info(“支付成功===>>” + price);
return 1;
}
}
由于是模拟支付和发送消息,所以,我在具体实现的方法中打印出了相关的日志,并没有实现具体的业务逻辑。
接下来,就是创建我们的Controller类PayController,在PayController类的接口pay()方法中使用了限流,每秒钟向桶中放入2个令牌,并且客户端从桶中获取令牌,如果在500毫秒内没有获取到令牌的话,我们可以则直接走服务降级处理。
PayController的代码如下所示。
package io.mykit.limiter.controller;
import com.google.common.util.concurrent.RateLimiter;
import io.mykit.limiter.service.MessageService;
import io.mykit.limiter.service.PayService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
/**
-
@author binghe
-
@version 1.0.0
-
@description 测试接口限流
*/
@RestController
public class PayController {
private final Logger logger = LoggerFactory.getLogger(PayController.class);
/**
- RateLimiter的create()方法中传入一个参数,表示以固定的速率2r/s,即以每秒2个令牌的速率向桶中放入令牌
*/
private RateLimiter rateLimiter = RateLimiter.create(2);
@Autowired
private MessageService messageService;
@Autowired
private PayService payService;
@RequestMapping(“/boot/pay”)
public String pay(){
//记录返回接口
String result = “”;
//限流处理,客户端请求从桶中获取令牌,如果在500毫秒没有获取到令牌,则直接走服务降级处理
boolean tryAcquire = rateLimiter.tryAcquire(500, TimeUnit.MILLISECONDS);
if (!tryAcquire){
result = “请求过多,降级处理”;
logger.info(result);
return result;
}
int ret = payService.pay(BigDecimal.valueOf(100.0));
if(ret > 0){
result = “支付成功”;
return result;
}
result = “支付失败,再试一次吧…”;
return result;
}
}
最后,我们来创建mykit-ratelimiter-test项目的核心启动类,如下所示。
package io.mykit.limiter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
-
@author binghe
-
@version 1.0.0
-
@description 项目启动类
*/
@SpringBootApplication
public class MykitLimiterApplication {
public static void main(String[] args){
SpringApplication.run(MykitLimiterApplication.class, args);
}
}
至此,我们不使用注解方式实现限流的Web应用就基本完成了。
运行项目
项目创建完成后,我们来运行项目,运行SpringBoot项目比较简单,直接运行MykitLimiterApplication类的main()方法即可。
项目运行成功后,我们在浏览器地址栏输入链接:http://localhost:8080/boot/pay。页面会输出“支付成功”的字样,说明项目搭建成功了。如下所示。
此时,我只访问了一次,并没有触发限流。接下来,我们不停的刷浏览器,此时,浏览器会输出“支付失败,再试一次吧…”的字样,如下所示。
在PayController类中还有一个sendMessage()方法,模拟的是发送消息的接口,同样使用了限流操作,具体代码如下所示。
@RequestMapping(“/boot/send/message”)
public String sendMessage(){
//记录返回接口
String result = “”;
//限流处理,客户端请求从桶中获取令牌,如果在500毫秒没有获取到令牌,则直接走服务降级处理
boolean tryAcquire = rateLimiter.tryAcquire(500, TimeUnit.MILLISECONDS);
if (!tryAcquire){
result = “请求过多,降级处理”;
logger.info(result);
return result;
}
boolean flag = messageService.sendMessage(“恭喜您成长值+1”);
if (flag){
result = “消息发送成功”;
return result;
}
result = “消息发送失败,再试一次吧…”;
return result;
}
sendMessage()方法的代码逻辑和运行效果与pay()方法相同,我就不再浏览器访问 http://localhost:8080/boot/send/message 地址的访问效果了,小伙伴们可以自行验证。
不使用注解实现限流缺点
通过对项目的编写,我们可以发现,当在项目中对接口进行限流时,不使用注解进行开发,会导致代码出现大量冗余,每个方法中几乎都要写一段相同的限流逻辑,代码十分冗余。
如何解决代码冗余的问题呢?我们可以使用自定义注解进行实现。
使用自定义注解,我们可以将一些通用的业务逻辑封装到注解的切面中,在需要添加注解业务逻辑的方法上加上相应的注解即可。针对我们这个限流的实例来说,可以基于自定义注解实现。
实现自定义注解
实现,我们来创建一个自定义注解,如下所示。
package io.mykit.limiter.annotation;
import java.lang.annotation.*;
/**
-
@author binghe
-
@version 1.0.0
-
@description 实现限流的自定义注解
*/
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRateLimiter {
//向令牌桶放入令牌的速率
double rate();
//从令牌桶获取令牌的超时时间
long timeout() default 0;
}
自定义注解切面实现
接下来,我们还要实现一个切面类MyRateLimiterAspect,如下所示。
package io.mykit.limiter.aspect;
import com.google.common.util.concurrent.RateLimiter;
import io.mykit.limiter.annotation.MyRateLimiter;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
/**
-
@author binghe
-
@version 1.0.0
-
@description 一般限流切面类
*/
@Aspect
@Component
public class MyRateLimiterAspect {
private RateLimiter rateLimiter = RateLimiter.create(2);
@Pointcut(“execution(public * io.mykit.limiter.controller..(…))”)
public void pointcut(){
}
/**
- 核心切面方法
*/
@Around(“pointcut()”)
public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
//使用反射获取方法上是否存在@MyRateLimiter注解
MyRateLimiter myRateLimiter = signature.getMethod().getDeclaredAnnotation(MyRateLimiter.class);
if(myRateLimiter == null){
//程序正常执行,执行目标方法
return proceedingJoinPoint.proceed();
}
//获取注解上的参数
//获取配置的速率
double rate = myRateLimiter.rate();
//获取客户端等待令牌的时间
long timeout = myRateLimiter.timeout();
//设置限流速率
rateLimiter.setRate(rate);
//判断客户端获取令牌是否超时
boolean tryAcquire = rateLimiter.tryAcquire(timeout, TimeUnit.MILLISECONDS);
if(!tryAcquire){
//服务降级
fullback();
return null;
}
//获取到令牌,直接执行
return proceedingJoinPoint.proceed();
}
/**
- 降级处理
*/
private void fullback() {
response.setHeader(“Content-type”, “text/html;charset=UTF-8”);
PrintWriter writer = null;
try {
writer = response.getWriter();
writer.println(“出错了,重试一次试试?”);
writer.flush();;
} catch (IOException e) {
e.printStackTrace();
}finally {
if(writer != null){
总结:心得体会
既然选择这个行业,选择了做一个程序员,也就明白只有不断学习,积累实战经验才有资格往上走,拿高薪,为自己,为父母,为以后的家能有一定的经济保障。
学习时间都是自己挤出来的,短时间或许很难看到效果,一旦坚持下来了,必然会有所改变。不如好好想想自己为什么想进入这个行业,给自己内心一个答案。
面试大厂,最重要的就是夯实的基础,不然面试官随便一问你就凉了;其次会问一些技术原理,还会看你对知识掌握的广度,最重要的还是你的思路,这是面试官比较看重的。
最后,上面这些大厂面试真题都是非常好的学习资料,通过这些面试真题能够看看自己对技术知识掌握的大概情况,从而能够给自己定一个学习方向。包括上面分享到的学习指南,你都可以从学习指南里理顺学习路线,避免低效学习。
大厂Java架构核心笔记(适合中高级程序员阅读):
private void fullback() {
response.setHeader(“Content-type”, “text/html;charset=UTF-8”);
PrintWriter writer = null;
try {
writer = response.getWriter();
writer.println(“出错了,重试一次试试?”);
writer.flush();;
} catch (IOException e) {
e.printStackTrace();
}finally {
if(writer != null){
总结:心得体会
既然选择这个行业,选择了做一个程序员,也就明白只有不断学习,积累实战经验才有资格往上走,拿高薪,为自己,为父母,为以后的家能有一定的经济保障。
学习时间都是自己挤出来的,短时间或许很难看到效果,一旦坚持下来了,必然会有所改变。不如好好想想自己为什么想进入这个行业,给自己内心一个答案。
面试大厂,最重要的就是夯实的基础,不然面试官随便一问你就凉了;其次会问一些技术原理,还会看你对知识掌握的广度,最重要的还是你的思路,这是面试官比较看重的。
最后,上面这些大厂面试真题都是非常好的学习资料,通过这些面试真题能够看看自己对技术知识掌握的大概情况,从而能够给自己定一个学习方向。包括上面分享到的学习指南,你都可以从学习指南里理顺学习路线,避免低效学习。
大厂Java架构核心笔记(适合中高级程序员阅读):
[外链图片转存中…(img-oXm9hAWq-1721166083921)]