Guava并发使用学习

Guava并发使用学习

前言:

在开发高并发系统,有三把利器用来保护系统:缓存,降级,限流。

在开放api接口时候,有时也需要用限流来控制,防止并发过高,系统奔溃。

1.Maven配置:

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>27.0.1-jre</version>
</dependency>

2.基本类方法

//0.5代表一秒最多多少个

RateLimiter rateLimiter = RateLimiter.create(0.5);

//rateLimiter.acquire()该方法会阻塞线程,直到令牌桶中能取到令牌为止才继续向下执行,并返回等待的时间。

rateLimiter.acquire()

//从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,
//或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)

tryAcquire(long timeout, TimeUnit unit)

3. 限流

//5代表一秒最多5个

RateLimiter rateLimiter = RateLimiter.create(5);

 

@RequestMapping(value = "/miaosha")
@ResponseBody
public String miaosha(int count, String code) {


    System.out.println("等待时间:" + rateLimiter.acquire());
    if (update(code, count) > 0) {
        return "购买成功";
    }
    return "购买失败";
}

请求过来时,调用RateLimiter.acquire,如果每秒超过了5个请求,就阻塞等待

jmeter演示:

等待时间:0.0
等待时间:0.0
等待时间:0.0

等待时间:0.0

等待时间:0.0
等待时间:0.175592
等待时间:0.375583
等待时间:0.575578
等待时间:0.775507
等待时间:0.975484
等待时间:1.175457
等待时间:1.375442
等待时间:1.575435
等待时间:1.775429
等待时间:1.975424

结论:5个请求无需等待直接成功,后面的开始被1秒5次限流了,基本上每0.2秒放行一个。

 

4. 降级

RateLimiter rateLimiter = RateLimiter.create(10);

 

@RequestMapping("/buy")
@ResponseBody
public String buy(int count, String code) {
    //判断能否在1秒内得到令牌,如果不能则立即返回false,不会阻塞程序
    if (!rateLimiter.tryAcquire(100, TimeUnit.MILLISECONDS)) {
        System.out.println("短期无法获取令牌,真不幸,排队也瞎排");
        return "失败";
    }
    if (update(code, count) > 0) {
        System.out.println("购买成功");
        return "成功";
    }
    System.out.println("数据不足,失败");
    return "失败";
}

 tryAcquire(long timeout, TimeUnit unit)
* 从RateLimiter 获取许可如果该许可可以在不超过timeout的时间内获取得到的话,
* 或者如果无法在timeout 过期之前获取得到许可的话,那么立即返回false(无需等待)

jmeter演示(100个请求):

购买成功
* 购买成功
* 购买成功
* 购买成功
* 购买成功
* 购买成功
* 购买成功
* 购买成功
* 购买成功
* 购买成功
* 购买成功
* 购买成功
* 短期无法获取令牌,真不幸,排队也瞎排
* 短期无法获取令牌,真不幸,排队也瞎排
* 短期无法获取令牌,真不幸,排队也瞎排
* 短期无法获取令牌,真不幸,排队也瞎排
* 短期无法获取令牌,真不幸,排队也瞎排
* 短期无法获取令牌,真不幸,排队也瞎排
* 短期无法获取令牌,真不幸,排队也瞎排
* 购买成功
* 短期无法获取令牌,真不幸,排队也瞎排
* ...

基本上就是前10个成功,后面的就开始按照固定的速率而成功了。

这种场景更符合实际的应用场景,按照固定的单位时间进行分割,每个单位时间产生一个令牌,可供购买。

5. guava缓存

创建:

public static com.google.common.cache.CacheLoader<String, String> createCacheLoader() {
    return new com.google.common.cache.CacheLoader<String, String>() {
        @Override
        public String load(String key) throws Exception {
            System.out.println( "加载创建key:" + key);
            return key+"+value";
        }
    };
}

 

//CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
        //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
        .maximumSize(1000)
        //设置写缓存后30L过期
        .expireAfterAccess(30L, TimeUnit.MILLISECONDS)
        //build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
        .build(createCacheLoader());

 

//放入缓存 cache.put(key,value);
// 移除缓存 cache.invalidate(key);
//批量删除缓存 cache.invalidateAll(keys);  List<String> keys
//会重新加载创建cache   cache.getUnchecked(key);
//不会重新加载创建cache   cache.getIfPresent(key);
//获取,会抛出异常   cache.get(key);
//任何时候,你都可以显式地清除缓存项,而不是等到它被回收:
// 个别清除:Cache.invalidate(key)
//批量清除:Cache.invalidateAll(keys)
// 清除所有缓存项:Cache.invalidateAll()
//正如LoadingCache.refresh(K)所声明,刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值,而不像回收操作,读缓存的线程必须等待新值加载完成。
//expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。
//expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回 收。如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。

 

原文地址:https://www.cnblogs.com/ltian123/p/10457702.html