《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)

目录

  • 1 Redis的8个重要特性
    • 1.1 速度快
    • 1.2 基于键值对的数据结构服务器
    • 1.3 丰富的功能
    • 1.4 简单稳定
    • 1.5 客户端语言多
    • 1.6 持久化
    • 1.7 主从复制
    • 1.8 高可用和分布式
  • 2 Redis不可以做什么
  • 3 单线程架构
    • 3.1 引出单线程模型
    • 3.2 为什么单线程还能这么快
  • 4 典型使用场景
    • 4.1 缓存功能
    • 4.2 计数
    • 4.3 共享Session
    • 4.4 限速
  • 5 迁移键
    • 5.1(1)move
    • 5.2(2)dump+restore
    • 5.3(3)migrate
  • 6 遍历键
    • 6.1 全量遍历键
    • 6.2 渐进式遍历
  • 7 慢查询分析
    • 7.1 慢查询的两个配置参数
    • 7.2 最佳实践
  • 8 Pipeline
    • 8.1 Pipeline概念
    • 8.2 性能测试
    • 8.3 原生批量命令与Pipeline对比
    • 8.4 最佳实践

1 Redis的8个重要特性

1.1 速度快

正常情况下,Redis执行命令的速度非常快,官方给出的数字是读写性能可以达到10万/秒,当然这也取决于机器的性能,但这里先不讨论机器性能上的差异,只分析一下是什么造就了Redis除此之快的速度,可以大致归纳为以下四点:
1.Redis的所有数据都是存放在内存中的,数据放在内存中是Redis速度快的最主要原因。
2.Redis是用C语言实现的,一般来说C语言实现的程序“距离”操作系统更近,执行速度相对会更快。
3.Redis使用了单线程架构,预防了多线程可能产生的竞争问题。
4.作者对于Redis源代码可以说是精打细磨,曾经有人评价Redis是少有的集性能和优雅于一身的开源代码。

1.2 基于键值对的数据结构服务器

几乎所有的编程语言都提供了类似字典的功能,例如Java里的map,类似于这种组织数据的方式叫作基于键值的方式,与很多键值对数据库不同的是,Redis中的值不仅可以是字符串,而且还可以是具体的数据结构,这样不仅能便于在许多应用场景的开发,同时也能够提高开发效率。Redis主要提供了5种数据结构:字符串、哈希、列表、集合、有序集合,同时在字符串的基础之上演变 出了位图(Bitmaps)和HyperLogLog两种神奇的“数据结构”,并且随着 LBS(Location Based Service,基于位置服务)的不断发展,Redis3.2版本中加入有关GEO(地理信息定位)的功能,总之在这些数据结构的帮助下,开发者可以开发出各种“有意思”的应用。

1.3 丰富的功能

除了5种数据结构,Redis还提供了许多额外的功能:
1.提供了键过期功能,可以用来实现缓存。
2.提供了发布订阅功能,可以用来实现消息系统。
3.支持Lua脚本功能,可以利用Lua创造出新的Redis命令。
4.提供了简单的事务功能,能在一定程度上保证事务特性。
5.提供了流水线(Pipeline)功能,这样客户端能将一批命令一次性传到 Redis,减少了网络的开销。

1.4 简单稳定

Redis的简单主要表现在三个方面。首先,Redis的源码很少,早期版本 的代码只有2万行左右,3.0版本以后由于添加了集群特性,代码增至5万行 左右,相对于很多NoSQL数据库来说代码量相对要少很多,也就意味着普通 的开发和运维人员完全可以“吃透”它。其次,Redis使用单线程模型,这样不仅使得Redis服务端处理模型变得简单,而且也使得客户端开发变得简单。最后,Redis不需要依赖于操作系统中的类库(例如Memcache需要依赖 libevent这样的系统类库),Redis自己实现了事件处理的相关功能。 Redis虽然很简单,但是不代表它不稳定。因为Redis自身bug而宕掉的情况几乎没有。

1.5 客户端语言多

Redis提供了简单的TCP通信协议,很多编程语言可以很方便地接入到 Redis,并且由于Redis受到社区和各大公司的广泛认可,所以支持Redis的客户端语言也非常多,几乎涵盖了主流的编程语言,例如Java、PHP、Python、C、C++、Nodejs等。

1.6 持久化

通常看,将数据放在内存中是不安全的,一旦发生断电或者机器故障,重要的数据可能就会丢失,因此Redis提供了两种持久化方式:RDB和AOF,即可以用两种策略将内存的数据保存到硬盘中(如图所示),这样就保证了数据的可持久性。

《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第1张图片
Redis内存到磁盘的持久化

1.7 主从复制

Redis提供了复制功能,实现了多个相同数据的Redis副本(如图所示),复制功能是分布式Redis的基础。
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第2张图片

1.8 高可用和分布式

Redis从2.8版本正式提供了高可用实现Redis Sentinel,它能够保证Redis节点的故障发现和故障自动转移。Redis从3.0版本正式提供了分布式实现Redis Cluster,它是Redis真正的分布式实现,提供了高可用、读写和容量的扩展性。

2 Redis不可以做什么

实际上和任何一门技术一样,每个技术都有自己的应用场景和边界,也就是说Redis并不是万金油,有很多适合它解决的问题,但是也有很多不合适它解决的问题。我们可以站在数据规模和数据冷热的角度来进行分析。站在数据规模的角度看,数据可以分为大规模数据和小规模数据,我们知道Redis的数据是存放在内存中的,虽然现在内存已经足够便宜,但是如果数据量非常大,例如每天有几亿的用户行为数据,使用Redis来存储的话,基本上是个无底洞,经济成本相当的高。站在数据冷热的角度看,数据分为热数据和冷数据,热数据通常是指需要频繁操作的数据,反之为冷数据,例如对于视频网站来说,视频基本信息基本上在各个业务线都是经常要操作的数据,而用户的观看记录不一定是经常需要访问的数据,这里暂且不讨论两者数据规模的差异,单纯站在数据冷热的角度上看,视频信息属于热数据,用户观看记录属于冷数据。如果将这些冷数据放在Redis中,基本上是对于内存的一种浪费,但是对于一些热数据可以放在Redis中加速读写,也可以减轻后端存储的负载,可以说是事半功倍。所以,Redis并不是万金油,相信随着我们对Redis的逐步学习,能够清楚Redis真正的使用场景。

3 单线程架构

3.1 引出单线程模型

Redis客户端与服务端的模型可以简化成下图所示,每次客户端调用都经历了发送命令、执行命令、返回结果三个过程。
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第3张图片
Redis客户端与服务端请求过程

其中第2步是重点要讨论的,因为Redis是单线程来处理命令的,所以一条命令从客户端达到服务端不会立刻被执行,所有命令都会进入一个队列中,然后逐个被执行。所以上面3个客户端命令的执行顺序是不确定的(如图1所示),但是可以确定不会有两条命令被同时执行(如图2所示),所以两条incr命令无论怎么执行最终结果都是2,不会产生并发问题,这就是Redis单线程的基本模型。但是像发送命令、返回结果、命令排队肯定不像描述的这么简单。
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第4张图片
1.所有命令在一个队列里排队等待被执行
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第5张图片
2.不存在多个命令被同时执行的情况

3.2 为什么单线程还能这么快

通常来讲,单线程处理能力要比多线程差,例如有10000斤货物,每辆车的运载能力是每次200斤,那么要50次才能完成,但是如果有50辆车,只要安排合理,只需要一次就可以完成任务。那么为什么Redis使用单线程模型会达到每秒万级别的处理能力呢?可以将其归结为三点:
第一,纯内存访问,Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础。
第二,非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间,如图所示:
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第6张图片
Redis使用IO多路复用和自身事件模型

第三,单线程避免了线程切换和竞态产生的消耗。既然采用单线程就能达到如此高的性能,那么也不失为一种不错的选择,因为单线程能带来几个好处:第一,单线程可以简化数据结构和算法的实现。如果对高级编程语言熟悉的读者应该了解并发数据结构实现不但困难而且开发测试比较麻烦。第二,单线程避免了线程切换和竞态产生的消耗,对于服务端开发来说,锁和线程切换通常是性能杀手。但是单线程会有一个问题:对于每个命令的执行时间是有要求的。如果某个命令执行过长,会造成其他命令的阻塞,对于Redis这种高性能的服务来说是致命的,所以Redis是面向快速执行场景的数据库。单线程机制很容易被初学者忽视,但Redis单线程机制是开发和运维人员使用和理解Redis的核心之一。

4 典型使用场景

4.1 缓存功能

下图是比较典型的缓存使用场景,其中Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
下面伪代码模拟了下图的访问过程:
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第7张图片
Redis + MySQL组成的缓存存储架构

1)该函数用于获取用户的基础信息:

UserInfo getUserInfo(long id){ 
... 
}

2)首先从Redis获取用户信息:

// 定义键 
userRedisKey = "user:info:" + id; 
// 从Redis获取值 
value = redis.get(userRedisKey); 
if (value != null) { 
// 将值进行反序列化为UserInfo并返回结果 
	userInfo = deserialize(value); 
	return userInfo; 
} 

3)如果没有从Redis获取到用户信息,需要从MySQL中进行获取,并将结果回写到Redis,添加1小时(3600秒)过期时间:

// 从MySQL获取用户信息 
userInfo = mysql.get(id); 
// 将userInfo序列化,并存入Redis 
redis.setex(userRedisKey, 3600, serialize(userInfo)); 
// 返回结果 
return userInfo 

整个功能的伪代码如下:

UserInfo getUserInfo(long id){
   userRedisKey = "user:info:" + id
   value = redis.get(userRedisKey);
   UserInfo userInfo;
   if (value != null) {
      userInfo = deserialize(value);
   } else {
      userInfo = mysql.get(id);
      if (userInfo != null){
         redis.setex(userRedisKey, 3600, serialize(userInfo));
      }
   }
   return userInfo;
}

4.2 计数

许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、 查询缓存的功能,同时数据可以异步落地到其他数据源。例如笔者所在团队的视频播放数系统就是使用Redis作为视频播放数计数的基础组件,用户每播放一次视频,相应的视频播放数就会自增1:

long incrVideoCounter(long id) { 
	key = "video:playCount:" + id; 
	return redis.incr(key); 
} 

开发提示:
实际上一个真实的计数系统要考虑的问题会很多:防作弊、按照不同维度计数,数据持久化到底层数据源等。

4.3 共享Session

如图所示,一个分布式Web服务将用户的Session信息(例如用户登录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要重新登录,这个问题是用户无法容忍的。
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第8张图片
Session分散管理

为了解决这个问题,可以使用Redis将用户的Session进行集中管理,如图所示,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取。
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第9张图片
Redis集中管理Session

4.4 限速

很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次,如图所示。
在这里插入图片描述
短信验证码限速

此功能可以使用Redis来实现,下面的伪代码给出了基本实现思路:

phoneNum = "138xxxxxxxx"; 
key = "shortMsg:limit:" + phoneNum; 
// SET key value EX 60 NX 
isExists = redis.set(key,1,"EX 60","NX"); 
if(isExists != null || redis.incr(key) <=5){ 
// 通过 
}else{
// 限速 
} 

上述就是利用Redis实现了限速功能,例如一些网站限制一个IP地址不能在一秒钟之内访问超过n次也可以采用类似的思路。 除了上面介绍的几种使用场景,字符串还有非常多的适用场景,开发人员可以结合字符串提供的相应命令充分发挥自己的想象力。

5 迁移键

迁移键功能非常重要,因为有时候我们只想把部分数据由一个Redis迁移到另一个Redis(例如从生产环境迁移到测试环境),Redis发展历程中提供了move、dump+restore、migrate三组迁移键的方法,它们的实现方式以及使用的场景不太相同,下面分别介绍。

5.1(1)move

move key db 

如下图所示,move命令用于在Redis内部进行数据迁移,Redis内部可以有多个数据库,由于多个数据库功能后面会进行介绍,这里只需要知道Redis内部可以有多个数据库,彼此在数据上是相互隔离的,move key db就是把指定的键从源数据库移动到目标数据库中,但笔者认为多数据库功能不建议在生产环境使用,所以这个命令读者知道即可。
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第10张图片
move命令在Redis内部数据库之间迁移数据

5.2(2)dump+restore

dump key 
restore key ttl value

dump+restore可以实现在不同的Redis实例之间进行数据迁移的功能,整个迁移的过程分为两步:
1)在源Redis上,dump命令会将键值序列化,格式采用的是RDB格式。
2)在目标Redis上,restore命令将上面序列化的值进行复原,其中ttl参数代表过期时间,如果ttl=0代表没有过期时间。
整个过程如图所示。
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第11张图片
dump+restore命令在Redis实例之间迁移数据。有关dump+restore有两点需要注意:第一,整个迁移过程并非原子性的,而是通过客户端分步完成的。第二,迁移过程是开启了两个客户端连接,所以dump的结果不是在源Redis和目标Redis之间进行传输,下面用一个例子演示完整过程。

1)在源Redis上执行dump:

redis-source> set hello world 
OK
redis-source> dump hello 
"\x00\x05world\x06\x00\x8f 

2)在目标Redis上执行restore:

redis-target> get hello 
(nil) 
redis-target> restore hello 0 "\x00\x05world\x06\x00\x8f 
OK
redis-target> get hello 
"world" 

上面2步对应的伪代码如下:

Redis sourceRedis = new Redis("sourceMachine", 6379); 
Redis targetRedis = new Redis("targetMachine", 6379); 
targetRedis.restore("hello", 0, sourceRedis.dump(key)); 

5.3(3)migrate

migrate host port key|"" destination-db timeout [copy] [replace] [keys key [key

migrate命令也是用于在Redis实例间进行数据迁移的,实际上migrate命令就是将dump、restore、del三个命令进行组合,从而简化了操作流程。migrate命令具有原子性,而且从Redis3.0.6版本以后已经支持迁移多个键的功能,有效地提高了迁移效率。整个过程如图所示,实现过程和dump+restore基本类似,但是有3点不太相同:第一,整个过程是原子执行的,不需要在多个Redis实例上开启客户端的,只需要在源Redis上执行migrate命令即可。第二,migrate命令的数据传输直接在源Redis和目标Redis上完成的。第三,目标Redis完成restore后会发送OK给源Redis,源Redis接收后会根据migrate对应的选项来决定是否在源Redis上删除对应的键。

《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第12张图片
migrate命令在Redis实例之间原子性的迁移数据
下面对migrate的参数进行逐个说明:
·host:目标Redis的IP地址。
·port:目标Redis的端口。
·key|“”:在Redis3.0.6版本之前,migrate只支持迁移一个键,所以此处是要迁移的键,但Redis3.0.6版本之后支持迁移多个键,如果当前需要迁移多个键,此处为空字符串""。
·destination-db:目标Redis的数据库索引,例如要迁移到0号数据库,这里就写0。
·timeout:迁移的超时时间(单位为毫秒)。
·[copy]:如果添加此选项,迁移后并不删除源键。
·[replace]:如果添加此选项,migrate不管目标Redis是否存在该键都会正常迁移进行数据覆盖。
·[keys key[key…]]:迁移多个键,例如要迁移key1、key2、key3,此处填写“keys key1 key2 key3”。
下面用示例演示migrate命令,为了方便演示源Redis使用6379端口,目标Redis使用6380端口,现要将源Redis的键hello迁移到目标Redis中,会分为如下几种情况:
情况1:源Redis有键hello,目标Redis没有:

127.0.0.1:6379> migrate 127.0.0.1 6380 hello 0 1000 
OK

情况2:源Redis和目标Redis都有键hello:

127.0.0.1:6379> get hello 
"world" 
127.0.0.1:6380> get hello 
"redis" 

如果migrate命令没有加replace选项会收到错误提示,如果加了replace会返回OK表明迁移成功:

127.0.0.1:6379> migrate 127.0.0.1 6379 hello 0 1000 
(error) ERR Target instance replied with error: BUSYKEY Target key name already
127.0.0.1:6379> migrate 127.0.0.1 6379 hello 0 1000 replace 
OK

情况3:源Redis没有键hello。如下所示,此种情况会收到nokey的提示:

127.0.0.1:6379> migrate 127.0.0.1 6380 hello 0 1000 
NOKEY

下面演示一下Redis3.0.6版本以后迁移多个键的功能。
·源Redis批量添加多个键:

127.0.0.1:6379> mset key1 value1 key2 value2 key3 value3 
OK

·源Redis执行如下命令完成多个键的迁移:

127.0.0.1:6379> migrate 127.0.0.1 6380 "" 0 5000 keys key1 key2 key3 
OK

至此有关Redis数据迁移的命令介绍完了,最后使用下表总结一下 move、dump+restore、migrate三种迁移方式的异同点,笔者建议使用migrate命令进行键值迁移。
move、dump+restore、migrate三个命令比较
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第13张图片

6 遍历键

Redis提供了两个命令遍历所有的键,分别是keys和scan,本节将对它们 介绍并简要分析。

6.1 全量遍历键

keys pattern

本章开头介绍keys命令的简单使用,实际上keys命令是支持pattern匹配的,例如向一个空的Redis插入4个字符串类型的键值对。

127.0.0.1:6379> dbsize 
(integer) 0 
127.0.0.1:6379> mset hello world redis best jedis best hill high 
OK

如果要获取所有的键,可以使用keys pattern命令:

127.0.0.1:6379> keys * 
1) "hill" 
2) "jedis" 
3) "redis" 
4) "hello"

上面为了遍历所有的键,pattern直接使用星号,这是因为pattern使用的是glob风格的通配符:
·*代表匹配任意字符。
·代表匹配一个字符。
·[]代表匹配部分字符,例如[1,3]代表匹配1,3,[1-10]代表匹配1到10的任意数字。
·\x用来做转义,例如要匹配星号、问号需要进行转义。
下面操作匹配以j,r开头,紧跟edis字符串的所有键:

127.0.0.1:6379> keys [j,r]edis 
1) "jedis" 
2) "redis" 

例如下面操作会匹配到hello和hill这两个键:

127.0.0.1:6379> keys hll* 
1) "hill" 
2) "hello" 

当需要遍历所有键时(例如检测过期或闲置时间、寻找大对象等), keys是一个很有帮助的命令,例如想删除所有以video字符串开头的键,可以执行如下操作:

redis-cli keys video* | xargs redis-cli del 

但是如果考虑到Redis的单线程架构就不那么美妙了,如果Redis包含了大量的键,执行keys命令很可能会造成Redis阻塞,所以一般建议不要在生产环境下使用keys命令。但有时候确实有遍历键的需求该怎么办,可以在以下三种情况使用:
·在一个不对外提供服务的Redis从节点上执行,这样不会阻塞到客户端的请求,但是会影响到主从复制。
·如果确认键值总数确实比较少,可以执行该命令。
·使用下面要介绍的scan命令渐进式的遍历所有键,可以有效防止阻塞。

6.2 渐进式遍历

Redis从2.8版本后,提供了一个新的命令scan,它能有效的解决keys命令存在的问题。和keys命令执行时会遍历所有键不同,scan采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题,每次scan命令的时间复杂度是O(1),但是要真正实现keys的功能,需要执行多次scan。Redis存储键值对实际使用的是hashtable的数据结构,其简化模型如图所示。
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第14张图片
hashtable示意图

那么每次执行scan,可以想象成只扫描一个字典中的一部分键,直到将字典中的所有键遍历完毕。scan的使用方法如下:

scan cursor [match pattern] [count number]

·cursor是必需参数,实际上cursor是一个游标,第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。
·match pattern是可选参数,它的作用的是做模式的匹配,这点和keys的模式匹配很像。
·count number是可选参数,它的作用是表明每次要遍历的键个数,默认值是10,此参数可以适当增大。

现有一个Redis有26个键(英文26个字母),现在要遍历所有的键,使用scan命令效果的操作如下。第一次执行scan0,返回结果分为两个部分:第一个部分6就是下次scan需要的cursor,第二个部分是10个键:

127.0.0.1:6379> scan 0 
1) "6" 
2) 1) "w" 
2) "i" 
3) "e" 
4) "x" 
5) "j" 
6) "q" 
7) "y" 
8) "u" 
9) "b" 
10) "o" 

使用新的cursor=“6”,执行scan6:

127.0.0.1:6379> scan 6 
1) "11" 
2) 1) "h" 
2) "n" 
3) "m" 
4) "t" 
5) "c" 
6) "d" 
7) "g" 
8) "p" 
9) "z" 
10) "a" 

这次得到的cursor=“11”,继续执行scan11得到结果cursor变为0,说明所有的键已经被遍历过了:

127.0.0.1:6379> scan 11 
1) "0" 
2) 1) "s" 
2) "f" 
3) "r" 
4) "v" 
5) "k" 
6) "l" 

除了scan以外,Redis提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如hgetall、smembers、zrange可能产生的阻塞问题,对应的命令分别是hscan、sscan、zscan,它们的用法和scan基本类似,下面以sscan为例子进行说明,当前集合有两种类型的元素,例如分别以old:user和new:user开头,先需要将old:user开头的元素全部删除,可以参考如下伪代码:

String key = "myset"; 
// 定义pattern 
String pattern = "old:user*"; 
// 游标每次从0开始 
String cursor = "0"; 
while (true) { 
	// 获取扫描结果 
	ScanResult scanResult = redis.sscan(key, cursor, pattern); 
	List elements = scanResult.getResult(); 
	if (elements != null && elements.size() > 0) { 
		// 批量删除 
		redis.srem(key, elements); 
	}
	// 获取新的游标 
	cursor = scanResult.getStringCursor(); 
	// 如果游标为0表示遍历结束 
	if ("0".equals(cursor)) { 
		break; 
	} 
} 

渐进式遍历可以有效的解决keys命令可能产生的阻塞问题,但是scan并非完美无瑕,如果在scan的过程中如果有键的变化(增加、删除、修改),164那么遍历效果可能会碰到如下问题:新增的键可能没有遍历到,遍历出了重复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键,这些是我们在开发时需要考虑的。

7 慢查询分析

许多存储系统(例如MySQL)提供慢查询日志帮助开发和运维人员定位系统存在的慢操作。所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阀值,就将这条命令的相关信息(例如:发生时间,耗时,命令的详细信息)记录下来,Redis也提供了类似的功能。如图所示,Redis客户端执行一条命令分为如下4个部分:
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第15张图片
一条客户端命令的生命周期
1)发送命令
2)命令排队
3)命令执行
4)返回结果
需要注意,慢查询只统计步骤3)的时间,所以没有慢查询并不代表客户端没有超时问题。

7.1 慢查询的两个配置参数

对于慢查询功能,需要明确两件事:
·预设阀值怎么设置?
·慢查询记录存放在哪?
Redis提供了slowlog-log-slower-than和slowlog-max-len配置来解决这两个问题。从字面意思就可以看出,slowlog-log-slower-than就是那个预设阀值, 它的单位是微秒(1秒=1000毫秒=1000000微秒),默认值是10000,假如执行了一条“很慢”的命令(例如keys*),如果它的执行时间超过了10000微秒,那么它将被记录在慢查询日志中。

运维提示:
如果slowlog-log-slower-than=0会记录所有的命令,slowlog-log-slower-than<0对于任何命令都不会进行记录。 从字面意思看,slowlog-max-len只是说明了慢查询日志最多存储多少条,并没有说明存放在哪里?实际上Redis使用了一个列表来存储慢查询日志,slowlog-max-len就是列表的最大长度。一个新的命令满足慢查询条件时被插入到这个列表中,当慢查询日志列表已处于其最大长度时,最早插入的一个命令将从列表中移出,例如slowlog-max-len设置为5,当有第6条慢查询插入的话,那么队头的第一条数据就出列,第6条慢查询就会入列。

在Redis中有两种修改配置的方法,一种是修改配置文件,另一种是使用config set命令动态修改。例如下面使用config set命令将slowlog-log-slower-than设置为20000微秒,slowlog-max-len设置为1000:

config set slowlog-log-slower-than 20000 
config set slowlog-max-len 1000 
config rewrite

如果要Redis将配置持久化到本地配置文件,需要执行config rewrite命令,如图所示。
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第16张图片
config rewrite命令重写配置文件

虽然慢查询日志是存放在Redis内存列表中的,但是Redis并没有暴露这个列表的键,而是通过一组命令来实现对慢查询日志的访问和管理。下面介绍这几个命令。

(1)获取慢查询日志

slowlog get [n] 

下面操作返回当前Redis的慢查询,参数n可以指定条数:
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第17张图片
可以看到每个慢查询日志有4个属性组成,分别是慢查询日志的标识id、发生时间戳、命令耗时、执行命令和参数,慢查询列表如图所示。
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第18张图片
慢查询日志数据结构

(2)获取慢查询日志列表当前的长度

slowlog len 

例如,当前Redis中有45条慢查询:

127.0.0.1:6379> slowlog len 
(integer) 45 

(3)慢查询日志重置

slowlog reset 

实际是对列表做清理操作,例如:

127.0.0.1:6379> slowlog len 
(integer) 45 
127.0.0.1:6379> slowlog reset 
OK
127.0.0.1:6379> slowlog len 
(integer) 0

7.2 最佳实践

慢查询功能可以有效地帮助我们找到Redis可能存在的瓶颈,但在实际使用过程中要注意以下几点:
·slowlog-max-len配置建议:线上建议调大慢查询列表,记录慢查询时Redis会对长命令做截断操作,并不会占用大量内存。增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可设置为1000以上。
·slowlog-log-slower-than配置建议:默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值。由于Redis采用单线程响应命令,对于高流量的场景,如果命令执行时间在1毫秒以上,那么Redis最多可支撑OPS不到1000。因此对于高OPS场景的Redis建议设置为1毫秒。
·慢查询只记录命令执行时间,并不包括命令排队和网络传输时间。因此客户端执行命令的时间会大于命令实际执行时间。因为命令执行排队机制,慢查询会导致其他命令级联阻塞,因此当客户端出现请求超时,需要检查该时间点是否有对应的慢查询,从而分析出是否为慢查询导致的命令级联阻塞。
·由于慢查询日志是一个先进先出的队列,也就是说如果慢查询比较多的情况下,可能会丢失部分慢查询命令,为了防止这种情况发生,可以定期执行slow get命令将慢查询日志持久化到其他存储中(例如MySQL),然后可以制作可视化界面进行查询,Redis私有云CacheCloud提供了这样的功能,好的工具可以让问题排查事半功倍。

8 Pipeline

8.1 Pipeline概念

Redis客户端执行一条命令分为如下四个过程:
1)发送命令
2)命令排队
3)命令执行
4)返回结果
其中1)+4)称为Round Trip Time(RTT,往返时间)。

Redis提供了批量操作命令(例如mget、mset等),有效地节约RTT。但大部分命令是不支持批量操作的,例如要执行n次hgetall命令,并没有 mhgetall命令存在,需要消耗n次RTT。Redis的客户端和服务端可能部署在不同的机器上。例如客户端在北京,Redis服务端在上海,两地直线距离约为1300公里,那么1次RTT时间=1300×2/(300000×2/3)=13毫秒(光在真空中传输速度为每秒30万公里,这里假设光纤为光速的2/3),那么客户端在1秒内大约只能执行80次左右的命令,这个和Redis的高并发高吞吐特性背道而驰。Pipeline(流水线)机制能改善上面这类问题,它能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端,下图为没有使用Pipeline执行了n条命令,整个过程需要n次RTT。
《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第19张图片
没有Pipeline执行n次命令模型

下图为使用Pipeline执行了n次命令,整个过程需要1次RTT。 Pipeline并不是什么新的技术或机制,很多技术上都使用过。而且RTT 在不同网络环境下会有不同,例如同机房和同机器会比较快,跨机房跨地区会比较慢。Redis命令真正执行的时间通常在微秒级别,所以才会有Redis性能瓶颈是网络这样的说法。 redis-cli的–pipe选项实际上就是使用Pipeline机制,例如下面操作将set hello world和incr counter两条命令组装:

echo -en '*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n*2\r\n$4\r\nincr\r\ 
n$7\r\ncounter\r\n' | redis-cli --pipe 

但大部分开发人员更倾向于使用高级语言客户端中的Pipeline,目前大部分Redis客户端都支持Pipeline。

8.2 性能测试

下表给出了在不同网络环境下非Pipeline和Pipeline执行10000次set操作的效果,可以得到如下两个结论:
·Pipeline执行速度一般比逐条执行要快。
·客户端和服务端的网络延时越大,Pipeline的效果越明显。

《Redis开发与运维》- 核心知识整理一(特性、使用场景、慢查询等)_第20张图片
使用Pipeline执行n条命令模型

注意
因测试环境不同可能得到的具体数字不尽相同,本测试Pipeline每次携带100条命令。
表:在不同网络下,10000条set非Pipeline和Pipeline的执行时间对比
在这里插入图片描述

8.3 原生批量命令与Pipeline对比

可以使用Pipeline模拟出批量操作的效果,但是在使用时要注意它与原生批量命令的区别,具体包含以下几点:
·原生批量命令是原子的,Pipeline是非原子的。
·原生批量命令是一个命令对应多个key,Pipeline支持多个命令。
·原生批量命令是Redis服务端支持实现的,而Pipeline需要服务端和客户端的共同实现。

8.4 最佳实践

Pipeline虽然好用,但是每次Pipeline组装的命令个数不能没有节制,否则一次组装Pipeline数据量过大,一方面会增加客户端的等待时间,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的Pipeline拆分成多次较小的Pipeline来完成。 Pipeline只能操作一个Redis实例,但是即使在分布式Redis场景中,也可以作为批量操作的重要优化手段。

你可能感兴趣的:(redis,redis,运维,缓存,1024程序员节)