什么是Redis?

Redis是一种基于内存的NoSQL数据库,对数据的读写都在内存中完成,因此读写的速度非常快,Redis常用于缓存、消息队列、分布式锁等场景。

Redis支持多种数据类型,如String(字符串)、Hash(哈希)、List(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、HyperLogLog(基数统计)、GEO(地理信息)、Stream(流)等。并且Redis对数据的操作都是原子性的,采用单线程执行命令,不存在并发的问题。除此之外,Redis 还支持事务 、持久化、Lua 脚本、多种集群方案(主从复制模式、哨兵模式、切片机群模式)、发布/订阅模式,内存淘汰机制、过期删除机制

Redis 和 Memcached 有什么区别?

Redis 和 Memcached都是基于内存的数据库,一般都当作缓存使用、都有过期策略且两者的性能都非常高。那为什么我们一般都选择Redis做数据缓存呢?

  • Redis 支持的数据类型更丰富(String、Hash、List、Set、ZSet),而 Memcached 只支持最简单的 key-value 数据类型;
  • Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memcached 不支持数据的持久化功能,数据全部存在内存之中,Memcached 重启或者挂掉后,数据就丢失了;
  • Redis 原生支持集群模式,Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;
  • Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持;

为什么用 Redis 作为 MySQL 的缓存?

主要是因为 Redis 具备「高性能」和「高并发」两种特性,高性能不做赘述,Redis的高并发主要体现在单台设备的 Redis 的 QPS(Query Per Second,每秒钟处理完请求的次数) 是 MySQL 的 10 倍,Redis 单机的 QPS 能轻松破 10w,而 MySQL 单机的 QPS 很难破 1w。

Redis 数据类型以及使用场景分别是什么?

  • String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。
  • List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
  • Hash 类型:缓存对象、购物车等。
  • Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
  • Zset 类型:排序场景,比如排行榜、电话和姓名排序等。
  • BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;
  • HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等;
  • GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车;
  • Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。

Redis采用单线程执行命令,为什么速度还非常快?

①由于Redis是基于内存的读写,比从磁盘读写快的多(官方基准测试显示,单线程的Redis吞吐量可达10W/每秒

②Redis采用了I/O多路复用机制处理客户端的Socket请求

注:I/O 多路复用机制是指一个线程处理多个 I/O 流,就是经常听到的 select/epoll 机制。简单来说就是在Redis只运行单线程的情况下,在机制允许的内核内,同时存在多个监听 Socket 和已连接的Socket。内核会一直监听这些 Socket上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 I/O 流的效果。

③良好的代码编码

④使用单线程,单线程可以避免了多线程之间的竞争,省去多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题。

Redis是单线程吗?

从严格意义上来说,Redis并不是单线程的,Redis在启动的时候会启动后台线程

Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发送数据给客户端」这个过程是由一个线程(主线程)来完成的,简单来说就是 Redis 在网络 I/O和键值的读写操作是由一个线程来完成的,这也是我们常说 Redis 是单线程的原因。

  • 在Redis的2.6版本中,Redis会启动两个线程,分别处理文件关闭、AOF刷盘这两个任务
  • 在Redis的4.0版本以后,新增了一个lazyfree线程,用于异步释放Redis内存

lazyfree线程的作用:例如在执行 unlink key / flushdb async / flushall async 等命令时,会把这些删除操作交给后台线程来执行,好处是不会导致 Redis 主线程卡顿。因此,当我们要删除一个大 key 的时候,不要使用 del 命令删除,因为 del 是在主线程处理的,这样会导致 Redis 主线程卡顿,因此我们应该使用 unlink 命令来异步删除大key

Redis 为「关闭文件、AOF 刷盘、释放内存」这些任务创建单独的线程来处理的原因?

因为这些任务的操作都是很耗时的,如果把这些任务都放在主线程来处理,那么 Redis 主线程就很容易发生阻塞,这样就无法处理后续的请求了。

Redis 6.0之前使用单线程的原因

官方解释:CPU 并不是制约 Redis 性能表现的瓶颈所在,更多情况下是受到内存大小和网络I/O的限制,所以 Redis 核心网络模型使用单线程并没有什么问题,如果你想要使用服务的多核CPU,可以在一台服务器上启动多个节点或者采用分片集群的方式。

其次使用了单线程后,可维护性高,多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗

为什么在Redis 6.0 之后就引入了多线程?

虽然 Redis 的主要工作(网络 I/O 和执行命令)一直是单线程模型,但是在 Redis 6.0 版本之后,也采用了多个 I/O 线程来处理网络请求这是因为随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 I/O 的处理上。所以为了提高网络I/O的并行度,Redis 6.0 对网络I/O采用多线程处理,但是对命令的执行,依旧采用单线程

Redis 6.0 版本之后,Redis 在启动的时候,默认情况下会额外创建 6 个线程这里的线程数不包括主线程):

  • Redis-server : Redis的主线程,主要负责执行命令;
  • bio_close_file、bio_aof_fsync、bio_lazy_free:三个后台线程,分别异步处理关闭文件任务、AOF刷盘任务、释放内存任务;
  • io_thd_1、io_thd_2、io_thd_3:三个 I/O 线程,io-threads 默认是 4 ,所以会启动 3(4-1)个 I/O 多线程,用来分担 Redis 网络 I/O 的压力。