缓存知识点:![](https://i-blog.csdnimg.cn/blog_migrate/eb5f953e4533856edec88040d1dff289.png)
缓存有哪些类型?
缓存时高并发场景下提高热点数据访问性能的一个有效手段,在开发阶段经常用到。
类型可以分成本地缓存,分布式缓存,多级缓存
本地缓存
将数据存到JVM堆中,可以使用LRUMap来实现,也可以使用Ehcache这样的工具实现
本地缓存是内部访问,不需要远程交互的开销,性能最好,但受于JVM和单机容量,一般缓存较小且无法扩展。
分布式缓存
分布式缓存具有良好的水平扩展能力,对较大数据的场景也可以应付自如,缺点就是需要进行远程请求,性能不如本地缓存
多级缓存
为了平衡这这情况,实际业务中一般采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其他热点数据放在分布式缓存
淘汰策略
不管是本地缓存还是分布式缓存,为了保证高性能,都是使用内存来保持数据,由于成本和内存限制,当初从的数据超过容量时,需要对缓存的数据进行剔除。
一般的提出策略有FIFO淘汰最早数据,LRU剔除最近最少使用,和LFU剔除最近使用频率最低的数据几种策略
- noeviction返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
- allkeys-lru:尝试回收最少使用的键(LRU),是的新添加的数据有空间存放
- volatile-lru:尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放
- allkeys-random:回收随机的键使得新添加的数据有空间存放
- volatile-random:回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键
- volatile-ttl:回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放
如果没有键满足回收条件德华,策略volatile-lru,volatile-random以及volatile-ttl就和noeviction差不多了
Memcache
- Memcache处理请求时使用多线程异IO的方式,合理使用cpu多核的有事,性能非常优秀;
- Memcache功能简单,使用内存存储处数据
- Memcache内存结构以及钙化问题。
- Memcache兑换成的数据可以设置失效期,过期后的数据会被清除;
- 失效的策略采用延迟是小,就是当再次使用数据时检查是否失效;
- 当容量存满时,会剔除缓存中的数据,不仅对过期的key进行清理,还会按LRU策略对数据进行剔除
使用 MC 有一些限制,这些限制在现在的互联网场景下很致命,成为大家选择Redis、MongoDB的重要原因:
- key不可以超过250字节
- value不超过1M
- 最大失效时间30天
- 支持k-v结构,不提供持久化和主从同步功能。
Redis
- redis采取单线程处理请求,因为其内部采用了非阻塞的异步事件处理机制;另一个是缓存数据都是内存操作IO事件不会太长,单线程可以避免线程上下文切换产生的代价
- Reids支持持久化,不仅仅可以当成缓存,还可以作为数据库
- 除了传统的<K,V>结构外,还支持hash,list,set等类型
- Redis提供主从架构,集群部署能力,可以提供高可用服务
Redis的基础类型
String:
是Redis最经常使用的类型,内部是通过SDS(Simple Dynamic String)来储存的,SDS类似于java的ArrayList,可以通过预分配荣誉空间来减少内存的频繁分配,是一个安全的数据类型。
String的实际应用场景还是比较广泛的:
缓存功能:String时最常用的数据类型,不仅仅是Redis,各个语言都是最基本的类型,因此,利用Redis作为缓存配合其他数据库作为存储层,利用Redis支持高并发的特点,可以大大加快系统的读写速度,降低后端数据库的压力。
计数器:许多系统都会使用Redis作为系统的实时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其他存储介质中进行永久保存。
共享用户Session:用户重新刷新一次界面,可能需要访问一下数据进行重新登录,或者访问页面缓存Cookie,但是可以利用Redis将用户的Session集中管理,这种模式只需要保证Redis的高可用,每次用的Session更新和获取都可以快速完成,大大提高效率。
Hash:
这是一个类似Map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没有嵌套别的对象)给缓存在Redis中,每次读写缓存的时候,可以就操作Hash里的某个字段。
但当前的对象大多都是互相嵌套的,使用的场景反而不多
List:
list是有序的列表,可以用在文章评论,商品秒杀等。同时其双向进入,双向提出的结构可以轻松的实现,栈形式,队列形式的数据结构
可以通过Irange命令。读取谋和封闭区间的元素,可以基于List实现分页查询,这是个很棒的功能,基于Redis实现高性能的分页,或懒加载的分页设计(类似微博那种下拉不断分页的东西),高性能,就一页一页走。
消息队列:Redis的链表结构,可以轻松实现阻塞队列,可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过Lpush命令从左边插入数据,多个数据消费者,可以使用BRpop命令阻塞的“抢”列表尾部的数据。
比如,我们常用的博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用Redis的列表,列表不但有序同时还支持按照范围内获取元素,可以完美解决分页查询功能。大大提高查询效率。
Set:
set是无需的集合,会自动去重。
可以基于set操作一些交集,并集,差集的操作。反正这些场景比较多,因为对比很快,操作也简单,两个查询一个Set搞定。
Sorted Set:
Sorted set是排序的set。可去重可排序写进去的时候给一个分数,根据分数可以自动排序。
- 可以用在网页会话的列表可以使用该方法(轻松去重,自动排序)
- 排行榜(同上)
高级用法
Bitmap:
位图是支持按照bit来存储信息,可以实现布隆过滤器(BloomFilter);
Geospatial:
可以用来保持地理位置,并作位置距离计算,或根据班级计算位置等。有没有想过用Redis实现最优路径?
pub/sub:
订阅发布功能,可以用做简单的消息队列
Popeline
可以批量执行一组指令,一次性返回全部的结果,减少频繁的请求应答
Lua:
Redis支持Lua脚本来执行一系列的功能,可以实现原子性
持久化
Redis提供了EDB和AOF两种持久化的方式,RDB是以快照的形式将数据集写入磁盘,实际操作是通过fork子进程执行,采用二进制压缩;AOF十一日至的形式记录Redis的每一个写入或删除操作
RDB:把整个redis的数据保存在单一文件中,适合用来做灾备,缺点是快照保存完成之前如果宕机,这段时间的数据就丢失,另外保存快照可能导致服务段时间不可用
AOF对纸质文件写入操作使用的追加模式。有灵活的同步策略,支持每秒同步,每次修改同步和不同步,缺点就是相同规模的数据集,AOF大于RDB,AOF在运行效率上会慢于RDB。
高可用
redis支持只从同步,集群模式,通过sentinel监控redis主服务器的状态,当主挂掉之后,从节点中根据一定的策略,将节点中的从节点升级为主节点
- slave的priority设置的越低,优先级越高
- 同等情况下,slave复制的数据越多,优先级越高
- 相同条件下,runid越小,越容易被选中
哨兵必须使用三个实例来保证自己的健壮性,哨兵+主从并不能保证数据不对事,但是可以保证集群的高可用
如果只有两个实例,那么无法保证当实例1机器挂了,实例2不存在其他哨兵来运行故障转移了。
如果用三个实例。那么主机器挂了。哨兵还有两个,仍然支持故障转移
哨兵的主要功能:
集群监控:负责监控Redis master和slave进程是否正常工作
消息通知:如果某个Redis实例有故障,那么哨兵会发送消息作为劲爆通知给管理员
故障转移:如果主机器挂掉了,会自动升级从节点为主节点,转移故障
配置中心:如果发生了故障转移,通知client客户端新的master地址
主从
主从是基于数据持久化的RDB和AOF的
单机的redis的QPS是有上限的,让master机器去写,数据同步给别的slave机器。分散请求,增大redis的QPS上限。而且扩容的时候可以轻松实现水平扩容
当你启动以爱slave的时候,他会发送一个psync的命令给master,如果这个slave第一次连接到master,会触发一个全量复制。master会启动一个线程,生成RDB快照,并把新的请求都缓存在内存中,RDB文件生成后吗,master会把这个RDB发送给slave。slave拿到之后的第一件事就是写进本地的磁盘,加载进内存,然后master会把内存里面缓存的那些新命令都发给slave。
key失效机制
定时过期后的数据mRedis采取主动结合被动的失效机制,一个是和MC应用访问时触发被动删除,一种是定时的主动删除
缓存常见问题
缓存更新方法
给缓存设置失效时间,通过缓存失效,重新查询数据,实现更新。
但是当以来的远程服务在更新时出现异常,吹吹导致数据不可以,改进的方法时异步更新,县不清除实销数据,使用旧的缓存,由异步线程执行更新任务,这样就避免了失效瞬间的空窗期。
另外还有一种纯异步更新的方法,定期对数据进行分批更新,实际使用可以根据业务场景选择更新方式
数据不一致
缓存不一致主要是因为主动更新失败,短期的数据不一致不会影响业务,那么下次更新时可以成功,能保证最终一致性就可以
缓存穿透
缓存穿透。产生这个问题的原因可能是外部的恶意攻击,例如,对用户信息进行了缓存,但恶意攻击者使用不存在的用户id频繁请求接口,导致查询缓存不命中,然后穿透 DB 查询依然不命中。这时会有大量请求穿透缓存访问到 DB。
使用 BloomFilter 过滤器,BloomFilter 的特点是存在性检测,如果 BloomFilter 中不存在,那么数据一定不存在;如果 BloomFilter 中存在,实际数据也有可能会不存在。非常适合解决这类的问题。
缓存击穿
缓存击穿,就是某个热点数据失效时,大量针对这个数据的请求会穿透到数据源。
解决这个问题有如下办法。
- 可以使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到 DB,减小 DB 压力。
- 使用随机退避方式,失效时随机 sleep 一个很短的时间,再次查询,如果失败再执行更新。
- 针对多个热点 key 同时失效的问题,可以在缓存时使用固定时间加上一个小的随机数,避免大量热点 key 同一时刻失效。
缓存雪崩
缓存雪崩,产生的原因是缓存挂掉,这时所有的请求都会穿透到 DB。
解决方法:
- 使用快速失败的熔断策略,减少 DB 瞬间压力;
- 使用主从模式和集群模式来尽量保证缓存服务的高可用。
- 使用FDB和AOF实时备份数据
- 增加本地缓存应急
- ...
加分项
如果想要在面试中获得更好的表现,还应了解下面这些加分项。
结合实际应用场景来表达,什么时候使用什么类型的数据
使用什么类型的数据解决了什么问题
Redis是单线程处理请求,应避免耗时较高的单个请求,防止相互影响,Redis服务器应避免和其他cpu密集型的进程部署在同一机器;或者禁用Swap内存交换,放置redis的缓存数据交换到硬盘上,影响性能,