原文:https://www.cnblogs.com/xrq730/p/8890896.html
https://www.cnblogs.com/xrq730/p/8944539.html
由于原文写的较早,这里可能出现问题的已经修改过来
Redis从入门到精通:初级篇
平时陆陆续续看了不少Redis的文章了,工作中也一直在用Redis,感觉是时候对过往Redis的所学进行一次系统性的总结。《Redis从入门到精通》系列会分为初级、中级、高级三篇,从浅入深讲解Redis相关知识点。
在本文中,我们将看到以下内容:
- Redis简介
- Redis安装、启动
- Redis登录授权
- Redis配置文件redis.conf中参数详细的一个解读
- Redis性能测试
这些内容无关具体用法,作为一些初级的知识,系统地先认识一下Redis。
Redis简介
Redis是一款开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存也可持久化的日志型、Key-Value高性能数据库。Redis与其他Key-Value缓存产品相比有以下三个特点:
- 支持数据持久化,可以将内存中的数据保存在磁盘中,重启可再次加载使用
- 支持简单的Key-Value类型的数据,同时还提供List、Set、Zset、Hash等数据结构的存储
- 支持数据的备份,即Master-Slave模式的数据备份
同时,我们再看下Redis有什么优势:
- 读速度为110000次/s,写速度为81000次/s,性能极高
- 具有丰富的数据类型,这个上面已经提过了
- Redis所有操作都是原子的,意思是要么成功执行要么失败完全不执行,多个操作也支持事务
- 丰富的特性,比如Redis支持publish/subscribe、notify、key过期等
Redis安装、启动
这次写Redis系列的文章,LZ特意去阿里云上买了一个月的服务器,操作系统是Linux,因为Redis项目本身不正式支持Windows系统。不过微软开放技术小组开发和维护了Windows版本的Redis,下载地址为https://github.com/MicrosoftArchive/redis/releases,感兴趣的可以自己去试下,LZ在自己笔记本上安装启动过,没有问题,但就不细说了。
下面说一下在Linux系统上安装并启动Redis的步骤(我的Redis安装在/usr/local/目录下,每一步使用的命令标红加粗):
- 进入目录,cd /usr/local/
- 下载Redis,我是在官网下好的然后上传到虚拟机,rz
- 原文可能写的比较早,以下步骤略做修改,不然编译不成功
- 构建完毕我们会发现src目录下多出了redis-benchmark、redis-check-aof、redis-check-rdb、redis-cli、redis-sentinel、redis-server几个可执行文件,这几个可执行文件后面会说到
- 使用redis-server即可启动redis,redis-server /usr/local/redis-4.0.10/redis.conf
不过这个时候我们的启动稍微有点问题,不是后台启动的,即ctrl+c之后Redis就停了:
为了解决这个问题,我们需要修改一下redis.conf,将Redis设置为以守护进程的方式进行启动,打开redis.conf,找到daemonize,将其设置为yes即可:
这个时候先关闭一下再启动,Redis就在后台自动运行了,关闭Redis有两种方式:
- redis-cli shutdown,这是种安全关闭redis的方式,但这种写法只适用于没有配置密码的场景,比较不安全,配置密码下一部分会讲
- kill -9 pid,这种方式就是强制关闭,可能会造成数据未保存
重启后,我们可以使用ps -ef | grep redis,netstat -ant | grep 6379命令来验证Redis已经启动。
Redis登录授权
上面我们安装了Redis,但这种方式是非常不安全的,因为没有密码,这样任何连接上Redis服务器的用户都可以对Redis执行操作,所以这一部分我们来讲一下给Redis设置密码。
打开redis.conf,找到"requirepass"部分,打开原本关闭的注释,替换一下自己想要的密码即可:
重启Redis,授权登录有两种做法:
- 连接的时候直接指定密码,redis-cli -h 127.0.0.1 -p 6379 -a 123456
- 连接后授权,redis-cli -h 127.0.0.1 -p 6379,auth 123456
在配置了密码的情况下,没有进行授权,那么对Redis发送的命令,将返回"(error) NOAUTH Authentication required."。
Redis配置文件redis.conf
上面两小节,设置使用守护线程启动、设置密码,都需要修改redis.conf,说明redis.conf是Redis核心的配置文件,本小节我们来看一下redis.conf中一些常用配置:
配置 | 作用 | 默认 |
bind | 当配置了bind之后:
| 127.0.0.1 |
protected-mode | protected-mode是Redis3.2之后的新特性,用于加强Redis的安全管理,当满足以下两种情况时,protected-mode起作用:
当满足以上两种情况且protected-mode=yes的时候,访问Redis将报错,即密码未设置的情况下,无密码访问Redis只能通过安装Redis的本机进行访问 | yes |
port | Redis访问端口,由于Redis是单线程模型,因此单机开多个Redis进程的时候会修改端口,不然一般使用大家比较熟悉的6379端口就可以了 | 6379 |
tcp-backlog | 半连接队列的大小,对半连接队列不熟的可以看我以前的文章TCP:三次握手、四次握手、backlog及其他 | 511 |
timeout | 指定在一个client空闲多少秒之后就关闭它,0表示不管 | 0 |
tcp-keepalive | 设置tcp协议的keepalive,从Redis的注释来看,这个参数有两个作用:
| 300 |
daemonize | 这个前面说过了,指定Redis是否以守护进程的方式启动 | no |
supervised | 这个参数表示可以通过upstart和systemd管理Redis守护进程,这个具体和操作系统相关,资料也不是很多,就暂时不管了 | no |
pidfile | 当Redis以守护进程的方式运行的时候,Redis默认会把pid写到pidfile指定的文件中 | /var/run/redis_6379.pid |
loglevel | 指定Redis的日志级别,Redis本身的日志级别有notice、verbose、notice、warning四种,按照文档的说法,这四种日志级别的区别是:
| notice |
logfile | 配置log文件地址,默认打印在命令行终端的窗口上 | "" |
databases | 设置Redis数据库的数量,默认使用0号DB | 16 |
save | 把Redis数据保存到磁盘上,这个是在RDB的时候用的,介绍RDB的时候专门说这个 | save 900 1 save 300 10 save 60 10000 |
stop-writes-on-bgsave-error | 当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。 这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。 如果Redis重启了,那么又可以重新开始接收数据了 | yes |
rdbcompression | 是否在RBD的时候使用LZF压缩字符串,如果希望省点CPU,那就设为no,不过no的话数据集可能就比较大 | yes |
rdbchecksum | 是否校验RDB文件,在RDB文件中有一个checksum专门用于校验 | yes |
dbfilename | dump的文件位置 | dump.rdb |
dir | Redis工作目录 | ./ |
slaveof | 主从复制,使用slaveof让一个节点称为某个节点的副本,这个只需要在副本上配置 | 关闭 |
masterauth | 如果主机使用了requirepass配置进行密码保护,使用这个配置告诉副本连接的时候需要鉴权 | 关闭 |
slave-serve-stale-data | 当一个Slave与Master失去联系或者复制正在进行中,Slave可能会有两种表现:
| yes |
slave-read-only | 配置Redis的Slave实例是否接受写操作,即Slave是否为只读Redis | yes |
slave-priority | 从站优先级是可以从redis的INFO命令输出中查到的一个整数。当主站不能正常工作时,redis sentinel使用它来选择一个从站并将它提升为主站。 低优先级的从站被认为更适合于提升,因此如果有三个从站优先级分别是10, 100, 25,sentinel会选择优先级为10的从站,因为它的优先级最低。 然而优先级值为0的从站不能执行主站的角色,因此优先级为0的从站永远不会被redis sentinel提升。 | 100 |
requirepass | 设置客户端认证密码 | 关闭 |
rename-command | 命令重命名,对于一些危险命令例如:
作为服务端redis-server,常常需要禁用以上命令来使得服务器更加安全,禁用的具体做法是是:
也可以保留命令但是不能轻易使用,重命名这个命令即可:
这样,重启服务器后则需要使用新命令来执行操作,否则服务器会报错unknown command | 关闭 |
maxclients | 设置同时连接的最大客户端数量,一旦达到了限制,Redis会关闭所有的新连接并发送一个"max number of clients reached"的错误 | 关闭,默认10000 |
maxmemory | 不要使用超过指定数量的内存,一旦达到了,Redis会尝试使用驱逐策略来移除键 | 关闭 |
maxmemory-policy | 当达到了maxmemory之后Redis如何移除数据,有以下的一些策略:
注意,当写操作且Redis发现没有合适的数据可以移除的时候,将会报错 | 关闭,noeviction |
appendonly | 是否开启AOF,关于AOF后面再说 | no |
appendfilename | AOF文件名称 | appendonly.aof |
appendfsync | 操作系统实际写数据到磁盘的频率,有以下几个选项:
当不确定是使用哪种的时候,官方推荐使用everysec,它是速度与数据安全之间的一种折衷方案 | everysec |
no-appendfsync-on-rewrite | aof持久化机制有一个致命的问题,随着时间推移,aof文件会膨胀,当server重启时严重影响数据库还原时间,因此系统需要定期重写aof文件。 重写aof的机制为bgrewriteaof(另外一种被废弃了,就不说了),即在一个子进程中重写从而不阻塞主进程对其他命令的处理,但是这依然有个问题。 bgrewriteaof和主进程写aof,都会操作磁盘,而bgrewriteaof往往涉及大量磁盘操作,这样就会让主进程写aof文件阻塞。 针对上述问题,可以使用此时可以使用no-appendfsync-on-rewrite参数做一个选择:
| no |
auto-aof-rewrite-percentage | 本次aof文件超过上次aof文件该值的百分比时,才会触发rewrite | 100 |
auto-aof-rewrite-min-size | aof文件最小值,只有到达这个值才会触发rewrite,即rewrite由auto-aof-rewrite-percentage+auto-aof-rewrite-min-size共同保证 | 64mb |
aof-load-truncated | redis在以aof方式恢复数据时,对最后一条可能出问题的指令的处理方式:
| yes |
slowlog-log-slower-than | Redis慢查询的最低条件,单位微妙,即查询时间>这个值的会被记录 | 10000 |
slowlog-max-len | Redis存储的慢查询最大条数,超过该值之后会将最早的slowlog剔除 | 128 |
lua-time-limit | 一个lua脚本执行的最大时间,单位为ms | 5000 |
cluster-enabled | 正常来说Redis实例是无法称为集群的一部分的,只有以集群方式启动的节点才可以。为了让Redis以集群方式启动,就需要此参数。 | 关闭 |
cluster-config-file | 每个集群节点应该有自己的配置文件,这个文件是不应该手动修改的,它只能被Redis节点创建且更新,每个Redis集群节点需要不同的集群配置文件 | 关闭,nodes-6379.conf |
cluster-node-timeout | 集群中一个节点向其他节点发送ping命令时,必须收到回执的毫秒数 | 关闭,15000 |
cluster-slave-validity-factor | 如果该项设置为0,不管Slave节点和Master节点间失联多久都会一直尝试failover。 比如timeout为5,该值为10,那么Master与Slave之间失联50秒,Slave不会去failover它的Master | 关闭,10 |
cluster-migration-barrier | 当一个Master拥有多少个好的Slave时就要割让一个Slave出来。 例如设置为2,表示当一个Master拥有2个可用的Slave时,它的一个Slave会尝试迁移 | 关闭,1 |
cluster-require-full-coverage | 有节点宕机导致16384个Slot全部被覆盖,整个集群是否停止服务,这个值一定要改为no | 关闭,yes |
以上把redis.conf里面几乎所有的配置都写了一遍(除了ADVANCED CONFIG部分),感觉其他博客很少有看到比我这个还全的了^_^,给大家作为参考吧。
Redis性能测试
之前说过Redis在make之后有一个redis-benchmark,这个就是Redis提供用于做性能测试的,它可以用来模拟N个客户端同时发出M个请求。首先看一下redis-benchmark自带的一些参数:
参数 | 作用 | 默认值 |
-h | 服务器名称 | 127.0.0.1 |
-p | 服务器端口 | 6379 |
-s | 服务器Socket | 无 |
-c | 并行连接数 | 50 |
-n | 请求书 | 10000 |
-d | SET/GET值的字节大小 | 2 |
-k | 1表示keep alive,0表示重连 | 1 |
-r | SET/GET/INC使用随机Key而不是常量,在形式上key样子为mykey_ran:000000012456 -r的值决定了value的最大值 | 无 |
-p | 使用管道请求 | 1,即不使用管道 |
-q | 安静模式,只显示query/sec值 | 无 |
--csv | 使用csv格式输出 | 无 |
-l | 循环,无限运行测试 | 无 |
-t | 只运行使用逗号分割的命令的测试 | 无 |
-I | 空闲模式,只打开N个空闲线程并且等待 | 无 |
首先我们运行最简单的redis-benchmark -q,运行结果为:
打印了每个命令的QPS,看到基本都在读写速度基本都在五万多次/s以上(虚拟机配置很低的情况下都有这么快了,配置好点基本都在10万次以上)。
接着换一个命令进行测试,因为实际场景中我们的Key和Value一定是非常丰富的,不可能是单一的Key和单一的Value,因此接着去的测试使用-r模拟value到100000且将运行次数提高到1000000次,具体命令为redis-benchmark -q -r 100000 -n 1000000,运行结果为:
看到整个读写效率基本都在五万次/s以上,证明了读写的高效率。
简单对于Redis的性能测试就到这儿,这个测试结果看起来很美,但是实际应用却完全不是,主要体现在以下几点:
- 网络与带宽,这是现实中最主要的影响因素,上面的测试还是太过于低级,现实使用中Redis里面存一个用户信息、订单信息,几KB的大小,100000qps根本不可能大家可以算算需要多大的带宽,粗粗算一下超过1个G吧,很多线上服务的带宽根本达不到1G/s,所以Redis的吞吐量最先会被网络带宽限制住
- Redis由于是单线程模型,因此CPU性能非常重要,尤其是大缓存的快速CPU,我这里的CPU上面写过了,Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz总体还是可以的
- 客户端连接数,上面使用了默认的连接数50,实际上10W、20W甚至100W+呢?不过得益于epoll模型,整个下降的可以接受,下面有一张连接数和qps的关系,我也是网上找来的
- RDB和AOF可能会对Redis造成的阻塞并未考虑进去
- 尽可能使用大内存,避免SWAP
无论如何,总而言之,Redis整个性能是非常不错的,个人认为如果要选一款存储系统,那么Redis应当是首选。
上一部分以认识Redis为主,写了Redis系列的第一篇,现在开启第二部分的学习,在本文中,我们将看到以下内容:
- Redis数据结构String、Hash、List、Set、SortedSet及相关操作,提一下Redis在3.2.0之后有新增了一种GEO的数据类型表示地理位置,不过本文这种数据结构略过
- Redis其他一些常用命令,分为Key操作与服务器操作
- Redis事务机制
主要以实战为主,希望通过本文可以让大家掌握Redis的基本使用。
本来这篇文章还准备加上Redis线程模型分析的,但是写完发现篇幅实在太长,就把Redis线程模型放到最后一篇中了,也挺好的,本文专注于对Redis命令的讲解。
另外说一下,本文讲Redis中的数据结构,但是数据结构本身不在本文的讲解范围内,如果想知道Hash、List、Set等数据结构特点及使用场景,可以自己查阅数据结构相关资料。
String数据结构的基本操作
首先说一下数据结构String,这是Redis中最简单的一种数据结构,和MemCache数据结构是一样的,即Key-Value型的数据,根据Redis官方文档,Value最大值为512M。
下面用表格来看一下String操作的相关命令:
命令 | 描述 | 用法 |
SET | (1)将字符串值Value关联到Key (2)Key已关联则覆盖,无视类型 (3)原本Key带有生存时间TTL,那么TTL被清除 | SET key value [EX seconds] [PX milliseconds] [NX|XX] |
GET | (1)返回key关联的字符串值 (2)Key不存在返回nil (3)Key存储的不是字符串,返回错误,因为GET只用于处理字符串 | GET key |
MSET | (1)同时设置一个或多个Key-Value键值对 (2)某个给定Key已经存在,那么MSET新值会覆盖旧值 (3)如果上面的覆盖不是希望的,那么使用MSETNX命令,所有Key都不存在才会进行覆盖 (4)MSET是一个原子性操作,所有Key都会在同一时间被设置,不会存在有些更新有些没更新的情况 | MSET key value [key value ...] |
MGET | (1)返回一个或多个给定Key对应的Value (2)某个Key不存在那么这个Key返回nil | MGET key [key ...] |
SETEX | (1)将Value关联到Key (2)设置Key生存时间为seconds,单位为秒 (3)如果Key对应的Value已经存在,则覆盖旧值 (4)SET也可以设置失效时间,但是不同在于SETNX是一个原子操作,即关联值与设置生存时间同一时间完成 | SETEX key seconds value |
SETNX | (1)将Key的值设置为Value,当且仅当Key不存在 (2)若给定的Key已经存在,SEXNX不做任何动作 | SETNX key value |
首先,演示一下SET、GET、SETEX的效果:(手快中间敲错一行)
图中我们应该能看到SET、GET、SETNX几个命令的效果了,在这之外,专门提两点:
- Redis的命令不区分大小写
- Redis的Key区分大小写
接着我们演示一下SETEX命令的效果:
这里顺带介绍了TIME命令,它返回的是当前服务器Unix时间戳,但单位为秒(通常Unix时间戳取的时间为毫秒)。看到设置Redis-Expire这个Key,马上获取不失效,过一会再次获取的时候失效,关于失效,Redis的策略是这样的:
- 被动触发,即GET的时候检查一下Key是否失效
- 主动触发,后台每1秒跑10次定时任务(通过redis.conf的hz参数配置,默认为10,这个上文没有写),随机选择100个设置了过期时间的Key,对过期的Key进行失效
最后看一下MGET和MSET命令:
看到可以同时设置多个Key-Value,也可以同时获取多个Key对应的Value,再次注意,Redis的Key是严格区分大小写的。
特殊的String操作:INCR/DECR
前面介绍的是基本的Key-Value操作,下面介绍一种特殊的Key-Value操作即INCR/DECR,可以利用Redis自动帮助我们对一个Key对应的Value进行加减,用表格看一下相关命令:
命令 | 描述 | 用法 |
INCR | (1)Key中存储的数字值+1,返回增加之后的值 (2)Key不存在,那么Key的值被初始化为0再执行INCR (3)如果值包含错误类型或者字符串不能被表示为数字,那么返回错误 (4)值限制在64位有符号数字表示之内,即-9223372036854775808~9223372036854775807 | INCR key |
DECR | (1)Key中存储的数字值-1 (2)其余同INCR | DECR key |
INCRBY | (1)将key所存储的值加上增量返回增加之后的值 (2)其余同INCR | INCRBY key increment |
DECRBY | (1)将key所存储的值减去减量decrement (2)其余同INCR | DECRBY key decrement |
下面实际看一下四个命令相关使用:
INCR/DECR在实际工作中还是非常管用的,举两个例子:
- 原先单机环境中统计在线人数,变成分布式部署之后可以使用INCR/DECR
- 由于Redis本身极高的读写性能,一些秒杀的场景库存增减可以基于Redis来做而不是直接操作DB
Hash数据结构相关操作
接着讲一下Hash,Hash本质上和String是一样的,无非String是纯粹的Key-Value,Hash是外面套了一层东西,里面还是Key-Value,接着我们用表格看一下Hash数据结构的相关命令:
命令 | 描述 | 用法 |
HSET | (1)将哈希表Key中的域field的值设为value (2)key不存在,一个新的Hash表被创建 (3)field已经存在,旧的值被覆盖 | HSET key field value |
HGET | (1)返回哈希表key中给定域field的值 | HGET key field |
HDEL | (1)删除哈希表key中的一个或多个指定域 (2)不存在的域将被忽略 | HDEL key filed [field ...] |
HEXISTS | (1)查看哈希表key中,给定域field是否存在,存在返回1,不存在返回0 | HEXISTS key field |
HGETALL | (1)返回哈希表key中,所有的域和值 | HGETALL key |
HINCRBY | (1)为哈希表key中的域field加上增量increment (2)其余同INCR命令 | HINCRYBY key filed increment |
HKEYS | (1)返回哈希表key中的所有域 | HKEYS key |
HLEN | (1)返回哈希表key中域的数量 | HLEN key |
HMGET | (1)返回哈希表key中,一个或多个给定域的值 (2)如果给定的域不存在于哈希表,那么返回一个nil值 | HMGET key field [field ...] |
HMSET | (1)同时将多个field-value对设置到哈希表key中 (2)会覆盖哈希表中已存在的域 (3)key不存在,那么一个空哈希表会被创建并执行HMSET操作 | HMSET key field value [field value ...] |
HVALS | (1)返回哈希表key中所有的域和值 | HVALS key |
同样的,实际看一下这些命令的相关使用:
稍乱,但是除了HMSET、HMGET以外把所有命令都演示到了。
List数据结构相关操作
接着我们看一下Redis中的List,相关命令有:
命令 | 描述 | 用法 |
LPUSH | (1)将一个或多个值value插入到列表key的表头 (2)如果有多个value值,那么各个value值按从左到右的顺序依次插入表头 (3)key不存在,一个空列表会被创建并执行LPUSH操作 (4)key存在但不是列表类型,返回错误 | LPUSH key value [value ...] |
LPUSHX | (1)将值value插入到列表key的表头,当且晋档key存在且为一个列表 (2)key不存在时,LPUSHX命令什么都不做 | LPUSHX key value |
LPOP | (1)移除并返回列表key的头元素 | LPOP key |
LRANGE | (1)返回列表key中指定区间内的元素,区间以偏移量start和stop指定 (2)start和stop都以0位底 (3)可使用负数下标,-1表示列表最后一个元素,-2表示列表倒数第二个元素,以此类推 (4)start大于列表最大下标,返回空列表 (5)stop大于列表最大下标,stop=列表最大下标 | LRANGE key start stop |
LREM | (1)根据count的值,移除列表中与value相等的元素 (2)count>0表示从头到尾搜索,移除与value相等的元素,数量为count (3)count<0表示从从尾到头搜索,移除与value相等的元素,数量为count (4)count=0表示移除表中所有与value相等的元素 | LREM key count value |
LSET | (1)将列表key下标为index的元素值设为value (2)index参数超出范围,或对一个空列表进行LSET时,返回错误 | LSET key index value |
LINDEX | (1)返回列表key中,下标为index的元素 | LINDEX key index |
LINSERT | (1)将值value插入列表key中,位于pivot前面或者后面 (2)pivot不存在于列表key时,不执行任何操作 (3)key不存在,不执行任何操作 | LINSERT key BEFORE|AFTER pivot value |
LLEN | (1)返回列表key的长度 (2)key不存在,返回0 | LLEN key |
LTRIM | (1)对一个列表进行修剪,让列表只返回指定区间内的元素,不存在指定区间内的都将被移除 | LTRIM key start stop |
RPOP | (1)移除并返回列表key的尾元素 | RPOP key |
RPOPLPUSH | 在一个原子时间内,执行两个动作: (1)将列表source中最后一个元素弹出并返回给客户端 (2)将source弹出的元素插入到列表desination,作为destination列表的头元素 | RPOPLPUSH source destination |
RPUSH | (1)将一个或多个值value插入到列表key的表尾 | RPUSH key value [value ...] |
RPUSHX | (1)将value插入到列表key的表尾,当且仅当key存在并且是一个列表 (2)key不存在,RPUSHX什么都不做 | RPUSHX key value |
接着看一下这些命令的实际使用效果:
工具所限,LSET、LINSERT、RPOPLPUSH几个命令没法演示,上面演示了其他的基本命令,应该足以理解Redis的List了,操作List千万注意区分LPUSH、RPUSH两个命令,把数据添加到表头和把数据添加到表尾是完全不一样的两种结果。
另外List还有BLPOP、BRPOP、BRPOPLPUSH三个命令没有说,它们是几个POP的阻塞版本,即没有数据可以弹出的时候将阻塞客户端直到超时或者发现有可以弹出的元素为止。
SET数据结构相关操作
接着我们看一下SET数据结构的相关操作:
命令 | 描述 | 用法 |
SADD | (1)将一个或多个member元素加入到key中,已存在在集合的member将被忽略 (2)假如key不存在,则只创建一个只包含member元素做成员的集合 (3)当key不是集合类型时,将返回一个错误 | SADD key number [member ...] |
SCARD | (1)返回key对应的集合中的元素数量 | SCARD key |
SDIFF | (1)返回一个集合的全部成员,该集合是第一个Key对应的集合和后面key对应的集合的差集 | SDIFF key [key ...] |
SDIFFSTORE | (1)和SDIFF类似,但结果保存到destination集合而不是简单返回结果集 (2) destination如果已存在,则覆盖 | SDIFFSTORE destionation key [key ...] |
SINTER | (1)返回一个集合的全部成员,该集合是所有给定集合的交集 (2)不存在的key被视为空集 | SINTER key [key ...] |
SINTERSTORE | (1)和SINTER类似,但结果保存早destination集合而不是简单返回结果集 (2)如果destination已存在,则覆盖 (3)destination可以是key本身 | SINTERSTORE destination key [key ...] |
SISMEMBER | (1)判断member元素是否key的成员,0表示不是,1表示是 | SISMEMBER key member |
SMEMBERS | (1)返回集合key中的所有成员 (2)不存在的key被视为空集 | SMEMBERS key |
SMOVE | (1)原子性地将member元素从source集合移动到destination集合 (2)source集合中不包含member元素,SMOVE命令不执行任何操作,仅返回0 (3)destination中已包含member元素,SMOVE命令只是简单做source集合的member元素移除 | SMOVE source desination member |
SPOP | (1)移除并返回集合中的一个随机元素,如果count不指定那么随机返回一个随机元素 (2)count为正数且小于集合元素数量,那么返回一个count个元素的数组且数组中的元素各不相同 (3)count为正数且大于等于集合元素数量,那么返回整个集合 (4)count为负数那么命令返回一个数组,数组中的元素可能重复多次,数量为count的绝对值 | SPOP key [count] |
SRANDMEMBER | (1)如果count不指定,那么返回集合中的一个随机元素 (2)count同上 | SRANDMEMBER key [count] |
SREM | (1)移除集合key中的一个或多个member元素,不存在的member将被忽略 | SREM key member [member ...] |
SUNION | (1)返回一个集合的全部成员,该集合是所有给定集合的并集 (2)不存在的key被视为空集 | SUNION key [key ...] |
SUNIONSTORE | (1)类似SUNION,但结果保存到destination集合而不是简单返回结果集 (2)destination已存在,覆盖旧值 (3)destination可以是key本身 | SUNION destination key [key ...] |
同样,实际测试一下Set:
除了SINTER没有用到,其他应该比较全面地展示了Set的相关使用。
SortedSet数据结构相关操作
数据结构最后说一下SortedSet相关操作,最近有一个场景需要实现Redis分页+高效移除数据,一下子没找到好的数据结构,后来想起了SortedSet才解决了问题,看来积累与储备还是非常有用的,
SortedSet顾名思义,即有序的Set,看下相关命令:
命令 | 描述 | 用法 |
ZADD | (1)将一个或多个member元素及其score值加入有序集key中 (2)如果member已经是有序集的成员,那么更新member对应的score并重新插入member保证member在正确的位置上 (3)score可以是整数值或双精度浮点数 | ZADD key score member [[score member] [score member] ...] |
ZCARD | (1)返回有序集key的元素个数 | ZCARD key |
ZCOUNT | (1) 返回有序集key中,score值>=min且<=max的成员的数量 | ZCOUNT key min max |
ZRANGE | (1)返回有序集key中指定区间内的成员,成员位置按score从小到大排序 (2)具有相同score值的成员按字典序排列 (3)需要成员按score从大到小排列,使用ZREVRANGE命令 (4)下标参数start和stop都以0为底,也可以用负数,-1表示最后一个成员,-2表示倒数第二个成员 (5)可通过WITHSCORES选项让成员和它的score值一并返回 | ZRANGE key start stop [WITHSCORES] |
ZRANK | (1)返回有序集key中成员member的排名,有序集成员按score值从小到大排列 (2)排名以0为底,即score最小的成员排名为0 (3)ZREVRANK命令可将成员按score值从大到小排名 | ZRANK key number |
ZREM | (1)移除有序集key中的一个或多个成员,不存在的成员将被忽略 (2)当key存在但不是有序集时,返回错误 | ZREM key member [member ...] |
ZREMRANGEBYRANK | (1)移除有序集key中指定排名区间内的所有成员 | ZREMRANGEBYRANK key start stop |
ZREMRANGEBYSCORE | (1)移除有序集key中,所有score值>=min且<=max之间的成员 | ZREMRANGEBYSCORE key min max |
还有若干不是很常用的命令没有写,就略过了,有兴趣的可以自己看一下,接着看一下SortedSet实际使用:
这个地方排名的时候稍微注意下,和我们认为的排名有些微区别,比如1 1 2 3,由于有两个1,因此3正序的Rank应当为2(以0为下标),但实际上会是3,所以Rank应当理解为元素在集合中的下标位置更加准确。
Redis的Key相关操作
写完了Redis的数据结构,接着我们看下Redis的Key相关操作:
命令 | 描述 | 用法 |
DEL | (1)删除给定的一个或多个key (2)不存在的Key将被忽略 | DEL key [key ...] |
EXISTS | (1)检查给定key是否存在 | EXISTS key |
EXPIRE | (1)为给定key设置生存时间,key过期时它会被自动删除 (2)对一个已经指定生存时间的Key设置执行EXPIRE,新的值会代替旧的值 | EXPIRE key seconds |
EXPIREAT | (1)同EXPIRE,但此命令指定的是UNIX时间戳,单位为秒 | EXPIRE key timestamp |
KEYS | (1)查找所有符合给定模式pattern的key,下面举一下例子 (2)KEYS *匹配所有key (3)KEYS h?llo匹配hello、hallo、hxllo等 (4)KEYS h*llo匹配hllo、heeeeello等 (5)KEYS h[ae]llo匹配hello和hallo (6)特殊符号想当做查找内容经的使用\ | KEYS pattern |
MIGRATE | (1)原子性地将key从当前实例传送到目标实例指定的数据库上 (2)原数据库Key删除,新数据库Key增加 (3)阻塞进行迁移的两个实例,直到迁移成功、迁移失败、等待超时三个之一发生 | MIGRATE host port key destination-db timeout [COPY] [REPLACE] |
MOVE | (1)将当前数据库的key移动到给定数据库的db中 (2)执行成功的条件为当前数据库有key,给定数据库没有key | MOVE key db |
PERSIST | (1)移除给定key的生存时间,将key变为持久的 | PERSIST key |
RANDOMKEY | (1)从当前数据库随机返回且不删除一个key, | RANDOMKEY |
RENAME | (1)将key改名为newkey (2)当key和newkey相同或key不存在,报错 (3)newkey已存在,RENAME将覆盖旧值 | RENAME key newkey |
TTL | (1)以秒为单位,返回给定的key剩余生存时间 | TTL key |
PTTL | (1)以毫秒为单位,返回给定的key剩余生存时间 | PTTL key |
TYPE | (1)返回key锁存储的值的类型 | TYPE key |
简单看一下实际使用:
这里特别注意KEYS命令,虽然KEYS命令速度非常快,但是当Redis中百万、千万甚至过亿数据的时候,扫描所有Redis的Key,速度仍然会下降,由于Redis是单线程模型,这将导致后面的命令阻塞直到KEYS命令执行完。
因此当Redis中存储的数据达到了一定量级(经验值从10W开始就值得注意了)的时候,必须警惕KEYS造成Redis整体性能下降。
系统相关命令
接着介绍一下部分系统相关命令:
命令 | 描述 | 用法 |
BGREWRITEAOF | (1)手动触发AOF重写操作,用于减小AOF文件体积 | BGREWRITEAOF |
BGSAVE | (1)后台异步保存当前数据库的数据到磁盘 | BGSAVE |
CLIENT KILL | (1)关闭地址为ip:port的客户端 (2)由于Redis为单线程设计,因此当当前命令执行完之后才会关闭客户端 | CLIENT KILL ip:port |
CLIENT LIST | (1)以可读的格式,返回所有连接到服务器的客户端信息和统计数据 | CLIENT LIST |
CONFIG GET | (1)取得运行中的Redis服务器配置参数 (2)支持* | CONFIG GET parameter |
CONFIG RESETSTAT | (1)重置INFO命令中的某些统计数据,例如Keyspace hits、Keyspace misses等 | CONFIG RESETSTAT |
CONFIG REWRITE | (1)对启动Redis时指定的redis.conf文件进行改写 | CONFIG REWRITE |
CONFIG SET | (1)动态调整Redis服务器的配置而无需重启 (2)修改后的配置立即生效 | CONFIG SET parameter value |
SELECT | (1)切换到指定数据库,数据库索引index用数字指定,以0作为起始索引值 (2)默认使用0号数据库 | SELECT index |
DBSIZE | (1)返回当前数据库的Key的数量 | DBSIZE |
DEBUG OBJECT | (1)这是一个调试命令,不应当被客户端使用 (2)key存在时返回有关信息,key不存在时返回错误 | DEBUG OBJECT key |
FLUSHALL | (1)清空整个Redis服务器的数据 | FLUSHALL |
FLUSHDB | (1)清空当前数据库中的所有数据 | FLUSHDB |
INFO | (1)以一种易于解释且易于阅读的格式,返回Redis服务器的各种信息和统计数值 (2)通过给定可选参数section,可以让命令只返回某一部分信息 | INFO [section] |
LASTSAVE | (1)返回最近一次Redis成功将数据保存到磁盘上的时间,以UNIX时间戳格式表示 | LASTSAVE |
MONITOR | (1)实时打印出Redis服务器接收到的命令,调试用 | MONITOR |
SHUTDOWN | (1)停止所有客户端 (2)如果至少有一个保存点在等待,执行SAVE命令 (3)如果AOF选项被打开,更新AOF文件 (4)关闭Redis服务器 | SHUTDOWN [SAVE|NOSAVE] |
看下命令的使用演示:
SELECT命令忘了,想起来的时候数据库已经清空了就算了,使用SELECT后控制台会变成"127.0.0.1:6379[3]>",即带上数据库的index。
Redis的事务
最后,本文简单说一下Redis的事务机制,首先Redis的事务是由DISCARD、EXEC、MULTI、UNWATCH、WATCH五个命令来保证的:
命令 | 描述 | 用法 |
DISCARD | (1)取消事务 (2)如果正在使用WATCH命令监视某个/某些key,那么取消所有监视,等同于执行UNWATCH | DISCARD |
EXEC | (1)执行所有事务块内的命令 (2)如果某个/某些key正处于WATCH命令监视之下且事务块中有和这个/这些key相关的命令,那么EXEC命令只在这个/这些key没有被其他命令改动的情况下才会执行并生效,否则该事务被打断 | EXEC |
MULTI | (1)标记一个事务块的开始 (2)事务块内的多条命令会按照先后顺序被放入一个队列中,最后由EXEC命令原子性地执行 | MULTI |
UNWATCH | (1)取消WATCH命令对所有key的监视 (2)如果WATCH之后,EXEC/DISCARD命令先被执行了,UNWATCH命令就没必要执行了 | UNWATCH |
WATCH | (1)监视一个/多个key,如果在事务执行之前这个/这些key被其他命令改动,那么事务将被打断 | WATCH key [key ...] |
首先我们看一下事务没有被打断的情况:
看到开启事务之后,所有的命令返回的都是QUEUED,即放入队列,而不是直接执行。
接着模拟一下事务被打断的情况,WATCH一下Number这个Key,我另外起了一个Redis客户端INCR了一下Number,结果为:
看到,并没有命令被执行,返回nil即事务被打断。
接着简单说一下事务,和数据库类似的,事务保证的是两点:
- 隔离,所有命令序列化、按顺序执行,事务执行过程中不会被其他客户端发来的命令打断
- 原子性,事务中的命令要么全部执行,要么全部不执行
另外,Redis的事务并不支持回滚,这个其实网上已经说法挺多了,大致上是两个原因:
- Redis命令只会因为语法而失败(且这些问题不能再入队时被发现),或是命令用在了错误类型的键上面,也就是说,从实用性角度来说,失败的命令是由于编程错误造成的,而这些错误应该在开发的过程中被发现而不应该出现在生产环境中
- Redis内部可以保持简单且快速,因为不需要对回滚进行支持
总而言之,对Redis来说,回滚无法解决编程错误带来的问题,因此还不如更简单、更快速地无回滚处理事务。
下期预告
最后预告一下最后一篇文章会写的内容,四部分:
- Redis线程模型
- Redis的RDB
- Redis的AOF
- Redis的集群方式