0 什么是Redis?
Redis(Remote Dictionary Server。)本质上是一个 Key-Value 类型的内存数据库, 整个数据库加载在内存当中进行操作, 定期通过异步操作把数据库数据 flush 到硬盘上进行保存,支持5种数据结构:String、 List、 Set、 Sorted Set(zset)、 hash。
因为是纯内存操作, Redis 的性能非常出色, 每秒可以处理超过 10 万次读写操作, 是已知性能
最快的 Key-Value DB。
Redis 的出色之处不仅仅是性能, Redis 最大的魅力是支持保存多种数据结构, 此外单个value 的最大限制是 1GB, 不像 memcached 只能保存 1MB 的数据, 因此 Redis 可以用来实现很多有用的功能,比方说用他的 List 来做 FIFO 双向链表,实现一个轻量级的高性 能消息队列服务, 用他的 Set 可以做高性能的 tag 系统等等。另外 Redis 也可以对存入的Key-Value 设置 expire 时间, 因此也可以被当作一 个功能加强版的 memcached 来用。
Redis 的主要缺点是数据库容量受到物理内存的限制, 不能用作海量数据的高性能读写, 因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上
1 Redis几种数据结构命令
1.1 键(key)命令
Redis 是 key-value 型数据库,使用 key 对 value 进行存储,因此,键(Key)命令是 Redis 中经常使用的一类命令。常用的键命令如下所示:
命令 | 说明 |
---|---|
DEL | 若键存在的情况下,该命令用于删除键 |
DUMP | 用于序列化给定 key ,并返回被序列化的值 |
EXISTS | 用于检查键是否存在,若存在则返回 1,否则返回 0 |
EXPIRE | 设置 key 的过期时间,以秒为单位 |
EXPIREAT | 该命令与 EXPIRE 相似,用于为 key 设置过期时间,不同在于,它的时间参数值采用的是时间戳格式。 |
KEYS | 此命令用于查找与指定 pattern 匹配的 key |
MOVE | 将当前数据库中的 key 移动至指定的数据库中(默认存储为 0 库,可选 1-15中的任意库) |
PERSIST | 该命令用于删除 key 的过期时间,然后 key 将一直存在,不会过期 |
PEXPIRE | 设置 key 的过期,以毫秒为单位 |
RANDOMKEY | 从当前数据库中随机返回一个 key |
RENAME | 修改 key 的名称 |
SCAN | 基于游标的迭代器,用于迭代数据库中存在的所有键,cursor 指的是迭代游标 |
TTL | 用于检查 key 还剩多长时间过期,以秒为单位 |
TYPE | 该命令用于获取 value 的数据类型。 |
1.2 Hash 命令
Hash(哈希散列)是 Redis 基本数据类型之一,它以字符串映射表的形式来进行存储。Hash 特别适合用于存储对象。常用的命令如下所示:
命令 | 说明 |
---|---|
HDEL | 用于删除一个或多个哈希表字段 |
HEXISTS | 用于确定哈希字段是否存在 |
HGET | 获取存储在 key 中的哈希字段的值 |
HGETALL | 获取存储在 key 中的所有哈希字段值 |
HINCRBY | 为存储在 key 中的哈希表指定字段做整数增量运算 |
HKEYS | 获取存储在 key 中的哈希表的所有字段 |
HLEN | 获取存储在 key 中的哈希表的字段数量 |
HSET | 用于设置存储在 key 中的哈希表字段的值 |
HVALS | 用于获取哈希表中的所有值 |
1.3 String命令
Strings(字符串)结构是 Redis 的基本数据类型之一,我们可以通过相关字符串命令对其进行操作,比如设置、检索、删除等等。
字符串类型有诸多的应用场景,比如微博粉丝的关注与取消等。下面介绍了 Redis 中常营的字符串命令:
命令 | 说明 |
---|---|
APPEND | 该命令将 value 追加到 key 所存储值的末尾 |
BITCOUNT | 该命令用于计算字符串中,被设置为 1 的比特位的数量。 |
DECR | 将 key 所存储的整数值减 1 |
DECRBY | 将 key 所储存的值减去给定的递减值(decrement) |
GET | 用于检索指定键的值 |
GETBIT | 对 key 所存储的字符串值,获取其指定偏移量上的位(bit) |
GETRANGE | 返回 key 中字符串值的子字符 |
GETSET | 将给定 key 的值设置为 value,并返回 key 的旧值 |
INCR | 将 key 所存储的整数值加 1 |
INCRBY | 将 key 所储存的值加上给定的递增值(increment) |
INCRBYFLOAT | 将 key 所储存的值加上指定的浮点递增值(increment) |
MGET | 一次性获取一个或多个 key 所存储的值 |
MSET | 该命令允许同时设置多个键值对 |
MSETNX | 当指定的 key 都不存在时,用于设置多个键值对 |
SET | 用于设定指定键的值 |
SETBIT | 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit) |
SETEX | 将值 value 存储到 key中 ,并将 key 的过期时间设为 seconds (以秒为单位) |
STRLEN | 返回 key 所储存的字符串值的长度 |
SETNX | 当 key 不存在时设置 key 的值 |
SETRANGE | 从偏移量 offset 开始,使用指定的 value 覆盖的 key 所存储的部分字符串值 |
1.4 List命令
List 是 Redis 中最常用数据类型之一。Redis 提供了诸多用于操作列表类型的命令,通过这些命令你可以实现将一个元素添加到列表的头部,或者尾部等诸多操作。
List 常用的命令如下所示:
命令 | 说明 |
---|---|
BLPOP | 用于删除并返回列表中的第一个元素(头部操作),如果列表中没有元素,就会发生阻塞,直到列表等待超时或发现可弹出元素为止 |
BRPOP | 用于删除并返回列表中的最后一个元素(尾部操作),如果列表中没有元素,就会发生阻塞,直到列表等待超时或发现可弹出元素为止 |
BRPOPLPUSH | 从列表中取出最后一个元素,并插入到另一个列表的头部。如果列表中没有元素,就会发生阻塞,直到等待超时或发现可弹出元素时为止 |
LINDEX | 通过索引获取列表中的元素 |
LINSERT | 指定列表中一个元素在它之前或之后插入另外一个元素 |
LLEN | 用于获取列表的长度 |
LPOP | 从列表的头部弹出元素,默认为第一个元素 |
LPUSH | 在列表头部插入一个或者多个值 |
LPUSHX | 当储存列表的 key 存在时,用于将值插入到列表头部 |
LRANGE | 获取列表指定范围内的元素 |
LREM | 表示从列表中删除元素与 value 相等的元素。count 表示删除的数量,为 0 表示全部移除 |
LSET | 表示通过其索引设置列表中元素的值 |
LTRIM | 保留列表中指定范围内的元素值 |
1.5 Set命令
Redis set 数据类型由键值对组成,这些键值对具有无序、唯一的性质,这与 Python 的 set 相似。当集合中最后一个元素被移除之后,该数据结构也会被自动删除,内存也同样会被收回。
Redis 的 Set 是 string 类型的无序集合。
集合成员是唯一的,这就意味着集合中没有重复的数据。
在 Redis 中,添加、删除和查找的时间复杂都是 O(1)(不管 Set 中包含多少元素)。
集合中最大的成员数为
$$
2^{32} – 1
$$
(4294967295), 每个集合可存储 40 多亿个成员。
由于 set 集合可以实现去重,因此它有很多适用场景,比如用户抽奖活动,使用 set 集合可以保证同一用户不被第二次选中。
Redis set 常用的命令如下所示:
命令 | 说明 |
---|---|
SADD | 向集合中添加一个或者多个元素,并且自动去重 |
SCARD | 返回集合中元素的个数 |
SDIFF | 求两个或对多个集合的差集 |
SDIFFSTORE | 求两个集合或多个集合的差集,并将结果保存到指定的集合(key)中 |
SINTER | 求两个或多个集合的交集 |
SINTERSTORE | 求两个或多个集合的交集,并将结果保存到指定的集合(key)中 |
SMEMBERS | 查看集合中所有元素 |
SMOVE | 将集合中的元素移动到指定的集合中 |
SPOP | 弹出指定数量的元素 |
SRANDMEMBER | 随机从集合中返回指定数量的元素,默认返回 1个 |
SREM | 删除一个或者多个元素,若元素不存在则自动忽略 |
SUNION | 求两个或者多个集合的并集 |
SUNIONSTORE | 求两个或者多个集合的并集,并将结果保存到指定的集合(key)中 |
2 Redis相关面试题
2.1 相比 memcached 有哪些优势?
- memcached 所有的值均是简单的字符串, Redis 作为其替代者, 支持更为丰富的数据类型
- Redis 的速度比 memcached 快很多
- Redis 可以持久化其数据
2.2 Redis 有哪几种数据淘汰策略?
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令, 但 DEL 和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU), 使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU), 但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键, 并且优先回收存活时间(TTL) 较短的键,使得新添加的数据有空间存放
Redis为什么采用跳表而不是红黑树
在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。
平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。
从算法实现难度上来比较,skiplist比平衡树要简单得多。
2.3 介绍一下HyperLogLog?
HyperLogLog 是一种概率数据结构,用来估算数据的基数。数据集可以是网站访客的 IP 地址,E-mail 邮箱或者用户 ID。
基数就是指一个集合中不同值的数目,比如 a, b, c, d 的基数就是 4,a, b, c, d, a 的基数还是 4。虽然 a 出现两次,只会被计算一次。
使用 Redis 统计集合的基数一般有三种方法,分别是使用 Redis 的 HashMap,BitMap 和 HyperLogLog。前两个数据结构在集合的数量级增长时,所消耗的内存会大大增加,但是 HyperLogLog 则不会。
Redis 的 HyperLogLog 通过牺牲准确率来减少内存空间的消耗,只需要12K内存,在标准误差0.81%的前提下,能够统计2^64个数据。所以 HyperLogLog 是否适合在比如统计日活月活此类的对精度要不不高的场景。
这是一个很惊人的结果,以如此小的内存来记录如此大数量级的数据基数。
2.4 为什么 Redis 需要把所有数据放到内存中?
Redis 为了达到最快的读写速度将数据都读到内存中, 并通过异步的方式将数据写入磁盘。
所以 Redis 具有快速和数据持久化的特征。 如果不将数据放在内存中, 磁盘 I/O 速度为严重
影响 Redis 的性能。 在内存越来越便宜的今天, Redis 将会越来越受欢迎。
2.5 sds相对c的改进?
获取长度:c字符串并不记录自身长度,所以获取长度只能遍历一遍字符串,redis直接读取len即可。
缓冲区安全:c字符串容易造成缓冲区溢出,比如:程序员没有分配足够的空间就执行拼接操作。而redis会先检查sds的空间是否满足所需要求,如果不满足会自动扩充。
内存分配:由于c不记录字符串长度,对于包含了n个字符的字符串,底层总是一个长度n+1的数组,每一次长度变化,总是要对这个数组进行一次内存重新分配的操作。因为内存分配涉及复杂算法并且可能需要执行系统调用,所以它通常是比较耗时的操作。
2.6 Redis链表源码?有什么特性?
双端、无环、带长度记录、
多态:使用 void* 指针来保存节点值, 可以通过 dup 、 free 、 match 为节点值设置类型特定函数, 可以保存不同类型的值。
2.7 字典是如何实现的?
其实字典这种数据结构也内置在很多高级语言中,但是c语言没有,所以redis自己实现了。
应用也比较广泛,比如redis的数据库就是字典实现的。不仅如此,当一个哈希键包含的键值对比较多,或者都是很长的字符串,redis就会用字典作为哈希键的底层实现。
2.8 LRU?Redis里的具体实现?
LRU全称是Least Recently Used,即最近最久未使用的意思。
LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
redis原始的淘汰算法简单实现:当需要淘汰一个key时,随机选择3个key,淘汰其中间隔时间最长的key。基本上,我们随机选择key,淘汰key效果很好。后来随机3个key改成一个配置项”N随机key”。但把默认值提高改成5个后效果大大提高。考虑到它的效果,你根本不用修改他。
2.9 Redis的持久化?
RDB持久化可以手动执行,也可以配置定期执行,可以把某个时间的数据状态保存到RDB文件中,反之,我们可以用RDB文件还原数据库状态。
AOF持久化是通过保存服务器执行的命令来记录状态的。还原的时候再执行一遍即可。
如何选择合适的持久化方式?
一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。 如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot) 非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快, 除此之外, 使用 RDB 还可以避免之前提到的 AOF 程序的 bug。
2.10 Redis 集群方案应该怎么做? 都有哪些方案?
twemproxy,大概概念是,它类似于一个代理方式,使用方法和普通Redis无任何区别,设置好它下属的多个Redis实例后,使用时在本需要连接Redis的地方改为连接twemproxy, 它会以一个代理的身份接收请求并使用一致性 hash算法,将请求转接到具体Redis,将结果再返回twemproxy。使用方式简便(相对 Redis 只需修改连接端口),对旧项目扩展的首选。
问题:twemproxy 自身单端口实例的压力, 使用一致性 hash后, 对Redis节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。codis,目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在节点数量改变情况下,旧节点数据可恢复到新hash节点。
Redis cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。
在业务代码层实现,起几个毫无关联的Redis实例,在代码层,对key进行hash计算,然后去对应的Redis实例操作数据。这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等
MySQL 里有 2000w 数据, Redis 中只存 20w 的数据,
如何保证 Redis 中的数据都是热点数据?
Redis 内存数据集大小上升到一定大小的时候, 就会施行数据淘汰策略
2.11 Redis 有哪些适合的场景?
会话缓存(Session Cache)
最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?
幸运的是,随着Redis这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。全页缓存(FPC)
除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件wp-Redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。队列
Reids在内存存储引擎领域的一大优点是提供list和set操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对list的push/pop操作。如果你快速的在Google中搜索“Redis queues”, 你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。排行榜/计数器
Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:1
ZRANGE user_scores 0 10 WITHSCORES
Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。
发布/订阅
最后是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统。
2.12 说说 Redis 哈希槽的概念?
Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
2.13 为什么Redis集群有16384个槽
如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。
如上所述,在消息头中,最占空间的是myslots[CLUSTER_SLOTS/8]。 当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb 因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽。redis的集群主节点数量基本不可能超过1000个。
如上所述,集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。槽位越小,节点少的情况下,压缩比高
Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots/N很高的话(N表示节点数),bitmap的压缩率就很低。如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。
2.14 Redis 集群会有写操作丢失吗? 为什么?
Redis 并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
2.15 Redis 分区有什么缺点?
涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。同时操作多个key,则不能使用Redis事务。分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set)。当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB/AOF文件。分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。
2.16 Redis 与其他 key-value 存储有什么不同?
Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,应为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
2.17 Redis 的内存用完了会发生什么?
如果达到设置的上限,Redis的写命令会返回错误信息(但是读命令还可以正常返回。)或者你可以将Redis当缓存来使用配置淘汰机制,当Redis达到内存上限时会冲刷掉旧的内容。Redis是单线程的,如何提高多核CPU的利用率?可以在同一个服务器部署多个Redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的,所以,如果你想使用多个CPU,你可以考虑一下分片(shard)。
2.18 一个 Redis 实例最多能存放多少的 keys? List、 Set、Sorted Set 他们最多能存放多少元素?
理论上Redis可以处理多达232的keys,并且在实际中进行了测试,每个实例至少存放了2亿5千万的keys。我们正在测试一些较大的值。任何list、set、和sorted set都可以放232个元素。换句话说,Redis的存储极限是系统中的可用内存值修改配置不重启Redis会实时生效吗?针对运行实例,有许多配置选项可以通过CONFIG SET命令进行修改,而无需执行任何形式的重启。从Redis 2.2开始,可以从AOF切换到RDB的快照持久性或其他方式而不需要重启Redis。检索‘CONFIG GET’命令获取更多信息。但偶尔重新启动是必须的,如为升级Redis程序到新的版本,或者当你需要修改某些目前CONFIG命令还不支持的配置参数的时候
哨兵
Redis sentinel是一个分布式系统中监控redis主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:
- 监控(Monitoring):Sentinel会不断地检查你的主服务器和从服务器是否运作正常。
- 提醒(Notification):被监控的某个Redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover):当一个主服务器不能正常工作时,Sentinel会开始一次自动故障迁移操作。
特点:
- 保证高可用
- 监控各个节点
- 自动故障迁移
缺点:主从模式,切换需要时间丢数据
没有解决 master 写的压力
2.19 缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就去后端系统查找(比如DB)。
一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免?
- 对查询结果为空的情况也进行缓存,这样,再次访问时,缓存层会直接返回空值。缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
- 对一定不存在的key进行过滤。具体请看布隆过滤器
2.20 缓存击穿
是针对缓存中没有但数据库有的数据。
场景是,当Key失效后,假如瞬间突然涌入大量的请求,来请求同一个Key,这些请求不会命中Redis,都会请求到DB,导致数据库压力过大,甚至扛不住,挂掉。
解决办法
- 设置热点Key,自动检测热点Key,将热点Key的过期时间加大或者设置为永不过期,或者设置为逻辑上永不过期
- 加互斥锁。当发现没有命中Redis,去查数据库的时候,在执行更新缓存的操作上加锁,当一个线程访问时,其它线程等待,这个线程访问过后,缓存中的数据会被重建,这样其他线程就可以从缓存中取值。
2.21 缓存雪崩
是指大量Key同时失效,对这些Key的请求又会打到DB上,同样会导致数据库压力过大甚至挂掉。
解决办法
- 让Key的失效时间分散开,可以在统一的失效时间上再加一个随机值,或者使用更高级的算法分散失效时间。
- 构建多个redis实例,个别节点挂了还有别的可以用。
- 多级缓存:比如增加本地缓存,减小redis压力。
- 对存储层增加限流措施,当请求超出限制,提供降级服务(一般就是返回错误即可)
2.22 单线程的redis为什么这么快
- 纯内存操作
- 单线程操作,避免了频繁的上下文切换
- 采用了非阻塞I/O多路复用机制(其实就是历史遗留问题,非要吹的这么好。。。)
2.23 Redis采用的删除策略
redis采用的是定期删除+惰性删除策略。
2.23.1 为什么不用定时删除策略?
定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.定期删除+惰性删除是如何工作的呢?
定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
2.23.2 为什么Redis的操作是原子性的,怎么保证原子性的?
对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。Redis的操作之所以是原子性的,是因为Redis是单线程的。Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。
2.24 多个命令在并发中也是原子性的吗?
不一定, 将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.
2.25 消息队列
不要使用redis去做消息队列,这不是redis的设计目标。但实在太多人使用redis去做去消息队列,redis的作者看不下去。
Reference
- http://c.biancheng.net/redis_command/
- redis启动出错Creating Server TCP listening socket 127.0.0.1:6379: bind: No error
- How to connect remote redis server
- https://www.51cto.com/article/604707.html
写在最后
欢迎大家关注鄙人的公众号【麦田里的守望者zhg】,让我们一起成长,谢谢。