关于redis:Redis知识谱

1次阅读

共计 9111 个字符,预计需要花费 23 分钟才能阅读完成。

Redis

可基于内存亦可长久化的日志型、Key-Value 数据库

五种数据结构


字符串(String)

       与其它编程语言或其它键值存储提供的字符串十分类似,键 (key)—— 值(value) (字符串格局), 字符串领有一些操作命令,如:get set del 还有一些比方自增或自减操作等等。redis 是应用 C 语言开发,但 C 中并没有字符串类型,只能应用指针或符数组的模式示意一个字符串,所以 redis 设计了一种简略动静字符串(SDS[Simple Dynamic String]) 作为底实现:

定义 SDS 对象,此对象中蕴含三个属性:

  • len buf 中曾经占有的长度(示意此字符串的理论长度)
  • free buf 中未应用的缓冲区长度
  • buf[] 理论保留字符串数据的中央

所以取字符串的长度的工夫复杂度为 O(1),另,buf[]中仍然采纳了 C 语言的以 0 结尾能够间接应用 C 语言的局部规范 C 字符串库函数。

空间分配原则:当 len 小于 IMB(1024*1024)时减少字符串调配空间大小为原来的 2 倍,当 len 大于等于 1M 时每次调配 额定多调配 1M 的空间。

由此能够得出以下个性:

  • redis 为字符调配空间的次数是小于等于字符串的长度 N,而原 C 语言中的分配原则必为 N。升高了调配次数进步了追加速度,代价就是多占用一些内存空间,且这些空间不会主动开释。
  • 二进制平安的
  • 高效的计算字符串长度(工夫复杂度为 O(1))
  • 高效的追加字符串操作。

列表(List)

         redis 对键表的构造反对使得它在键值存储的世界中自成一家,一个列表构造能够有序地存储多个字符串,领有例如:lpush lpop rpush rpop 等等操作命令。在 3.2 版本之前,列表是应用 ziplist 和 linkedlist 实现的,在这些老版本中,当列表对象同时满足以下两个条件时,列表对象应用 ziplist 编码:

  • 列表对象保留的所有字符串元素的长度都小于 64 字节
  • 列表对象保留的元素数量小于 512 个

当有任一条件 不满足时将会进行一次转码,应用 linkedlist。

而在 3.2 版本之后,从新引入了一个 quicklist 的数据结构,列表的底层都是由 quicklist 实现的,它联合了 ziplist 和 linkedlist 的长处。依照原文的解释这种数据结构是【A doubly linked list of ziplists】意思就是一个由 ziplist 组成的双向链表。那么这两种数据结构怎么样联合的呢?

ziplist 的构造

         由表头和 N 个 entry 节点和压缩列表尾部标识符 zlend 组成的一个间断的内存块。而后通过一系列的编码规定,进步内存的利用率,次要用于存储整数和比拟短的字符串。能够看出在插入和删除元素的时候,都须要对内存进行一次扩大或缩减,还要进行局部数据的挪动操作,这样会造成更新效率低下的状况。

这篇文章对 ziplist 的构造讲的还是比拟具体的:

https://blog.csdn.net/yellowriver007/article/details/79021049

linkedlist 的构造

        意思为一个双向链表,和一般的链表定义雷同,每个 entry 蕴含向前向后的指针,当插入或删除元素的时候,只须要对此元素前后指针操作即可。所以插入和删除效率很高。但查问的效率却是 O(n)[n 为元素的个数]。

理解了下面的这两种数据结构,咱们再来看看下面说的“ziplist 组成的双向链表”是什么意思?实际上,它整体宏观上就是一个链表构造,只不过每个节点都是以压缩列表 ziplist 的构造保留着数据,而每个 ziplist 又能够蕴含多个 entry。也能够说一个 quicklist 节点保留的是一片数据,而不是一个数据。总结:

  • 整体上 quicklist 就是一个双向链表构造,和一般的链表操作一样,插入删除效率很高,但查问的效率却是 O(n)。不过,这样的链表拜访两端的元素的工夫复杂度却是 O(1)。所以,对 list 的操作少数都是 poll 和 push。
  • 每个 quicklist 节点就是一个 ziplist,具备压缩列表的个性。

在 redis.conf 配置文件中,有两个参数能够优化列表:

  1. list-max-ziplist-size 示意每个 quicklistNode 的字节大小。默认为 -2 示意 8KB
  2. list-compress-depth 示意 quicklistNode 节点是否要压缩。默认是 0 示意不压缩

哈希(hash)

        redis 的散列能够存储多个键 值 对之间的映射,散列存储的值既能够是字符串又能够是数字值,并且用户同样能够对散列存储的数字值执行自增操作或者自减操作。散列能够看作是一个文档或关系数据库里的一行。hash 底层的数据结构实现有两种:

  • 一种是 ziplist,下面曾经提到过。当存储的数据超过配置的阀值时就是转用 hashtable 的构造。这种转换比拟耗费性能,所以应该尽量避免这种转换操作。同时满足以下两个条件时才会应用这种构造:

    • 当键的个数小于 hash-max-ziplist-entries(默认 512)
    • 当所有值都小于 hash-max-ziplist-value(默认 64)
  • 另一种就是 hashtable。这种构造的工夫复杂度为 O(1),然而会耗费比拟多的内存空间。

汇合(Set)

         redis 的汇合和列表都能够存储多个字符串,它们之间的不同在于,列表能够存储多个雷同的字符串,而汇合则通过应用散列表(hashtable)来保障自已存储的每个字符串都是各不相同的 (这些散列表只有键,但没有与键相关联的值),redis 中的汇合是无序的。还可能存在另一种汇合,那就是 intset,它是用于存储整数的有序汇合,外面寄存同一类型的整数。共有三种整数:int16_t、int32_t、int64_t。查找的工夫复杂度为 O(logN),然而插入的时候,有可能会波及到降级(比方:原来是 int16_t 的汇合,当插入 int32_t 的整数的时候就会为每个元素降级为 int32_t)这时候会对内存重新分配,所以此时的工夫复杂度就是 O(N) 级别的了。留神:intset 只反对降级不反对降级操作。

intset 在 redis.conf 中也有一个配置参数 set-max-intset-entries 默认值为 512。示意如果 entry 的个数小于此值,则能够编码成 REDIS_ENCODING_INTSET 类型存储,节约内存。否则采纳 dict 的模式存储。

有序汇合(zset)

        有序汇合和散列一样,都用于存储键值对:有序汇合的键被称为成员(member), 每个成员都是各不相同的。有序汇合的值则被称为分值(score),分值必须为浮点数。有序汇合是 redis 外面惟一一个既能够依据成员拜访元素(这一点和散列一样), 又能够依据分值以及分值的排列程序拜访元素的构造。它的存储形式也有两种:

  • 是 ziplist 构造。

          与下面的 hash 中的 ziplist 相似,member 和 score 程序寄存并按 score 的顺序排列

  • 另一种是 skiplist 与 dict 的联合。

         skiplist 是一种跳跃表构造,用于有序汇合中疾速查找,大多数状况下它的效率与均衡树差不多,但比均衡树实现简略。redis 的作者对一般的跳跃表进行了批改,包含增加 spantailbackward 指针、score 的值可反复这些设计,从而实现排序功能和反向遍历的性能。

个别跳跃表的实现,次要蕴含以下几个局部:

  • 表头(head):指向头节点
  • 表尾(tail):指向尾节点
  • 节点(node):理论保留的元素节点,每个节点能够有多层,层数是在创立此节点的时候随机生成的一个数值,而且每一层都是一个指向前面某个节点的指针。
  • 层(level):目前表内节点的最大层数
  • 长度(length):节点的数量。

跳跃表的遍历总是从高层开始,而后随着元素值范畴的放大,缓缓升高到低层。

跳跃表的实现原理能够参考:https://blog.csdn.net/Acceptedxukai/article/details/17333673

后面也说了,有序列表是应用 skiplist 和 dict 联合实现的,skiplist 用来保障有序性和拜访查找性能,dict 就用来存储元素信息,并且 dict 的拜访工夫复杂度为 O(1)。

利用场景

redis 个别利用场景

  1. 缓存会话(单点登录)
  2. 分布式锁,比方:应用 setnx
  3. 各种排行榜或计数器
  4. 商品列表或用户根底数据列表等
  5. 应用 list 作为音讯对列
  6. 秒杀,库存扣减等

五种类型的利用场景

  1. String,redis 对于 KV 的操作效率很高,能够间接用作计数器。例如,统计在线人数等等,另外 string 类型是二进制存储平安的,所以也能够应用它来存储图片,甚至是视频等。
  2. hash,寄存键值对,个别能够用来存某个对象的根本属性信息,例如,用户信息,商品信息等,另外,因为 hash 的大小在小于配置的大小的时候应用的是 ziplist 构造,比拟节约内存,所以针对大量的数据存储能够思考应用 hash 来分段存储来达到压缩数据量,节约内存的目标,例如,对于大批量的商品对应的图片地址名称。比方:商品编码固定是 10 位,能够选取前 7 位做为 hash 的 key, 后三位作为 field,图片地址作为 value。这样每个 hash 表都不超过 999 个,只有把 redis.conf 中的 hash-max-ziplist-entries 改为 1024,即可。
  3. list,列表类型,能够用于实现音讯队列,也能够应用它提供的 range 命令,做分页查问性能。
  4. set,汇合,整数的有序列表能够间接应用 set。能够用作某些去重性能,例如用户名不能反复等,另外,还能够对汇合进行交加,并集操作,来查找某些元素的共同点
  5. zset,有序汇合,能够应用范畴查找,排行榜性能或者 topN 性能。

Redis 缓存模式

Redis 缓存模式基于是否接管写申请,能够分成只读缓存和读写缓存:

只读缓存:只解决读操作,所有的更新操作都在数据库中,这样数据不会有失落的危险。

  • Cache Aside 模式

读写缓存,读写操作都在缓存中执行,呈现宕机故障,会导致数据失落。缓存回写数据到数据库有分成两种同步和异步:

  • 同步:拜访性能偏低,其更加侧重于保证数据可靠性

    • Read-Throug 模式
    • Write-Through 模式
  • 异步:有数据失落危险,其侧重于提供低提早拜访

    • Write-Behind 模式

Cache Aside 模式

查问数据先从缓存读取数据,如果缓存中不存在,则再到数据库中读取数据,获取到数据之后更新到缓存 Cache 中,但更新数据操作,会先去更新数据库种的数据,而后将缓存种的数据生效。

而且 Cache Aside 模式会存在并发危险:执行读操作未命中缓存,而后查询数据库中取数据,数据曾经查问到还没放入缓存,同时一个更新写操作让缓存生效,而后读操作再把查问到数据加载缓存,导致缓存的脏数据。

Read/Write-Throug 模式

查问数据和更新数据都间接拜访缓存服务,缓存服务同步形式地将数据更新到数据库。呈现脏数据的概率较低,然而就强依赖缓存,对缓存服务的稳定性有较大要求,但同步更新会导致其性能不好。

Write Behind 模式

查问数据和更新数据都间接拜访缓存服务,但缓存服务应用异步形式地将数据更新到数据库(通过异步工作) 速度快,效率会十分高,然而数据的一致性比拟差,还可能会有数据的失落状况,实现逻辑也较为简单。

在理论我的项目开发中依据理论的业务场景需要来进行抉择缓存模式。那理解上述后,咱们的利用中为什么须要应用到 redis 缓存呢?

在利用应用 Redis 缓存能够进步零碎性能和并发,次要体现在

  • 高性能:基于内存查问,KV 构造,简略逻辑运算
  • 高并发:Mysql 每秒只能反对 2000 左右的申请,Redis 轻松每秒 1W 以上。让 80% 以上查问走缓存,20% 以下查问走数据库,能让零碎吞吐量有很大的进步

应用缓存常见的问题

应用了缓存,会呈现一些问题,次要体现在:

  • 缓存与数据库双写不统一
  • 缓存雪崩: Redis 缓存无奈解决大量的利用申请,转移到数据库层导致数据库层的压力激增;
  • 缓存穿透:拜访数据不存在在 Redis 缓存中和数据库中,导致大量拜访穿透缓存间接转移到数据库导致数据库层的压力激增;
  • 缓存击穿:缓存无奈解决高频热点数据,导致间接高频拜访数据库导致数据库层的压力激增;

缓存与数据库数据不统一

只读缓存(Cache Aside 模式)

对于 只读缓存 (Cache Aside 模式),读操作都产生在缓存中,数据不统一只会产生在 删改操作 上(新增操作不会,因为新增只会在数据库解决),当产生删改操作时,缓存将数据中标记为有效和更新数据库。因而在更新数据库和删除缓存值的过程中,无论这两个操作的执行程序谁先谁后,只有有一个操作失败了就会呈现数据不统一的状况。

总结出,当不存在并发的状况应用重试机制(音讯队列应用),当存在高并发的状况,应用提早双删除(在第一次删除后,睡眠肯定工夫后,再进行删除),具体如下:

读写缓存(Read/Write-Throug、Write Behind 模式)

对于读写缓存,写操作都产生在缓存中,后再更新数据库,只有有一个操作失败了就会呈现数据不统一的状况。

总结出,当不存在并发的状况应用重试机制(音讯队列应用),当存在高并发的状况,应用散布锁。具体如下:

缓存雪崩

缓存雪崩,因为缓存中有大量数据同时过期生效或者缓存呈现宕机,大量的利用申请无奈在 Redis 缓存中进行解决,进而发送到数据库层导致数据库层的压力激增,重大的会造成数据库宕机。

对于缓存中有大量数据同时过期,导致大量申请无奈失去解决,解决形式:

  • 数据预热 将产生大并发拜访前手动触发加载缓存不同的 key,能够防止在用户申请的时候,先查询数据库
  • 设置不同的过期工夫,让缓存生效的工夫点尽量平均
  • 双层缓存策略,在原始缓存上加上拷贝缓存,原始缓存生效时能够拜访拷贝缓存,且原始缓存生效工夫设置为短期,拷贝缓存设置为长期
  • 服务降级,产生缓存雪崩时,针对不同的数据采取不同的降级计划,比方,非核心数据间接返回预约义信息、空值或是错误信息

对于缓存呈现宕机,解决形式:

  • 业务零碎中实现服务熔断或申请限流机制,避免大量拜访导致数据库呈现宕机

缓存穿透

缓存穿透,数据在数据库和缓存中都不存在,这样就导致查问数据,在缓存中找不到对应 key 的 value,都要去数据库再查问一遍,而后返回空(相当于进行了两次无用的查问)。

当有大量拜访申请,且其绕过缓存间接查数据库,导致数据库层的压力激增,重大的会造成数据库宕机。

对于缓存穿透,解决形式:

  • 缓存空值或缺省值,当一个查问返回的数据为空时,空后果也将进行缓存,并将它的过期工夫设置比拟短,下次访问间接从缓存中取值,防止了把大量申请发送给数据库解决,造成数据库出问题。
  • 布隆过滤器(BloomFilter ),将所有可能查问数据 key 哈希到一个足够大的 bitmap 中 , 在查问的时候先去 BloomFilter 去查问 key 是否存在,如果不存在就间接返回,存在再去查问缓存,缓存中没有再去查询数据库,从而防止了数据库层的压力激增呈现宕机。

缓存击穿

缓存击穿,针对某个拜访十分频繁的热点数据过期生效,导致拜访无奈在缓存中进行解决,进而会有导致大量的间接申请数据库,从而使得数据库层的压力激增,重大的会造成数据库宕机。

对于缓存击穿,解决形式:

  • 不设置过期工夫,对于拜访特地频繁的热点数据,不设置过期工夫。

总结

在大多数业务场景下,Redis 缓存作为只读缓存应用。针对只读缓存来说,优先应用先更新数据库再删除缓存的办法保证数据一致性。

其中,缓存雪崩,缓存穿透,缓存击穿三大问题的起因和解决形式

Redis 的过期策略

Redis 随着数据的增多,内存占用率会继续变高,咱们认为一些键达到设置的删除工夫就会被删除,然而工夫到了,内存的占用率还是很高,这是为什么呢?

Redis 采纳的是 定期删除 惰性删除 的内存淘汰机制。

定期删除

定期删除和定时删除是有区别的:

  • 定时删除是必须严格依照设定的工夫去删除缓存,这就须要咱们设置一个定时器去一直地轮询所有的 key,判断是否须要进行删除。然而这样的话 cpu 的资源会被大幅度地占据,资源的利用率变低。所以咱们抉择采纳定期删除,。
  • 定期删除是工夫由咱们定,咱们能够每隔 100ms 进行查看,但还是不能查看所有的缓存,Redis 还是会卡死,只能随机地去查看一部分缓存,然而这样会有一些缓存无奈在规定工夫内删除。这时惰性删除就派上用场了。

惰性删除

举个简略的例子:中学的时候,平时作业太多,基本做不完,老师说下节课要讲这个卷子,你们都做完了吧?其实有很多人没做完,所以须要在下节课之前连忙补上。

惰性删除也是这个情理,咱们的这个值按理说应该没了,然而它还在,当你要获取这个 key 的时候,发现这个 key 应该过期了,连忙删了,而后返回一个 ’ 没有这个值,曾经过期了!’。

当初咱们有了定期删除 + 惰性删除的过期策略,就能够居安思危了吗?并不是这样的,如果这个 key 始终不拜访,那么它会始终滞留,也是不合理的,这就须要咱们的内存淘汰机制了。

Redis 的内存淘汰机制

Redis 的内存淘汰机制个别有 6 种,如下图所示:

那么咱们如何去配置 Redis 的内存淘汰机制呢?

在 Redis.conf 中咱们能够进行配置

# maxmemory-policy allkeys-lru

Redis 长久化

长久化就是把内存的数据写到磁盘中去,避免服务宕机了内存数据失落。

Redis 提供了两种长久化形式:RDB(默认)和 AOF 

RDB:

rdb 是 Redis DataBase 缩写

性能外围函数 rdbSave(生成 RDB 文件)和 rdbLoad(从文件加载内存)两个函数

AOF:

Aof 是 Append-only file 缩写

每当执行服务器 (定时) 工作或者函数时 flushAppendOnlyFile 函数都会被调用,这个函数执行以下两个工作

aof 写入保留:

WRITE:依据条件,将 aof_buf 中的缓存写入到 AOF 文件

SAVE:依据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保留到磁盘中。

存储构造:

  内容是 redis 通信协定 (RESP) 格局的命令文本存储。

比拟

1、aof 文件比 rdb 更新频率高,优先应用 aof 还原数据。

2、aof 比 rdb 更平安也更大

3、rdb 性能比 aof 好

4、如果两个都配了优先加载 AOF

架构模式

 单机版

特点:简略

问题:

1、内存容量无限 2、解决能力无限 3、无奈高可用。

主从复制

Redis 的复制(replication)性能容许用户依据一个 Redis 服务器来创立任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创立进去的服务器复制品则为从服务器(slave)。只有主从服务器之间的网络连接失常,主从服务器两者会具备雷同的数据,主服务器就会始终将产生在本人身上的数据更新同步 给从服务器,从而始终保障主从服务器的数据雷同。

特点:

1、master/slave 角色

2、master/slave 数据雷同

3、升高 master 读压力在转交从库

问题:

无奈保障高可用

没有解决 master 写的压力

哨兵

Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时主动进行故障转移。其中三个个性:

监控(Monitoring):Sentinel  会一直地查看你的主服务器和从服务器是否运作失常。

揭示(Notification):当被监控的某个 Redis 服务器呈现问题时,Sentinel 能够通过 API 向管理员或者其余应用程序发送告诉。

主动故障迁徙(Automatic failover):当一个主服务器不能失常工作时,Sentinel 会开始一次主动故障迁徙操作。

特点:

1、保障高可用

2、监控各个节点

3、主动故障迁徙

毛病:主从模式,切换须要工夫丢数据

没有解决 master 写的压力

集群(proxy 型):

Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 疾速 / 轻量级代理服务器;Twemproxy 是一个疾速的单线程代理程序,反对 Memcached ASCII 协定和 redis 协定。

特点:1、多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins 

2、反对失败节点主动删除

3、后端 Sharding 分片逻辑对业务通明,业务方的读写形式和操作单个 Redis 统一

毛病:减少了新的 proxy,须要保护其高可用。

failover 逻辑须要本人实现,其自身不能反对故障的主动转移可扩展性差,进行扩缩容都须要手动干涉

集群(直连型):

从 redis 3.0 之后版本反对 redis-cluster 集群,Redis-Cluster 采纳无核心构造,每个节点保留数据和整个集群状态, 每个节点都和其余所有节点连贯。

特点:

1、无核心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。

2、数据依照 slot 存储散布在多个节点,节点间数据共享,可动静调整数据分布。

3、可扩展性,可线性扩大到 1000 个节点,节点可动静增加或删除。

4、高可用性,局部节点不可用时,集群仍可用。通过减少 Slave 做备份数据正本

5、实现故障主动 failover,节点之间通过 gossip 协定替换状态信息,用投票机制实现 Slave 到 Master 的角色晋升。

毛病:

1、资源隔离性较差,容易呈现相互影响的状况。

2、数据通过异步复制, 不保证数据的强一致性

面试题

Redis 反对的数据类型?
什么是 Redis 长久化?Redis 有哪几种长久化形式?优缺点是什么?
Redis 有哪些架构模式?讲讲各自的特点
应用过 Redis 分布式锁么,它是怎么实现的?
先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期工夫避免锁遗记了开释。
如果在 setnx 之后执行 expire 之前过程意外 crash 或者要重启保护了,那会怎么样?
set 指令有非常复杂的参数,这个应该是能够同时把 setnx 和 expire 合成一条指令来用的!
应用过 Redis 做异步队列么,你是怎么用的?有什么毛病?
个别应用 list 构造作为队列,rpush 生产音讯,lpop 生产音讯。当 lpop 没有音讯的时候,要适当 sleep 一会再重试。
毛病:
在消费者下线的状况下,生产的音讯会失落,得应用业余的音讯队列如 rabbitmq 等。
能不能生产一次生产屡次呢?
应用 pub/sub 主题订阅者模式,能够实现 1:N 的音讯队列。
什么是缓存穿透?如何防止?什么是缓存雪崩?何如防止?

转载:https://segmentfault.com/a/11…

正文完
 0