Redis为什么这么快?

关注微信公众号:非典型理科男 回复:redis获取redis三本经典著作

序言

作为企业级的存储组件, Redis被用到很多的业务场景。

Redis经常被用作做缓存, 一致性要求不高场景,还可以当做存储使用。

另外, Redis还提供了消息订阅、事务、索引等特性。 我们还可以利用集群特性搭建分布式存储服务,实现非强一致性的分布式锁服务。

Redis用到上述场景, 都有一个共同的优势, 就是处理速度快(高性能)。

面试中,面试官经常会问到单线程的Redis为什么这么快? 为了阐明这个问题, 下面将分三部分讲解: (1) 第一部分: Redis到底有多快 (2) 第二部分: 详细讲解Redis高性能原因 (3) 第三部分: 影响Redis性能的因素

Redis到底有多快

要了解Reids的到底有多么快, 首先需要有相应的评估工具。 其次,需要Redis 在一些平台经验数据,来评估Redis性能数量级。 幸运的是Redis提供了这样的工具,并给出了常用的硬件平台一些经验数据。

下面篇幅比较长,核心观点如下:

  1. 可以使用redis-benchmark对Redis的性能进行评估,命令行提供了普通/流水线方式、不同压力评估特定命令的性能的功能。
  2. redis性能卓越,作为key-value系统最大负载数量级为10W/s, set和get耗时数量级为10ms和5ms。使用流水线的方式可以提升redis操作的性能。

不关心具体数据的小伙伴,可以直接跳到第二部分,直接了解redis性能卓越的原因。

Redis性能评估工具

Redis包含的redis-benchmark实用程序可模拟N个客户端同时发送M个总查询的运行命令(类似于Apache的ab实用程序)。可以使用redis-benchmark对redis的性能进行评估。

支持以下选项:

Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]

 -h <hostname>      服务器 hostname (默认 127.0.0.1)
-p <port> 服务器 port (默认 6379)
-s <socket> 服务器 socket (覆盖host和port)
-a <password> 服务器鉴权密钥
-c <clients> 启动的客户端数量(并行度) (默认 50)
-n <requests> 总请求量(默认 100000)
-d <size> GET和SET请求数据大小(默认 2个字节)
--dbnum <db> 选择的db编号 (默认 0)
-k <boolean> 1=keep alive 0=reconnect (默认 1)
-r <keyspacelen> 在SET/GET/INCR使用随机的key值, 在SADD使用随机的va
-P <numreq> 一个Pipeline包含的请求数. 默认值1 (不使用Pipeline).
-q 安静模式. 仅仅展示QPS值
--csv 以csv格式输出
-l 生成循环 永久执行测试
-t <tests> 制定测试命令的命令, 命令列表以逗号分隔
-I Idle模式,仅打开N个idle连接并等待

启动基准之前,您需要具有运行中的Redis实例。我在自己工作的笔记本上, 使用默认参数跑了一个例子:

D:\data\soft\redis-windows>redis-benchmark.exe
.....
====== SET ======
100000 requests completed in 0.81 seconds
50 parallel clients
3 bytes payload
keep alive: 1 99.90% <= 1 milliseconds
99.93% <= 2 milliseconds
99.95% <= 78 milliseconds
99.96% <= 79 milliseconds
100.00% <= 79 milliseconds
123609.39 requests per second ====== GET ======
100000 requests completed in 0.70 seconds
50 parallel clients
3 bytes payload
keep alive: 1 100.00% <= 0 milliseconds
142045.45 requests per second ====== INCR ======
100000 requests completed in 0.71 seconds
50 parallel clients
3 bytes payload
keep alive: 1 99.95% <= 1 milliseconds
99.95% <= 2 milliseconds
100.00% <= 2 milliseconds
140252.45 requests per second
.....

上面的例子中,截取了SET/GET/INCR的测试结果。

测试结果包括测试的环境参数(请求量、client数量、有效载荷)以及请求耗时的TP值。

redis-benchmark默认使用10万请求量, 50个clinet,有效载荷为3字节进行测试。

返回结果可以看出SET/GET/INCR命令在10万的请求量下,总的请求耗时均低于0.1s以内。 以QPS=10W为例, 计算出来的平均耗时为2ms左右(1/(10W/50))。

Reids基准测试经验数据

Redis的性能跟很多因素相关, 在第三部分会详细介绍。比如客户端网络状况、是否使用流水,链接的客户端。为了说明Redis到底有多快,我们使用Reidis官网使用redis-benchmark测试的一组数据。

警告:请注意,以下大多数基准测试已有数年历史,并且是与今天的标准相比使用旧硬件获得的。该页面应该进行更新,但是在很多情况下,使用硬硬件状态,您会期望看到的数字是此数字的两倍。此外,在许多工作负载中,Redis 4.0比2.6快

硬件环境和软件配置

测试是由50个同时执行200万个请求的客户端完成的。
所有测试在Redis 2.6.14上运行。
使用回环地址(127.0.0.1)执行了测试。
使用一百万个键的键空间执行测试。
使用和不使用流水线(16条命令流水线)执行测试。
Intel(R) Xeon(R) CPU E5520 @ 2.27GHz

Redis系统负载

  1. 不使用流水线测试结果
$ ./redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -q
SET: 122556.53 requests per second
GET: 123601.76 requests per second
LPUSH: 136752.14 requests per second
LPOP: 132424.03 requests per second
  1. 使用流水线测试结果
$ ./redis-benchmark -r 1000000 -n 2000000 -t get,set,lpush,lpop -q -P 16
SET: 195503.42 requests per second
GET: 250187.64 requests per second
LPUSH: 230547.55 requests per second
LPOP: 250815.16 requests per second

从以上可以看出Redis作为key-value系统读写负载大致在10W+QPS, 使用流水线技术能够显著提升读写性能。

耗时情况

  1. 不使用流水线测试结果
$ redis-benchmark -n 100000

====== SET ======
100007 requests completed in 0.88 seconds
50 parallel clients
3 bytes payload
keep alive: 1 58.50% <= 0 milliseconds
99.17% <= 1 milliseconds
99.58% <= 2 milliseconds
99.85% <= 3 milliseconds
99.90% <= 6 milliseconds
100.00% <= 9 milliseconds
114293.71 requests per second ====== GET ======
100000 requests completed in 1.23 seconds
50 parallel clients
3 bytes payload
keep alive: 1 43.12% <= 0 milliseconds
96.82% <= 1 milliseconds
98.62% <= 2 milliseconds
100.00% <= 3 milliseconds
81234.77 requests per second
....

所有set操作均在10ms内完成, get操作均在5ms以下。

Redis为什么那么快

Redis是一个单线程应用,所说的单线程指的是Redis使用单个线程处理客户端的请求。 虽然Redis是单线程的应用,但是即便不通过部署多个Redis实例和集群的方式提升系统吞吐, 从官网给出的数据可以看出,Redis处理速度非常快。

Redis性能非常高的原因主要有以下几点:

  • 内存存储:Redis是使用内存(in-memeroy)存储,没有磁盘IO上的开销
  • 单线程实现:Redis使用单个线程处理请求,避免了多个线程之间线程切换和锁资源争用的开销
  • 非阻塞IO:Redis使用多路复用IO技术,在poll,epool,kqueue选择最优IO实现
  • 优化的数据结构:Redis有诸多可以直接应用的优化数据结构的实现,应用层可以直接使用原生的数据结构提升性能

下面详细介绍非阻塞IO和优化的数据结构

多路复用IO

在《unix网络编程 卷I》中详细讲解了unix服务器中的5种IO模型。

一个IO操作一般分为两个步骤:

  1. 等待数据从网络到达, 数据到达后加载到内核空间缓冲区
  2. 数据从内核空间缓冲区复制到用户空间缓冲区

按照两个步骤是否阻塞线程,分为阻塞/非阻塞, 同步/异步。

五种IO模型分类:

阻塞 非阻塞
同步 阻塞IO 非阻塞IO,IO多路复用,信号驱动IO
异步IO 异步IO

阻塞IO

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

阻塞IO

非阻塞IO

Linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

IO多路复用

IO multiplexing这个词可能有点陌生,但是如果我说select/epoll,大概就都能明白了。有些地方也称这种IO方式为事件驱动IO(event driven IO)。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:

信号驱动IO

信号驱动IO

异步IO

Linux下的asynchronous IO其实用得不多,从内核2.6版本才开始引入。先看一下它的流程:

介绍完unix或者类unix系统IO模型之后, 我们看下redis怎么处理客户端连接的?

Reids的IO处理

总的来说Redis使用一种封装多种(select,epoll, kqueue等)实现的Reactor设计模式多路复用IO处理客户端的请求。

Reactor设计模式

Reactor设计模式常常用来实现事件驱动。除此之外, Redis还封装了不同平台多路复用IO的不同的库。处理过程如下:

IO库封装

因为 Redis 需要在多个平台上运行,同时为了最大化执行的效率与性能,所以会根据编译平台的不同选择不同的 I/O 多路复用函数作为子模块。

具体选择过程如下:

Redis 会优先选择时间复杂度为 O(1) 的 I/O 多路复用函数作为底层实现,包括 Solaries 10 中的 evport、Linux 中的 epoll 和 macOS/FreeBSD 中的 kqueue,上述的这些函数都使用了内核内部的结构,并且能够服务几十万的文件描述符。

但是如果当前编译环境没有上述函数,就会选择 select 作为备选方案,由于其在使用时会扫描全部监听的描述符,所以其时间复杂度较差 O(n),并且只能同时服务 1024 个文件描述符,所以一般并不会以 select 作为第一方案使用。

丰富高效的数据结构

Redis提供了丰富的数据结构,并且不同场景下提供不同实现。

Redis作为key-value系统,不同类型的key对应不同的操作或者操作对应不同的实现,相同的key也会有不同的实现。Redis对key进行操作时,会进行类型检查,调用不同的实现。

为了解决以上问题, Redis 构建了自己的类型系统, 这个系统的主要功能包括:

redisObject 对象。 基于 redisObject 对象的类型检查。 基于 redisObject 对象的显式多态函数。 对 redisObject 进行分配、共享和销毁的机制。

redisObject定义:

/*
* Redis 对象
*/
typedef struct redisObject { // 类型
unsigned type:4; // 对齐位
unsigned notused:2; // 编码方式
unsigned encoding:4; // LRU 时间(相对于 server.lruclock)
unsigned lru:22; // 引用计数
int refcount; // 指向对象的值
void *ptr; } robj;

type 、 encoding 和 ptr 是最重要的三个属性。

Redis支持4种type, 8种编码, 分别为:

有了redisObject之后, 对于特定key的操作过程就可以很容易的实现:

Redis除了提供丰富的高效的数据结构外, 还提供了如HyperLogLog, Geo索引这样高效的算法。

篇幅的原因,影响Redis性能的因素将在另外一篇文章中介绍。

参考文档:

  1. Redis和IO多路复用
  2. 5种网络IO模型(有图,很清楚)

关注微信公众号:非典型理科男 回复:redis获取redis三本经典著作

Redis为什么这么快?的更多相关文章

  1. 为什么说Redis是单线程的以及Redis为什么这么快!

    参考文章:https://blog.csdn.net/xlgen157387/article/details/79470556 redis简介 Redis是一个开源的内存中的数据结构存储系统,它可以用 ...

  2. 为什么说Redis是单线程的以及Redis为什么这么快!(转)

    文章转自https://blog.csdn.net/chenyao1994/article/details/79491337 一.前言 近乎所有与Java相关的面试都会问到缓存的问题,基础一点的会问到 ...

  3. Redis为什么这么快

    Redis为什么这么快 1.完全基于内存,绝大部分请求是纯粹的内存操作,非常快速.数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1): 2.数据结构简单, ...

  4. Redis性能解析--Redis为什么那么快?

    echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!!! Red ...

  5. 《为什么说Redis是单线程的以及Redis为什么这么快!》

    为什么说Redis是单线程的以及Redis为什么这么快!   一.前言 近乎所有与Java相关的面试都会问到缓存的问题,基础一点的会问到什么是“二八定律”.什么是“热数据和冷数据”,复杂一点的会问到缓 ...

  6. [转帖]Redis性能解析--Redis为什么那么快?

    Redis性能解析--Redis为什么那么快? https://www.cnblogs.com/xlecho/p/11832118.html echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加e ...

  7. 为什么说Redis是单线程的以及Redis为什么这么快!(转)

    一.前言 近乎所有与Java相关的面试都会问到缓存的问题,基础一点的会问到什么是“二八定律”.什么是“热数据和冷数据”,复杂一点的会问到缓存雪崩.缓存穿透.缓存预热.缓存更新.缓存降级等问题,这些看似 ...

  8. 性能测试 | 理解单线程的Redis为何那么快?

    前言 Redis是一种基于键值对(Key-Value)的NoSQL数据库,Redis的Value可以由String,hash,list,set,zset,Bitmaps,HyperLogLog等多种数 ...

  9. Redis 为什么这么快?

    1. 纯内存操作,肯定快 数据存储在内存中,读取的时候不需要进行磁盘的 IO 2. 单线程,无锁竞争损耗 单线程保证了系统没有线程的上下文切换 使用单线程,可以避免不必要的上下文切换和竞争条件,没有多 ...

随机推荐

  1. 原生 XMLHttpRequest

    一.什么是XMLHttpRequest? XHR英文全名XmlHttpRequest,中文可以解释为可扩展超文本传输请求.Xml可扩展标记语言,Http超文本传输协议,Request请求.XMLHtt ...

  2. python ATM项目

    1.需求: 指定最大透支额度 可取款 定期还款(每月指定日期还款,如15号) 可存款 定期出账单 支持多用户登陆,用户间转帐 支持多用户 管理员可添加账户.指定用户额度.冻结用户等 购物车: 商品信息 ...

  3. Python利用元类来控制实例创建

    问题: 改变实例创建方式,以此来实现单例模式,缓存或者其他类似的特性. 解决方法: 如果想定制化创建实例的过程,可以通过定制一个元类并以某种方式重新实现它的__call__()方法. 使用元类的单例模 ...

  4. 使用 KM 处理 HHKB 方向键

    对于上了 HHKB 这条贼船的人来说,刚开始使用起来最大的别扭可能就是没有方向键的问题了. 最早的我使用 Karabiner 来解决,里边有一些内置的组合可以替代方向键,我用 control + hj ...

  5. Java 多线程(上)

    启动一个多线程 多线程即在同一时间,可以做多件事情,创建多线程有3种方式,分别是继承线程类,实现Runnable接口,匿名类 线程概念 首先要理解进程(Processor)和线程(Thread)的区别 ...

  6. 表单验证之JQuery Validate控件

    概述 jQuery Validation Plugin v1.14.0,基于JQuery,官网http://jqueryvalidation.org/ 该插件捆绑了一套有用的验证方法,包括 URL 和 ...

  7. diary20180428

    17:05:59 今天早晨去了图书馆.学习了一把vscode.试图在河边看电脑,总有小虫不让我专心. 23:27:34 看纯黑直播打战神,有点感触. 动漫或游戏,角色觉醒,实力大增,小时候(甚至现在) ...

  8. ubuntu 18.04门等字不正常

    sudo vim /etc/fonts/conf.d/64-language-selector-prefer.conf 该文件配置了 Noto Sans CJK 的优先级.i进入编辑后,可以看到,JP ...

  9. Burpsuite专题学习指南

    点击蓝色字体即可 开启你的Burp学习之旅BurpSuite系列(一)----Proxy模块(代理模块) BurpSuite系列(二)----Target模块(目标模块) BurpSuite系列(三) ...

  10. idea导入 spring framework项目

    准备的环境:gradle,idea 注意:gradle版本不一致会报各种错误,那么怎么查找依赖的版本呢? 首先在git上把spring framework项目拉取下来, 步骤一:复制URL路径 步骤二 ...