Redis总结

目录

概述

Redis是什么?简述它的优缺点?

Redis是一个Key-Value类型的内存数据库,整个数据库加载在内存当中操作,定期通过异步操作把数据库中的数据保存到硬盘上。C语言编写

因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value 数据库。

Redis主要由一个dict字典和一个expires字典组成
在这里插入图片描述

优点

  1. 读写性能极高, Redis能读的速度是110000次/s,写的速度是81000次/s。
  2. 支持数据持久化,支持AOF和RDB两种持久化方式。
  3. 支持事务, Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  4. 数据结构丰富,除了支持string类型的value外,还支持hash、set、zset、list等数据结构。
  5. 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
  6. 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等特性。

缺点

  • 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

为什么要使用Redis/缓存

  1. 高性能:当用户需要访问数据库中的数据时,如果每次访问都去数据库中,那么过程会很慢。如果将第一次访问到的数据缓存在内存中,下一次再访问这些数据时可以直接从内存中读取,速度很快
  2. 高并发:在高并发条件下,直接访问数据库会给数据库带来极大的压力,因此可以将一些数据存在缓存中,用户直接访问缓存,缓存能承受的并发数是远远大于数据库的

Redis为什么这么快?

  1. 内存存储:Redis是使用内存(in-memeroy)存储,没有磁盘IO上的开销。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1)。
  2. 单线程实现( Redis 6.0以前):Redis使用单个线程处理请求,避免了多个线程之间线程切换和锁资源争用的开销。注意:单线程是指的是在核心网络模型中,网络请求模块使用一个线程来处理,即一个线程处理所有网络请求。
  3. 非阻塞IO:Redis使用多路复用IO技术,将epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。
  4. 优化的数据结构:Redis有诸多可以直接应用的优化数据结构的实现,应用层可以直接使用原生的数据结构提升性能。
  5. 使用底层模型不同:Redis直接自己构建了 VM (虚拟内存)机制 ,自己实现冷热数据分离,使热数据仍在内存中、冷数据保存到磁盘。这样就可以避免因为内存不足而造成访问速度下降的问题

Redis提高数据库容量的办法有两种:一种是可以将数据分割到多个RedisServer上;另一种是使用虚拟内存把那些不经常访问的数据交换到磁盘上。需要特别注意的是Redis并没有使用OS提供的Swap,而是自己实现。

Redis相比Memcached有哪些优势?

  • 数据类型:Memcached所有的值均是简单的字符串,Redis支持更为丰富的数据类型,支持string(字符串),list(列表),Set(集合)、Sorted Set(有序集合)、Hash(哈希)等。
  • 持久化:Redis支持数据落地持久化存储,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。Memcached不支持数据持久存储 。
  • 集群模式:Redis提供主从同步机制,以及 Cluster集群部署能力,能够提供高可用服务。Memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据
  • 性能对比:Redis的速度比Memcached快很多。
  • 网络IO模型:Redis使用单线程的多路 IO 复用模型,Memcached使用多线程的非阻塞IO模式。
  • Redis支持服务器端的数据操作:Redis相比Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。
    这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果需要缓存能够支持更复杂的结构和操作,那么Redis会是不错的选择。

Redis的常用场景有哪些?

1、缓存

缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。

2、排行榜

很多网站都有排行榜应用的,如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。

3、计数器

什么是计数器,如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。

4、分布式Session

集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。

5、分布式锁

在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。

6、 社交网络

点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。如在微博中的共同好友,通过Redis的set能够很方便得出。

7、最新列表

Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。

8、消息系统
消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。

数据类型

Redis的数据类型有哪些?

redis内部使用一个redisObject对象来表示所有的key和value
Redis常用的有5种数据类型,包括String,List,Set,Zset,Hash

数据类型可以存储的值底层数据结构操作应用场景
String字符串、整数或者浮点数简单动态字符串SDS,多种编码方式对整个字符串或者字符串的其中一部分执行操作;对整数和浮点数执行自增或者自减操作常规key-value缓存应用。常规计数: 微博数, 粉丝数
Hashziplist和hashtable包含键值对的无序散列表增删改查单个键值对;获取所有键值对
List有序列表ziplist 和 linkedlist遵循先进先出原则,底层是依赖双向链表实现的,因此支持正向、反向双重查找存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据
Set无序集合intset和hashset增删改查单个元素;计算交集、并集、差集;从集合里面随机获取元素对于求共同好友、共同关注什么的功能实现特别方便
ZSet(SortedSet)有序集合ziplist和跳表添加、获取、删除元素;根据分值范围或者成员来获取元素;计算一个键的排名适用于排行榜和带权重的消息队列等场景。

三种特殊的数据类型:

  • Bitmap:位图,Bitmap想象成一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在Bitmap中叫做偏移量。使用Bitmap实现统计功能,更省空间。如果只需要统计数据的二值状态,例如商品有没有、用户在不在等,就可以使用 Bitmap,因为它只用一个 bit 位就能表示 0 或 1。
  • HyperLogLog:HyperLogLog 是一种用于统计基数的数据集合类型,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。
    场景:统计网页的UV(即Unique Visitor,不重复访客,一个人访问某个网站多次,但是还是只计算为一次)。
    要注意,HyperLogLog 的统计规则是基于概率完成的,所以它给出的统计结果是有一定误差的,标准误算率是 0.81%。
  • Geospatial :主要用于存储地理位置信息,并对存储的信息进行操作,适用场景如朋友的定位、附近的人、打车距离计算等。

Redis基本数据类型的底层原理?

redis内部使用一个redisObject对象来表示所有的key和value

String

三种编码方式encoding:

  1. 字符串(raw):SDS,long double也是作为字符串保存的
  2. 整数(int):long类型的整数值
  3. 短字符串(embstr):SDS,长度<39字节,将redisObject和sdshdr连续分配

long double也是作为字符串保存的

简单动态字符串(SDS)由三部分组成

  1. free:表示SDS分配的未使用空间
  2. len:表示SDS保存了长度为len的字符串
  3. buf:char数组,保存了字符串中的字符,最后一个是空字符’\0’,不一定等于字符串长度+1
    在这里插入图片描述

优点

  1. 常数复杂度获取字符串长度:维护len

  2. 杜绝缓冲区溢出:对SDS修改时先判断是否需要空间扩展,需要则先扩展空间,不会产生未分配足够多的内存造成冲突的问题

  3. 减少修改字符串时带来的内存重分配次数:

    • 空间预分配:对SDS进行空间扩展时,会给SDS分配额外的未使用空间,将连续增长N次字符串的内存重分配次数从N降到至少N。如修改后len = 13,会分配13字节的未使用空间,buf = 13 + 13 + 1 = 27字节,额外的1用于空字符;修改后len >= 1MB,分配1MB未使用空间 。
    • 惰性空间释放: 缩短时不立即使用内存重分配来回收多余字节,而是使用free先记录下来,等待使用
  4. 二进制安全:buf在写入时什么样,读取时就是什么样,使用len判断字符串是否结束。可以保存任意格式二进制数据

  5. 兼容部分 C 字符串函数

list:ziplist或linkedlist

ziplist:连续内存块组成的顺序型数据结构
在这里插入图片描述
linkedlist:双向列表
在这里插入图片描述

hash:ziplist 或者 hashtable

在这里插入图片描述

hashtable编码的哈希对象的底层结构是字典,由哈希表实现。字典包括两个节点,ht[0]用来指向哈希表,ht[1]用来rehash时新建更大的哈希表,将ht[0]数据迁移进来,ht[0]指向这个更大的哈希表。

解决哈希冲突:链地址法

rehash方法:渐进式rehash,逐渐将ht[0]中的数据迁移到ht[1]中

在这里插入图片描述

set:intset 或者 hashtable

intset :整数集合
在这里插入图片描述
在这里插入图片描述

zset:ziplist 或者 skiplist

ziplist:在这里插入图片描述
skiplist:包含一个字典和一个跳表
在这里插入图片描述

在这里插入图片描述
zset 结构中的 zsl 跳跃表按分值从小到大保存了所有集合元素, 每个跳跃表节点都保存了一个集合元素: 跳跃表节点的 object 属性保存了元素的成员, 而跳跃表节点的 score 属性则保存了元素的分值。 通过这个跳跃表, 程序可以对有序集合进行范围型操作, 比如 ZRANK 、 ZRANGE 等命令就是基于跳跃表 API 来实现的。

除此之外, zset 结构中的 dict 字典为有序集合创建了一个从成员到分值的映射, 字典中的每个键值对都保存了一个集合元素: 字典的键保存了元素的成员, 而字典的值则保存了元素的分值。 通过这个字典, 程序可以用 O(1) 复杂度查找给定成员的分值, ZSCORE 命令就是根据这一特性实现的, 而很多其他有序集合命令都在实现的内部用到了这一特性。

有序集合每个元素的成员都是一个字符串对象, 而每个元素的分值都是一个 double 类型的浮点数。 值得一提的是, 虽然 zset 结构同时使用跳跃表和字典来保存有序集合元素, 但这两种数据结构都会通过指针来共享相同元素的成员和分值, 所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值, 也不会因此而浪费额外的内存。

为什么有序集合需要同时使用跳跃表和字典来实现?

在理论上来说, 有序集合可以单独使用字典或者跳跃表的其中一种数据结构来实现, 但无论单独使用字典还是跳跃表, 在性能上对比起同时使用字典和跳跃表都会有所降低。

举个例子, 如果我们只使用字典来实现有序集合, 那么虽然以 O(1) 复杂度查找成员的分值这一特性会被保留, 但是, 因为字典以无序的方式来保存集合元素, 所以每次在执行范围型操作 —— 比如 ZRANK 、 ZRANGE 等命令时, 程序都需要对字典保存的所有元素进行排序, 完成这种排序需要至少 O(N \log N) 时间复杂度, 以及额外的 O(N) 内存空间 (因为要创建一个数组来保存排序后的元素)。

另一方面, 如果我们只使用跳跃表来实现有序集合, 那么跳跃表执行范围型操作的所有优点都会被保留, 但因为没有了字典, 所以根据成员查找分值这一操作的复杂度将从 O(1) 上升为 O(\log N) 。

因为以上原因, 为了让有序集合的查找和范围型操作都尽可能快地执行, Redis 选择了同时使用字典和跳跃表两种数据结构来实现有序集合。

讲下跳表

在这里插入图片描述
左边是zskiplist结构

  • header :指向跳跃表的表头节点。表头结点不存储对象,32层
  • tail :指向跳跃表的表尾节点。
  • level :记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)。
  • length :记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内)。

右边是zskiplistNode 结构

  • 层(level):节点中用 L1 、 L2 、 L3 等字样标记节点的各个层, L1 代表第一层, L2 代表第二层,以此类推。每个层都带有两个属性:前进指针和跨度。前进指针用于访问位于表尾方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离。在上面的图片中,连线上带有数字的箭头就代表前进指针,而那个数字就是跨度。当程序从表头向表尾进行遍历时,访问会沿着层的前进指针进行。
  • 后退(backward)指针:节点中用 BW 字样标记节点的后退指针,它指向位于当前节点的前一个节点。后退指针在程序从表尾向表头遍历时使用。
  • 分值(score):各个节点中的 1.0 、 2.0 和 3.0 是节点所保存的分值。在跳跃表中,节点按各自所保存的分值从小到大排列。
  • 成员对象(obj):各个节点中的 o1 、 o2 和 o3 是节点所保存的成员对象。是一个指针, 它指向一个字符串对象, 而字符串对象则保存着一个 SDS 值。

每个跳跃表节点的层高都是 1 至 32 之间的随机数。
在同一个跳跃表中, 多个节点可以包含相同的分值, 但每个节点的成员对象必须是唯一的。
跳跃表中的节点按照分值大小进行排序, 当分值相同时, 节点按照成员对象的大小进行排序。

线程模型

Redis为何选择单线程?

在Redisv6.0以前,Redis的核心网络模型(网络事件处理器:文件事件处理器)选择用单线程来实现

原因如下:

  • 避免过多的上下文切换开销
  • 避免同步机制的开销
  • 简单可维护:所有的底层数据结构不用实现成线程安全的

Redis真的是单线程?

Redis 4.0

  • 文件事件分派器是单线程的

  • I/O多路复用:

    • redis 需要在合适的时间暂停对某个 IO 事件的处理,转而去处理另一个 IO 事件,这样 redis 就好比一个开关,当开关拨到哪个 IO 事件这个电路上,就处理哪个 IO 事件,其他 IO 事件就暂停处理了。这就是IO多路复用技术。IO 多路复用有 3 种实现机制:select、poll、epoll
      在这里插入图片描述
  • 同时引入多线程处理异步任务,如资源释放,清理脏数据,删除大key等工作)
    在这里插入图片描述

Redis 6.0
将IO模型从多路复用变为多线程
在这里插入图片描述

Redis 6.0为何引入多线程?

Redis的单线程模式会导致系统消耗很多 CPU 时间在网络 I/O 上从而降低吞吐量
解决方案是利用多核优势优化网络I/O

过期键的删除策略

Redis过期键的删除策略?

Redis的过期删除策略就是:惰性删除和定期删除两种策略配合使用

惰性删除

  • 只有当访问一个key时,才会判断该key是否已过期,过期则清除。未过期则不作操作,继续执行原有的命令。
  • 优点:最大化地节省CPU资源
  • 缺点:对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存,造成内存泄露。

定期删除

  • 每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key
  • 优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。
  • 缺点:难以确定删除操作执行的时长和频率。在获取某个键时,如果某个键的过期时间已经到了,但是还没执行定期删除,那么就会返回这个键的值,这是业务不能忍受的错误

过期键的删除策略都有哪些?

定时删除

  • 在设置某个key 的过期时间同时,创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。
  • 优点:定时删除对内存是最友好的,能够保存内存的key一旦过期就能立即从内存中删除。
  • 缺点:对CPU最不友好,在过期键比较多的时候,删除过期键会占用一部分 CPU 时间,对服务器的响应时间和吞吐量造成影响。

惰性删除

  • 只有当访问一个key时,才会判断该key是否已过期,过期则清除。未过期则不作操作,继续执行原有的命令。
  • 优点:最大化地节省CPU资源
  • 缺点:对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存,造成内存泄露。

定期删除

  • 每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key
  • 优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。
  • 缺点:难以确定删除操作执行的时长和频率。在获取某个键时,如果某个键的过期时间已经到了,但是还没执行定期删除,那么就会返回这个键的值,这是业务不能忍受的错误

内存相关

MySQL里有2000w数据,redis中只能存20w的数据,如何保证redis中的数据都是热点数据?

当现有内存大于 maxmemory 时,便会触发Redis主动淘汰内存机制

Redis内存淘汰机制?

全局的键空间选择性移除

  • no-eviction:当内存不足以容纳新写入数据时,新写入操作会报错。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。(LRU算法)(这个是最常用的)
  • allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key(LFU算法)
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。

设置过期时间的键空间选择性移除

  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
  • volatile-lfu:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最不经常使用的key。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

Redis如何做内存优化?

可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面

持久化

Redis持久化机制?

为了能够重用Redis数据,或者防止系统故障,我们需要将Redis中的数据写入到磁盘空间中,即持久化。Redis提供了两种不同的持久化方法可以将数据存储在磁盘中,一种叫快照RDB,另一种叫只追加文件AOF

讲下RDB

  • Redis默认的持久化机制

  • 将某个时间点上内存中的数据集快照写入磁盘,它恢复时是将快照文件直接读到内存里。

  • RDB文件是一个经过压缩的二进制文件

  • 优点:适合大规模的数据恢复;对数据完整性和一致性要求不高

  • 劣点:在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

RDB文件的创建与载入

有两种命令可以生成RDB文件,且只能调用一个

1、 save:阻塞Redis服务器进程,直到RDB文件创建完毕,在此期间服务器不能处理任何请求

2、 bgsave:会使用fork命令派生一个子进程,由子进程负责创建RDB文件,父进程继续处理命令。子父进程之间是采用copyOnWrite(写时复制),子进程生成虚拟空间,指向父进程的物理空间,这样父子进程共享一个内存,当父进程想改变共享内存时,为子进程分配物理空间并复制父进程的物理空间内容。共享空间的粒度是页。

  • 自动间隔性保存就是使用bgsave实现的
    在这里插入图片描述

讲下AOF

通过保存Redis服务器所执行的写命令来记录数据库状态。

  • 优点:有不同的同步机制可以尽可能的保证数据完整性

  • 缺点:相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb。aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同

数据持久化与载入

打开AOF持久化之后,服务器执行完写命令后会将该命令追加到服务器的aof_buf缓冲区末尾,结束一个事件循环,按照策略将缓冲区内的内容写入和保存到AOF文件中,默认是每秒同步
在这里插入图片描述

  • 每修改同步:效率最低,最安全
  • 每秒同步:效率较低,相对安全
  • 不同步

载入时,创建用来执行命令的伪客户端不断执行AOF中的命令

AOF文件重写

AOF采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时, Redis就会启动AOF文件重写,读取数据库中键值对,用一条命令来记录键值对,来代替之前的多条命令,写入AOF。
重写在子进程中进行,重写过程中的命令会被追加到AOF缓冲区和AOF重写缓冲区。子进程完成AOF后,会将AOF重写缓冲区中的内容写入新AOF文件中,原子地覆盖旧文件

如何选择RDB和AOF?

  • 如果是数据不那么敏感,且可以从其他地方重新生成补回的,那么可以关闭持久化。
  • 如果是数据比较重要,不想再从其他地方获取,且可以承受数分钟的数据丢失,比如缓存等,那么可以只使用RDB。
  • 如果是用做内存数据库,要使用Redis的持久化,建议是RDB和AOF都开启,或者定期执行bgsave做快照备份,RDB方式更适合做数据的备份,AOF可以保证数据的不丢失。

Redis4.0 对于持久化机制的优化?

4.0的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据,如下图:
在这里插入图片描述

  • 优势:混合持久化结合了RDB持久化 和 AOF 持久化的优点, 由于绝大部分都是RDB格式,加载速度快,同时结合AOF,增量的数据以AOF方式保存了,数据更少的丢失。
  • 劣势:兼容性差,一旦开启了混合持久化,在4.0之前版本都不识别该aof文件,同时由于前部分是RDB格式,阅读性较差。

Redis持久化数据和缓存怎么做扩容?

如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容。

如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。

缓存异常

缓存穿透

高并发条件下,用户访问的数据在redis和数据库中都不存在,导致所有的请求都会落在数据库上,数据库短时间承受大量请求而崩掉

解决方案

  • 接口层增加数据校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
  • 空数据的缓存:从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
  • 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap(位图,每个元素只能记录1bit的数据,0/1) 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力

布隆过滤器:引入了k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。
它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。
Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。
Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。

缓存击穿

高并发条件下,大量用户同时访问一个缓存中没有但数据库中有的数据,请求都会落在数据库上,造成数据库的过大压力

解决方案:

  • 设置热点数据永不过期
  • 加互斥锁:缓存不存在数据后将请求加上锁,只有一个请求能去数据库访问,得到数据后更新缓存

缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效,导致用户请求直接打到数据库中,造成数据库过大压力

解决方案:

  • 服务器重启造成的:加多层缓存,redis,memcache
  • 缓存数据的过期时间加上一个随机值,防止同一时间大量数据过期现象发生。
  • 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
  • 设置热点数据永不过期

缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据

解决方案

  • 直接写个缓存刷新页面,上线时手工操作一下;

  • 数据量不大,可以在项目启动的时候自动进行加载;

  • 定时刷新缓存;

缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

事务

Redis中的事务?

redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。具有原子性、一致性、隔离性,如果服务运行在AOF持久化模式且每次请求都保存(always)时,事务也具有持久性

将一个事务中的所有命令序列化,然后按顺序执行

  1. redis 不支持回滚,“Redis 事务在某个命令执行期间出现了错误,事务不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速
  2. 如果在一个事务中的命令在入事务队列时发生错误,那么当前事务中所有的命令都不会执行;
  3. 如果在一个事务中的命令在入队时没有发现错误,在执行时发现错误,那么会继续执行事务中的其他命令

因为Redis的核心文件时间处理器是单线程的,因此Redis的事务执行的过程中会按照顺序串行化执行,不存在事务并发问题,总是有ACID中的一致性和隔离性,开启持久化后也有持久性,不保证原子性

Redis事务中的相关命令?

  • MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。

  • WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。

  • EXEC执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil

  • DISCARD客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。

  • UNWATCH命令可以取消watch对所有key的监控。

集群方案

什么是哨兵模式?

在这里插入图片描述
哨兵的介绍

sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能:

  • 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
  • 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
  • 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
  • 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

  • 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
  • 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的

哨兵的核心知识

  • 哨兵至少需要 3 个实例,来保证自己的健壮性。
  • 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
  • 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。

redis 集群模式的工作原理能说一下么?

官方的集群模式为Redis Cluster,没有使用一致性哈希算法,而是使用slot槽的概念,一共16384个槽。将请求发送到任意节点,接收请求的节点会将请求发送到正确的节点上执行

方案如下

  1. 通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384 个槽位
  2. 每份数据分片会存储在多个互为主从的多节点上
  3. 数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
  4. 同一分片多个节点间的数据不保持一致性
  5. 读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
  6. 扩容时时需要需要把旧节点的数据迁移一部分到新节点

节点间内部通讯机制:Gossip协议

优点

  • 无中心架构,支持动态扩容,对业务透明
  • 具备Sentinel的监控和自动Failover(故障转移)能力
  • 客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
  • 高性能,客户端直连redis服务,免去了proxy代理的损耗

缺点

  • 运维也很复杂,数据迁移需要人工干预
  • 只能使用0号数据库
  • 不支持批量操作(pipeline管道操作)
  • 分布式逻辑和存储模块耦合等

分布式寻址都有哪些算法?

  • hash算法,直接hash % Redis节点数,会导致大量缓存冲关键
  • 一致性哈希算法(自动缓存迁移) + 虚拟节点(自动负载均衡)

讲讲一致性哈希算法?

为了解决分布式缓存中,在移除或添加新的缓存服务器节点的情况下,尽可能小的改变服务器和请求之间的映射关系。能够避免因某个节点挂掉导致的缓存雪崩

常用的哈希算法,直接hash % N,N是服务器节点的数目,这样存在的问题是无法负载均衡,一台服务器挂掉N-1,可能造成所有的缓存都将失效

一致性哈希是hash % 232 = hash & 232- 1,映射到0 ~232-1的圆环节点上,按顺时针方向组织
在这里插入图片描述
假设有三台服务器,请求计算在哈希环上的位置后,按顺时针方向放入第一个遇到的服务器,
在这里插入图片描述
服务器B失效,那么3会缓存到服务器C中,4、2、1不变。
如果ABC均匀不分布,会造成哈希环的偏斜,那么可以虚拟出分布均匀的虚拟节点来保证

Redis集群主从复制原理?

在这里插入图片描述

  • 当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。

  • 如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,

  • 同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,

  • 接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。

  • slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。

说说Redis哈希槽的概念?

Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。

说说Redis哈希槽的概念?

Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。

Redis集群会有写操作丢失吗?为什么?

由于CAP定义,Redis实现可用性和分区容错性,并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。

Redis集群之间是如何复制的?

异步复制

Redis集群最大节点个数是多少?

16384个

Redis集群如何选择数据库?

Redis集群目前无法做数据库选择,默认在0数据库。

分区

Redis是单线程的,如何提高多核CPU的利用率?

可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个CPU,你可以考虑一下分片(shard)。

为什么要做Redis分区?

分区可以让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。分区使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。

你知道有哪些Redis分区实现方案?

  • 客户端分区就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。
  • 代理分区 意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy
  • 查询路由(Query routing) 的意思是客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。

Redis分区有什么缺点?

  • 涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。
  • 同时操作多个key,则不能使用Redis事务.
  • 分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集
  • 使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。
  • 分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。

分布式

都有哪些分布式锁?

数据库、Redis、Zookeeper,其中redis性能最高,Zookeeper可靠

Redis实现分布式锁

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。Redis中可以使用SETNX命令实现分布式锁。

当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

返回值:设置成功,返回 1 。设置失败,返回 0 。
在这里插入图片描述
使用SETNX完成同步锁的流程及事项如下:

  • 使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
  • 为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
  • 释放锁,使用DEL命令将锁数据删除

使用SETNX完成同步锁的流程及事项如下:

  • 使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功

  • 为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间

  • 释放锁,使用DEL命令将锁数据删除

如何解决 Redis 的并发竞争 Key 问题

所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同

推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)

基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。

在实践中,当然是从以可靠性为主。所以首推Zookeeper。

什么是 RedLock

Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:

  • 安全特性:互斥访问,即永远只有一个 client 能拿到锁
  • 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
  • 容错性:只要大部分 Redis 节点存活就可以正常提供服务

其他

如何保证缓存与数据库双写时的数据一致性?

数据库和缓存没办法做到绝对的一致性,这是由CAP理论决定的,缓存系统使用的场景是非强一致性的场景,属于AP。

缓存:删除,由下个请求去缓存,发现不存在后读取数据库,写入缓存

  • 线程安全(A更新数据库B更新数据库B更新缓存A更新缓存)、性能考虑

那么我选择先更新数据库,再删除缓存,但也会存在并发问题,A查,B写

  1. 缓存失效
  2. A查询数据库,得到旧值
  3. B将新值写入数据库
  4. B删除缓存
  5. A将旧值写入缓存

(3比2速度快才会导致4先于5,但数据库的读操作比写操作快)

但我们可以做到最终一致性

延时双删:旧值写入缓存后,再次删除缓存

Redis官方为什么不提供Windows版本?

因为目前Linux版本已经相当稳定,而且用户量很大,无需开发windows版本,反而会带来兼容性等问题。

一个字符串类型的值能存储最大容量是多少?

512M

假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用keys指令可以扫出指定模式的key列表。

对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

使用Redis做过异步队列吗,是如何实现的

使用list类型保存数据信息,rpush生产消息,lpop消费消息,当lpop没有消息时,可以sleep一段时间,然后再检查有没有信息,如果不想sleep的话,可以使用blpop, 在没有信息的时候,会一直阻塞,直到信息的到来。redis可以通过pub/sub主题订阅模式实现一个生产者,多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。

Redis如何实现延时队列

使用sortedset,使用时间戳做score, 消息内容作为key,调用zadd来生产消息,消费者使用zrangbyscore获取n秒之前的数据做轮询处理

Redis变慢,如何处理

慢查询日志slowlog,记录了查询时间超过给定时长的命令

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值