分布式缓存

引用: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持久化既可以通过saveBGSAVE命令进行手动持久化,也可以根据服务器配置选项定期执行,其实定期执行的进程也是执行的BGSAVE命令。

SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何客户端命令请求,所以一般不推荐使用SAVE命令。

BGSAVE不会直接阻塞服务器进程,相反它会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理请求。处理流程图如下:

image.png-31.5kB

AOF

AOF,英文是Append Only File,即只允许追加不允许改写的文件。

AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的。在服务器重启时,Redis服务器会载入和执行AOF文件中保存的命令来还原服务器关闭之前的数据库状态。

image.png-25.4kB

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就可以用从服务器承担。

主从架构中,就可以考虑关闭主服务器的数据持久化功能,只让从服务器进行持久化,这样就可以提高主服务器的处理性能。

全量同步:

  1. 从服务器向主服务器发送 PSYNC命令。
  2. 收到PSYNC命令的主服务器执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
  3. 当主服务器的BGSAVE命令执行完毕时,主服务器会将RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态。
  4. 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器当前所处的状态。

image.png-22.7kB

另外,要说的一点是,即使有多个从服务器同时发来SYNC指令,主服务器也只会执行一次BGSAVE,然后把持久化好的RDB文件发给多个下游。

因为这个过程对从服务器和主服务器都是一个非常消耗资源的过程:

  1. 主服务器需要执行BGSAVE命令来生成RDB文件,这个生成操作会耗费主服务器大量的CPU、内存和磁盘I/O资源。
  2. 主服务器需要将自己生成的RDB文件发送给从服务器,这个发送操作会耗费主从服务器大量的网络资源(带宽和流量),并对主服务器响应命令请求的事件产生影响。
  3. 接收到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缓存一致性解决办法

将不一致分为三种情况:
  1. 数据库有数据,缓存没有数据;
  2. 数据库有数据,缓存也有数据,数据不相等;
  3. 数据库没有数据,缓存有数据。
解决方案大概有以下几种:
  1. 对删除缓存进行重试,数据的一致性要求越高,越是重试得快。
  2. 定期全量更新,简单地说,就是定期把缓存全部清掉,然后再全量加载。
  3. 给所有的缓存一个失效期。
并发不高的情况:
读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;
写: 写mysql->成功,再写redis
并发高的情况:
读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;
写:异步话,先写入redis的缓存,就直接返回;定期或特定动作将数据保存到mysql,可以做到多次更新,一次保存;

二、Memcache

Memcache可以利用多核优势,单实例吞吐量极高,可以达到几十万QPS,适用于最大程度扛量;

数据存在内存中,只支持简单的key/value数据结构,无法进行持久化,数据不能备份,只能用于缓存使用,且重启后数据全部丢失;

三、缓存的问题

缓存穿透:

 缓存穿透指的是查询一个根本不存在的数据,缓存层不命中,又去查存储层,又不命中。但如果有大量这种查询不存在的数据的请求过来,DB可能挂掉,若是恶意攻击,就是漏洞。

解决方案:

  • 布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
  • 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方案:

  • 使用互斥锁(mutex key):就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
  • 在原有的失效时间基础上增加一个随机值,这样每一个缓存的过期时间的重复率就会降低。

posted @ 2018-09-13 15:07  提拉米苏007  阅读(122)  评论(0编辑  收藏  举报