无关Redis之前有独自写过几篇文章
Redis缓存穿透、击穿、雪崩,数据库与缓存一致性
谈谈Redis五种数据结构及实在利用场景
一文让你明确Redis长久化(RDB、AOF)
怎么实现Redis的高可用?(主从、哨兵、集群)
之所以独自写,是因为这几块内容比拟大,而且也很重要,所以想写的具体点、深刻点,让大家在了解的根底上记住它们,这让就不容易遗记。
所以无关以上相干的面试题就不再独自整顿了,具体能够看相干文章。
- redis是什么?
- Redis为什么这么快?
- 为什么Redis 6.0之后改多线程呢?
- 你理解Redis的过期策略吗?
- 聊聊Redis内存淘汰策略?
- Redis事务机制是怎么的?
- 说说Redis哈希槽的概念?
- 为什么Redis Cluster会设计成16384个槽呢?
- Redis在集群中查找key的时候,是怎么定位到具体节点的?
- Redis底层应用的什么协定?
- Redis的Hash抵触怎么办?
- Redis相比memcached有哪些劣势?
- 有哪些方法能够升高 Redis 的内存应用状况呢?
- Redis中获取海量数据的正确操作形式?
- 如何应用Redis的性能更高?
- 如何解决Redis的并发竞争Key问题?
- 应用过Redis做异步队列么,你是怎么用的?
- 用Redis做过延时队列吗? 具体应该怎么实现?
- 应用Redis统计网站的UV,应该怎么做?
- 什么是热Key问题,如何解决热key问题?
1、redis是什么
Redis是C语言开发的一个开源的高性能键值对(key-value)的内存数据库,它是一种NoSQL(泛指非关系型)的数据库。
与MySQL数据库不同的是,Redis的数据是存在内存中的。它的读写速度十分快,每秒反对并发10W QPS。因而Redis被广泛应用于缓存,另外,Redis也常常用来做分布式锁,也能够用来做消息中间件等。
除此之外,Redis还反对事务、长久化、LUA脚本、多种集群计划。
2、Redis为什么这么快?
1) 齐全基于内存存储实现
齐全基于内存,绝大部分申请是纯正的内存操作,十分疾速。数据存在内存中,相似于HashMap,HashMap的劣势就是查找和操作的工夫复杂度都是O(1);
2) 正当的数据编码
Redis 反对多种数据数据类型,每种根本类型可能对多种数据编码。什么时候,应用什么样数据类型,应用什么样编码,是redis设计者总结优化的后果。
3) 单线程模型
Redis是单线程模型的,而单线程防止了CPU不必要的上下文切换和竞争锁的耗费。也正因为是单线程,如果某个命令执行过长(如keys,hgetall命令),会造成排队阻塞。
Redis 6.0 引入了多线程提速,它的执行命令操作内存的依然是个单线程的。
4) 正当的线程模型
应用多路I/O复用模型,非阻塞IO;
多路I/O复用技术能够让单个线程高效的解决多个连贯申请,而Redis应用epoll作为I/O多路复用技术的实现。并且,Redis本身的事件处理模型将epoll中的连贯、读写、敞开都转换为事件,不在网络I/O上节约过多的工夫。
3、为什么Redis 6.0 之后改多线程呢?
Redis6.0之前,Redis在解决客户端的申请时,包含读socket、解析、执行、写socket等都由一个程序串行的主线程解决,这就是所谓的“单线程”。
redis应用多线程并非是齐全摒弃单线程,redis还是应用单线程模型来解决客户端的申请,只是应用多线程来解决数据的读写和协定解析,执行命令还是应用单线程。
这样做的目标是因为redis的性能瓶颈在于网络IO而非CPU,应用多线程能晋升IO读写的效率,从而整体进步redis的性能。
4、你理解Redis的过期策略吗?
咱们在set key的时候,能够给它设置一个过期工夫,比方expire key 60。指定这key 60s后过期,60s后redis是如何解决的?咱们先来介绍几种过期策略:
定时过期
每个设置过期工夫的key都须要创立一个定时器,到过期工夫就会立刻对key进行革除。该策略能够立刻革除过期的数据,对内存很敌对;然而会占用大量的CPU资源去解决过期的数据,从而影响缓存的响应工夫和吞吐量。
惰性过期
只有当拜访一个key时,才会判断该key是否已过期,过期则革除。该策略能够最大化地节俭CPU资源,却对内存十分不敌对。极其状况可能呈现大量的过期key没有再次被拜访,从而不会被革除,占用大量内存。
定期过期
每隔肯定的工夫,会扫描肯定数量的数据库的expires字典中肯定数量的key,并革除其中已过期的key。该策略是前两者的一个折中计划。通过调整定时扫描的工夫距离和每次扫描的限定耗时,能够在不同状况下使得CPU和内存资源达到最优的均衡成果。
Redis中同时应用了惰性过期
和定期过期
两种过期策略。
假如Redis以后寄存30万个key,并且都设置了过期工夫,如果你每隔100ms就去查看这全副的key,CPU负载会特地高,最初可能会挂掉。
因而,redis采取的是定期过期,每隔100ms就随机抽取肯定数量的key来检查和删除的。
然而呢,最初可能会有很多曾经过期的key没被删除。这时候,redis采纳惰性删除。在你获取某个key的时候,redis会检查一下,这个key如果设置了过期工夫并且曾经过期了,此时就会删除。
然而呢,如果定期删除漏掉了很多过期的key,而后也没走惰性删除。就会有很多过期key积在内存中,间接会导致内存爆的。或者有些时候,业务量大起来了,redis的key被大量应用,内存间接不够了,
这个时候就须要内存淘汰策略来爱护本人了。
5、聊聊Redis内存淘汰策略?
redis有8种内存淘汰策略。
- volatile-lru:当内存不足以包容新写入数据时,从设置了过期工夫的key中应用LRU(最近起码应用)算法进行淘汰;
- allkeys-lru:当内存不足以包容新写入数据时,从所有key中应用LRU(最近起码应用)算法进行淘汰;
- volatile-lfu:4.0版本新增,当内存不足以包容新写入数据时,在过期的key中,应用LFU算法进行删除key;
- allkeys-lfu:4.0版本新增,当内存不足以包容新写入数据时,从所有key中应用LFU算法进行淘汰;
- volatile-random:当内存不足以包容新写入数据时,从设置了过期工夫的key中,随机淘汰数据;
- allkeys-random:当内存不足以包容新写入数据时,从所有key中随机淘汰数据;
- volatile-ttl:当内存不足以包容新写入数据时,在设置了过期工夫的key中,依据过期工夫进行淘汰,越早过期的优先被淘汰;
- noeviction:默认策略,当内存不足以包容新写入数据时,新写入操作会报错;
6、聊聊Redis事务机制?
Redis通过MULTI、EXEC、WATCH
等一组命令汇合,来实现事务机制。事务反对一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会依照程序串行化执行队列中的命令,其余客户端提交的命令申请不会插入到事务执行命令序列中。
简言之,Redis事务就是程序性、一次性、排他性的执行一个队列中的一系列命令。
Redis执行事务的流程如下:
- 开始事务(MULTI)
- 命令入队
- 执行事务(EXEC)、撤销事务(DISCARD )
命令 | 形容 |
---|---|
EXEC | 执行所有事务块内的命令 |
DISCARD | 勾销事务,放弃执行事务块内的所有命令 |
MULTI | 标记一个事务块的开始 |
UNWATCH | 勾销 WATCH 命令对所有 key 的监督。 |
WATCH | 监督key ,如果在事务执行之前,该key 被其余命令所改变,那么事务将被打断。 |
无关redis事务须要留神的就是
1)与mysql中事务不同,在redis事务遇到执行谬误的时候,不会进行回滚,而是简略的放过了,并保障其余的命令失常执行(所以说redis的事务并不是保障原子性)。
2)当事务的执行过程中,如果redis意外的挂了。很遗憾只有局部命令执行了,前面的也就被抛弃了。
7、说说Redis哈希槽的概念?
Redis 集群没有应用一致性 hash,而是引入了哈希槽的概念,Redis 集群有16384
个哈希槽,每个 key 通过 CRC16
校验后对 16384 取模来决定搁置哪个槽,集群的每个节点负责一部分 hash 槽。
应用哈希槽的益处就在于能够不便的增加或移除节点。这种构造无论是增加删除或者批改某一个节点,都不会造成集群不可用的状态。
当须要减少节点时,只须要把其余节点的某些哈希槽挪到新节点就能够了;
当须要移除节点时,只须要把移除节点上的哈希槽挪到其余节点就行了;
在这一点上,咱们当前新增或移除节点的时候不必先停掉所有的 redis 服务。
8、为什么RedisCluster会设计成16384个槽呢?
2的14次方就是16384,这个当然不说肯定要设计成16384个槽,作者对这个也做了解释。
地址如下: https://github.com/antirez/re...
1) 如果槽位为65536,发送心跳信息的音讯头达8k,发送的心跳包过于宏大。
如上所述,在音讯头中,当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb 因为每秒钟,redis节点须要发送肯定数量的ping音讯作为心跳包,如果槽位为65536,这个ping音讯的音讯头太大了,节约带宽。
2) redis的集群主节点数量根本不可能超过1000个。
如上所述,集群节点越多,心跳包的音讯体内携带的数据越多。如果节点过1000个,也会导致网络拥挤。因而redis作者,不倡议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。
3) 槽位越小,节点少的状况下,压缩率高
Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的模式来保留的,在传输过程中,会对bitmap进行压缩,然而如果bitmap的填充率slots / N很高的话(N示意节点数),bitmap的压缩率就很低。
如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。
9、Redis在集群中查找key的时候,是怎么定位到具体节点的?
应用CRC16算法对key进行hash,再将hash值对16384取模,失去具体的槽位依据节点和槽位的映射信息(与集群建设连贯后,客户端能够获得槽位映射信息),找到具体的节点地址 去具体的节点找key如果key不在这个节点上,则redis集群会返回moved指令,加上新的节点地址给客户端。
同时,客户端会刷新本地的节点槽位映射关系如果槽位正在迁徙中,那么redis集群会返回asking指令给客户端,这是长期纠正,客户端不会刷新本地的节点槽位映射关系
10、Redis底层,应用的什么协定?
RESP
英文全称是Redis Serialization Protocol,它是专门为redis设计的一套序列化协定. 这个协定其实在redis的1.2版本时就曾经呈现了,然而到了redis2.0才最终成为redis通信协定的规范。
RESP次要有实现简略、解析速度快、可读性好等长处。
11、Redis的Hash抵触怎么办
Redis 中的 Hash和 Java的 HashMap 更加类似,都是数组+链表的构造,当产生 hash 产生碰撞时将会把元素追加到链表上。
在Redis中hash的内部结构也是一样的: 第一维是数组,第二维是链表.组成一个 全局哈希表
。
在 Java 中 HashMap 扩容是个很耗时的操作,须要去申请新的数组,扩容的老本并不低,因为须要遍历一个工夫复杂度为O(n)的数组,并且为其中的每个enrty进行hash计算。退出到新数组中。
为了谋求高性能,Redis 采纳了渐进式 rehash
策略.这也是 hash 中最重要的局部.
redis在扩容的时候执行 rehash 策略会保留新旧两个 两个全局哈希表,查问时也会同时查问两个全局哈希表 ,Redis会将旧 全局哈希表 中的内容一点一点的迁徙到新的 全局哈希表 中,当迁徙实现时,就会用新的 全局哈希表 取代之前的。当 全局哈希表 移除了最初一个元素之后,这个数据结构将会被删除.
失常状况下,当 全局哈希表 中元素的个数等于数组的长度时,就会开始扩容,扩容的新数组是原数组大小的 2 倍。
如果 Redis 正在做 bgsave(长久化) 时,可能不会去扩容,因为要缩小内存页的过多拆散(Copy On Write).然而如果 全局哈希表 曾经十分满了,元素的个数达到了数组长度的 5 倍时,Redis 会强制扩容。
12、Redis相比memcached有哪些劣势
- Memcached 所有的值均是简略的字符串,redis 作为其替代者,反对更为丰盛的数据类
- Redis 的速度比 Memcached 快很多
- Redis 能够长久化其数据
13、有哪些方法能够升高 Redis 的内存应用状况呢?
当你的业务利用在 Redis 中存储数据很少时,你可能并不太关怀内存资源的应用状况。但随着业务的倒退,你的业务存储在 Redis 中的数据就会越来越多。
那在应用 Redis 时,怎么做能力更节俭内存呢?这里总结了4点倡议:
1) 管制 key 的长度
最简略间接的内存优化,就是管制 key 的长度。
在开发业务时,你须要提前预估整个 Redis 中写入 key 的数量,如果 key 数量达到了百万级别,那么,过长的 key 名也会占用过多的内存空间。
所以,你须要保障 key 在简略、清晰的前提下,尽可能把 key 定义得短一些。
例如,原有的 key 为 user123,则能够优化为 u:bk:123。
这样一来,你的 Redis 就能够节俭大量的内存,这个计划对内存的优化十分间接和高效。
2) 防止存储 bigkey
bigkey,意思就是这个key的value值很大。除了管制 key 的长度之外,你同样须要关注 value 的大小,如果大量存储 bigkey,也会导致 Redis 内存增长过快。
除此之外,客户端在读写 bigkey 时,还有产生性能问题。
所以,你要防止在 Redis 中存储 bigkey,个别倡议是:
- String:大小管制在 10KB 以下
- List/Hash/Set/ZSet:元素数量管制在 1 万以下
3) 尽可能地都设置过期工夫
Redis 数据存储在内存中,这也意味着其资源是无限的。你在应用 Redis 时,要把它当做缓存来应用,而不是数据库。
所以,你的利用写入到 Redis中的数据,尽可能地都设置 过期工夫。采纳这种计划,能够让 Redis 中只保留常常拜访的 热数据,内存利用率也会比拟高。
4) 实例设置 maxmemory + 淘汰策略
尽管你的 Redis key 都设置了过期工夫,但如果你的业务利用写入量很大,并且过期工夫设置得比拟久,那么短期间内 Redis 的内存依旧会快速增长。
如果不管制 Redis 的内存下限,也会导致应用过多的内存资源。
对于这种场景,你须要提前预估业务数据量,而后给这个实例设置 maxmemory 管制实例的内存下限,这样能够防止 Redis 的内存继续收缩。
配置了 maxmemory,此时你还要设置数据淘汰策略,而淘汰策略如何抉择,你须要联合你的业务特点来决定。
14、Redis中获取海量数据的正确操作形式?
有时候须要从Redis实例成千上万的key中找出特定前缀的key
列表来手动解决数据,可能是批改它的值,也可能是删除 key。这里就有一个问题,如何从海量的 key 中找出满足特定前缀的 key 列表来?
比方咱们的用户token缓存是采纳了【user_token:userid】格局的key,保留用户的token的值。这时候咱们想看下有多少用户在线。
在Redis2.8版本之前,咱们能够应用keys命令依照正则匹配失去咱们须要的key。Redis 提供了一个简略暴力的指令 keys 用来列出所有满足特定正则字符串规定的 key。
keys user_token*
然而这个命令有一些毛病:
- 没有 offset、limit 参数,一次性吐出所有满足条件的 key,万一实例中有几百 w 个 key 满足条件,当你看到满屏的字符串刷的没有止境时,你就晓得好受了。
- keys 算法是遍历算法,复杂度是 O(n),如果实例中有千万级以上的 key,这个指令就会导致 Redis 服务卡顿,
- 所有读写 Redis 的其它的指令都会被延后甚至会超时报错,因为 Redis 是单线程程序,程序执行所有指令,其它指令必须等到以后的 keys 指令执行完了才能够持续。
- 倡议生产环境屏蔽keys命令
在满足需要和存在造成Redis卡顿之间到底要如何抉择呢?面对这个两难的抉择,Redis在2.8版本给咱们提供了解决办法——scan命令
。
相比于keys命令,scan命令有两个比拟显著的劣势:
- scan命令的工夫复杂度尽管也是O(N),但它是分次进行的,不会阻塞线程。
- scan命令提供了limit参数,能够管制每次返回后果的最大条数。
这两个劣势就帮忙咱们解决了下面的难题,不过scan命令也并不是完满的,它返回的后果有可能反复,因而须要客户端去重这点十分重要。
15、如何应用Redis的性能更高?
1)master敞开长久化
个别咱们在生产上采纳的长久化策略为master敞开长久化,slave开RDB即可,必要的时候AOF和RDB都开启。
2) 不应用简单度过高的命令
Redis 是单线程模型解决申请,在执行简单度过高的命令时,会耗费更多的 CPU 资源,主线程中的其它申请只能期待,这时也会产生排队提早。
所以,你须要防止执行例如 sort、sinter、sinterstore、zunionstore、zinterstore 等聚合类命令。
对于这种聚合类操作,我倡议你把它放到客户端来执行,不要让 Redis 承当太多的计算工作。
3)执行 O(N) 命令时,关注 N 的大小
躲避应用简单度过高的命令,就能够居安思危了么?
答案是否定的。
当你在执行 O(N) 命令时,同样须要留神 N 的大小。
就好比下面说的应用keys命令,如果一次性能查问过多的数据,也会在网络传输过程中耗时过长,操作提早变大。
所以,对于容器类型(List/Hash/Set/ZSet),在元素数量未知的状况下,肯定不要无脑执行 LRANGE key 0 -1 / HGETALL / SMEMBERS / ZRANGE key 0 -1。
在查问数据时,你要遵循以下准则:
- 先查问数据元素的数量(LLEN/HLEN/SCARD/ZCARD)
- 元素数量较少,可一次性查问全量数据
- 元素数量十分多,分批查问数据(LRANGE/HASCAN/SSCAN/ZSCAN)
4) 批量命令代替单个命令
当你须要一次性操作多个 key 时,你应该应用批量命令来解决。
批量操作相比于屡次单个操作的劣势在于,能够显著缩小客户端、服务端的来回网络 IO 次数。
所以我给你的倡议是:
- String / Hash 应用 MGET/MSET 代替 GET/SET,HMGET/HMSET 代替 HGET/HSET
- 其它数据类型应用 Pipeline,打包一次性发送多个命令到服务端执行
5) 防止集中过期 key
Redis 清理过期 key 是采纳定时 + 懈怠的形式来做的,而且这个过程都是在主线程中执行。
如果你的业务存在大量 key 集中过期的状况,那么 Redis 在清理过期 key 时,也会有阻塞主线程的危险。
想要防止这种状况产生,你能够在设置过期工夫时,减少一个随机工夫,把这些 key 的过期工夫打散,从而升高集中过期对主线程的影响。
6) 只应用 db0
只管 Redis 提供了 16 个 db,但我只倡议你应用 db0。
为什么呢?我总结了以下 3 点起因:
- 在一个连贯上操作多个 db 数据时,每次都须要先执行 SELECT,这会给 Redis 带来额定的压力
- 应用多个 db 的目标是,按不同业务线存储数据,那为何不拆分多个实例存储呢?拆分多个实例部署,多个业务线不会相互影响,还能进步 Redis 的拜访性能
- Redis Cluster 只反对 db0,如果前期你想要迁徙到 Redis Cluster,迁徙老本高
16、如何解决 Redis 的并发竞争 Key 问题
这个也是线上十分常见的一个问题,就是多客户端同时并发写一个 key,可能原本应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个 key,批改值之后再写回去,只有程序错了,数据就错了。
举荐一种计划:分布式锁(zookeeper和redis都能够实现分布式锁)。(如果不存在Redis的并发竞争Key问题,不要应用分布式锁,这样会影响性能)
17、应用过 Redis 做异步队列么,你是怎么用的?
个别应用list构造作为队列,rpush生产音讯,lpop生产音讯
。当lpop没有音讯的时候,要适当sleep一会再重试。
如果不想sleep呢?list还有个指令叫blpop,在没有音讯的时候,它会阻塞住直到音讯到来。
但如果是这样你发现redis作为音讯队列是不平安的,它不能反复生产,一旦生产就会被删除。同时做消费者确认ACK也麻烦所以个别在理论开发中个别很少用redis中音讯队列,因为当初曾经有Kafka、RabbitMQ等成熟的音讯队列了,它们的性能更加欠缺。
18、用Redis做延时队列,具体应该怎么实现?
提早队列能够应用 zset(有序列表)实现,咱们将音讯序列化成一个字符串作为列表的value,这个音讯的到期解决工夫作为score,而后用定时器定时去扫描,一旦有执行工夫小于或等于以后工夫的工作,就立刻执行。
19、应用Redis统计网站的UV,应该怎么做?
UV与PV不同,UV须要去重。个别有2种计划:
1、用BitMap。存的是用户的uid,计算UV的时候,做下bitcount就行了。
2、用布隆过滤器。将每次拜访的用户uid都放到布隆过滤器中。长处是省内存,毛病是无奈得 到准确的UV。然而对于不须要准确晓得具体UV,只须要大略的数量级的场景,是个不错的抉择。
20、什么是热Key问题,如何解决热key问题
所谓热key问题就是,忽然有几十万的申请去拜访redis上的某个特定key。那么,这样会造成流量过于集中,达到物理网卡下限,从而导致这台redis的服务器宕机。
而热点Key是怎么产生的呢?次要起因有两个:
- 用户生产的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。
- 申请分片集中,超过单Redi服务器的性能,比方固定名称key,Hash落入同一台服务器,霎时访问量极大,超过机器瓶颈,产生热点Key问题。
那么在日常开发中,如何辨认到热点key呢?
- 凭教训判断哪些是热Key;
- 客户端统计上报;
- 服务代理层上报
如何解决热key问题?
- Redis集群扩容:减少分片正本,平衡读流量;
- 将热key扩散到不同的服务器中;
- 应用二级缓存,即JVM本地缓存,缩小Redis的读申请。
参考
[1] Redis 最佳实际指南: https://mp.weixin.qq.com/s/Fz...
[2] Redis经典面试题: https://mp.weixin.qq.com/s/fB...
[3] Redis面试20题: http://www.gameboys.cn/articl...
关注公众号:后端元宇宙。继续输入优质好文