今天就来复习下分布式缓存相关的基础知识
一点点相对自己说的话
在以后的学习中希望自己能把思维导图画出来,因为图文结合非常不错.
开始吧
项目中为什么要用缓存呢?
- 高性能
下面来一张图了解下缓存的用处
- 高并发
对于我来说,高并发这点确实经验不足,因为学校里面做过的项目总共也就用那么几台电脑,所以只能简单使用一下,真正要面对大量的QPS还是得在未来的项目中积累经验
项目中使用缓存可能会出现的问题
有缓存雪崩、数据库双写不一致、缓存击穿、缓存穿透、缓存并发竞争等等的问题,这些在项目中都需要考虑到,否则便是一个不合格的demo级别的项目,时刻面临亏损或者宕机的风险,这些问题在下文中会简单的回顾并提供一些的解决思路
了解一下Redis的单线程模型吧
redis会与相互通信的客户端建立连接,监听相关的指令按顺序进行分发处理
为什么Redis是单线程的却能有如此高的性能呢?
原因可以归结为两点:
- 基于非阻塞的IO多路复用程序,该程序只负责监听socket连接中的各种请求并压入队列,不进行复杂的处理,所以非常迅速
- 基于内存的文件事件处理,因为纯内存,所以天生就是非常高效
- 单线程避免了多线程频繁上下文切换(依靠时间片分配cpu资源,每个线程获得一定时间的cpu使用时间,切换使用肯定会有一定的消耗)
Redis和Memcached的几个区别
-
Redis单线程而Mem是多线程(缩写下)
-
Redis支持更多的数据结构,包括字符串,散列,列表,集合,有序集,位图,超级日志和空间索引而Mem支持字符串和整数
String 做缓存kv操作
hash 可存储对象
list 可做统计列表(如粉丝列表)lrange实现redis级别的高性能分页
set 自动去重 可做交并差集
sorted set 可进行排序 -
Mem没有集群模式,需要实现只能往集群中分片写入,比较费事,而redis有原生的Redis Cluster(后面会介绍包括redis主从复制等相关知识)
-
小数据量Redis更优,100k以上的数据Mem更优,没有一个绝对的界定,机器性能也有关系,所以说主要的区别还是在于2和3两点吧
Redis过期策略
对于redis 的过期策略来讲当然离不开过期时间的设置,当键过期以后,redis会进行
定期删除+ 惰性删除的处理…
定期删除 : 默认100ms会从设置了过期时间的键中抽取部分进行检查并删除
惰性删除 : 少数未被抽取删除的过期键会在下一次被访问时删除并返回空
在这两个方式下redis可以保证一个过期了的键一定会被删除,但是删除归删除,如果定期删除太慢,而且不怎么进行访问key,还不断写入新key,那redis不就满了吗
答:走内存淘汰
内存淘汰有以下几个策略
1)noeviction (写满了再写就报错,差不多这意思,没啥人会用)
2)allkeys-lru (比较常用的算法,删除最少访问的key)
3)allkeys-random(所有键随机删,什么鬼东西)
4) volatile-lru(设置了过期事件的key中删除最少访问的key)
5) volatile-random (设置了过期键的随机删)
6) volatile-ttl(在设置了过期时间的key中删除有更早过期时间的key)
整体看下来肯定Lru策略比较符合正常人的口味
// Lru算法
public class LRUCache<K,V> extends LinkedHashMap<K,V>{
private final int CACHE_SIZE;
public LRUCache(int cacheSize){
super((int)Math.ceil(cacheSize/0.75)+1,0.75f,true)
//设置hashmap的初始大小,true是让linkedhashmap按照最近访问
来排序,最近访问在头,最老访问在尾
CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest){
return size() > CACHE_SIZE;
//map中的数据量大于指定的缓存个数,就自动删除最老的数据
}
}
裂开…
Redis如何保证高可用
对于一般的项目而言,缓存可以基于Redis的主从架构部署实现读写分离
对于高数据量需求的项目可以使用集群一般来说Redis Cluster = Redis 主从架构(高并发)+ 持久化 + 哨兵保证高可用
Redis必须进行持久化操作,如果不进行持久化,当master宕机后再重启,数据丢失,slave在同步数据后也都会丢失,100%!!! 虽然高可用机制中,slave node 可替换 master node继续工作, 但是不排除还未检测到master down便重新启动的情况 , 此时master数据已经100%丢失,还是有清空数据的风险
两种数据复制恢复策略
-
RDB
每隔几分钟或几小时几天,生成redis数据的一份完整的快照RDB快照的数据丢失:因为是每隔一段时间进行快照备份处理,如果宕机…
-
AOF
每秒调用操作系统的fsync,强制将os cache中的数据刷入磁盘文件中生成AOF文件存放每条写指令,当膨胀到一定程度后执行rewrite操作,会根据redis缓存中最新的数据进行构建
总结 : RDB适合做冷备份,AOF适合作为数据丢失的第一恢复策略
缓存雪崩、穿透、击穿
缓存雪崩:
下面放一张图来解释何为缓存雪崩
解决思路也用一张图来解决吧:
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 设置热点数据永远不过期。
缓存穿透
为恶意访问的结果,比如一秒有5000个请求,但其中有4000个是来自恶意访问,根本无法在redis查到缓存数据甚至mysql也无法查到因为根本不存在该数据,所以4000个恶意的请求会直接落至mysql,导致宕机
解决思路:
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存击穿
缓存中无数据,数据库有数据,用户并发请求过高,可以尝试分布式锁,先放一个线程去获取数据,将数据添至缓存,然后释放锁,其他的线程即可通过缓存获取,锁的时间应该根据系统性能进行判断,不宜过长
- 缓存中有数据,直接返回数据
- 缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。
- 当然这是简化处理,理论上如果能根据key值加锁就更好了,就是线程A从数据库取key1的数据并不妨碍线程B取key2的数据
双写不一致性问题
双写不一致,数据库与缓存中存储数据不同
进行一次数据更新时,先修改了数据库,再更新缓存, 但是在更新缓存时出现了某些问题,导致数据出现不一致的情况
初步解决,将操作顺序更换,即先删除缓存,然后更新数据库,即使更新数据库失败了,但起码数据库与缓存中的数据是一致的
在并发的情况下,写请求在修改一个数据的时候,可能会有一个读请求读取了数据,读取完后写操作才完成,那么又出现了双写不一致的情况
既然出现了先后交替的情况,自然可以想到将操作串行化进行解决
使用这样的方案进行处理时,应该注意读请求超时的情况,可以根据实际情况进行机器的增加,使每个内存队列分配的操作更少
Redis并发竞争问题
在Redis中进行写操作时,并发竞争问题是不可以忽视的,因为线程上下文切换导致写操作执行顺序不固定,具体情况如图
重点: 时间管理者 555555…
在进行并发问题解决时,只要争取的处理好时间戳,那么在数据新旧程度上的判断就非常轻松了
缓存架构问题该如何设计
例:redis cluster , 4台机器,2台部署主实例,2台部署从实例,每个主实例挂了一个从实例,2个节点提供读写服务,每个节点读写高峰达到每秒5W的QPS,2台机器是10W的QPS
机器配置 : 32G内核+8核cpu+1T磁盘,但是分配给redis的内存是10g,一般线上生产环境,redis的内存尽量不超过10g,超过10g会出问题
2台对外提供读写 , 一共有20g的内存
因为每个主实例都挂了一个从实例 , 所以是高可用的,任何一个主实例宕机都会自动故障迁移,redis从实例会自动变为主实例提供读写服务
内存写的一般是商品数据,每条大概是10KB,10万条1g。常驻内存的是100万条商品数据,占用内存20g不到
完结撒花*~
今天或许很菜,那明天至少不会比今天还菜,祝自己早点找到工作