慢查询命令指的是执行较慢的命令,Redis自身提供了许多的命令,并不是所有的命令都慢,这和命令的操作复杂度有关,因此必须知道Redis不同命令的复杂度。
如说,Value
类型为 String
时,GET/SET
操作主要就是操作 Redis 的哈希表索引。这个操作复杂度基本是固定的,即 O(1)
。但是,当 Value
类型为 Set
时,SORT
、SUNION/SMEMBERS
操作复杂度分别为 O(N+M*log(M))
和 O(N)
。其中,N
为 Set
中的元素个数,M
为 SORT
操作返回的元素个数。这个复杂度就增加了很多。Redis 官方文档中对每个命令的复杂度都有介绍,当你需要了解某个命令的复杂度时,可以直接查询。
当你发现 Redis 性能变慢时,可以通过 Redis 日志,或者是 latency monitor
工具,查询变慢的请求,根据请求对应的具体命令以及官方文档,确认下是否采用了复杂度高的慢查询命令。
如果确实存在大量的慢查询命令,建议如下两种方式:
用其他高效的命令代替:比如说,如果你需要返回一个 SET
中的所有成员时,不要使用 SMEMBERS
命令,而是要使用 SSCAN
多次迭代返回,避免一次返回大量数据,造成线程阻塞。
当你需要执行排序、交集、并集操作时,可以在客户端完成,而不要用 SORT
、SUNION、SINTER
这些命令,以免拖慢 Redis 实例。
keys
这个命令是最容易忽略的慢查询命令,因为keys命令需要遍历存储的键值对,所以操作延时很高,在生产环境使用很可能导致Redis阻塞;因此不建议在生产环境中使用keys
命令。
Redis作为内存数据库,一切的数据都是在内存中,一旦内存占用过大则会大大影响性能,因此需要对有时间限制的数据需要设置过期时间,这样Redis能够定时的删除过期的数据。
默认情况下,Redis 每 100
毫秒会删除一些过期 key
,具体的算法如下:
采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
个数的 key,并将其中过期的 key 全部删除;
如果超过 25%
的 key
过期了,则重复删除的过程,直到过期 key
的比例降至 `25%`` 以下。
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
是 Redis 的一个参数,默认是 20
,那么,一秒内基本有 200
个过期 key
会被删除。这一策略对清除过期 key、释放内存空间很有帮助。如果每秒钟删除 200 个过期 key,并不会对 Redis 造成太大影响。
但是,如果触发了上面这个算法的第二条,Redis 就会一直删除以释放内存空间。注意,删除操作是阻塞的(Redis 4.0
后可以用异步线程机制来减少阻塞影响)。所以,一旦该条件触发,Redis 的线程就会一直执行删除,这样一来,就没办法正常服务其他的键值操作了,就会进一步引起其他键值操作的延迟增加,Redis 就会变慢。
频繁使用带有相同时间参数的 EXPIREAT 命令设置过期 key 将会触发算法第二条,这就会导致在一秒内存在大量的keys过期。
因此开发中一定要禁止批量的给keys
设置过期时间。
Redis 常用的数据结构一共有五种:string
、hash
、list
、set
、zset
(sorted set)。可以发现,大多数场景下使用 string 都可以去解决问题。但是,这并不一定是最优的选择。下面,简单说明下它们各自的适用场景:
string
:单个的缓存结果,不与其他的 KV 之间有联系
hash
:一个 Object 包含有很多属性,且这些属性都需要单独存储。注意:这种情况不要使用 string,因为 string 会占据更多的内存
list
:一个 Object 包含很多数据,且这些数据允许重复、要求有顺序性
set
:一个 Object 包含很多数据,不要求数据有顺序,但是不允许重复
zset
:一个 Object 包含很多数据,且这些数据自身还包含一个权重值,可以利用这个权重值来排序
另外Redis还提供了几种的扩展类型,如下:
HyperLogLog
:适合用于基数
统计,比如PV,UV的统计,存在误差
问题,不适合精确统计。
BitMap
:适合二值状态
的统计,比如签到打卡,要么打卡了,要么未打卡。
Redis4.0之后使用了如下三种持久化策略:
AOF日志
:一种采用文件追加的方式将命令记录在日志中的策略,针对同步和异步追加还提供了三个配置项,有兴趣的可以查看官方文档。
RDB快照
:以快照的方式,将某一个时刻的内存数据,以二进制的方式写入磁盘。
AOF
和RDB
混用:Redis4.0新增的方式,为了采用两种方式各自的优点,在RDB快照的时间段内使用的AOF日志记录这段时间的操作的命令,这样一旦发生宕机,将不会丢失两段快照中间的数据。
由于写入磁盘有IO性能瓶颈,因此不是将Redis作为数据库的话(可以从后端恢复),建议禁用持久化或者调整持久化策略。
由于AOF日志的重写对磁盘的压力较大,很可能会阻塞,如果需要使用到持久化,建议使用高速的固态硬盘作为日志写入设备。
由于虚拟机增加了虚拟化软件层,与物理机相比,虚拟机本身就存在性能的开销,可以使用如下命令来分别测试下物理机和虚拟机的基线性能
:
./redis-cli --intrinsic-latency 120
测试结果可以知道,使用物理机的基线性能明显比虚拟机的基线性能更好。
物理机器的内存不足将会导致操作系统内存的Swap
。
内存 swap
是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制,涉及到磁盘的读写,所以,一旦触发 swap
,无论是被换入数据的进程,还是被换出数据的进程,其性能都会受到慢速磁盘读写的影响。
Redis 是内存数据库,内存使用量大,如果没有控制好内存的使用量,或者和其他内存需求大的应用一起运行了,就可能受到 swap
的影响,而导致性能变慢。
这一点对于 Redis 内存数据库而言,显得更为重要:正常情况下,Redis 的操作是直接通过访问内存就能完成,一旦 swap
被触发了,Redis 的请求操作需要等到磁盘数据读写完成才行。而且,和我刚才说的 AOF
日志文件读写使用 fsync
线程不同,swap
触发后影响的是 Redis 主 IO
线程,这会极大地增加 Redis 的响应时间。
因此增加机器的内存或者使用Redis集群能够有效的解决操作系统内存的Swap
,提高性能。
Pipeline
(管道技术) 是客户端提供的一种批处理技术,用于一次处理多个 Redis 命令,从而提高整个交互的性能。
在客户端的使用上我们除了要尽量使用 Pipeline
的技术外,还需要注意要尽量使用 Redis 连接池,而不是频繁创建销毁 Redis 连接,这样就可以减少网络传输次数和减少了非必要调用指令。
Redis 分布式架构有三个重要的手段:
主从同步
哨兵模式
Redis Cluster
集群
使用主从同步功能我们可以把写入放到主库上执行,把读功能转移到从服务上,因此就可以在单位时间内处理更多的请求,从而提升的 Redis 整体的运行速度。
而哨兵模式是对于主从功能的升级,但当主节点奔溃之后,无需人工干预就能自动恢复 Redis 的正常使用。
Redis Cluster
是 Redis 3.0
正式推出的,Redis 集群是通过将数据分散存储到多个节点上,来平衡各个节点的负载压力。
Redis Cluster
采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383
整数槽内,计算公式:slot = CRC16(key) & 16383
,每一个节点负责维护一部分槽以及槽所映射的键值数据。这样 Redis 就可以把读写压力从一台服务器,分散给多台服务器了,因此性能会有很大的提升。
在这三个功能中,我们只需要使用一个就行了,毫无疑问 Redis Cluster
应该是首选的实现方案,它可以把读写压力自动的分担给更多的服务器,并且拥有自动容灾的能力。
频繁的新增修改会导致内存碎片的增多,因此需要时刻的清理内存碎片。
Redis提供了INFO memory
可以查看内存的使用信息,如下:
INFO memory
# Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G
…
mem_fragmentation_ratio:1.86
这里有一个 mem_fragmentation_ratio
的指标,它表示的就是 Redis 当前的内存碎片率。那么,这个碎片率是怎么计算的呢?其实,就是上面的命令中的两个指标 used_memory_rss
和 used_memory
相除的结果。
mem_fragmentation_ratio = used_memory_rss/ used_memory
used_memory_rss
是操作系统实际分配给 Redis 的物理内存空间,里面就包含了碎片;而 used_memory
是 Redis 为了保存数据实际申请使用的空间。
那么,知道了这个指标,我们该如何使用呢?在这儿,我提供一些经验阈值:
mem_fragmentation_ratio
大于 1 但小于 1.5。这种情况是合理的。这是因为,刚才我介绍的那些因素是难以避免的。毕竟,内因的内存分配器是一定要使用的,分配策略都是通用的,不会轻易修改;而外因由 Redis 负载决定,也无法限制。所以,存在内存碎片也是正常的。
mem_fragmentation_ratio
大于 1.5 。这表明内存碎片率已经超过了 50%。一般情况下,这个时候,我们就需要采取一些措施来降低内存碎片率了。
一旦内存碎片率过高了,此时就应该采用手段清理内存碎片了,具体如何清理,参考文章:Redis清理内存碎片