一、请求应答协议和RTT

Redis是一种典型的基于C/S模型的TCP服务器。

在客户端与服务器的通讯过程中,通常都是客户端率先发起请求,服务器在接收到请求后执行相应的任务,最后再将获取的数据或处理结果以应答的方式发送给客户端。在此过程中,客户端都会以阻塞的方式等待服务器返回的结果。

见如下命令序列:

Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4

在每一对请求与应答的过程中,都不得不承受网络传输所带来的额外开销。我们通常将这种开销称为 RTT (Round Trip Time)。现在我们假设每一次请求与应答的RTT为250毫秒,而我们的服务器可以在一秒内处理100k的数据,可结果则是我们的服务器每秒至多处理4条请求,1000毫秒/250毫秒=4条请求。要想解决这一性能问题,我们该如何进行优化呢?

 

二、管线(pipelining)

Redis在很早的版本中就已经提供了对命令管线的支持。

在给出具体解释之前,我们先将上面的同步应答方式的例子,改造为基于命令管线的异步应答方式,这样可以让大家有一个更好的感性认识。

Client: INCR X
Client: INCR X
Client: INCR X
Client: INCR X
Server: 1
Server: 2
Server: 3
Server: 4

从以上示例可以看出,客户端在发送命令之后,不用立刻等待来自服务器的应答,而是可以继续发送后面的命令

在命令发送完毕后,再一次性的读取之前所有命令的应答,这样便节省了同步方式中RTT的开销。

需要说明的是,如果Redis服务器发现客户端的请求是基于管线的,那么服务器端在接受到请求并处理之后,会将每条命令的应答数据存入队列,之后再发送到客户端

 

三、Benchmark

以下是来自Redis官网的测试用例和测试结果。

需要说明的是,该测试是基于loopback(127.0.0.1)的,因此RTT所占用的时间相对较少,如果是基于实际网络接口,那么管线机制所带来的性能提升就更为显著了。

require 'rubygems'
    require 'redis'
    
    def bench(descr)
        start = Time.now
        yield
        puts "#{descr} #{Time.now-start} seconds"
    end
    
    def without_pipelining
        r = Redis.new
        10000.times {
            r.ping
        }
    end
    
    def with_pipelining
        r = Redis.new
        r.pipelined {
            10000.times {
                r.ping
            }
        }
    end
    
    bench("without pipelining") {
        without_pipelining
    }
    bench("with pipelining") {
        with_pipelining
    }
    //without pipelining 1.185238 seconds
    //with pipelining 0.250783 seconds

 

 

参考推荐

Redis 常用命令

Redis系列(2)—— 概述

Redis系列(3)—— 数据结构

Redis系列(4)—— 高级功能

Redis系列(5)—— 排序与订阅

Redis实例(8)—— 事务

Redis实例(10)—— 持久化

Redis实例(11)—— 虚拟内存

Redis实例(12)—— 管线

Redis实例(13)—— 服务器管理

Redis实例(14)—— 内存优化

Redis,MemCached,MongoDB概述

Redis 核心知识图谱

单机开启多个 Redis 实例

Redis 主从集群配置高可用技术方案

BloomFilter + Redis 大数据去重策略的实现

Python 操作 redis 接口函数

Python 操作 Redis 数据库的函数

Redis 双主备份实现

PHP-redis 中文文档

php-redis 各种函数中文手册