分布式缓存方案

编程之家收集整理的这篇文章主要介绍了分布式缓存方案编程之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

一、从数据说起

我们再做缓存之前需要把数据先分好类

按变化频率:

  • 静态数据:一般不变的,类似于字典表
  • 准静态数据:变化频率很低,部门结构设置,全国行政区划数据
  • 中间状态数据:一些计算的可复用中间数据,变量副本,配置中心的本地副本

按使用频率:

  • 热数据:使用频率高的
  • 读写比大的:读的频率远大于写的频率

这些数据就比较适合使用缓存。

缓存无处不在。内存可以看作是cpu和磁盘之间的缓存。cpu与内存的处理速度也不一致,所以出现了L1&L2 Cache

缓存的本质:系统各级之间处理速度不匹配,利用空间换时间。

缓存加载时间

1. 启动时全量加载

2. 懒加载

2.1. 同步使用加载

先看缓存里是否有数据,没有的话从数据库读取。读取的数据,先放到内存,然后返回给调用方。

2.2. 延迟异步加载

从缓存里获取数据,不管有没有都直接返回。

策略1:如果缓存为空的话,则发起一个异步线程负责加载。

策略2:异步线程负责维护缓存的数据,定期或根据条件触发更新。

缓存过期策略

  • 按FIFO或LRU
  • 固定时间过期
  • 根据业务进行时间的加权。

二、本地缓存

  1. Map 缓存
public static final Map<String,Object> CACHE=new HashMap();
CACHE.put("key","value");
  1. Guava缓存
Cache<String,String> cache = CacheBuilder.newBuilder() .maximumSize(1024) .expireAfterWrite(60,TimeUnit.SECONDS) .weakValues() .build();
cache.put("word","Hello Guava Cache");
System.out.println(cache.getIfPresent("word"));
  1. Spring Cache
  • 基于注解和AOP,使用方便
  • 可以配置Condition和SPEL,非常灵活
  • 需要注意:绕过Spring的话,注解无效

核心功能:@Cacheable、@CachePut、@CacheEvict

本地缓存的缺点:

  • 在集群环境中,如果每个节点都保存一份缓存,导致占用内存变大
  • 在JVM中长期存在,会影响GC
  • 缓存数据的调度处理,影响业务线程,争夺资源

三、远程缓存

  1. Redis

Redis是一个开源的使用ANSI C语言编写的,基于内存也可以持久化的key-value数据库,并提供多种语言的API

  1. Memcached

memcached是一套分布式的高速缓存系统,由LiveJournal的Brad Fitzpatrick开发,但被许多网站使用。这是一套开放源代码软件,以BSD license授权发布。

四、内存网格

  1. Hazelcast
  2. lgnite

五、缓存常见问题

1. 缓存穿透

问题描述:大量并发查询不存在的KEY,导致都直接把压力透传到数据库上。

分析:因为数据库里没有值,所以没有建立缓存,导致一直打到数据库上。

解决办法:

  1. 缓存空值的KEY
  2. Bloom过滤或RoaringBitmap判断KEY是否存在
  3. 完全以缓存为准,使用延迟异步加载的方式去加载数据库数据到缓存。

Bloom过滤器示例:
(引入guava依赖)

 public static void main(String[] args) {
        BloomFilter<CharSequence> filter = BloomFilter.create(
                Funnels.stringFunnel(Charsets.UTF_8),//Funnels.integerFunnel(),//数据格式
                1000000,//预计存入数据量
                0.01);//误判率

        System.out.println(filter.mightContain("abcdefg"));
        filter.put("abcdefg");
        System.out.println(filter.mightContain("abcdefg"));
    }

RoaringBitmap示例:
引入依赖:

<dependency>
			<groupId>org.roaringbitmap</groupId>
			<artifactId>RoaringBitmap</artifactId>
			<version>0.8.1</version>
		</dependency>
 public static void test3(){
        Roaring64NavigableMap roaring64NavigableMap = Roaring64NavigableMap.bitmapOf(3,4,5,90);
        //是否包含
        boolean contains = roaring64NavigableMap.contains(3);
        long l = roaring64NavigableMap.rankLong(3);
        System.out.println(l);
        System.out.println(contains);
    }

2. 缓存击穿

问题:当某个KEY失效的时候,正好有大量并发请求访问这个KEY

分析:跟缓存穿透比较像,这个是属于偶然的

解决办法:

  1. KEY的更新的时候添加全局互斥锁
  2. 完全以缓存为准,使用延迟异步加载的策略

3. 缓存雪崩

问题:当某一个时刻发生大规模的缓存失效的情况,会有大量请求打到数据库,导致数据库压力过大而宕机

分析:一般来说,由于更新策略、或者数据热点、缓存服务宕机等原因,导致缓存数据同时大规模不可以。

解决办法:

  1. 缓存更新、失效策略在时间上做到比较均匀
  2. 使用的热数据尽量分散到不同机器上
  3. 多台机器做主从复制,实现高可用
  4. 实现熔断限流机制,对系统进行负载能力控制
  5. 使用本地缓存兜底

4. 先更新数据库还是先删除缓存?

4.1 先删除缓存,再更新数据库

  • 并发情况下:
    如果线程A删除了缓存还没来得及更新数据库。那么线程B就会拿不到缓存,从数据库查询旧值并写入缓存。就会导致后来的请求都是从缓存里拿到旧值。

  • 异常情况下:如果线程A删除缓存成功,更新数据库异常。线程B拿不到缓存也没关系,从数据库查,此时数据库和缓存的值是一致的,没问题。

4.2 先更新数据库,再删除缓存

  • 并发情况下:如果线程A更新了数据库还没来得及删除缓存。那么线程B从缓存里取到旧值,但是后续的请求过来缓存已经被删除了,还是会从数据库查询到新值并放入缓存。

  • 异常情况下:如果线程A更新数据库成功,删除缓存异常。那么线程B拿到的缓存的值是旧的,有不一致的情况。

综上,虽然都有不一致的情况,但是第二种更好处理一点。即选择先更新数据库,再删除缓存,模型如下:

如果删除缓存的时候出了异常,可以将删除缓存的任务放入消息中间件,让他重试删除

番外:

布隆过滤器:

目标就是要基于过滤器已存储生成的原始元数据,进行比较过滤,如果是在原始元数据集合里面的,一定会被发现。也有可能不是里面的被误杀。

BloomFilter 会开辟一个m位的bitArray(位数组),开始所有数据都部署为0,当一个元素过来的时候,通过多个hash函数计算出不同的值,然后根据hash值找到对应的下标处,将里面的值改为1.

优点:使用计算,节省存储空间。

缺点:有失误率。不是在过滤器原始表里的数据也会被误算进去。

使用场景:目标就是要基于过滤器已存储生成的原始元数据,进行比较过滤,如果是在原始元数据集合里面的,一定会被发现。布隆过滤器核心正确的使用就是进行过滤禁止,进行正确的否定。

举例:如我们有100万个黑名单的URL地址,过来一个地址我们算出来不在里面,那就肯定可以放行。

BitMap:

BitMap的基本思想是用一个bit位来标记某个元素对应的值,这样就可以大大节省空间。

在Java中一个int占4个字节,也就是32bit。按int存储和按位存储的大小差距是32倍。

那么怎么表示一个数呢?可以使用1表示存在,0表示不存在。

如下面:表示{2,6}

一个byte只有8个位置,如果想表示13怎么办呢?只能再用一个byte了,就成了一个二维数组了

1个int占32位,那么我们只需要申请一个int数组长度为 int tmp[1+N/32] 即可存储,其中N表示要存储的这些数中的最大值

使用场景:

  1. 快速排序

把数放进去之后,遍历一遍,把值是1的都取出来就排好序了。

  1. 快速去重

20亿个整数中找出不重复的整数的个数?

内存不足以容纳这20亿个整数。我们怎么表示数字的状态呢?一个数的状态可以分为3种,不存在、存在一次、存在两次及以上。这就需要两个bit来表示。00代表不存在,01代表一次,11代表两次及以上。

接下来我们就把这20亿个整数放进去,如果状态为00,就改为01,如果状态为01就改为11.如果状态为11,就不动了。都放完后,遍历取出值为01的,就是不重复的数据的个数。。
3. 快速查找

给定一个整数M,M/32就能得到int数组的下标,M%32就知道在这个下标里面的具体位置。

如13,就能算出在int[0]里面的第13个

总结

以上是编程之家为你收集整理的分布式缓存方案全部内容,希望文章能够帮你解决分布式缓存方案所遇到的程序开发问题。

如果觉得编程之家网站内容还不错,欢迎将编程之家网站推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
喜欢与人分享编程技术与工作经验,欢迎加入编程之家官方交流群!

猜你在找的Java相关文章

前言 日常开发中,难免会遇到需要查询到数据库所有记录的业务场景,在索引完善的情况下,当数据量达到百万级别或者以上的时候,全表查询就需要耗费不少的时间,这时候我们可以从以下几个方向着手优化 优化sql
【设计模式】汉堡中的设计模式——观察者模式 情景带入 对于爱吃麦当劳的我来说,自然是不想错过各种动态,可是我又不可能【无时无刻】的蹲在店里等新品吧(还是要搬砖的) 那么有没有一种好的方法,在麦当劳【推
一、前提知识: http协议规定一次请求对应一次响应,根据不同的请求方式,请求的内容会有所不同; 发送GET请求是没有请求体的,参数会直接拼接保留到url后一并发送; 而POST请求是带有请求体的,带
关于Java中的重载与重写,每一个java人肯定都学习过,这里就再梳理一遍,加深一下印象,忘记的时候拿出来看一看就好了 重载与重写的区别 重载发生在同一个类里面,同一个方法,拥有不同的参数列表,不同的
1、正常创建一个List,对List进行操作 List&lt;Integer&gt; collect = Stream.of(1 ,3 ,5 ,7 ,9).collect(Collectors.toL
我发现,自从我学了Stream流式操作之后,工作中使用到的频率还是挺高的,因为stream配合着lambda表达式或者双冒号(::)使用真的是优雅到了极致!今天就简单分(搬)享(运)一下我对strea
性能优化-数据库篇1 首先我们要谈论什么是性能? 吞吐和延迟 没有量化就没有改进 :监控和度量指标可以指导我们从哪里入手 80/20原则:先优化性能瓶颈的地方 过早的优化是万恶之源:选择合适的优化时机
一、从数据说起 我们再做缓存之前需要把数据先分好类 按变化频率: 静态数据:一般不变的,类似于字典表 准静态数据:变化频率很低,部门结构设置,全国行政区划数据 中间状态数据:一些计算的可复用中间数据,
微信公众号搜索 “ 程序精选 ” ,选择关注!
微信公众号搜 "程序精选"关注