什么是Redis?
Redis是一种基于键值对的NoSQL数据库。
主要的特点是把数据放在内存中,读写速度相比于磁盘会快很多。
对于性能要求很高的场景,比如缓存热点数据,防止接口爆刷,都会用到Redis
Redis还支持持久化,将缓存中的数据异步落盘,以便服务器宕机时恢复数据。
Redis和MySQL的区别?
- Redis是非关系型数据库,MySQL是关系型数据库
- Redis的数据存储在内存,MySQL的数据主要存储在磁盘
- Redis存储的是键值对,MySQL数据以行和列的形式存储。
实际开发中,Redis和MySQL都会使用到,查询数据先用Redis,未命中再查MySQL并写回Redis
项目里哪里用到了Redis?
用户活跃榜使用了ZSet有序集合(score字段进行排序),作者白名单里用到了Set集合结构
用户登录后的Session,使用了字符串类型
通过Lua脚本封装Redis的setnex命令来实现分布式锁,保证在高并发场景下,热点数据短时间内的高频访问不会击穿MySQL,多个线程没有从Redis中获得数据时,只有一个线程成功获取锁并重建缓存。Lua脚本保证原子性,防止在设置过期时间前崩溃造成死锁,要么都成功,要么都失败,避免锁永远不被释放。设置一个监听线程,任务未完成时自动续期。
部署过Redis吗?
我有在生产环境中部署过单机版Redis,先在官网下载源码包后执行make && make install编译安装。安装后编辑redis.conf文件,配置远程访问、设置密码、限制内存、设置内存过期淘汰策略、开启AOF持久化等。
Redis的高可用方案有部署过吗?
有部署过哨兵机制,一主两从的Redis实例,再加上三个哨兵节点监控它们,哨兵节点主要设置了故障转移的判定条件和超时阈值。当主节点发生故障时,哨兵节点之间能够自动协商并选出新的主节点,这个过程大概需要10-15秒。
另一个项目部署过集群方案,该项目数据量大并且增长快,需要水平扩展能力。我们部署了6个主节点,每个主节点配备一个从节点,形成了一个3主3从的初始集群。
使用集群节点的好处是能够自动进行分片映射,可以简单地增加节点来水平扩展集群容量。故障转移也很快,通常在几秒内完成。
Redis可以用来干什么?
主要用作缓存,把高频访问的数据放到Redis中,并通过设置过期时间来保证数据一致性,减轻数据库访问压力。
秒杀接口可以通过Lua脚本实现令牌桶算法实现流量控制。(HMSET)
Redis中有哪些数据类型?
字符串、哈希、列表、集合、有序集合
详细介绍下字符串?
最常见的数据类型,存储文本、数字或二进制数据,最大容量是512MB,
适合存储单个对象,比如验证码、token、计数等。
详细介绍下列表?
列表是一个有序的元素集合,支持从头部/尾部插入/删除元素,常见于消息队列或任务列表。
LRANGE key start end范围查询
详细介绍下哈希?
key value的集合,适合存储对象,比如商品信息,用户信息。value = {name : 'zhangsan', age = 18}l
详细介绍下集合?
集合是无序且不重复的,支持交集、并集操作,查询效率能达到 O(1)
级别,主要用于去重、标签、共同好友等场景。
详细介绍下有序集合?
按照score排序的有序集合,支持范围查询,适合排行榜或优先级队列
详细介绍下BitMap?
BitMap可以把一组二进制位紧凑地放到一组连续的内存空间中,每一位代表一个对象的状态,比如是否签到,是否活跃等
详细介绍下HyperLogLog?
用于估算集合中的唯一元素数量(基数)的概率性数据结构,仅用12KB的内存实现,误差范围为0.81%。
底层把每个元素哈希为一个二进制串,然后取前14位进行分组,放到16384个桶中,计算每组最大的前导0数量,最大前导0数量越多代表这个桶内的元素大概率越多,最后用一个公式近似推算总体基数,观察稀有事件的频率来推测总数。
详细介绍下GEO?
GEO存储地理位置信息,可以用来计算两点之间的距离,某位置为中心的半斤内查找其他元素等
应用场景包括:附近的人或商家、计算外卖员与商家的距离、判断用户是否进入某个区域等
底层基于ZSet有序不重复集合实现,通过特定算法把经纬度转换为score
为什么使用Hash类型而不是字符串类型序列化存储?
Hash可以只读取或修改某一个字段,String必须全部取出来反序列化后读取
Redis为什么快?
内存
因为读写数据都是在内存中,内存的读写速度本来就比磁盘快好几个数量级
异步持久化
即使Redis的数据需要持久化(RDB或AOF),也是异步或后台进行的,不会阻塞读写操作
I/O多路复用
Redis采用了I/O多路复用技术和事件驱动模型,一个线程就能监听成千上万个连接。I/O多路复用线程会持续监听注册的连接(文件描述符),当有请求的事件就绪后,会被放入一个事件列表。Redis主线程可以主动一次性获取所有的就绪事件,这些事件被有序地分发给预设的事件处理器,来执行相应的accept、read、write操作。
Linux下获取就绪事件一般用epoll_wait(),macos一般用kqueue等。
多线程
Redis6.0之前,连接建立,请求读取、响应发送以及命令执行都是在主线程中进行的,这样可以避免多线程情况下的锁竞争和上下文切换。网络IO和命令执行是串行的,性能瓶颈主要来源于网络IO的序列化。
Redis6.0之后,为了解决网络IO的性能瓶颈,Redis引入了多线程机制,把网络IO和命令执行分开,网络IO交给线程池处理,命令执行仍然在主线程中,网络IO的线程主要是负责解析请求以及序列化发送,这样可以充分利用多核CPU的性能。
底层数据结构优化
比如String的底层数据结构动态字符串支持动态扩容、预分配冗余空间,能够减少内存碎片和内存分配的开销
能详细说一下IO多路复用吗?
IO 多路复用是一种允许单个进程同时监视多个文件描述符的技术,使得程序能够高效处理多个并发连接而无需创建大量线程。
主要的实现机制包含select poll epoll kqueue IOCP等。
说说select、poll、epoll、kqueue和IOCP的区别?
select的缺点是单个进程能够监控的文件描述符有限,只有1024个,并且每次调研都需要将文件描述符集合从用户空间拷贝到内核空间,然后遍历找出就绪的文件描述符后再拷贝回来,性能开销大
poll的优点是没有最大文件描述符数量的限制,不再用 BitsMap 来传入 FD,取而代之的是动态数组 pollfd。但是每次调用仍然需要将文件描述符集合从用户空间复制到内核空间,仍然需要遍历,性能较差
epoll引入了事件驱动/内核态的回调机制,将文件描述符一次性注册到内核的红黑树中,在内核中通过回调机制主动维护就绪事件列表,而不需要像之前一样请求一次维护一次,更不需要全部拷贝,而是仅返回就绪的文件描述符列表,避免了遍历和重复拷贝的开销。
kqueue是BSD/macos下的IO多路复用机制,类似于epoll,支持大规模并发连接,使用事件驱动模型
IOCP是windows系统下的IO多路复用机制,关注的是IO操作是否已经完成而非事件就绪通知,完全的异步I/O
举例说一下阻塞I/O和I/O多路复用的区别?
传统的阻塞I/O是指线程/进程发起I/O请求后需阻塞等待内核准备就绪并且I/O操作完成后才能执行后续的代码,一个线程/进程只能负责一个I/O连接,不具有并发能力
I/O多路复用是指一个线程通过文件描述符监听多个socket连接, 通过一个系统调用就可以知晓所有就绪的事件,只要有一个事件就绪就可以开始执行,可以处理大量并发的I/O请求
Redis早期为什么要使用单线程?
- 为了避免上下文频繁切换和锁竞争,避免并发控制的复杂性
- Redis是I/O密集型而不是CPU密集型,主要受内存和网络I/O的限制,而不是CPU的计算能力限制,多线程收益不明显
- 单线程可以保证命令执行的原子性,无需额外的同步机制
所以单线程 + I/O多路复用机制已经足够了
Redis6.0为什么要引入多线程?
Redis 6.0引入的多线程并不是指数据操作的命令执行是多线程的,而是指网络I/O操作是在一个线程池中并发执行的。这是因为,虽然IO多路复用机制可以监控成千上万个就绪事件,但是epoll_wait()获取就绪事件列表之后还需要执行实际的网络I/O操作,实际的read()和write()系统调用是非常耗时的,所以Redis 6.0之后就将IO线程从主线程中剥离了出来,可以并发执行请求的解析和数据的响应,大大解决单线程下网络I/O操作的瓶颈问题
说说Redis的常用命令
- 操作字符串GET/SET/INCR
- 操作哈希HGET/HSET/HGETALL
- 操作列表LPUSH/LPOP/LRANGE
- 操作集合SADD/SISMEMBER
- 操作有序集合ZADD/ZRANGE/ZINCRBY
详细说说set命令?
用于设置字符串的key,一般会覆盖原的值,支持过期时间和条件写入,一般用来实现分布式锁,存储token,延长Session时间等
SETNX仅不存在的时候设置,SETXX仅存在的时候覆盖
sadd命令的时间复杂度是多少?
一次性添加N个元素就是O(N),只添加一个元素就是O(1)
incr命令了解吗?
用于将指定键的值加一,是一个原子的命令。如果key不存在,先将值设置为0再加1。常用于网站访问量,文章点赞数等场景
单线程的Redis QPS能达到多少?
10w