Redis宝典

什么是Redis

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis的优点

  1. 丰富的数据类型(string、hash、list、set、sorted set)
  2. 支持持久化(AOF、RDB)
  3. Redis支持数据的备份,即master-slave模式的数据备份
  4. 读写速度快. 数据存放在内存中,数据结构类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  5. Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的IO复用模型
  6. 支持事务,而且操作都是原子性
  7. 其他:publish/subscribe, 通知, key 过期等等特性

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

批量操作在发送 EXEC 命令前被放入队列缓存。
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:
开始事务。
命令入队。
执行事务。

在Redis事务发生错误解决办法
redis事务错误分两种,
(1)在事务开始之前,命令可能排队失败。如,命令的语法可能是错误 (错误的参数个数,错误的命令名字 …),或一些重要的环境问题,如,内存不足。
解决办法:在命令排队期间发生错误,Redis会拒绝执行 EXEC,并返回一个错误,然后自动放弃这个事务。
(2)在事务执行中。当EXEC 调用后 ,一些命令可能执行失败,如,在一个字符串上进行了列表命令的操作。
解决办法:redis会跳过发生错误的命令,继续接下来的命令。
注:Redis没有回滚机制

redis和memcached的区别
(1) memcached所有的值均是简单的字符串,redis支持更为丰富的数据类型

(2)由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据时,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。

(3) 虽然redis和Memcached都是内存数据库,但是redis可以持久化其数据,Memcached不支持持久化. (Redis并不是所有的数据都一直存储在内存中的,当物理内存用完时,Redis可以将一些很久没用到的value交换到磁盘,但memcached超过内存比例会抹掉前面的数据。)所以memcached挂掉后(比如说断电,重启系统等等),数据不可恢复;redis数据丢失后可以通过AOF恢复

(4)分布式存储

Memcached是全内存的数据缓冲系统,Redis虽然支持数据的持久化,但是全内存毕竟才是其高性能的本质。作为基于内存的存储系统来说,机器物理内存的大小就是系统能够容纳的最大数据量。如果需要处理的数据量超过了单台机器的物理内存大小,就需要构建分布式集群来扩展存储能力。

Memcached本身并不支持分布式,因此只能在客户端通过像一致性哈希这样的分布式算法来实现Memcached的分布式存储,关于分布式一致性哈希算法见总结:分布式一致性hash算法。当客户端向Memcached集群发送数据之前,首先会通过内置的分布式算法计算出该条数据的目标节点,然后数据会直接发送到该节点上存储。但客户端查询数据时,同样要计算出查询数据所在的节点,然后直接向该节点发送查询请求以获取数据。

相较于Memcached只能采用客户端实现分布式存储,Redis更偏向于在服务器端构建分布式存储,但没有采用一致性哈希,关于Redis集群分析见总结:分布式缓存Redis之cluster集群。最新版本的Redis已经支持了分布式存储功能。Redis Cluster是一个实现了分布式且允许单点故障的Redis高级版本,它没有中心节点,具有线性可伸缩的功能。为了保证单点故障下的数据可用性,Redis Cluster引入了Master节点和Slave节点。在Redis Cluster中,每个Master节点都会有对应的两个用于冗余的Slave节点。这样在整个集群中,任意两个节点的宕机都不会导致数据的不可用。当Master节点退出后,集群会自动选择一个Slave节点成为新的Master节点。

redis支持master-slave复制模式

memcache可以使用一致性hash做分布式

如果有持久化方面的需求或者对数据类型和处理有要求的应该选择redis;如果是简单的key/value存储可以考虑memcached
内存管理机制
Memcached主要的cache机制是LRU(最近最少使用Least Recently Used)算法+超时失效。

Redis采用的是包装的mallc/free,相较于Memcached的内存管理方法来说,要简单很多。

redis的单线程为什么那么快
redis分客户端和服务端,一次完整的redis请求事件有多个阶段(客户端到服务器的网络连接–>redis读写事件发生–>redis服务端的数据处理(单线程)–>数据返回)。平时所说的redis单线程模型,本质上指的是服务端的数据处理

客户端和服务器是socket通信方式,socket服务端监听可同时接受多个客户端请求也就是说,redis服务同时面对多个redis客户端连接请求,而redis服务本身是单线程运行。

redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处理这个事。在内存的情况下,这个方案就是最佳方案

使用单线程的方式是无法发挥多核CPU 性能, 为了充分利用多核CPU,常常在一台server上会启动多个实例(即多个redis进程)。而为了减少切换的开销,有必要为每个实例(redis进程)指定其所运行的CPU

而且因为redis是单线程的,所以不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

总结:CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)

redis的主从结构
主从结构一是可以进行冗余备份,二是可以实现读写分离

主从复制
冗余备份(还可以称为:主从复制,数据冗余,数据备份,可以实现容灾快速恢复)

持久化保证了即使redis服务重启也会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障. 例如:我们搭建一个主叫做redis0,两个从,分别叫做redis1和redis2,即使一台redis服务器宕机其它两台redis服务也可以继续提供服务。主redis中的数据和从redis上的数据保持实时同步,当主redis写入数据时通过主从复制机制会复制到两个从redis服务上。

①一个Master可以有多个Slave,不仅主服务器可以有从服务器,从服务器也可以有自己的从服务器

②复制在Master端是非阻塞模式的,这意味着即便是多个Slave执行首次同步时,Master依然可以提供查询服务;

③复制在Slave端也是非阻塞模式的:如果你在redis.conf做了设置,Slave在执行首次同步的时候仍可以使用旧数据集提供查询;你也可以配置为当Master与Slave失去联系时,让Slave返回客户端一个错误提示;

④当Slave要删掉旧的数据集,并重新加载新版数据时,Slave会阻塞连接请求

读写分离
主从架构中,可以考虑关闭主服务器的数据持久化功能,只让从服务器进行持久化,这样可以提高主服务器的处理性能。从服务器通常被设置为只读模式,这样可以避免从服务器的数据被误修改。

基于Redis分布式锁原理
分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
这时候对方会告诉你说你回答得不错,然后接着问如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
这时候你要给予惊讶的反馈:唉,是喔,这个锁就永远得不到释放了。紧接着你需要抓一抓自己得脑袋,故作思考片刻,好像接下来的结果是你主动思考出来的,然后回答:我记得set指令有非常复杂的参数(如下),这个应该是可以同时把setnx和expire合成一条指令来用的!对方这时会显露笑容,心里开始默念:嗯,这小子还不错。

SET resource-name anystring NX EX max-lock-time

如果上面的命令返回,客户端可以获取锁OK(如果命令返回Nil,则在一段时间后重试),并使用DEL删除锁。
到达过期时间后,锁定将自动释放。

可以使此系统更加健壮,修改解锁模式如下:
不要设置固定字符串,而是设置一个名为token的不可猜测的大型随机字符串。
不是使用DEL释放锁,而是发送一个脚本,该脚本仅在值匹配时才删除密钥。
这避免了客户端在过期时间之后尝试释放锁定,从而删除由稍后获取锁定的另一个客户端创建的密钥。

Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来
使用keys指令可以扫出指定模式的key列表。
对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

如何使用Redis做异步队列
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

如果对方追问可不可以不用sleep呢?
list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

如果对方追问能不能生产一次消费多次呢?
使用pub/sub主题订阅者模式,可以实现1:N的消息队列。

如果对方追问pub/sub有什么缺点?
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

如果对方追问redis如何实现延时队列?
我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话,怎么问的这么详细。但是你很克制,然后神态自若的回答道:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。
到这里,面试官暗地里已经对你竖起了大拇指。但是他不知道的是此刻你却竖起了中指,在椅子背后。

如果有大量的key需要设置同一时间过期,一般需要注意什么
如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。

Redis如何做持久化的?
bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,优先使用aof来恢复内存的状态,如果没有aof日志,就会使用rdb文件来恢复。
如果再问aof文件过大恢复时间过长怎么办?你告诉面试官,Redis会定期做aof重写,压缩aof文件日志大小。如果面试官不够满意,再拿出杀手锏答案,Redis4.0之后有了混合持久化的功能,将bgsave的全量和aof的增量做了融合处理,这样既保证了恢复的效率又兼顾了数据的安全性。这个功能甚至很多面试官都不知道,他们肯定会对你刮目相看。
如果对方追问那如果突然机器掉电会怎样?取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

Redis的同步机制
从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

Redis主要消耗什么物理资源?

内存。

Redis的全称是什么?

Remote Dictionary Server。

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

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

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

512M

为什么Redis需要把所有数据放到内存中?

一个字 “快” ,内存存取速度远超于磁盘I/O

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

redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略

Redis的数据淘汰策略

redis 提供 6种数据淘汰策略通过maxmemory-policy设置策略:

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据

redis默认不采用淘汰策略(no-enviction),直接返回错误。

Redis常见数据结构使用场景

  1. String
    常用命令: set,get,decr,incr,mget 等。
    String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。
    常规key-value缓存应用;
    常用场景:常规计数:微博数,粉丝数等。

  2. Hash
    常用命令: hget,hset,hgetall 等。
    常用场景:存储部分变更数据,如网站首页缓存、用户对象信息等
    我们简单举个实例来描述下Hash的应用场景,比如我们要存储一个用户对象数据,包含以下信息:

用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2种存储方式:
在这里插入图片描述
第一种方式(如上图)将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。

在这里插入图片描述
第二种方法(如上图)是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。

那么Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口,如下图:
在这里插入图片描述
也就是说,Key仍然是用户ID, value是一个Map,这个Map的key是成员的属性名,value是属性值,这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。很好的解决了问题。

这里同时需要注意,Redis提供了接口(hgetall)可以直接取到全部的属性数据,但是如果内部Map的成员很多,那么涉及到遍历整个内部Map的操作,由于Redis单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意。

Hash实现方式:

上面已经说到Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。

  1. List
    常用命令: lpush,rpush,lpop,rpop,lrange等
    使用场景:最新消息排行、消息队列等

  2. Set
    常用命令: sadd,spop,smembers,sunion 等
    使用场景:
    set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的。
    (1)在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同喜好、二度好友等功能。

    (2)点赞点踩
    当前用户点赞的话,就将当前用户id存入到对应点赞集合当中,同时判断点反对集合中是否有此id值,有的话就移除;
    当前用户点反对的话,与上面操作相反。
    页面显示的时候就根据当前用户id在点赞集合和反对集合中查找,若id值在点赞集合中有对应值,就显示1,表示当前用户点赞;若在反对集合中有值,反对处就显示1

  3. Sorted Set
    常用命令: zadd,zrange,zrem,zcard等
    和Set相比,Sorted Set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列并且是插入有序的,即自动排序。
    使用场景:
    (1)比如一个存储全班同学成绩的Sorted Set,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。
    (2)用Sorted Set来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。

说说Redis哈希槽的概念?

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

Redis常见性能问题和解决方案

  1. Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
  2. 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  3. 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
  4. 尽量避免在压力很大的主库上增加从库

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

Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。

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

异步复制

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

16384个。因为Redis集群有16384个哈希槽

Redis中的管道有什么用?

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。

这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。

Redis事务相关的命令有哪几个?

MULTI、EXEC、DISCARD、WATCH

Redis key的过期时间和永久有效分别怎么设置?

EXPIRE和PERSIST命令。

Redis如何做内存优化

尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。

比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面。

Redis回收进程如何工作

一个客户端运行了新的命令,添加了新的数据。

Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。

一个新的命令被执行,等等。

所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。
如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。

解决redis主从结构宕机
a)这个相对而言比较简单,在Redis中从库重新启动后会自动加入到主从架构中,自动完成同步数据

b) 如果从库在断开期间,主库的变化不大,从库再次启动后,主库依然会将所有的数据做RDB操作吗?还是增量更新?(从库有做持久化的前提下)

不会的,因为在Redis2.8版本后就实现了,主从断线后恢复的情况下实现增量复制。

主Redis宕机

手动恢复

i. 第一步,在从数据库中执行SLAVEOFNO ONE命令,断开主从关系并且将从库提升为主库继续服务;

ii.第二步,将主库重新启动后,执行SLAVEOF命令,将其设置为其他库的从库,这时数据就能更新回来;

哨兵功能自动恢复

通过sentinel模式启动redis后,自动监控master/slave的运行状态, 已经被集成在redis2.4+的版本中

如果Master异常,则会进行Master-Slave切换,将其中一个Slave作为Master,将之前的Master作为Slave

基本原理是:心跳机制+投票裁决

每个sentinel会向其它sentinal、master、slave定时发送消息,以确认对方是否“活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的“主观认为宕机” Subjective Down,简称SDOWN)。

若”哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master”彻底死亡”(即:客观上的真正down机,Objective Down,简称ODOWN),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。

缓存穿透
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

解决办法

①对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃。还有最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

②也可以采用一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存雪崩
如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。

解决办法

①在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

②可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存

③不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀. 比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件

④做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。

缓存击穿
缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

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

缓存预热解决方案:

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

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

(3)定时刷新缓存;

缓存更新
我们知道通过expire来设置key 的过期时间,那么对过期的数据怎么处理呢?

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:

(1)定时去清理过期的缓存;

(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。

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

降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;

(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

缓存热点key
使用缓存 + 过期时间的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但是有两个问题如果同时出现,可能就会对应用造成致命的危害:

当前 key 是一个热点 key( 可能对应应用的热卖商品、热点新闻、热点评论等),并发量非常大。

重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次 IO、多个依赖等。

在缓存失效的瞬间,有大量线程来重建缓存 ,造成后端负载加大,甚至可能会让应用崩溃。

热点 key 失效后大量线程重建缓存

要解决这个问题也不是很复杂,但是不能为了解决这个问题给系统带来更多的麻烦,所以需要制定如下目标:

减少重建缓存的次数

数据尽可能一致

较少的潜在危险

1)互斥锁 (mutex key)

此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可

2)永远不过期

永远不过期”包含两层意思:

从缓存层面来看,确实没有设置过期时间,所以不会出现热点 key 过期后产生的问题,也就是“物理”不过期。

从功能层面来看,为每个 value 设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存

从实战看,此方法有效杜绝了热点 key 产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不一致

作为一个并发量较大的应用,在使用缓存时有三个目标
第一,加快用户访问速度,提高用户体验。
第二,降低后端负载,减少潜在的风险,保证系统平稳。
第三,保证数据“尽可能”及时更新。下面将按照这三个维度对上述两种解决方案进行分析。

互斥锁 (mutex key):这种方案思路比较简单,但是存在一定的隐患,如果构建缓存过程出现问题或者时间较长,可能会存在死锁和线程池阻塞的风险,但是这种方法能够较好的降低后端存储负载并在一致性上做的比较好。

” 永远不过期 “:这种方案由于没有设置真正的过期时间,实际上已经不存在热点 key 产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。

新手一枚,欢迎各位道友一起讨论
如有错误,请在评论区指正

点关注,不迷路哟
老铁,点个赞呗
收藏一手,随时观看

Redis官方文档

更多优秀文章:
https://blog.csdn.net/qq_34337272/article/details/80012284
https://blog.csdn.net/yangzhong0808/article/details/81196472
https://blog.csdn.net/qq_36071795/article/details/83988177
https://blog.csdn.net/weixin_45154607/article/details/92969779

redis面试题库

发布了70 篇原创文章 · 获赞 154 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/weixin_39815001/article/details/99586988
今日推荐