缓存相关记录

概念

缓存乱用

1、缓存大对象
2、缓存不常用的
3、缓存频繁更新的
4、没有及时的更新或者删除再缓存中已经过期或者失效的数据

缓存穿透、并发、失效、一致性、雪崩

缓存击穿

热点key问题,永不失效或者并发回源
方案一,使用进程内的锁进行限制,这样每一个节点都可以以一个并发回源数据库;
方案二,不使用锁进行限制,而是使用类似Semaphore的工具限制并发数,比如限制为10,
这样既限制了回源并发数不至于太大,又能使得一定量的线程可以同时回源。

穿透

    一般缓存数据流程都是先查缓存系统,如果缓存系统不存在,再查db,然后异步更新缓存系统,正常的这样流程是没有
任何风险的,可当黑客攻击的时候,会模拟一个不存在的缓存KEY,导致缓存无法命中直接查询DB。
方案一
防止缓存穿透首先要过滤缓存key,比如key不符合要求直接打回,如果key查询db后返回的结果为null,我们可以为这个key预设一个
特定的值比如“&&”,这样后续的查询返回的都是“&&”,就不会直接查询db了,但是缓存的失效时间需要短下
这样会导致一个问题,会有大量的无用数据占据内存,
方案二(布隆过滤器)
算法:
1. 首先需要k个hash函数,每个函数可以把key散列成为1个整数
2. 初始化时,需要一个长度为n比特的数组,每个比特位初始化为0
3. 某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1
4. 判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。

优点:不需要存储key,节省空间

缺点:
1. 算法判断key在集合中时,有一定的概率key其实不在集合中
2. 无法删除

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难
布隆过滤器有一定的误判概率,但是如果布隆过滤器判断某个元素不在布隆过滤器中,那么这个值就一定不在。
可以使用guava实现布隆过滤器(BloomFilter ),布隆过滤器不保存原始值,空间效率很高



将数据库中所有的查询条件,放入布隆过滤器中,
当一个查询请求过来时,先经过布隆过滤器进行查,
如果判断请求查询值存在,则继续查;如果判断请求查询不存在,直接丢弃

并发

    比如手机app首页的推荐位数据一般都是从缓存里面取的,比如在用户访问高峰期的时候缓存失效了,用户打开app首页的时候,加载的
推荐位数据会并发请求到数据库,这种情况就需要对查询db的操作进行加分布式锁的功能了。可以使用一个锁,也可以初始化多个锁,保证
打到数据库的请求是在可控范围。或者使用本地缓存,当nosql缓存失效的时候,直接先拿本地缓存挡着,但是要注意本地缓存的大小。

失效

    一般使用缓存都会加上失效时间,防止一些冷数据一直占用缓存。假如用户集中在18:00登录,登录后会把用户信息放到缓存里面,同时设置
缓存失效时间是24小时,也就是说到第二天18:00的时候,这些用户的缓存信息同时失效了,系统会有大量请求打到DB,为了防止缓存集中性的
失效,我们可以对缓存的失效设置一个随机时间,比如20~24小时之间,将缓存的失效时间分开减小并发压力。

一致性

 一般都是先修改DB然后再修改缓存,如果缓存系统突然挂了,就会导致db与缓存的数据不一致。
如何解决:更新缓存系统的时候增加重试功能,比如更新个三四次,如果三四次还不成功那就往消息队列里面
丢一条更新缓存的请求,异步的去更新缓存。

缓存雪崩

缓存雪崩的常见解决方法有两种:更新锁机制和后台更新机制。
当缓存服务器重启或者大量缓存集中在某一个时间段失效
如果缓存服务器不是高可用了,使用hystrix拦截。

缓存热点

缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力

缓存同步数据库策略

如果服务存在缓存,则直接从缓存取,如果缓存不存在,则请求数据库。并且将数据放到缓存,同时返回值

写的操作会导致数据库与缓存出现不一致的问题,目前缓存存在以下下三种策略
1、cacha aside(旁路缓存)
    1、更新缓存再更新db
         如果db更新失败,不手动回滚缓存会导致一致性问题
         同时多线程也会有aba导致数据不一致问题

    2、更新db再更新缓存
         多线程时候会有aba问题,同时如果每个资源都更新到缓存会导致性能问题
         a和b 都顺序完成了更新db更新,但是更新缓存都顺序确是ba,导致缓存不一致

    3、删除缓存,更新db
        多线程也会有数据不一致问题,例如并发情况下,线程A删除了缓存。但是没有更新db
        这时候线程b 看缓存为空。就会捞db的老值并且放到缓存里面。
    4、更新db 然后删除缓存
2、read/writ through ,around
 应用程序只需要维护缓存,数据库的维护工作由缓存代理了
 1、writ through 更新cache,再db
 2、writ around 更新db,删除缓存

3、write behind cacahing
该策略只更新缓存,不会立马更新数据库,只会在一定的时间异步的批量去操作数据库;
这样的好处在于直接操作缓存,效率极高,并且操作数据是异步的,
还可以将多次的操作数据库语句合并到一个事务中一起提交,因此效率很客观;


关于缓存写入,至少有4种写入的策略
THROUGH("through"),   //发生在Cache层,应用程序只需要维护缓存,数据库的维护由缓存代理完成,先cache 再db
AROUND("around"),  //发生在Cache层,直接写入数据到数据库,不必写到缓存中。这个不必过多的解释,缓存的数据应该被立即过期
BEHIND("behind"),  //还是发生在Cache层,刚开始时,写入到缓存中,当设定的缓存容量达到上限,或等到一定的时间间隔后,再写到数据库中。
ASIDE("aside");  //发生在应用层,应用层保证缓存结果同DB的数据一致性,应用层来负责写入到数据库和整理缓存,缓存层则不必插手此事。

redis相关

redis概述

redis是基于内存的,内存的读写会非常快,而且是单线程的,省去了不必要的上下文线程切换时间
同时redis使用的是多路复用技术,可以处理并发的请求

redis主从

redis一主可以对应多个从节点,主节点可以读写,从节点只能查询

配置文件

salve节点文件里面添加以下内容
slaveof <masterip> <masterport> //主节点的ip
masterauth <master-password>   //主节点的密码

客户端实现

对于写的操作需要路由到主节点redis数据源
对于读操作需要路由到从节点的数据源。

缺点

如果redis主节点挂了,从节点不会自动切换, 需要借助redis的Sentinel或者keepalive来实现主的故障转移

redis Sentinel

配置可参考:https://cachecloud.github.io/2016/03/11/Redis%203.2.0-Sentinel%E6%95%85%E9%9A%9C%E8%BD%AC%E7%A7%BB%E6%B5%8B%E8%AF%95/
./redis-server.sh sentinel.conf --sentinel //--sentinel是哨兵模式启动需要带的参数

redis集群

因为单台服务器内存的局限性,必须要进行横向扩展,主从模式不能保证高可用,redis官方自带的redis-cluter

memcache

memcache是完全基于内存的,不能持久化存储,只能存储key value

缓存序列化与反序列化

内存访问速度

名称 速度
内存 0.3ns
固态硬盘 50~150us
物理硬盘 1-10ms
网络 20ms

固态硬盘SSD与机械硬盘HDD

项目 SSD HDD
容 量 较小
价 格 便宜
随机存取 极快 便宜
写入次数 SLC:10万次/MLC:1万次 无限制
防震能力 很好 较差
数据恢复 容易

RAID磁盘阵列

RAID ( Redundant Array of Independent Disks )即独立磁盘冗余阵列,简称为「磁盘阵列」,
其实就是用多个独立的磁盘组成在一起形成一个大的磁盘系统,从而实现比单块磁盘更好的存储性能和更高的可靠性

RAID0

RAID0 是一种非常简单的的方式,它将多块磁盘组合在一起形成一个大容量的存储。
当我们要写数据的时候,会将数据分为N份,以独立的方式实现N块磁盘的读写,那么这N份数据会同时并发的写到磁盘中,因此执行性能非常的高
但RAID0的问题是,它并不提供数据校验或冗余备份,因此一旦某块磁盘损坏了,数据就直接丢失,无法恢复了。
因此RAID0就不可能用于高要求的业务中,但可以用在对可靠性要求不高,对读写性能要求高的场景中

RAID1

RAID1 是磁盘阵列中单位成本最高的一种方式。因为它的原理是在往磁盘写数据的时候,
将同一份数据无差别的写两份到磁盘,分别写到工作磁盘和镜像磁盘,那么它的实际空间使用率只有50%了,
两块磁盘当做一块用,这是一种比较昂贵的方案
数据的可靠性非常强,但性能就没那么好了

RAID5

RAID5模式中,不再需要用单独的磁盘写校验码了。它把校验码信息分布到各个磁盘上。
例如,总共有N块磁盘,那么会将要写入的数据分成N份,并发的写入到N块磁盘中,
同时还将数据的校验码信息也写入到这N块磁盘中(数据与对应的校验码信息必须得分开存储在不同的磁盘上)。
一旦某一块磁盘损坏了,就可以用剩下的数据和对应的奇偶校验码信息去恢复损坏的数据
RAID5的方式,最少需要三块磁盘来组建磁盘阵列,允许最多同时坏一块磁盘。
如果有两块磁盘同时损坏了,那数据就无法恢复了

Guava Cache

使用场景

轻量级单机缓存,缓存存放在内存里面,不像ehcache支持缓存数据落地磁盘
可以将程序频繁用到的少量数据存储到Guava Cache中
guava支持多重缓存失效策略,同时支持缓存并发

###

JSR-107 cache规范

依赖

<dependency>
  <groupId>javax.cache</groupId>
  <artifactId>cache-api</artifactId>
  <version>1.0.0</version>
</dependency>

核心接口

. CachingProvider
定义了建立,配置,得到,管理和控制0个或多个CacheManager,一个应用在运行时可能访问0个或者多个CachingProvider。
2.CacheManager
它定义了建立,配置,得到,管理和控制0个或多个有着唯一名字的Cache ,一个CacheManager被包含在单一的CachingProvider.
3. Cache
Cache是一个Map类型的数据结构,用来存储基于键的数据,很多方面都像java.util.Map数据类型。一个Cache 存在在单一的CacheManager.