Redis为何这么快

前言

本篇博客已被收录GitHub:https://zhouwenxing.github.io/

在平常开发中,为了保证数据的一致性,咱们通常都选择关系型数据库来存储数据,如 MySQLOracle 等,由于关系型数据库有着事务的特性。然而在并发量比较大的业务场景,关系型数据库却又每每会成为系统瓶颈,没法彻底知足咱们的需求,因此就须要使用到缓存,而非关系型数据库,即 NoSQL 数据库每每又会成为最佳选择。git

NoSQL 数据库最多见的解释是 non-relational,也有人解释为 Not Only SQL。非关系型数据库不保证事务,也就是不具有事务 ACID 特性,这也是非关系型数据库和关系型数据库最大的区别,而咱们即将介绍的 Redis 就属于 NoSQL 数据库的一种。github

什么是 Redis

Redis 全称是:REmote DIctionary Service,即远程字典服务。Redis 是一个开源的(遵照 BSD 协议)、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库。
Redis 具备如下特性:redis

  • 一、支持丰富的数据类型:字符串(strings),散列(hashes),列表(lists),集合(sets),有序集合(sorted sets),位图等。
  • 二、功能丰富:提供了持久化机制,过时策略,订阅/发布等功能。
  • 三、高性能,高可用且支持集群。
  • 四、提供了多种语言的 API

Redis 的安装

  • 一、下载对应版本的安装包,如:Redis 5.0.5 版本,其余版本也能够点击这里进行下载。
  • 二、下载好以后传到服务器指定目录,执行命令 tar -zxvf redis-5.0.5.tar.gz 进行解压。
  • 三、解压成功以后,进入 Redis 主目录,执行命令 make && make install PREFIX=/xxx/xxx/redis-5.0.5 进行安装,若是不指定目录,则默认是安装在 /usr/local 目录下。
  • 四、安装成功以后能够看到 Redis 主目录下多了一个 bin 目录,bin 目录内包含了一些可执行脚本。
  • 五、回到 Redis 主目录下,找到 redis.conf 配置文件,将其中的配置 daemonize no 修改成 daemonize yes,表示在后台启动服务。
  • 六、而后就能够执行命令 /xxx/xxx/redis-5.0.5/bin/redis-server /xxx/xxx/redis-5.0.5/redis.conf 启动 Redis 服务。

Redis 到底有多快

你们可能都知道 Redis 很快,但是 Redis 到底能有多快呢,好比 Redis 的吞吐量能达到多少?我想这就不是每个人都能说的上来一个具体的数字了。数据库

Redis 官方提供了一个测试脚本,能够供咱们测试 Redis 的 吞吐量。编程

  • redis-benchmark -q -n 100000 能够测试经常使用命令的吞吐量。
  • redis-benchmark -t set,lpush -n 100000 -q 测试 Redis 处理 setlpush 命令的吞吐量。
  • redis-benchmark -n 100000 -q script load "redis.call('set','foo','bar')" 测试 Redis 处理 Lua 脚本等吞吐量。

下图就是我这边执行第一条命令的自测结果,能够看到大部分命令的吞吐量均可以达到 4 万以上,也就是说每秒钟能够处理 4 万次以上请求:缓存

可是若是你觉得这就是 Redis 的真实吞吐量,那就错了。实际上,Redis 官方的测试结果是能够达到 10 万的吞吐量,下图就是官方提供的一个基准测试结果(纵坐标就是吞吐量,横坐标是链接数):安全

Redis 是单线程仍是多线程

这个问题比较经典,由于在不少人的认知里,Redis 就是单线程的。然而 Redis4.0 版本开始就有了多线程的概念,虽然处理命令请求的核心模块确实是保证了单线程执行,然而在其余许多地方已经有了多线程,好比:在后台删除对象,经过 Redis 模块实现阻塞命令,生成 dump 文件,以及 6.0 版本中网络 I/O 实现了多线程等,并且在将来 Redis 应该会有愈来愈多的模块实现多线程。服务器

所谓的单线程,只是说 Redis 的处理客户端的请求(即执行命令)时,是单线程去执行的,并非说整个 Redis 都是单线程。网络

Redis 为何选择使用单线程来执行请求

Redis 为何会选择使用单线程呢?这是由于 CPU 成为 Redis 瓶颈的状况并不常见,成为 Redis 瓶颈的一般是内存或网络带宽。例如,在一个普通的 Linux 系统上使用 pipelining 命令,Redis 能够每秒完成 100 万个请求,因此若是咱们的应用程序主要使用 O(N)O(log(N)) 复杂度的命令,它几乎不会使用太多的 CPU多线程

那么既然 CPU 不会成为瓶颈,理所固然的就不必去使用多线程来执行命令,咱们须要明确的一个问题就是多线程必定比单线程快吗?答案是不必定。由于多线程也是有代价的,最直接的两个代价就是线程的建立和销毁线程(固然能够经过线程池来必定程度的减小频繁的建立线程和销毁线程)以及线程的上下文切换。

在咱们的平常系统中,主要能够区分为两种:CPU 密集型 和 IO 密集型。

  • CPU 密集型:这种系统就说明 CPU 的利用率很高,那么使用多线程反而会增长上下文切换而带来额外的开销,因此使用多线程效率可能会不升反降。举个例子:假如你如今在干活,你一直不停的在作一件事,须要 1 分钟能够作完,可是你中途老是被人打断,须要花 1 秒钟时间步行到旁边去作另外一件事,假如这件事也须要 1 分钟,那么你由于反复切换作两件事,每切换一次就要花 1 秒钟,最后作完这 2 件事的时间确定大于 2 分钟(取决于中途切换的次数),可是若是中途不被打断,你作完一件事再去作另外一件事,那么你最多只须要切换 1 次,也就是 21 秒就能作完。
  • IO 密集型:IO 操做也能够分为磁盘 IO 和网络 IO 等操做。大部分 IO 操做的特色是比较耗时且 CPU 利用率不高,因此 Redis 6.0 版本网络 IO 会改进为多线程。至于磁盘 IO,由于 Redis 中的数据都存储在内存(也能够持久化),因此并不会过多的涉及到磁盘操做。举个例子:假如你如今给树苗浇水,你每浇完一次水以后就须要等别人给你加水以后你才能继续浇,那么假如这个等待过程须要 5 秒钟,也就是说你浇完一次水就能够休息 5 秒钟,而你切换去作另外一件事来回只须要 2 秒,那么你彻底能够先去作另外一件事,作完以后再回来,这样就能够充分利用你空闲的 5 秒钟时间,从而提高了效率。

使用多线程还会带来一个问题就是数据的安全性,因此多线程编程都会涉及到锁竞争,由此也会带来额外的开销。

什么是 I/O 多路复用

I/O 指的是网络 I/O, 多路指的是多个 TCP 链接(如 Socket),复用指的是复用一个或多个线程。I/O 多路复用的核心原理就是再也不由应用程序本身来监听链接,而是由服务器内核替应用程序监听。

Redis 中,其多路复用有多种实现,如:selectepollevportkqueue 等。

咱们用去餐厅吃饭为的例子来解释一下 I/O 多路复用机制(点餐人至关于客户端,餐厅的厨房至关于服务器,厨师就是线程)。

  • 阻塞 IO:张三去餐厅吃饭,点了一道菜,这时候他啥事也不干了,就是一直等,等到厨师炒好菜,他就把菜端走开始吃饭了。也就是在菜被炒好以前,张三被阻塞了,这就是 BIO(阻塞 IO),效率会很是低下。
  • 非阻塞 IO:张三去餐厅吃饭,点了一道菜,这时候张三他不会一直等,找了个位置坐下,刷刷抖音,打打电话,作点其余事,而后每隔一段时间就去厨房问一下本身的菜好了没有。这种就属于非阻塞 IO,这种方式虽然能够提升性能,可是若是有大量 IO 都来按期轮询,也会给服务器形成很是大的负担。
  • 事件驱动机制:张三去餐厅吃饭,点了一道菜,这时候他找了个位置坐下来等:
    • 厨房那边菜作好了就会把菜端出来了,可是并不知道这道菜是谁的,因而就挨个询问顾客,这就是多路复用中的 select 模型,不过 select 模型最多只能监听 1024socketpoll 模型解决了这个限制问题)。
    • 厨房作好了菜直接把菜放在窗口上,大喊一声,某某菜作好了,是谁的快过来拿,这时候听到通知的人就会本身去拿,这就是多路复用中的 epoll 模型。

须要注意的是在 IO 多路复用机制下,客户端能够阻塞也能够选择不阻塞(大部分场景下是阻塞 IO),这个要具体状况具体分析,可是在多路复用机制下,服务端就能够经过多线程(上面示例中能够多几个厨师同时炒菜)来提高并发效率。

Redis 中 I/O 多路复用的应用

Redis 服务器是一个事件驱动程序,服务器须要处理两类事件:文件事件和时间事件。

  • 文件事件:Redis 服务器和客户端(或其余服务器)进行通讯会产生相应的文件事件,而后服务器经过监听并处理这些事件来完成一系列的通讯操做。
  • 时间事件:Redis 内部的一些在给定时间以内须要进行的操做。

Redis 的文件事件处理器以单线程的方式运行,其内部使用了 I/O 多路复用程序来同时监听多个套接字(Socket)链接,提高了性能的同时又保持了内部单线程设计的简单性。下图就是文件事件处理器的示意图:

I/O 多路复用程序虽然会同时监听多个 Socket 链接,可是其会将监听的 Socket 都放到一个队列里面,而后经过这个队列有序的,同步的将每一个 Socket 对应的事件传送给文件事件分派器,再由文件事件分派器分派给对应的事件处理器进行处理,只有当一个 Socket 所对应的事件被处理完毕以后,I/O多路复用程序才会继续向文件事件分派器传送下一个 Socket 所对应的事件,这也能够验证上面的结论,处理客户端的命令请求是单线程的方式逐个处理,可是事件处理器内并非只有一个线程。

Redis 为何这么快

Redis 为何这么快的缘由前面已经基本提到了,如今咱们再进行总结一下:

  • 一、Redis 是一款纯内存结构,避免了磁盘 I/O 等耗时操做。
  • 二、Redis 命令处理的核心模块为单线程,减小了锁竞争,以及频繁建立线程和销毁线程的代价,减小了线程上下文切换的消耗。
  • 三、采用了 I/O 多路复用机制,大大提高了并发效率。
相关文章
相关标签/搜索