Redis总结

亦碎流年 2020-06-11

一、概述

Redis是C语言开发的一个开源的(基于BSD协议)高性能键值对(key-value)的内存数据库,可以用作数据库、缓存、消息中间件等。

  • 它是一种NoSQL(Not-Only SQL)的数据库。

  • Redis作为一个内存数据库,性能优秀,数据在内存中,读写速度非常快,支持并发10W QPS。

  • 单进程单线程,是线程安全的,采用IO多路复用机制。

  • 丰富的数据类型,支持字符串(string)、散列(hash)、列表(list)、集合(set)、有序集合(sorted set)等。

  • 支持数据持久化。可以将内存中的数据保存到磁盘中,重启时加载。

  • 主从复制,哨兵,高可用。

  • 可以作为分布式锁。

  • 可以作为消息中间件使用,支持发布订阅模式。

二、数据类型

Redis总结

Redis内部使用一个RedisObject对象来表示所有的key和value。type表示一个value具体是什么数据类型,encoding是不同数据类型在Redis内部的存储方式。

比如type=string表示value存储的是一个普通字符串,那么encoding可以是raw或者int。

1.String:

Redis最基本的类型,可以理解成与Memcached一样的类型,一个Key对应一个Value。Value不仅是String,也可以是数字。

String类型是二进制安全的,意思是Redis的String类型可以包含任何数据,比如图片或者序列化的对象。String类型的值最大能存储512M。

常用命令:get, set

2.Hash:

Hash是一个兼职(key-value)的集合。Redis的Hash是一个String的Key和Value的映射表,Hash特别适合存储对象。

常用命令:hget, hset, hgetall

3.List:

List的简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)。

常用命令:lpush, rpush, lpop, rpop, lrange(获取列表片段)

应用场景:关注人列表, 粉丝列表

数据结构:List就是链表,可以用来当做消息队列。Redis提供了List的Push和Pop操作,还提供了操作某一段的API,可以查询或者删除某一段的元素。

实现方式:Redis List的实现是一个双向链表,即可以支持反向查找和遍历,更方便操作,不过会带来额外的内存开销。

4.Set:

Set是Sting的无需集合。

常用命令:sadd, spop, smembers, sunion等

应用场景:Redis Set对外提供的功能和List一样是一个列表,特殊之处在于Set是自动去重的,而且Set提供了判断某个成员是否在一个Set集合中。

实现方式:集合是通过HashTable来实现的。Set中的元素是没有顺序的,而且是没有重复的。

5.Sorted Set:

Sorted Set和Set一样是String类型元素的集合,且不允许有重复的元素。

和Set相比,Sorted Set关联了一个Double类型权重的参数score,使得集合中的元素能够按照score进行有序排列。Redis正是通过score来为集合中的成员进行从小到大的排序。

常用命令:zadd, zrange, zrem, zcard等

使用场景:Sorted Set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且插入是有序的,及自动排序。

实现方式:Redis Sorted Set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射。而跳跃表里存放的是所有成员,排序依据是HashMap里存的score,使用跳跃表的结构可获得比较高的查询效率,并且在实现上比较简单。

数据类型应用场景总结:

Redis总结

三、常见问题

数据一致性问题

分布式环境下非常容易出现缓存和数据库间数据一致性的问题。针对这一点,如果项目对缓存的要求是强一致性的,那么就不要使用缓存。

我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。

合适的策略包括合适的缓存更新策略,更新数据库后及时更新缓存,缓存失败时增加重试机制。

缓存雪崩

Redis雪崩指的是缓存数据大面积过期失效,流量直接打到数据库上,增加数据库压力导致数据库挂掉。

解决方法:

  • key的失效时间增加随机数 set(key,value,time+Math.random()*1000);
  • 如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效。
  • 设置热点数据永不过期,有更新操作就更新缓存。

缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,但用户一直发送请求,导致一直访问数据库,增加数据库的压力,严重会是数据库挂掉。

解决方法:

  • 在接口层增加校验,比如用户鉴权,参数校验,访问频次校验,不合法的校验直接return。
  • 布隆过滤器(Bloom Filter):利用高效的数据结构和算法快速判断出这个key是否在数据库中存在的,不存在则return,存在再查DB刷新KV在return。

缓存击穿

与缓存雪崩类似,但雪崩是因为大面积缓存失效,击垮了数据库。而缓存击穿是指一个key非常热点,在不停的扛着大量的请求,大并发集中对着一个点进行访问,当这个key是失效的瞬间,持续的大并发直接落到了数据库上,就在这个key的点上击穿了缓存。

解决方法:

  • 设置热点数据永不过期
  • 增加互斥锁

四、性能

Redis官方提供的数据可以达到10w+ 的QPS(每秒内的查询数),这个数据并不比Memcached差。

Redis是单进程单线程的模型,因为Redis完全是基于内存的操作,CPU不是瓶颈,Redis的瓶颈最有可能是机器内存的大小伙子网络带宽。避免了多线程线程切换带来的损耗。

  • Redis完全基于内存,绝大部分请求是纯粹的内存操作,非常迅速,数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度是O(1)。
  • 数据结构简单,对数据操作也简单。
  • 采用单线程,避免了不必要的上下文切换和竞争条件,不存在多线程导致的CPU切换,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有死锁导致的性能消耗。
  • 使用多路复用IO模型,非阻塞IO。

Redis和Memcached区别

  • 存储方式:Memcached会把数据全部存在内存中,断电后会丢失,数据不能超过内存大小。Redis有部分数据存在磁盘上,这样能保证数据的持久性。
  • 数据类型:Memcached对数据类型的支持简单,只支持简单的key-value,而Redis支持五种数据类型。
  • 底层模型:它们之间底层实现方式与客户端之间通信的应用协议不一样。Redis之间构建了VM机制,因为一般的系统调用系统函数的话会浪费一定的时间去移动和请求。
  • Value的大小:Redis的Value可以达到1GB,而Memcached只有1MB。

五、淘汰策略

Redis有6种淘汰策略:
Redis总结

六、Redis过期策略及实现原理

Redis设置过期时间:

  • 常用方式:expire key time(以秒为单位)

  • 字符串独有方式:setex(key,senconds,value)

除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠expire方法来设置时间。

如果没有设置时间,那就是永不过期。如果设置了过期时间,之后又想让不过期,使用persist key。

过期策略

定时删除

  • 含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除。
  • 有点:保证内存被尽快释放
  • 缺点:若过期key很多,删除这些key就会占用很多CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事情,还需要花时间删除这些key。定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器,将会有大量的定时器,性能影响严重。

懒汉式删除

  • 含义:key过期的时候不删除,每次通过key获取值的时候检查是否过期,若过期,则删除,返回null。(除获取key操作外,如setnx也会检查key是否过期)
  • 优点:删除操作只发生在通过key取值的时候,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除已经到了非删不可的地步(如果不删除的话,我们就会获取到已经过期的key)
  • 缺点:若大量的key在超出过期时间后,很久的一段时间内,都没有被获取过,那么可能发生内存泄露。

定期删除

  • 含义:每隔一段时间执行一次删除过期key的操作。
  • 优点:通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用
  • 缺点:在内存友好方面,不如“定时删除”(会造成一定时间的内存占用,但没有懒汉式那么占内存); 在CPU友好方面,不如“懒汉式删除”(会定期的去进行比较和删除操作,CPU方面不如懒汉式,但是比定时好)
  • 难点:合理设置删除操作的时长(每次执行删除的时长)和执行频率。每次执行时间太长或执行频率太高对CPU都是一种压力。每次进行定期删除操作之后,需要记录遍历循环到了哪个标志位,以便下一次定期时间来时,从上次位置开始进行循环遍历。

Redis的过期策略

【懒汉式删除+定期删除】

Memcached只用了懒汉式删除,而Redis同时使用了懒汉式删除与定期删除。

懒汉式删除流程:

1)在进行get或setnx等操作是,先检查key是否过期;

2)若过期,删除key,然后执行相应操作;

3)若没过期,直接执行相应操作;

定期删除流程:

对指定个数个库的每一个库随机删除小于等于指定个数个过期key

1)变量每个数据库;

2)检查当前库中的指定个数key(默认是每个库检查20个key);

3)如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历;

4)随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key;

5)判断定期删除操作是否已经达到了指定时长,若已达到,直接退出定期删除;

对于定期删除,会有全局全量current_db来记录下一个将要遍历的库,假如有16个库,本次删除遍历了10个,那此时current_db就是11,下次就会从11开始,假如current_db=15,那么只会遍历就从0开始。

七、持久化

Redis为了保证效率,数据缓存在了内存中,但是会周期性的把更新的数据写入磁盘或者吧修改操作写入追加的记录文件中,以保证数据的持久化。

  • RDB:已快照形式直接把内存中的数据保存到一个dump的文件中,定时保存。

  • AOF:把所有对Redis进行修改的命令都存到一个文件中。

Redis默认是使用RDB的持久化方式。当Redis重启的时候,它会优先使用AOF文件来还原数据集,因为AOF文件保存的数据集通常比RDB文件所保存的数据更完整。当然也可以关闭持久化功能,让数据只在服务器运行时存。

RDB

默认Redis是会以快照RDB的形式将数据持久化到磁盘的一个二进制文件dump.rdb。

当Redis需要做持久化是,Redis会fork一个子进程,子进程将数据写到磁盘上一个临时RDB文件中。当子进程完成写临时文件后,将原来的RDB替换掉,这样的好处是可以copy-on-write。

  • 优点:适合用于定时备份,已进行灾难恢复。
  • 缺点:可能会丢失数据

AOF

使用AOF做持久化,每一个命令都通过write函数追加到appendonly.aof中。

AOF可以做到全程持久化。这样Redis没执行一个修改操作命令,都会把它添加到AOF文件中,当Redis重启时,将会读取AOF文件进行重放,恢复到Redis关闭前的最后时刻。

  • 优点:会让Redis变得非常耐久。可以设置不同的持久化策略,AOF默认策略是每秒钟写一次,在这种配置下,就算发生故障停机,也最多丢失一秒钟的数据。
  • 缺点:对于想太多额数据集来说,AOF的文件体积通常要大于RDB文件的体积。根据使用的持久化策略,AOF的速度可能会比RDB慢。

总结:

RDB备份速度和恢复速度都要比AOF快,但可能会丢失数分钟内的数据。AOF处理巨大的写入会降低Redis的性能。
当然,Redis支持同时开启RDB和AOF,系统重启时,Redis会优先使用AOF来恢复数据,这样丢失的数据会最少。

八、主从复制

主从结构

Redis单节点会存在单点故障问题,可以通过对Redis配置从节点,主从配置结合哨兵模式能解决单点故障问题。提高Redis可用性。

从节点仅提供读操作,主节点提供写操作。对于读多写少的情况,可以给主节点配置多个从节点,从而提高响应效率。

复制过程

1.从节点执行slaveof[masterIP][matserPort],保存主节点信息。

2.从节点中的定时任务发现主节点信息,建立和主节点的Socket连接。

3.从节点发送Ping信号,主节点返回Pong,两边能互相通信。

4.连接建立后,主节点将所有数据发送给从节点(数据同步)。主节点把当前的数据同步给从节点后,便完成了复制的建立过程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。

数据同步

Redis2.8之前使用sync[runId][offset]同步命令,Redis2.8之后使用psync[runId][offset]命令。两者的不同在于,sync命令仅支持全量复制过程,psync支持全量和部分复制。

概念介绍:

  • runId:每个Redis节点启动都会生成唯一的uuid,每次Redis重启后,runId都会发生变化。
  • offset:主节点和从节点都各自维护自己的主从复制偏移量offset,当主节点有写入命令是,offset=offset+命令的字节长度。从节点在收到主节点发生的命令后,也会增加自己的offset,并把自己的offset发送给主节点。这样,主节点同时保持自己的offset和从节点的offset,通过对比offset来判断主从节点数据是否一致。
  • repl_backlog_size:保持在主节点上一个固定长度的先进先出队列,默认大小是1MB。

主节点发送数据给从节点过程中,主节点还会进行一些写操作,这时候的数据存储在复制缓冲区中。

从节点同步主节点数据完成后,主节点将缓冲区的数据继续发送给从节点,用于部分复制。

主节点相应写命令时,不但会把命名发送给从节点,还会写入复制积压缓冲区,用于复制命令丢失的数据补救。

psync执行流程:

Redis总结

从节点发送psync[runId][offset]命令,主节点有三种响应:

  • FULLRESYNC:第一次连接,进行全量复制
  • CONTINUE:进行部分复制
  • ERR:不支持psync命令,进行全量复制

全量复制

全量复制执行流程:

Redis总结

  • 1.从节点发送psync ? -1 (因为是第一次发送,不知道从节点的runId,所以为?,因为是第一次复制,所以offset=-1)
  • 主节点发现从节点是第一次复制,返回FULLRESYNC[runId][offset](runId是主节点的runId,offset是从节点目前的offset)
  • 从节点接收主节点信息后,保存到info中。
  • 主节点在发送FULLRESYNC后,启动bgsave命令,生成RDB文件(数据持久化)。
  • 主节点发送RDB文件给从节点。
  • 到从节点加载数据完成这段期间主节点的写命令放入缓冲区。
  • 从节点清理自己的数据库数据
  • 从节点加载RDB文件,将数据保存到自己的数据库中。
  • 如果从节点开启了AOF,从节点会异步重写AOF文件。

部分复制

1.部分复制主要是Redis针对全量复制的过高开销做出的一种优化措施,使用psync[runId][offset]命令实现。

当从节点正在复制主节点时,如果出现网络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,主节点的复制积压缓冲区将这部分数据直接发送给从节点。

这样就可以保持主从节点复制的一致性。补发的这部分数据一般远远小于全量数据。

2.主从连接中断期间主节点依然响应命令,但因复制连接中断命令无法发送给从节点,不过主节点内的复制积压缓冲区依然可以保存最近一段时间的写命令数据。

3.当主从连接恢复后,由于从节点之前保存了自身已复制的偏移量offset和主节点的runId。因此会把它们当做psync参数发送给主节点,要求进行部分复制。

4.主节点接收到psync命令后首先核对参数runId是否和自身一致,如果一致,说明之前复制的是当前主节点。

之后根据参数offset在复制积压缓冲区中查找,如果offset之后的数据存在,则对从节点发送+COUTINUE命令,表示可以进行部分复制。由于缓存区大小固定,若发生缓存溢出,则进行全量复制。

5.主节点根据偏移量offset吧复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。

复制问题

  • 一旦主节点宕机,从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预;
  • 主节点的写能力受到单机的限制;
  • 主节点的存储能力受到单机的限制;
  • 原生复制的弊端在早期的版本中也会比较突出,比如:Redis复制中断后,从节点会发起psync,此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时,可能会造成毫秒或秒级的卡顿。

九、哨兵

Redis Sentinel(哨兵)架构图:

Redis总结

主要功能

  • 主节点存活检测
  • 主从运行情况检测
  • 自动故障转移
  • 主从切换

配置

  • 最小配置是一主一从。
  • Redis的Sentinel系统可以用来管理多个Redis服务器。

可执行任务

  • 1 监控:不断检查主服务器和从服务器是否正常运行
  • 2 通知:当被监控的某个Redis服务器出现问题,Sentinel通过API脚本向管理员或者其他应用程序发出通知
  • 3 自动故障转移:当主节点不能正常工作时,Sentinel会开启一次自动的故障转移操作,它会将与失效节点是主从关系的其中一个从节点升级为主节点,并且将其他的从节点指向新的主节点,整个过程是完全自动进行的。
  • 4 配置提供者:在Redis Sentinel模式下,客户端应用在初始化时连接的是Sentinel节点集合,从中获取主节点的信息。

工作原理

  • 每个Sentinel节点都需要定期执行以下任务:每个Sentinel以每秒一次的频率,向它所知的主服务器、从服务器以及其他Sentinel实例发送一个PING命令。

Redis总结

  • 如果有一个实例距离最后一次有效回复PING命令的时间超过down-after-milliseconds所指定的值,那么这个实例就会被Sentinel标记为主观下线。

Redis总结

  • 如果一个主服务器被标记为主观下线,那么正在监视这个服务器的所有Sentinel节点,要以每秒一次的频率确认主服务器的确进入了主观下线状态。

Redis总结

  • 如果一个主服务器被标记为主观下线,并且有足够数量的Sentinel(至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断,那么这个主服务器就会被标记为客观下线。

Redis总结

  • 一般情况下,每个Sentinel会以每10秒一次的频率向它所知的所有主服务器和从服务器发送INFO命令。当一个主服务器被标记为客观下线时,Sentinel向下线主服务器的所有从服务器发送INFO命令的频率,会从10秒一次改为1秒一次。

Redis总结

  • Sentinel和其他Sentinel协商客观下线的主节点的状态,如果处于SDOWN状态,则投票自动选出新的主节点,将剩余从节点指向新的主节点进行数据复制。

Redis总结

  • 当没有足够数量的Sentinel同意主服务器下线时,主服务器的下线状态就会被移除。
  • 当主服务器重新向Sentinel的PING命令返回有效回复是,主服务器的主观下线状态就会被移除。

Redis总结

相关推荐

DiamondTao / 0评论 2020-08-30