redis基础知识扫盲
1. redis有哪些数据结构?其底层又是如何实现的?
基础数据结构:
-
String
- 字符串对象
- 底层实现
- int : 整数值实现
- 短字符串:embstr编码,sds实现
- 长字符串:raw编码,sds实现
-
List
- 列表对象
- 底层实现
-
ziplist 压缩列表
- 图例
- 图例
-
linkedlist 双端列表
- 图例
- 图例
-
满足特定条件的对象才使用ziplist实现,否则使用linkedlist实现
-
-
Hash
- 哈希对象
- 底层实现
- ziplist 压缩列表
- 图例
- 图例
- hashtable 哈希表,其底层采用字典实现
- 图例
- 图例
- 满足特定条件的对象才使用ziplist实现,否则使用hashtable实现
- ziplist 压缩列表
-
Set
- 集合对象
- 底层实现
- 整数集合 intset实现
- 图例
- 图例
- 非整数集合 hashtable实现,底层同样是dict
- 图例
- 图例
- 满足特定条件的对象使用intset实现,否则使用hashtable实现
- 整数集合 intset实现
-
SortedSet
- 有序集合对象
- 底层实现
- ziplist
- 样例
- 样例
- skiplist编码,底层包含一个skiplist和dict
- 样例
- 样例
- ziplist
大致总结一下:
高级数据结构:
-
HyperLogLog
- 作用: 以极小的内存占用提供稍微不精准的去重基数统计。
- 在redis中,只需要12kb就可以统计2^64个数据,
- 计数存在一定的误差,误差率整体较低。标准误差为 0.81%
-
Geo
- 用于计算地理位置信息相关的一些功能(比如附近的人),其底层依然采用sortedSet实现
- redis使用业界通用的地理位置距离排序算法GeoHash算法
- GeoHash算法将二维经纬度数据映射到一维整数,这样所有的元素都将挂载到一条线上距离相近的二维点映射到一维也会相近,然后再用有序set排序,就可实现附近的人功能
-
Pub/Sub
- 发布与订阅
- 在redis中,你可以对某一个key值进行消息发布和订阅,当一个key值进行了消息发布之后,所有订阅它的客户端都会收到相应的消息。
Redis Moudle:
-
RedisBloom:分布式环境下的布隆过滤器
- 布隆过滤器
- 作用:用于检索一个元素是否在一个合集中,它的优点是空间效率高,查询时间少,缺点是有一定的误识别率和无法删除
- 原理:当一个元素被加入合集时,通过K个哈希函数将元素映射成一个位数组中的K个点,将他们置为1,检索时,只需要看这些点是不是1就知道它是否在集合中,如果这些点,有任何一个为0,则被检元素一定不在,如果都是1,则被检元素很有可能存在。
- 应用:
- 主要用于防止缓存击穿,避免每次请求都不走缓存直接打到数据库。
- 布隆过滤器可以明确某个数据在数据库是否存在,所以可以过滤掉无效的查询到数据库,减小数据库的压力。
- 布隆过滤器
-
RedisSearch:全文检索组件,适合数据量适中,内存和存储有限的缓存。
2. redis的分布式锁是怎么回事?
- 先用setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘了释放。
- 并且set有非常复杂的参数,可以将setnx和expire合成一条指令来使用,不用担心setnx之后expire之前,进程意外crash或者重启维护了
setnx: 如果不存在,则set
3. 假如redis里有一亿个key,10w个key是固定前缀,如何将他们全部找出?
keys指令可以扫出指定模式的key列表
4. 如果redis正在给线上服务提供服务,使用keys指令会有什么问题?
- reids是单线程的,keys指令会导致线程阻塞一段时间,线上服务会停顿,直到keys指令执行完毕,服务才能恢复
- 这个时候可以采用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但有一定概率重复(scan是增量式的迭代命令),在客户端做一次去重就ok了,但比单keys命令耗时长。
5. 如何使用redis实现异步队列?
- 方式1:生产者消费者模式
- 使用list结构做队列,rpush生产消息,lpop消费消息,当lpop没有消息时,适当的sleep一会再重试,避免过高qps,或者直接使用blpop指令,在没有消息的时候,他会阻塞住,直到消息到来。
- 方式2:发布订阅模式
- 使用pub/sub主题订阅者模式,可以实现1:N的消息队列,即生产一次,消费多次,缺点就是在消费者下线的时候,生产的消息会丢失,此场景,建议MQ。
6. 如何使用redis实现延时队列?
- 使用sortedset,用时间戳作为score,消息内容作为key,使用zadd命令生产消息,使用zrangebyscore来消费最早的一条消息。
- 之所以可以用redis实现延时队列,
- 最主要的原因就是redis支持高性能的socre排序。
- 同时redis的持久化支持bgsave特性,保证了消息的消费和存储问题。
- bgsave的原理是fork和cow。
- fork是指redis通过创建子进程来进行bgsave操作。
- cow是指copy on write ,子进程创建后,父进程通过共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
7.redis持久化是怎么做的?各自有什么优缺点?突然掉电怎么办?
-
RDB做镜像全量持久化,AOF做增量持久化
-
因为RDB耗时长,不够实时,在停机的时候回导致大量丢失数据,所以需要AOF配合使用,在redis重启实例时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。
-
可以将RDB理解为一整个表的全量数据,AOF理解为每次操作的日志,服务器重启的时候先将表的数据全部搞进去,但可能不完整,再回放一下日志,数据就完整了。
-
RDB
- 优点
- 它会生成多个数据文件,每个数据文件都代表了某一时刻redis里面的数据,这种方式,适合做冷备份,比如你想要多少分钟前的redis数据。
- RDB对redis性能的影响也非常小,因为在同步数据时它只是fork了一个子进程去做持久化,而且在恢复数据的时候速度比AOF更快。
- 缺点
- RDB都是快照文件,都是默认五分钟或者更久才生成一次,这意味着两次同步时间之间的这五分钟的数据都会全部丢掉。而AOF最多丢失一秒数据
- 在生成RDB快照文件时,如果文件很大,客户端可能会暂停几毫秒或者几秒,这不能满足高性能要求场景,比如秒杀活动。
- 优点
-
AOF
- 优点
- AOF通过一个后台线程进行sync操作,最多丢失一秒的数据
- AOF的日志通过append-only的方式去写,追加的方式写数据,会少很多磁盘寻址的开销,写入性能很不错,文件也不容易破损
- AOF的日志是以一种非常可读的方式进行记录的,这种特性适合做灾难性数据误删除的紧急恢复操作。
- 缺点
- AOF数据文件体积比RDB大
- AOF开启后,redis支持写的QPS会比RDB支持写的要低,因为AOF每次都要去异步刷新一下日志fsync
- 优点
-
可以采用RDB做冷备份,AOF做热备份
-
掉电会有可能导致数据丢失,这个取决于AOF日志的sync属性,不要求性能时,在每条写指令时都sync磁盘,就不会丢失数据,但高性能要求下要求每次写指令都sync是不现实的,一般使用定时sync,比如1s一次,这个时候就最多丢失1s的数据。
8. RDB持久化的原理是什么?
fork和cow。
- fork是指redis通过创建子进程来进行rdb操作。
- cow是指copy on write ,子进程创建后,父进程通过共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
9.pipeline是什么?有什么好处?
- pipeline又叫做管道,是个队列。
- pipeline可以一次性发送多条命令,服务端依次处理完后,通过一条响应一次性将结果返回
- pipeline通过减少客户端与redis的通信次数来实现降低往返延时时间,总结其好处就是可以将多次IO往返时间缩减为一次。
- 同时pipeline具有事务隔离特性。
10. redis同步机制了解吗?
- redis可以使用主从同步,从从同步
- 第一次同步时,主节点做一次bgsave,并将后续操作记录到内存buf,待完成后将RDB文件全量同步到复制节点,复制节点接收完成后将RDB镜像加载到内存,加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程,后续的增量数据通过AOF日志同步即可。
11.redis缓存雪崩,击穿,穿透的各自含义?后果?如何避免?
-
缓存雪崩
- 含义:大面积的缓存失效,大量的请求直接打到了db。
- 后果:大数量级的请求直接打到db,后果几乎是灾难性的,比如说打挂的是一个用户服务的数据库,那么依赖这个库的所有接口都会报错,如果没有做熔断等策略,基本上就是瞬间挂一片的节奏。
- 避免方法:
- 往redis中批量存数据时,把每个key的失效时间都加一个随机值,保证数据不会在同一时间大量失效。
- 如果redis是集群部署,将热点数据均匀分布在不同redis库中也可。
- 或者直接设置热点数据永远不过期。
-
缓存穿透
- 含义:指缓存和db中都没有此数据,但用户不断发起请求。
- 后果:这种请求类似攻击,会造成数据库压力过大,严重可能会击垮数据库。
- 避免方法:
- 首先要追究产生缓存穿透的原因,比如请求用户ID为负数的请求,没有为负数的ID,服务端没有做任何限制,导致直接打到了db,我们要做的就是防备不合法请求打到db,比如在接口层增加校验,比如用户鉴权,参数做校验,不合法参数直接return等。
- 我们开发时,都要有一颗不信任的心,不信任接口调用者对传的任何参数。
- 对单秒内发起很多次请求的恶意用户,可以让运维对单个IP访问次数超过阈值的IP进行拉黑。
- 布隆过滤器,判断一个元素是否在合集中
-
缓存击穿
- 含义:一个key非常热点,在不停的扛着大并发,在这个key失效瞬间,持续的大并发就击破缓存直接打到DB,就像是在完好的桶上开了一个洞。
- 后果:后果类似缓存雪崩
- 避免:
- 热点数据设置成永不过期或长一点
- 加互斥锁,并发的多个请求中,只有一个请求线程可以拿到锁去数据库执行查询操作,执行完后,将查询的值设置到redis,
12. redis为什么这么快呢?
redis是采用基于内存的单进程单线程模型的KV数据库,C语言编写,官方提供的数据是可以达到10w+的QPS
- 完全基于内存,绝大部分请求都是内存操作,速度非常快
- 数据结构简单,对数据操作也简单,因为redis中的数据结构都是经过精心设计的
- 采用单线程,避免不必要的上下文切换和竞争,也不存在多进程或多线程的切换而消耗的CPU,不用去考虑各种锁的问题。
- 使用多路IO复用模型
- redis客户端使用RESP协议与redis服务器进行通信。
13. redis是单线程的,现在机器都是多核的,会不会浪费?
不会,但是我们可以在单机开多个redis实例
14. redis单机会有瓶颈,这个问题如何解决?
采用集群部署的方式,也就是redis cluster
- redis cluster是主从同步读写分离,rediscluster 支撑N个 reids master node,每个master node都可以挂载多个slave node.
- 读写分离的架构,对于每个master,写就写到master,读就从master对应的slave去读
- 每个master都有自己的slave节点,如果master挂掉,会自动将master的某个slave切换成master
- 整个redis可以进行横向扩容,如果需要支持更大数据量的缓存,就横向扩容多个master节点。
15. redis和memcached的区别?
分别从数据操作,内存管理机制,性能,集群管理四个方向进行分析
- 数据操作
- memcached仅支持简单kv存储,不支持其他数据类型
- redis支持更多的数据结构,有更加丰富的数据操作
- 内存管理机制
- memcached所有数据都是一直存储在内存中,不支持持久化,采用默认的slab allocation机制管理内存,其主要思想就是按照预先规定的大小,将分配的内存切割成特定长度的块以存储相应长度的key-value数据,以完全解决内存碎片问题
- redis支持RDB和AOF持久化
- 当物理内存用完时,reids可以将一些很久没有用的value写到磁盘,这种特性可以保证reids可以保存超过机器物理内存大小的数据。
- 性能
- redis只使用单核,memcached可以使用多核
- 所以平均每个核上redis在存储小数据时比memcached性能高,而在100k以上的大数据时,memcached性能较高
- 集群管理
- memcached本身不支持分布式,因此只能在客户端通过一致性hash这样的分布式算法来实现memcached的分布式存储
- redis偏向于在服务器端构建分布式存储