如何正确使用redis

2021/5/17 2:56:05

本文主要是介绍如何正确使用redis,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

如何正确使用redis

1. 概述

简单来说,Redis就是一个数据结构存储器,可以用作数据库、缓存和消息中间件,它和传统数据库主要有两点不同:

  1. 它是Key-Value型数据库,不是关系型数据库,所有数据以Key-Value的形式存在服务器的内存中,其中Value可以是多种数据结构:字符串(String), 哈希(hashes), 列表(list), 集合(sets) 和有序集合(sorted sets)等类型;
  2. 它所有运行时数据都存在内存中,总所周知,内存的存取效率比磁盘要高不止一个数量级,Redis的性能必定非常优秀,根据官方数据,Redis可以轻松支持超过10万次QPS的读写频率;

有了以上两个主要特性支撑,虽然它起步较晚,但发展迅速,目前已经成为主流架构中缓存服务的首选。除了以上两个特性,Redis还提供了非常丰富的能力,包括:

  1. 原子性,Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行,我们能非常方便地实现事务;
  2. 支持发布、订阅,可以用来实现消息系统;
  3. 支持过期逻辑,做缓存时非常实用;
  4. 支持内存中的数据持久化,不用担心服务器宕机带来的灾难性后果;
  5. 提供了简单的事务功能,能在一定程度上保证事务特性
  6. 支持Lua脚本,可以利用Lua创造出新的Redis命令;
  7. 提供了流水线(Pipeline)功能,这样客户端能将一批命令一次性传到Redis,减少了网络的开销,在请求数据较小的情况下,可以大幅提升吞吐量;
  8. Redis使用单线程模型,预防了多线程可能产生的竞争问题,简单且稳定。
  9. 原生支持主从复制,为高可用实现提供有力支持;
  10. 受到社区和各大公司的广泛认可,支持Redis的客户端语言非常多,几乎涵盖了主流的编程语言,例如Java、PHP、Python、C、C++、Nodejs等。

适合Redis的应用场景非常多,本节列几个简单的场景供大家参考:

  1. 缓存频繁读取但修改频率小的数据 ,比如首页推荐的产品列表等,不怕丢数据,丢了可以从数据库中重新加载;
  2. 用户Session ,不怕丢数据,丢了用户重新登录即可;
  3. 缓存批量任务的中间结果。不怕丢数据,丢了重新计算中间数据就可以了;
  4. 分布式锁。

2. 性能

由于各种原因,Redis的性能非常好,也由于Redis的性能非常好,才会有这么多人关注Redis,但是到底有多好,没有实验数据做支撑,别人问咱们的时候,咱们也不好张嘴就来,在本节中我们来做个简单的性能测试,测试目的主要有两点,第一就是看看Redis的性能到底有多好,在数据上有个感性认识,第二,我们来分析下影响Redis性能的因素到底有哪些。

首先我们总结下Redis性能好的原因:

  1. 业务数据的存取基于内存实现,内存的IO速度很快;
  2. Redis使用单线程模型,预防了多线程可能产生的CPU资源争夺造成的性能损耗;
  3. 支持Pipeline,将一批命令一次性传到Redis,减少了网络的开销。

2.1 性能测试环境

在做性能测试之前,我们把本次测试的环境说清楚。

  • 机器性能: 使用一台1核CPU,内存为1G的阿里云服务器去压测一台2核CPU,内存4G的Redis服务器,Redis服务器的CPU型号为2.5 GHz主频的Intel ® Xeon ® E5-2682 v4(Broadwell) 确保压测客户端的机器性能不会遇到瓶颈

  • 网络环境: 两台服务器的网卡都使用1000M,在同一个局域网内,内网带宽1000M 两台机器的内网IP分别为:172.17.167.56(Redis服务器)、172.17.167.55(压测机)

  • 系统环境 两台服务器都使用CentOS 7,将一下两个参数设置成: vm.overcommit_memory = 1 net.core.somaxconn = 2048

  • Redis相关配置 有三个配置需要注意一下: #后代运行 daemonize yes #不开启applend形式的数据持久化能力 appendfsync no #不开启快照能力

#save

  • 测试工具 使用Redis自带的测试工具redis-benchmark进行测试,下面是一条在压测机上运行的测试命令:
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 1000000 -t set -d 100 -P 8 -q

我们简单介绍下这条命令的各个参数的语义:

-h 目标Redis服务网络地址

-p 目标Reids服务的端口

-c 客户端并发长连接数

-n 本次测试需要发起的请求数

-t 测试请求的方法

-d 测试请求的数据大小

-P 开启Pipeline模式,并制定Pipeline通道数量

-q 只显示requests per second这一个结果

上面这条命令的语义就是,向172.17.167.56:6379这个Redis发送100万个请求,使用20个长连接发送,所有请求都是set命令,每个set命令的包体为100字节,使用8条Pipeline通道发送,并且只显示requests per second这一个结果。

./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 1000000 -t set -d 100 -P 8 -q
SET: 534759.38 requests per second

2.2 基本性能测试

本节中的性能测试主要观察一个指标,就是Redis每秒处理多少个请求,RPS(Request per second)。

2.2.1 客户端长连接数量对性能的影响

我们做四个测试,分别使用1个长连接,5个长连接,10个长连接,50个长连接发送100万个请求大小为100字节的请求,对比四个测试结果看看客户端长连接数量对Redis服务性能有什么影响:

  • 1个长连接 ./redis-benchmark -h 172.17.167.56 -p 6379 -c 1 -n 1000000 -t set -d 100 -q SET: 8768.55 requests per second
  • 5个长连接 ./redis-benchmark -h 172.17.167.56 -p 6379 -c 5 -n 1000000 -t set -d 100 -q SET: 35334.44 requests per second
  • 10个长连接 ./redis-benchmark -h 172.17.167.56 -p 6379 -c 10 -n 1000000 -t set -d 100 -q SET: 52430.14 requests per second
  • 50个长连接 ./redis-benchmark -h 172.17.167.56 -p 6379 -c 50 -n 1000000 -t set -d 100 -q SET: 52413.65 requests per second
连接数整体RPS单连接RPS
1个8768.558768.55
5个35334.447066.88
10个52430.145243.01
50个52413.651048.27

从上面三个测试用例的测试结果来看,我们可以发现:

  1. 只有一个长连接通信时,RPS是8700左右;
  2. 在长连接数量增加时,RPS的值会接近线性地增加;
  3. 长连接数量增加到一定数值时,整个Redis的RPS就稳定了,稳定在52400左右;

客户端长连接的数量会影响Redis整体吞吐量,但长连接数量增长到一个平衡值之后,长连接的数量不再影响系统的整体吞吐量,这个平衡值要看实际情况,网速、请求包大小等因素都会有影响。

2.2.2 请求包大小的影响

请求包的大小肯定会影响Redis每秒处理请求数量,这个是毋庸置疑的,但是具体是怎么影响的,我们做几个实验来观察下:

  • 请求包大小为2字节 ./redis-benchmark -h 172.17.167.56 -p 6379 -c 10 -n 1000000 -t set -d 2 -q SET: 52474.16 requests per second CPU平均损耗:42%
  • 请求包大小为1000字节 ./redis-benchmark -h 172.17.167.56 -p 6379 -c 10 -n 1000000 -t set -d 1000 -q SET: 52430.14 requests per second CPU损耗:48%
  • 请求包大小为1400字节 ./redis-benchmark -h 172.17.167.56 -p 6379 -c 10 -n 1000000 -t set -d 1400 -q SET: 45396.77 requests per second CPU平均损耗:41%
  • 请求包大小为1500字节 ./redis-benchmark -h 172.17.167.56 -p 6379 -c 10 -n 1000000 -t set -d 1500 -q SET: 25518.67 requests per second CPU平均损耗:29%
  • 请求包大小为5000字节 ./redis-benchmark -h 172.17.167.56 -p 6379 -c 10 -n 1000000 -t set -d 5000 -q SET: 12736.74 requests per second CPU平均损耗:24%
  • 请求包大小为10000字节 ./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 1000000 -t set -d 10000 -q SET: 6476.81 requests per second CPU平均损耗:18%
请求包体大小(字节)整体RPSredis server cpu
252474.1642%
100052430.1448%
140045396.7741%
150025518.6729%
500012736.7424%
100006476.8118%

从上面六个实验的结果来分析,我们可以得出以下结论:

  1. 在Redis服务器的CPU资源充足的情况下,2个字节的请求和1000个字节的请求对于Redis的整体吞吐量无任何影响;
  2. 请求大小在1400字节以内时,Redis的性能表现稳定,当请求大小等于1500字节时,Redis整体性能下降得很厉害。由于一般TCPIP网络的MTU设置为1500字节,一旦测试数据尺寸超过1500字节时会被拆分为多个数据包在网络上传输,加剧了性能下降的幅度。
  3. 请求大小大于1500之后,Redis的性能随着包体的增加成接近线性关系地下降。

2.3 Pipleline模式

Redis是基于同步的请求应答模型提供服务的,正常情况下,客户端发送一个请求,在等到Redis的应答后才会继续发送第二个请求。在这种情况下,如果同时需要执行大量的命令,每一个长连接的利用率不高,大多数时间都在等待,这种模式长连接的利用率不高,如下图。

在这里插入图片描述

Redis 提供了一种聚合请求和应答的pipeline模式,简单说就是讲多个命令聚合在一个请求中发送给Redis,Redis执行这一批命令,在执行过程中,讲执行结果缓存到内存中,等这所有一批命令都被执行完成后,讲所有的命令执行结果放在一个应答中返回给客户端。
在这里插入图片描述

Pipeline 在某些场景下非常有用,比如有多个 command对相应结果没有互相依赖,对结果响应也无需立即获得,那么 pipeline 就可以充当这种“批处理”的工具;而且在一定程度上,可以较大的提升性能,性能提升的原因主要是 TCP 连接中减少了“交互往返”的时间。本文图示的例子,三个正常的command一般数据量较小,放在一个pipeline请求,一般一个tcp报文就发送给服务器端了,而非pipeline模式需要发送三次,每次都需要等待应答回来后才能继续发送,在传输与处理效率上,pipeline机制明显要高效很多。下面我们做一个实验来验证下具体效率会高多少。

  • 命令大小为100字节,不使用pipeline传输
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 1000000 -t set -d 100 -q
SET: 52394.43 requests per second
  • 命令大小为100字节,使用pipeline传输,每个请求携带8个命令
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 2000000 -t set -d 100 -q -P 8

SET: 495662.97 requests per second
  • 命令包大小为100字节,使用pipeline传输,每个请求携带10个命令
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 2000000 -t set -d 100 -q -P 10

SET: 558659.25 requests per second
  • 命令包大小为100字节,使用pipeline传输,每个请求携带11个命令
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 2000000 -t set -d 100 -q -P 11

SET: 312940.09 requests per second
  • 命令包大小为100字节,使用pipeline传输,每个请求携带15个命令
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 2000000 -t set -d 100 -q -P 15

SET: 452386.34 requests per second
  • 请求包大小为100字节,使用pipeline传输,每个请求携带40个命令
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 5000000 -t set -d 100 -q -P 40

SET: 487519.53 requests per second
  • 请求包大小为200字节,不使用pipeline传输
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 2000000 -t set -d 200 -q

SET: 51167.91 requests per second
  • 请求包大小为200字节,使用pipeline传输,每个请求携带4个命令
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 2000000 -t set -d 200 -q -P 4

SET: 220288.56 requests per second
  • 请求包大小为200字节,使用pipeline传输,每个请求携带6个命令
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 2000000 -t set -d 200 -q -P 6

SET: 161147.36 requests per second
  • 请求包大小为200字节,使用pipeline传输,每个请求携带8个命令
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 2000000 -t set -d 200 -q -P 8

SET: 221361.38 requests per second
  • 请求包大小为3000字节,不使用pipeline传输
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 1000000 -t set -d 3000 -q

SET: 21104.17 requests per second
  • 请求包大小为3000字节,使用pipeline传输,每个请求携带8个命令
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 1000000 -t set -d 3000 -q -P 8

SET: 21713.17 requests per second
请求包体大小(字节)请求携带命令数整体RPS
100152474.16
1008495662.97
10010558659.25
10011312940.09
10015452386.34
10040487519.53
200151167.91
2004220288.56
2006161147.36
2008221361.38
3000121104.17
3000821713.17

从上面一系列实验数据我们可以得出以下结论:

  1. 命令包体越小时,pipeline机制会对Redis的整体性能帮助越大,命令包体100字节时,redis整体性能有一个数量级左右的提升;
  2. 一个pineline请求的整体大小(命令包大小乘以命令个数)如果和TCPIP网络的MTU设置比较匹配,不容易产生碎片请求时,性能最好,这个不好强求;
  3. 命令包体较大时,pipeline机制对Redis的整体性能没有任何帮助。

在命令传输内容较小,且命令之间无依赖关系时,我们使用pipeline机制可以大幅提供Redis的整体吞吐量。有些系统可能对可靠性要求很高,每次操作都需要立马知道这次操作是否成功,是否数据已经写进redis了,那这种场景就不适合。还有命令传输的内容较大时(比如3k及以上),pipeline对性能也没有优化能力,也不建议使用pipeline机制。

3. 数据持久化

Redis是一个支持持久化的内存数据库,也就是说redis支持将内存中的数据同步到磁盘来保证持久化。redis支持两种持久化方式,第一种是定期讲包含全量数据的内存快照保存到磁盘也是默认方式;第二种是记录所有数据写操作的日志,使用这些日志恢复数据,这种模式我们又称之为AOF模式。两种模式各有优劣,下面我们分别了解下两种模式的运行机制。

3.1 内存快照模式持久化

某个瞬间Redis服务器内存中的所有内容我们称之为内存快照,定期将内存快照异步保存到磁盘进行持久化,在数据出现问题,或者服务器宕机等情况出现是,将内存快照加载到内存,这个定期备份、恢复的机制对数据的安全性有重要的意义。

Redis会自动将内存快照保存成一个RDB类型的文件到Redis的根目录,调用BGSAVE命令能手动触发快照保存,保存快照的动作是后台进程完成的,保存快照期间其他客户端仍然和可以读写REDIS服务器。后台保存快照到磁盘时会占用大量内存。

如果调用SAVE命令保存内存中的数据到磁盘,将阻塞客户端请求,直到保存完毕。调用SHUTDOWN命令,Redis服务器会先调用SAVE,所有数据持久化到磁盘之后才会真正退出。在Redis的配置文件中可以配置定期保存内存快照的触发条件:

# save <时间> <变更次数>
# 两个条件同时满足,发生一次保存内存快照的动作,如果配置多条规则,规则之间是或的关系
# 如果不想落地内存中的数据,直接注释掉下面三个配置即可
# 如果配置成save "",之前落地的数据都可能被删除
# 下面这条配置的语义是距上次保存快照时间超过60秒,并且数据变更次数达到1000次,则保存一次内存快照
# 内存快照文件格式为dump.rdb
save 60 1000

stop-writes-on-bgsave-error yes 这个配置也是非常重要的一项配置,这是当备份进程出错时,主进程就停止接受新的写入操作,是为了保护持久化的数据一致性问题。如果自己的业务对数据一致性有较高的要求,需要打开这个配置。

Redis启动的时候会判断是否存在RDS文件,如果存在就从RDS文件中加载所有数据到内存。

3.2 AOF模式持久化

默认情况下Redis会异步落地内存快照数据到磁盘,这种模式对于很多场景是够用的。但这种模式有个缺点就是对于突发情况,比如突然停电,落地的文件数据会丢失几分钟数据,极端情况丢数据这事对于普通应用程序可能可以接收,但对于类似银行这种机构是苟能容忍的。因此Redis提供一种更可靠的模式来保证数据的安全,AOF是一种可选的更安全的持久化模式,能很好地解决上面说的数据丢失的问题。默认配置下,AOF模式在意外故障发生时最多丢失一秒钟的数据。

AOF文件是可识别的纯文本,它的内容就是一个个的Redis标准命令,有比较好的可读性。AOF日志也不是完全按客户端的请求来生成日志的,比如命令 INCRBYFLOAT 在记AOF日志时就被记成一条SET记录, 因为浮点数操作可能在不同的系统上会不同,所以为了避免同一份日志在不同的系统上生成不同的数据集,所以这里只将操作后的结果通过SET来记录。每一条写命令都生成一条日志,所以AOF文件会很大。

Redis在落地AOF文件的时候,有三种模式

  1. appendfsync always : 每次有客户端发送写操作,都需要落地到磁盘,性能最差,但最安全。
  2. appendfsync everysec : 顾名思义,每秒写一次,均衡模式。
  3. appendfsync no : 操作系统在需要的时候才落地数据到磁盘,性能最好,但可能有数据丢失风险。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。

Redis实用的默认模式是everysec,这是一种均衡的模式。

在AOF同步文件同步模式设置为always或者everysec的时候,会有一个后台线程去做这个事,同时产生大量磁盘IO。这些IO操作经常会阻塞后台内存快照落地线程和AOF日志重写线程,甚至导致整个Redis被阻塞,目前没有很好的解决方案。

为了缓解这个问题,Redis增加了AOF阻塞机制,生成AOF文件之前会先检查BGSAVE或者BGREWRITEAOF是否在运行,如果是,那么就先阻止AOF操作。这就意味这在BGSAVE或者BGREWRITEAOF时,Redis不会去写AOF,可能会因此丢掉30秒以内的数据。如果你因为AOF写入产生延迟问题,可以将AOF阻塞机制的相关配置no-appendfsync-on-rewrite设置为yes。该配置设置为no为最安全,最不可能丢失数据的方式。

AOF和内存快照两种持久化模式能同时启动,不会互相影响。如果AOF模式生效了,那么Redis启动的时候会首先载入AOF文件来保证数据的可靠性。

3.3 AOF重写

在AOF文件增长到足够大超过配置的百分比的时候,Redis提供AOF重写功能,AOF重写会聚合Key的所有操作,目的是让一个KEY只有一条记录留在AOF文件中,从而大大缩小AOF文件的尺寸。

AOF重写是重新生成一份AOF文件,新的AOF文件中一条记录的操作只会有一次,而不像一份老文件那样,可能记录了对同一个值的多次操作。其生成过程和RDB类似,也是fork一个进程,直接遍历数据,写入新的AOF临时文件。 在写入新文件的过程中,所有的写操作日志还是会写到原来老的 AOF文件中,同时还会记录在内存缓冲区中。当重完操作完成后,会将所有缓冲区中的日志一次性写入到临时文件中。然后调用原子性的rename命令用新的 AOF文件取代老的AOF文件。重写后,AOF文件变成一个非常小的全量文件。

命令:BGREWRITEAOF, 我们应该经常调用这个命令来来重写。

auto-aof-rewrite-percentage 100

当前的AOF文件大小超过上一次重写的AOF文件大小的百分之多少时会再次进行重写,如果之前没有重写过,则以启动时的AOF大小为依据。

auto-aof-rewrite-min-size 64mb

限制了允许重写的最小AOF文件尺寸。

3.4 从持久化文件中恢复数据

Redis重启的时候会自动从RDS文件或者AOF文件中加载数据到内存。Redis在启动的时候会优先判断AOF持久化文件是否存在,如果存在优先加载AOF持久化文件。如果AOF持久化文件不存在再去检查RDS持久化文件是否存在,存在的话,加载之。为什么优先加载AOF文件呢,因为AOF在持久化上能够做到更加安全。具体流程如下图所示:

在这里插入图片描述

3.5 持久化策略选择

不同的业务场景选择不同的持久化策略,具体业务场景分为以下几种:

  • 我们仅仅把Redis当做缓存来使用,Redis中的数据并不是特别敏感或者可以通过其它方式重写生成数据,比如排行榜数据,用户登录信息等,可以关闭持久化,如果丢失数据可以通过其它途径补回,这种情况最高效,也不会引起各种数据不一致问题;
  • Redis中的数据独此一份,并没有数据库打底,但Redis中数据并不特别重要,可以忍受丢失一段时间,比如一些统计计算的中间数据,丢失的数据可以根据原始数据重新计算出来,这个时候我们可以选择RDS或者appendfsync always模式以外的所有模式的AOF。这是一种讨巧的持久化方案,既不消耗很多性能,又能在出现意外情况时恢复大部分数据;
  • Redis中的数据独此一份,除了没有数据库打底,还非常重要,不允许丢失任何数据。这个时候我们必须使用appendfsync always模式的AOF持久化策略,并且把no-appendfsync-on-rewrite配置打开,保证AOF吃花花过程出现问题的时候,Redis拒绝服务,不会丢失任何数据。

还有一种做法就是将Redis的主从配置打开,利用一台从服务器去做持久化,其他服务器快速应答业务请求。

4. Redis数据结构详解

Redis并不是简单的key-value存储,实际上他是一个数据结构服务器,支持不同类型的值。也就是说,你不必仅仅把字符串当作键所指向的值。下列这些数据类型都可作为值类型。

二进制安全的 字符串 string

二进制安全的 字符串列表 list of string

二进制安全的 字符串集合 set of string,换言之:它是一组无重复未排序的element。可以把它看成JAVA中的HashSet。

有序集合sorted set of string,类似于集合set,但其中每个元素都和一个浮点数score(评分)关联。element根据score排序。可以把它看成JAVA的HashMap–其key等于element,value等于score,但元素总是按score的顺序排列,无需额外的排序操作。

4.1 Key

Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,比如”foo”的简单字符串到一个JPEG文件的内容都可以。空字符串也是有效key值。

关于key的几条规则:

太长的键值不是个好主意,例如1024字节的键值就不是个好主意,不仅因为消耗内存,而且在数据中查找这类键值的计算成本很高。

太短的键值通常也不是好主意,如果你要用”u:1000:pwd”来代替”user:1000:password”,这没有什么问题,但后者更易阅读,并且由此增加的空间消耗相对于key object和value object本身来说很小。当然,没人阻止您一定要用更短的键值节省一丁点儿空间。

最好坚持一种模式。例如:”object-type



这篇关于如何正确使用redis的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程