实战限流(guava的RateLimiter)

简介: guava的RateLimiter使用的是令牌桶算法,也就是以固定的频率向桶中放入令牌,本文实战一下RateLimiter的用法

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码): https://github.com/zq2599/blog_demos

关于限流

  • 常用的限流算法有漏桶算法和令牌桶算法,guava的RateLimiter使用的是令牌桶算法,也就是以固定的频率向桶中放入令牌,例如一秒钟10枚令牌,实际业务在每次响应请求之前都从桶中获取令牌,只有取到令牌的请求才会被成功响应,获取的方式有两种:阻塞等待令牌或者取不到立即返回失败,下图来自网上:

这里写图片描述

  • 本次实战,我们用的是guava的RateLimiter,场景是spring mvc在处理请求时候,从桶中申请令牌,申请到了就成功响应,申请不到时直接返回失败;
  • 对于的源码可以在我的git下载,地址是:https://github.com/zq2599/blog_demos,里面有多个工程,本次实战的工程为guavalimitdemo,如下图红框所示:

这里写图片描述

  • 这是一个maven工程,所以首先我们在pom中把guava的依赖添加进来:
<dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>18.0</version>
    </dependency>
  • 把限流服务封装到一个类中AccessLimitService,提供tryAcquire()方法,用来尝试获取令牌,返回true表示获取到,如下所示:
@Service
public class AccessLimitService {

    //每秒只发出5个令牌
    RateLimiter rateLimiter = RateLimiter.create(5.0);

    /**
     * 尝试获取令牌
     * @return
     */
    public boolean tryAcquire(){
        return rateLimiter.tryAcquire();
    }
}
  • 调用方是个普通的controller,每次收到请求的时候都尝试去获取令牌,获取成功和失败打印不同的信息,如下:
@Controller
public class HelloController {

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Autowired
    private AccessLimitService accessLimitService;

    @RequestMapping("/access")
    @ResponseBody
    public String access(){
        //尝试获取令牌
        if(accessLimitService.tryAcquire()){
            //模拟业务执行500毫秒
            try {
                Thread.sleep(500);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            return "aceess success [" + sdf.format(new Date()) + "]";
        }else{
            return "aceess limit [" + sdf.format(new Date()) + "]";
        }
    }
}
  • 以上就是服务端的代码了,打包部署在tomcat上即可,接下来我们写一个类,十个线程并发访问上面写的controller:
public class AccessClient {
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

    /**
     * get请求
     * @param realUrl
     * @return
     */
    public static String sendGet(URL realUrl) {
        String result = "";
        BufferedReader in = null;
        try {
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立实际的连接
            connection.connect();

            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送GET请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }



    public void access() throws Exception{
        final URL url = new URL("http://localhost:8080/guavalimitdemo/access");

        for(int i=0;i<10;i++) {
            fixedThreadPool.submit(new Runnable() {
                public void run() {
                    System.out.println(sendGet(url));
                }
            });
        }

        fixedThreadPool.shutdown();
        fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
    }

    public static void main(String[] args) throws Exception{
        AccessClient accessClient = new AccessClient();
        accessClient.access();
    }
}
  • 直接执行AccessClient的main方法,可以看到结果如下:

这里写图片描述

  • 部分请求由于获取的令牌可以成功执行,其余请求没有拿到令牌,我们可以根据实际业务来做区分处理。还有一点要注意,我们通过RateLimiter.create(5.0)配置的是每一秒5枚令牌,但是限流的时候发出的是6枚,改用其他值验证,也是实际的比配置的大1。
  • 以上就是快速实现限流的实战过程,此处仅是单进程服务的限流,而实际的分布式服务中会考虑更多因素,会复杂很多。

欢迎关注阿里云开发者社区博客:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...
相关文章
|
2月前
|
NoSQL fastjson Redis
自定义限流注解@RateLimiter
自定义限流注解@RateLimiter
83 2
|
8月前
|
安全 算法 应用服务中间件
互联网并发与安全系列教程(03) - RateLimiter使用AOP方式实现限流
互联网并发与安全系列教程(03) - RateLimiter使用AOP方式实现限流
56 0
|
存储 缓存 算法
Google Guava之RateLimiter
在日常开发中,限流是高并发系统的三把守护利器之一,它的另外两个好兄弟缓存、降级下次再说。而限流在绝大多数场景中用来限制并发和请求量,像秒杀之类的高流量业务的场景,都能见到它的身影,所以它就是保护系统和下游的业务系统不被流量冲垮的利器。
255 6
Google Guava之RateLimiter
|
2月前
|
Java Maven
Guava RateLimiter单机实战指南
Guava RateLimiter单机实战指南
28 0
|
缓存 算法 API
【如何】guava的RateLimiter使用
【如何】guava的RateLimiter使用
77 0
|
Java
SpringBoot整合RateLimiter实现限流
写作缘由 在和某学长炫耀在自己会用Redis+Lua实现滑动窗口限流时,他说现在都用RateLimiter,所以就我就想搞个Demo,但是度娘了一下,感觉我搜索到的博客有几个个人认为不太完善的地方,比如只贴了部分代码,没贴依赖。尤其是你用AOP实现的时候,其实依赖哪个还有有讲究的;还有一个问题就是大多都是基于AOP实现,拦截器实现也是一个不错的方式,所以此处用拦截器HandlerInterceptorAdapter实现。
249 0
|
存储 算法 NoSQL
RateLimiter源码分析
RateLimiter源码分析
133 0
RateLimiter源码分析
|
算法 Java
【Java技术开发专题】系列之「Guava RateLimiter」针对于限流器的入门到实战(含源码分析介绍)
【Java技术开发专题】系列之「Guava RateLimiter」针对于限流器的入门到实战(含源码分析介绍)
209 0
【Java技术开发专题】系列之「Guava RateLimiter」针对于限流器的入门到实战(含源码分析介绍)
|
存储 算法 Java
【Java技术指南】「并发编程专题」Guava RateLimiter针对于限流器的入门到精通(含源码分析介绍)
【Java技术指南】「并发编程专题」Guava RateLimiter针对于限流器的入门到精通(含源码分析介绍)
112 0
【Java技术指南】「并发编程专题」Guava RateLimiter针对于限流器的入门到精通(含源码分析介绍)
|
存储 缓存 NoSQL
详解Redisson分布式限流的实现原理
我们目前在工作中遇到一个性能问题,我们有个定时任务需要处理大量的数据,为了提升吞吐量,所以部署了很多台机器,但这个任务在运行前需要从别的服务那拉取大量的数据,随着数据量的增大,如果同时多台机器并发拉取数据,会对下游服务产生非常大的压力。之前已经增加了单机限流,但无法解决问题,因为这个数据任务运行中只有不到10%的时间拉取数据,如果单机限流限制太狠,虽然集群总的请求量控制住了,但任务吞吐量又降下来
383 0