redis必知会

Posted 又一春夏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis必知会相关的知识,希望对你有一定的参考价值。

Redis 是单进程单线程的?

Redis 是单进程单线程的,redis 利用队列技术将并发访问变为串行访问,消 除了传统数据库串行控制的开销。

Redis 的持久化机制是什么?各自的优缺点?

Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制

使用过Redis分布式锁么,它是怎么实现的?

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!

scan

当redis中key数量越大,keys 命令执行越慢,而且最重要的会阻塞服务器,对单线程的redis来说,简直是灾难,终于找到了替代命令scan。

SCAN cursor [MATCH pattern] [COUNT count]

SCAN 命令及其相关的 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都用于增量地迭代(incrementally iterate)一集元素(a collection of elements):

SCAN 命令用于迭代当前数据库中的数据库键。
SSCAN 命令用于迭代集合键中的元素。
HSCAN 命令用于迭代哈希键中的键值对。
ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值)。

什么是缓存雪崩

缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间
(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。

解决办法:

大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开。

如何避免?

1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

什么是缓存穿透?

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。每次都要去数据库再查询一遍,然后返回空,这样请求就绕过缓存直接查数据库,如果恶意请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。

解决办法;

  • 1.最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
  • 2.也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。

redis缓存击穿(热点Key)

缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案:对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询

redis 过期键的删除策略?

  • 1、定时删除:在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键 的过期时间来临时,立即执行对键的删除操作。
  • 2、惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是 否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
  • 3、定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至 于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

Redis 为什么是单线程的

Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)Redis利用队列技术将并发访问变为串行访问
1)绝大部分请求是纯粹的内存操作(非常快速)
2)采用单线程,避免了不必要的上下文切换和竞争条件
3)非阻塞IO优点:

对于大量的请求怎么样处理

redis是一个单线程程序,也就说同一时刻它只能处理一个客户端请求;
redis是通过IO多路复用(select,epoll, kqueue,依据不同的平台,采取不同的实现)来处理多个客户端请求的

为什么Redis的操作是原子性的,怎么保证原子性的?

对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。
Redis的操作之所以是原子性的,是因为Redis是单线程的。
Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。
多个命令在并发中也是原子性的吗?
不一定, 将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.

Redis实现分布式锁

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作


 
 

解锁:使用 del key 命令就能释放锁
解决死锁:
1)通过Redis中expire()给锁设定最大持有时间,如果超过,则Redis来帮我们释放锁。
2) 使用 setnx key “当前系统时间+锁持有的时间”和getset key “当前系统时间+锁持有的时间”组合的命令就可以实现。

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

  • 1、Master 最好不要写内存快照,如果 Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性 暂停服务
  • 2、如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一
  • 3、为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网
  • 4、尽量避免在压力很大的主库上增加从
  • 5、主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。

是否使用过 Redis 集群,集群的原理是什么?

1)、Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。
2)、Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行 分片存储。

说说 Redis 哈希槽的概念?

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

都有哪些办法可以降低 Redis 的内存使用情况呢?

如果你使用的是 32 位的 Redis 实例,可以好好利用 Hash,list,sorted set,set 等集合类型数据,因为通常情况下很多小的 Key-Value 可以用更紧凑的方式存放 到一起假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以 某个固定的已知的前缀开头的,如果将它们全部找出来?

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

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

 

如果对方追问 redis 如何实现延时队列?

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


Redis 集群方案应该怎么做?都有哪些方案?

  • 1.twemproxy,大概概念是,它类似于一个代理方式, 使用时在本需要连接 redis 的地方改为连接 twemproxy, 它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。
    缺点: twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。
  • 2.codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新 hash 节点
  • 3.redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。

Redis 有哪些架构模式?讲讲各自的特点

单机版

 
 

特点:简单
问题:
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、数据通过异步复制,不保证数据的强一致性

什么是一致性哈希算法?什么是哈希槽?

这两个问题篇幅过长 网上找了两个解锁的不错的文章
https://www.cnblogs.com/lpfuture/p/5796398.html
https://blog.csdn.net/z15732621582/article/details/79121213

Redis对象底层数据结构

但为了提高存储效率与程序执行效率,每种对象的底层数据结构实现都可能不止一种。encoding就表示了对象底层所使用的编码。下面先介绍每种底层数据结构的实现,再介绍每种对象类型都用了什么底层结构并分析他们之间的关系。

底层数据结构共有八种,如下表所示:

编码常量 编码所对应的底层数据结构
REDIS_ENCODING_INT long 类型的整数
REDIS_ENCODING_EMBSTR embstr 编码的简单动态字符串
REDIS_ENCODING_RAW 简单动态字符串
REDIS_ENCODING_HT 字典
REDIS_ENCODING_LINKEDLIST 双端链表
REDIS_ENCODING_ZIPLIST 压缩列表
REDIS_ENCODING_INTSET 整数集合
REDIS_ENCODING_SKIPLIST 跳跃表和字典

跳表

插入 level = 3,key = 1
当我们插入 level = 3,key = 1 时,结果如下:

 
level = 3,key = 1

插入 level = 1,key = 2
当继续插入 level = 1,key = 2 时,结果如下

 
level = 1,key = 2

插入 level = 2,key = 3
当继续插入 level = 2,key = 3 时,结果如下

level = 2,key = 3 

插入 level = 3,key = 5
当继续插入 level = 3,key = 5 时,结果如下

level = 3,key = 5

 插入 level = 1,key = 66

当继续插入 level = 1,key = 66 时,结果如下

 
level = 1,key = 66

 插入 level = 2,key = 100

当继续插入 level = 2,key = 100 时,结果如下

 
level = 2,key = 100

上述便是跳跃表插入原理,关键点就是层级–使用抛硬币的方式,感觉还真是挺随机的。每个层级最末端节点指向都是为 null,表示该层级到达末尾,可以往下一级跳。

跳跃表的查询

现在我们要找键为 66 的节点的值。那跳跃表是如何进行查询的呢?

跳跃表的查询是从顶层往下找,那么会先从第顶层开始找,方式就是循环比较,如过顶层节点的下一个节点为空说明到达末尾,会跳到第二层,继续遍历,直到找到对应节点。

如下图所示红色框内,我们带着键 66 和 1 比较,发现 66 大于 1。继续找顶层的下一个节点,发现 66 也是大于五的,继续遍历。由于下一节点为空,则会跳到 level 2

顶层遍历

上层没有找到 66,这时跳到 level 2 进行遍历,但是这里有一个点需要注意,遍历链表不是又重新遍历。而是从 5 这个节点继续往下找下一个节点。如下,我们遍历了 level 3 后,记录下当前处在 5 这个节点,那接下来遍历是 5 往后走,发现 100 大于目标 66,所以还是继续下沉。

第二层遍历

当到 level 1 时,发现 5 的下一个节点恰恰好是 66 ,就将结果直接返回。

以上是关于redis必知会的主要内容,如果未能解决你的问题,请参考以下文章

企业实操任务- 正式环境Redis服务启用密码认知

Android开发必知会的多进程详解

Python程序猿必知会的Django用户模块扩展方法

Python程序员必知会数据结构的3个小技巧

Redis扫盲:浅谈Redis面试必问——工程架构篇

Redis扫盲:浅谈Redis面试必问——工程架构篇