首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >高并发利器-guava分流与缓存

高并发利器-guava分流与缓存

作者头像
SAnBlog
发布2020-07-21 09:46:03
1.4K0
发布2020-07-21 09:46:03
举报
文章被收录于专栏:SAnBlogSAnBlogSAnBlog

guava依赖

 <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>23.6-jre</version>
 </dependency>

guava cache

高并发三件套之一,缓存

场景:

有个场景,接口请求获取数据频繁,但数据改动量小,一般情况是先去redis取,没有则从数据库取,再放入redis,返回?为了加快系统的响应速度,我们可以在内存加一层,先查询内存缓存。没有则查询数据库/redis,再加入内存。

是不是觉得挺麻烦的,这时候就用到guava cache了,guava封装看以上流程,只需直接调用get即可

GuavaCache提供了三种基本的缓存回收方式:

基于容量回收、定时回收和基于引用回收。

定时回收有两种:按照写入时间,最早写入的最先回收;按照访问时间,最早访问的最早回收。

它可以监控加载/命中情况。

 LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
         //最多存放十个数据
         .maximumSize(10)
         //缓存10秒,10秒之后进行回收
         .expireAfterWrite(10, TimeUnit.SECONDS)
         //移除时触发
         .removalListener(removalNotification -> {
             System.out.println("移除:"+removalNotification.getKey());
         })
         .recordStats()//开启,记录状态数据功能
         //当key的值不存在时触发
         .build(new CacheLoader<String, Integer>() {
             //数据加载,也可以是查询操作,如从redis/db
             @Override
             public Integer load(String key) throws Exception {
                 System.out.println("没有缓存,开始加载新缓存");
                 //这里可以对数据库 或者redis进行查询并加入缓存。
                 return -1;
             }
         });

测试

 //查询缓存,未命中,调用load方法,这里可以单独对一个key进行load操作。
 //如果cache对象是公用缓存,可以在不同业务时对load进行重写获取最新缓存
 System.out.println(cache.get("key2",() -> -1));
 //put数据,更新缓存
 cache.put("key2", 2);
 //查询得到最新的数据
 System.out.println(cache.get("key2"));
 System.out.println("size:" + cache.size());
 //插入十个数据
 for (int i = 3; i < 13; i++) {
     cache.put("key" + i, i);
 }
 //超过最大容量,删除最早插入的数据
 System.out.println("size:" + cache.size());
 System.out.println(cache.getIfPresent("key2"));
 //等待5秒
 Thread.sleep(5000);
 //此时key已经失效,但是size没有更新
 System.out.println("size :" + cache.size());
 //获取key2,发现已经失效,然后刷新缓存,遍历数据,去掉失效的所有数据
 System.out.println(cache.getIfPresent("key2"));
结果

Guava RateLimiter

高并发三件套之二,限流

场景:1.

在日常生活中,我们肯定收到过不少不少这样的短信,“京东最新优惠卷…”,“天猫送您…”。这种类型的短信是属于推广性质的短信。这种短信一般群发量会到千万级别。然而,要完成这些短信发送,我们是需要调用服务商的接口来完成的。倘若一次发送的量在200万条,而我们的服务商接口每秒能处理的短信发送量有限,只能达到200条每秒。那么这个时候就会产生问题了,我们如何能控制好程序发送短信时的速度昵?于是限流器就得用上了。

2.

提供服务接口的人或多或少遇到这样的问题,业务负载能力有限,为了防止过多请求涌入造成系统崩溃,如何进行流量控制?

流量控制策略有:分流,降级,限流等。这里我们讨论限流策略,他的作用是限制请求访问频率,换取系统高可用,是比较保守方便的策略。

3.常用的限流算法由:漏桶算法和令牌桶算法。

一、漏桶算法漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:

一个固定容量的漏桶,按照常量固定速率流出水滴;如果桶是空的,则不需流出水滴;可以以任意速率流入水滴到漏桶;如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下:

可见这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。

因为漏桶的漏出速率是固定的参数,所以,即使网络中不存在资源冲突(没有发生拥塞),漏桶算法也不能使流突发(burst)到端口速率.因此,漏桶算法对于存在突发特性的流量来说缺乏效率.

二、令牌桶算法令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:

假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌;桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝;当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上;如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务

令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量.

 /**
  * 定义公共的限流map,不同业务处理的QPS不同。
  */
 public static ConcurrentHashMap<String, RateLimiter> resourceRateLimiter = new ConcurrentHashMap<>();
 
 //初始化限流工具RateLimiter
 static {
     //订单限制QPS50(令牌)
     createResourceRateLimiter("order", 50);
     //其他限制10
     createResourceRateLimiter("common", 10);
 }
 
 
 public static void createResourceRateLimiter(String resource, double qps) {
     if (resourceRateLimiter.contains(resource)) {
         resourceRateLimiter.get(resource).setRate(qps);
     } else {
         //每秒产生多少令牌
         RateLimiter rateLimiter = RateLimiter.create(qps);
         resourceRateLimiter.putIfAbsent(resource, rateLimiter);
     }
 
 }
test
 /**
  * 模拟并发
   多线程并发获取许可,并发数量为500,且每个线程只获取尝试等待2s/3s
  * @param args
  */
 public static void main(String[] args) {
     for (int i = 0; i < 500; i++) {
         new Thread(() -> {
             //每秒生成50个令牌,尝试等待2s,如果获得令牌指令,则执行业务逻辑
             if (resourceRateLimiter.get("order").tryAcquire(2, TimeUnit.MICROSECONDS)) {
                 System.out.println("order执行业务逻辑");
             } else {
                 System.out.println("order限流");
             }
         }).start();
 
         new Thread(() -> {
             //每秒生成10个令牌,尝试等待3s,如果获得令牌指令,则执行业务逻辑
             if (resourceRateLimiter.get("common").tryAcquire(3, TimeUnit.MICROSECONDS)) {
                 System.out.println("common执行业务逻辑");
             } else {
                 System.out.println("common限流");
             }
         }).start();
     }
 }
结果
方法摘要

参考:

http://ifeve.com/guava-ratelimiter/

https://blog.csdn.net/aa1215018028/article/details/80866335

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 SAnBlog 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • guava依赖
  • guava cache
    • 结果
    • Guava RateLimiter
      • test
        • 结果
          • 方法摘要
          相关产品与服务
          云数据库 Redis
          腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档