简单来说,Redis就是一个数据结构存储器,可以用作数据库、缓存和消息中间件,它和传统数据库主要有两点不同:
有了以上两个主要特性支撑,虽然它起步较晚,但发展迅速,目前已经成为主流架构中缓存服务的首选。除了以上两个特性,Redis还提供了非常丰富的能力,包括:
适合Redis的应用场景非常多,本节列几个简单的场景供大家参考:
由于各种原因,Redis的性能非常好,也由于Redis的性能非常好,才会有这么多人关注Redis,但是到底有多好,没有实验数据做支撑,别人问咱们的时候,咱们也不好张嘴就来,在本节中我们来做个简单的性能测试,测试目的主要有两点,第一就是看看Redis的性能到底有多好,在数据上有个感性认识,第二,我们来分析下影响Redis性能的因素到底有哪些。
首先我们总结下Redis性能好的原因:
在做性能测试之前,我们把本次测试的环境说清楚。
机器性能: 使用一台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-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
本节中的性能测试主要观察一个指标,就是Redis每秒处理多少个请求,RPS(Request per second)。
我们做四个测试,分别使用1个长连接,5个长连接,10个长连接,50个长连接发送100万个请求大小为100字节的请求,对比四个测试结果看看客户端长连接数量对Redis服务性能有什么影响:
连接数 | 整体RPS | 单连接RPS |
---|---|---|
1个 | 8768.55 | 8768.55 |
5个 | 35334.44 | 7066.88 |
10个 | 52430.14 | 5243.01 |
50个 | 52413.65 | 1048.27 |
从上面三个测试用例的测试结果来看,我们可以发现:
客户端长连接的数量会影响Redis整体吞吐量,但长连接数量增长到一个平衡值之后,长连接的数量不再影响系统的整体吞吐量,这个平衡值要看实际情况,网速、请求包大小等因素都会有影响。
请求包的大小肯定会影响Redis每秒处理请求数量,这个是毋庸置疑的,但是具体是怎么影响的,我们做几个实验来观察下:
请求包体大小(字节) | 整体RPS | redis server cpu |
---|---|---|
2 | 52474.16 | 42% |
1000 | 52430.14 | 48% |
1400 | 45396.77 | 41% |
1500 | 25518.67 | 29% |
5000 | 12736.74 | 24% |
10000 | 6476.81 | 18% |
从上面六个实验的结果来分析,我们可以得出以下结论:
Redis是基于同步的请求应答模型提供服务的,正常情况下,客户端发送一个请求,在等到Redis的应答后才会继续发送第二个请求。在这种情况下,如果同时需要执行大量的命令,每一个长连接的利用率不高,大多数时间都在等待,这种模式长连接的利用率不高,如下图。
Redis 提供了一种聚合请求和应答的pipeline模式,简单说就是讲多个命令聚合在一个请求中发送给Redis,Redis执行这一批命令,在执行过程中,讲执行结果缓存到内存中,等这所有一批命令都被执行完成后,讲所有的命令执行结果放在一个应答中返回给客户端。
Pipeline 在某些场景下非常有用,比如有多个 command对相应结果没有互相依赖,对结果响应也无需立即获得,那么 pipeline 就可以充当这种“批处理”的工具;而且在一定程度上,可以较大的提升性能,性能提升的原因主要是 TCP 连接中减少了“交互往返”的时间。本文图示的例子,三个正常的command一般数据量较小,放在一个pipeline请求,一般一个tcp报文就发送给服务器端了,而非pipeline模式需要发送三次,每次都需要等待应答回来后才能继续发送,在传输与处理效率上,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
./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
./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
./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
./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
./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
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 2000000 -t set -d 200 -q SET: 51167.91 requests per second
./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
./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
./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
./redis-benchmark -h 172.17.167.56 -p 6379 -c 20 -n 1000000 -t set -d 3000 -q SET: 21104.17 requests per second
./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 |
---|---|---|
100 | 1 | 52474.16 |
100 | 8 | 495662.97 |
100 | 10 | 558659.25 |
100 | 11 | 312940.09 |
100 | 15 | 452386.34 |
100 | 40 | 487519.53 |
200 | 1 | 51167.91 |
200 | 4 | 220288.56 |
200 | 6 | 161147.36 |
200 | 8 | 221361.38 |
3000 | 1 | 21104.17 |
3000 | 8 | 21713.17 |
从上面一系列实验数据我们可以得出以下结论:
在命令传输内容较小,且命令之间无依赖关系时,我们使用pipeline机制可以大幅提供Redis的整体吞吐量。有些系统可能对可靠性要求很高,每次操作都需要立马知道这次操作是否成功,是否数据已经写进redis了,那这种场景就不适合。还有命令传输的内容较大时(比如3k及以上),pipeline对性能也没有优化能力,也不建议使用pipeline机制。
Redis是一个支持持久化的内存数据库,也就是说redis支持将内存中的数据同步到磁盘来保证持久化。redis支持两种持久化方式,第一种是定期讲包含全量数据的内存快照保存到磁盘也是默认方式;第二种是记录所有数据写操作的日志,使用这些日志恢复数据,这种模式我们又称之为AOF模式。两种模式各有优劣,下面我们分别了解下两种模式的运行机制。
某个瞬间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文件的时候,有三种模式
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文件来保证数据的可靠性。
在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文件尺寸。
Redis重启的时候会自动从RDS文件或者AOF文件中加载数据到内存。Redis在启动的时候会优先判断AOF持久化文件是否存在,如果存在优先加载AOF持久化文件。如果AOF持久化文件不存在再去检查RDS持久化文件是否存在,存在的话,加载之。为什么优先加载AOF文件呢,因为AOF在持久化上能够做到更加安全。具体流程如下图所示:
不同的业务场景选择不同的持久化策略,具体业务场景分为以下几种:
还有一种做法就是将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的顺序排列,无需额外的排序操作。
Redis key值是二进制安全的,这意味着可以用任何二进制序列作为key值,比如”foo”的简单字符串到一个JPEG文件的内容都可以。空字符串也是有效key值。
关于key的几条规则:
太长的键值不是个好主意,例如1024字节的键值就不是个好主意,不仅因为消耗内存,而且在数据中查找这类键值的计算成本很高。
太短的键值通常也不是好主意,如果你要用”u:1000:pwd”来代替”user:1000:password”,这没有什么问题,但后者更易阅读,并且由此增加的空间消耗相对于key object和value object本身来说很小。当然,没人阻止您一定要用更短的键值节省一丁点儿空间。
最好坚持一种模式。例如:”object-type