1. 缓存解决方案

1.1. 一、redisson解决常见缓存问题

1.1.1. 1、redisson概述

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务

分布式对象:

  • 通用对象桶(RBucket
  • 二进制流(RBinaryStream)
  • 地理空间对象桶(RGeo)
  • 原子整长形(RAtomicLong)
  • 原子浮点型(RAtomicDouble)
  • 布隆过滤器(RBloomFilter
  • 基数估计算法(RHyperLogLog
  • 整长型累加器(RLongAdder )
  • 双精度浮点累加器(RLongDouble )
  • 限流器(RRateLimiter )

分布式集合:

  • 映射(RMap
  • 多映射(RMultimap
  • 不重复集(RSet
  • 有序集(RSortedSet
  • 计分排序集(RScoredSortedSet
  • 字典排序集(RLexSortedSet)
  • 列表(RList

分布式锁和同步器:

  • 可重入锁(RLock )
  • 锁(RLock )
  • 联锁(RedissonMultiLock )
  • 红锁(RedissonRedLock )
  • 读写锁(RReadWriteLock )
  • 信号量(RSemaphore)
  • 可过期性信号量(RPermitExpirableSemaphore )
  • 闭锁(RCountDownLatch )

1.1.2. 2、缓存的那些事

1)缓存处理流程

img

前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果

2)缓存穿透

2.1) 什么是缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为key为“101010”的数据而key所对应的数据为不存在的数据。这时的用户很可能是攻击者,会不停的发起请求攻击数据库,这样会导致数据库压力过大

==限制的目标:==

==登录:限制同一个人在单位时间内访问同一个方法的次数==

==未登录:限制同IP在单位时间内访问同一个方法的次数==

2.2)解决方案

令牌桶算法 : 随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.

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

基于Redis的分布式限流器(RateLimiter)可以用来在分布式环境下现在请求方的调用频率。既适用于不同Redisson实例下的多线程限流,也适用于相同Redisson实例下的多线程限流。该算法不保证公平性。除了同步接口外,还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

img

RRateLimiter rateLimiter = redisson.getRateLimiter("myRateLimiter"); // 初始化 // 最大流速 = 每1秒钟产生10个令牌 rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);

CountDownLatch latch = new CountDownLatch(2); limiter.acquire(3); // ...

Thread t = new Thread(() -> { limiter.acquire(2); // ... });

2.3)测试

测试结果

50个线程在同一秒的请求

但是实际每秒只处理10个:达到限流的目的

img

3)缓存击穿

3.1)什么是缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

==限制的目标:不同的人单位时间内对同一个方法的访问频率==

3.2)解决方案

加锁,放过第一个请求

img

img

从上述日志中我们可以看出:放过的只有一个,其中等待锁成功后从缓存获得的3个,超时后再次请求从缓存获得的6个,其他40个直接缓存获得,合计50个,且每秒同一个用户同一个方法同一个key最多10个并发

4)缓存雪崩

4.1)什么是缓存雪崩

缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是:缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

==限制的目标:给全体缓存添加不同的超时时间==

4.2)解决方案

/* \ @Description 产生1800到3600秒之间的整数 */ private Integer RandomTime(){ int max=3600; int min=1800; Random random = new Random(); return random.nextInt(max)%(max-min+1) + min; }

如果当前未设置超时时间,则会使用随机策略,为其指定:0.5小时到1小时之间的时间

img

1.1.3. 3、juc并发包

1)CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作。

例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行。

CountDownLatch构造方法:

```java public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象 ```

CountDownLatch重要方法:

```java public void await() throws InterruptedException// 让当前线程等待 public void countDown() // 计数器进行减1 ``` 说明:

CountDownLatch中count down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的门栓,似乎有一点“三二一,芝麻开门”的感觉。

CountDownLatch是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用countDown()方法让计数器-1,当计数器到达0时,调用CountDownLatch。

await()方法的线程阻塞状态解除,继续执行。

2)CyclicBarrier

概述

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。

例如:公司召集5名员工开会,等5名员工都到了,会议开始。

我们创建5个员工线程,1个开会线程,几乎同时启动,使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。

CyclicBarrier构造方法:

```java public CyclicBarrier(int parties, Runnable barrierAction)// 用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景 ```

CyclicBarrier重要方法:

```java public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞 ```

使用场景

使用场景:CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。

需求:使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作。

1.2. 二、j2Cache二级缓存框架

1.2.1. 1、介绍

J2Cache 是 OSChina 目前正在使用的两级缓存框架(要求至少 Java 8)。第一级缓存使用内存(同时支持 Ehcache 2.x、Ehcache 3.x 和 Caffeine),第二级缓存使用 Redis(推荐)/Memcached 。 由于大量的缓存读取会导致 L2 的网络成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数。 该缓存框架主要用于集群环境中。单机也可使用,用于避免应用重启导致的缓存冷启动后对后端业务的冲击。

j2cache其实并不是在重复造轮子,而是作资源整合,即将Ehcache、Caffeine、redis、Spring Cache等进行整合。

1.2.2. 2、结构

J2Cache 的两级缓存结构

L1: 进程内缓存(caffeine\ehcache) L2: Redis/Memcached 集中式缓存

1.2.3. 3、数据读取

\1. 读取顺序 -> L1 -> L2 -> DB

\2. 数据更新

1 从数据库中读取最新数据,依次更新 L1 -> L2 ,发送广播清除某个缓存信息 2 接收到广播(手工清除缓存 & 一级缓存自动失效),从 L1 中清除指定的缓存信息

项目源码https://gitee.com/ld0522/cache.git

results matching ""

    No results matching ""