分布式缓存
引用:https://www.cnblogs.com/boothsun/p/8601474.html
一、redis
redis是nosql,数据存于内存,单线程,用了多路复用I/O,1秒可处理10w的并发
1、redis支持的数据类型:
string:二进制类型,一个键最大能存储512MB;
list:是简单的字符串列表,底层是由双向链表实现,可以将元素插入到列表的头部或尾部;
- 可以利用lists来实现一个消息队列,而且可以利用链表来确保先后顺序。
- 利用LRANGE还可以很方便的实现分页功能。
set :是无序集合,记录一些不能重复的数据,最大可以包含(2 的 32 次方-1)个元素。set 的是通过 hash table 实现的,所以添加,删除,查找的复杂度都是 O(1)
set包含集合的取并集(union),交集(intersection) ,差集(difference),可实现 SNS 中的好友推荐和 blog 的 tag 功能。
- set查询该用户名是否被注册,效率极高;
- 投票系统,比如一天用户只能投票一次;
sorted sets:是有序集合(sorted sets),其中每个元素都关联一个序号(score),这便是排序的依据。
- 对博客按照顶贴进行排序
hash:存的是字符串和字符串值之间的映射,比如一个用户要存储其全名、姓氏、年龄等等,就很适合使用哈希。
2、redis数据持久化
一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB文件持久化),另外一种是AOF(append only file)持久化(原理是将Reids的操作日志以追加的方式写入文件)。
RDB
RDB持久化既可以通过save
和BGSAVE
命令进行手动持久化,也可以根据服务器配置选项定期执行,其实定期执行的进程也是执行的BGSAVE
命令。
SAVE
命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何客户端命令请求,所以一般不推荐使用SAVE
命令。
BGSAVE
不会直接阻塞服务器进程,相反它会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理请求。处理流程图如下:
AOF
AOF,英文是Append Only File,即只允许追加不允许改写的文件。
AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的。在服务器重启时,Redis服务器会载入和执行AOF文件中保存的命令来还原服务器关闭之前的数据库状态。
AOF优点:
默认的AOF持久化策略是每秒钟fsync一次,即使redis故障,也只会丢失最近1秒钟的数据;
如果在追加日志时,恰好遇到磁盘空间满、inode满或断电等情况导致日志写入不完整,edis提供了redis-check-aof工具,可以用来进行日志修复;
因为采用了追加方式,AOF文件会变得越来越大,为此,redis提供了AOF文件重写(rewrite)机制,即简单地读取了当前数据库中该键对应的值,然后使用一条set指令将这个值写入到新的AOF文件中。
数据恢复:某同学在操作redis时,不小心执行了FLUSHALL,导致redis内存中的数据全部被清空,如果AOF文件没有被重写,可以将最后一条指令删除,重新执行进行数据恢复;
AOF缺点:
同样数据规模的情况下,AOF文件要比RDB文件的体积大。而且,AOF方式的恢复速度也要慢于RDB方式。
3、redis主从同步
像MySQL一样,Redis是支持主从同步的,而且也支持一主多从以及多级从结构。
主从结构,一是为了纯粹的冗余备份,二是为了提升读性能,比如很消耗性能的SORT就可以用从服务器承担。
主从架构中,就可以考虑关闭主服务器的数据持久化功能,只让从服务器进行持久化,这样就可以提高主服务器的处理性能。
全量同步:
- 从服务器向主服务器发送 PSYNC命令。
- 收到PSYNC命令的主服务器执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
- 当主服务器的BGSAVE命令执行完毕时,主服务器会将RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态。
- 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器当前所处的状态。
另外,要说的一点是,即使有多个从服务器同时发来SYNC指令,主服务器也只会执行一次BGSAVE,然后把持久化好的RDB文件发给多个下游。
因为这个过程对从服务器和主服务器都是一个非常消耗资源的过程:
- 主服务器需要执行BGSAVE命令来生成RDB文件,这个生成操作会耗费主服务器大量的CPU、内存和磁盘I/O资源。
- 主服务器需要将自己生成的RDB文件发送给从服务器,这个发送操作会耗费主从服务器大量的网络资源(带宽和流量),并对主服务器响应命令请求的事件产生影响。 接收到RDB文件的从服务器需要载入主服务器发过来的RDB文件,并且在载入期间,从服务器会因为阻塞而无法处理命令请求。
增量同步:
Redis增量复制是指从服务器初始化后,主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
Redis主从同步策略:
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求重新进行全量同步。
4、哨兵模式(Sentinel)
Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。
哨兵进程的工作方式:
1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令
2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线。
3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线
5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令
6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次
7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除,若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除
5、Redis的内存淘汰策略
Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
6、key过期清除策略
惰性过期(类比懒加载,这是懒过期):只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
7、其他
使用redis如何设计分布式锁?使用zk可以吗?如何实现啊?这两种哪个效率更高啊??
zk使用临时节点,使用完毕之后要主动删除临时节点,Zookeeper分布锁,首先创建加锁标志文件,如果需要等待其他锁,则添加监听后等待通知或者超时,当有锁释放,无须争抢,按照节点顺序,依次通知使用者。
Redis分布式锁,必须使用者自己间隔时间轮询去尝试加锁,当锁被释放后,存在多线程去争抢锁,并且可能每次间隔时间去尝试锁的时候,都不成功,对性能浪费很大。
8、Redis缓存一致性解决办法
- 数据库有数据,缓存没有数据;
- 数据库有数据,缓存也有数据,数据不相等;
- 数据库没有数据,缓存有数据。
- 对删除缓存进行重试,数据的一致性要求越高,越是重试得快。
- 定期全量更新,简单地说,就是定期把缓存全部清掉,然后再全量加载。
- 给所有的缓存一个失效期。
二、Memcache
Memcache可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS,适用于最大程度扛量;
数据存在内存中,只支持简单的key/value数据结构,无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失;
三、缓存的问题
缓存穿透:
缓存穿透指的是查询一个根本不存在的数据,缓存层不命中,又去查存储层,又不命中。但如果有大量这种查询不存在的数据的请求过来,DB可能挂掉,若是恶意攻击,就是漏洞。
解决方案:
- 布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
- 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
缓存雪崩:
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决方案:
- 使用互斥锁(mutex key):就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
-
在原有的失效时间基础上增加一个随机值,这样每一个缓存的过期时间的重复率就会降低。