概述
Redis是一个非关系型数据库,以键值对的方式来存储数据。
数据通过存储在内存中来获得高的读写性能,同时也可以存储到硬盘以实现持久化,既适合用作缓存,也适合用作数据存储。
非关系型数据库的本质,就是切掉一部分关系型数据库拥有的功能,而专注于高效的实现某些功能。
如在本笔记中发现错误,欢迎指正。
参考书籍
《Redis实战》,人民邮电出版社。
数据类型与命令
Redis可以存储键与5种不同数据结构类型之间的映射。
每种类型的指令均由该类型的首字母开头,然后用操作对应的英文单词表达。
STRING
可以是字符串、整数或者浮点数。
- 整数的取值范围和系统的长整数的取值范围相同。
- 浮点数的取值范围和精度与IEEE754标准的双精度浮点数相同。
数操作命令
对数类型进行增减的命令。
INCR、DECR、INCRBY、DECRBY、INCRBYFLOAT。
- 对一个不存在或者保存了空串的键进行了操作,会将该键的的值当做0来处理。
- 如果尝试对无法解释为整数或者浮点数的字符串操作,会返回错误。
子串和二进制位命令
对字符串类型进行字符或者位级别操作的指令。
APPEND、GETRANGE、SETRANGE、GETBIT、SETBIT、BITCOUNT、BITOP。
- 在使用SET*对字符串进行写入的时候,如果字符串长度不满足要求,会自动使用空自己(null)来将字符串扩展至所需的长度再执行。
- 在使用GETRANGE读取字符串时,超出字符串末尾的数据会被视为空串。
- 在使用GETBIT读取二进制串时,超过字符串末尾的数据会被视为0。
LIST
一个链表,链表每个节点都包含了一个字符串。
常用列表命令
推入、弹出、索引等命令。
RPUSH、LPUSH、RPOP、LPOP、LINDEX、LRANGE、LTRIM。
阻塞命令
阻塞命令可以阻塞执行命令的客户端,直到其他客户端给列表添加元素为止,常用于消息传递和任务队列。
BLPOP、BRPOP、BPOPLPUSH、BRPOPLPUSH。
SET
包含字符串的无序收集器,包含的每个字符串都是独一无二的。
常用集合命令
添加、删除、查询等命令。
SADD、SREM、SISMEMBER、SCARD、SMEMBERS、SRANDMEMBER、SPOP、SMOVE。
多集合操作命令
用于作集合的交集、差集、并集等命令。
SDIFF、SDIFFSTORE、SINTER、SINTERSTORE、SUNION、SUNIONSTORE。
HASH
包含键值对的无序散列表。
- 与字符串一样,对一个不存在的键进行自增时,会将值当做0来处理。
常用散列命令
添加、删除、查询等命令。
HMGET、HMSET、HDEL、HLEN。
高级散列命令
批量操作命令,以及一些字符串操作类似的散列命令。
HEXISTS、HKEYS、HVALS、HGETALL、HINCRBY、HINCRBYFLOAT。
ZEST
字符串成员与浮点数分值之间的有序映射,元素的排列顺序由分值的大小决定。当分值相同时,会按照字符串进行排序。
常用有序集合命令
添加、删除、查询、统计等命令。
ZADD、ZREM、ZCARD、ZINCRBY、ZCOUNT、ZRANK、ZSCORE、ZRANGE。
范围操作命令
逆序、子集、交集、并集等命令。
ZREVRANK、ZREVRANGE、ZRANGEBYSCORE、ZREVRANGEBYSCORE、ZREMRANGEBYRANK、ZREMRAMGEBYSCORE、ZINTERSTORE、ZUNIONSTORE。
- 交集和并集运算需要对分数使用聚合函数,默认函数是sum,此外还有min、max等。
- 普通集合的分值默认当做1来处理。
发布与订阅
订阅者负责订阅频道,发送者负责向频道发送二进制字符串消息。
Redis的发布订阅模式有两个缺点:
- 读取消息的速度如果赶不上发送消息的速度,会被自动断开连接。
- 遇到网络断线时期间的消息都会丢失。
常用命令
订阅与发布的命令。
SUBSCRIBE、UNSUBSCRIBE、PUBLISH、PSUBSCRIBE、PUNSUBSCRIBE。
其他通用指令
SORT
可以设置升降序,字符、数字、或者其他权重排序。
过期时间
设置、查看、取消键的过期时间,但是对于列表、集合、散列和有序集合,只能为整个键设置过期时间。
PERSIST、TTL、EXPIRE、EXPIREAT、PTTL、PEXPIRE、PEXPIREAT。
持久化
Redis提供两种不同形式的持久化方法,这两种方法可以不使用、单独使用或者同时使用:
- 快照,可以将存在于某一时刻的所有数据都写入硬盘。
- 发送BGSAVE命令,redis会用fork创建一个子进程负责将快照写入硬盘。如果系统剩余的内存很少,该命令可能导致系统长时间停顿。
- 发送SAVE命令,redis将开始创建快照并不再接收任何命令,直到快照创建完毕。因为不用和子进程争抢资源,所以该命令创建快照的速度会比BGSAVE更快。
- 通过SHUTDOWN命令接收关闭服务器请求时,会执行SAVE命令,执行完毕后再关闭服务器。
- 服务崩溃会导致最后一次快照之后的数据丢失。
- 从服务器连接到主服务器并发送SYNC命令时,如果主服务器没有在执行BGSAVE命令,则会执行该命令。
- 只追加文件,在执行写命令时将被执行的写命令复制到硬盘里面。
- appendfsync选项用于设置同步频率:
- no:操作系统决定,几乎不对redis性能带来影响,系统崩溃会丢失不定量数据,如果硬盘写入速度不够快导致缓冲区被填满,会阻塞redis写入。
- everysec:每秒一次,对redis性能影响较小。
- always:每次写命令执行,受硬盘性能限制,会严重降低redis性能,并减少固态硬盘的寿命。
- appendfsync选项用于设置同步频率:
- 发送BGREWRITEAOF命令可以移除AOF文件中的冗余命令来重写AOF文件以减少文件体积,该命令的工作原理和BGSAVE十分相似。
复制
与关系型数据库类似,通过主从复制,客户端每次向主服务器
写入时,都会同步到从服务器,读请求可以向任意的从服务器发送。
Redis不支持主主复制。
配置
- 为从服务器设置slaveof host port配置,redis在启动时首先会载入当前可用的任何快照或者AOF文件,然后连接主服务器。
- 使用SLAVEOF命令,redis会立即尝试连接主服务器。
- 从服务器默认是不允许写入的,但某些查询可能需要进行一些临时写入,此时需要将从服务器的salve-read-only配置选项从默认的yes改成no。
复制过程
- 从服务器发送SYNC命令连接主服务器。
- 主服务器执行BGSAVE。
- 主服务器发送快照文件并使用缓冲区记录这段期间的写命令,从服务器丢弃所有旧数据并载入主服务器的快照文件。
- 主服务器发送缓冲区的命令。
- 主服务器同步发送写命令。
对于主从链复制,如果从服务器X拥有从服务器Y,那么当X执行对快照文件的解释时,会断开与Y的连接,导致Y需要重新连接和同步。
故障处理
验证快照文件或者AOF文件
redis提供了两个命令行程序来检查快照文件和AOF文件的状态并在需要的情况下进行恢复:
- redis-check-aof:redis会扫描aof文件,当发现第一个出错命令时,会删除出错的命令以及位于出错命令后的所有命令。
- redis-check-dump
更换redis服务器
更换主服务器
因为从服务器是无法执行写命令的,所以可以通过快照的方式发送数据,整个过程不会出现数据的不一致性。
- 从服务器执行SAVE命令生成快照。
- 将快照文件发送给新的主服务器,让主服务器从快照启动。
- 将从服务器连接上新的主服务器。
升级从服务器
修改配置文件让从服务器升级成为主服务器。
事务
与关系型数据库的差异
redis的事务和传统的关系型数据库不同。
- 关系型数据库事务以BEGIN命令开始,然后执行各个相互一致的读写操作,最后用户可以选择COMMIT确认修改或者ROLLBACK放弃修改。
- redis事务以MULTI命令开始,然后传入多个命令,最后以EXEC结束,但是在EXEC命令被调用前不会执行任何实际操作,所以用户没法根据读取到的数据来决定。
命令
redis事务由五个命令组成:
- MULTI:事务开始的命令。
- EXEC:事务结束的命令。
- WATCH:对键进行监视,在用户执行EXEC之前,如果有其他客户对被监视的键进行了修改,那么事务将失败并返回错误。
- UNWATCH:在WATCH命令之后、MULTI命令之前对连接进行重置。
- DISCARD:在MULTI命令执行之后、EXEC命令执行之前对连接进行重置,该指令会取消WATCH命令,并且清空已入队的命令。
客户端流水线
- 客户端往往会用队列缓存指令,直到输入EXEC命令后再一次性将事务发送给服务器,以减少通信数量,提高速度。
- 非事务型操作也可以使用流水线的方法来提高速度。
锁
锁分类
在共享数据库中,通过加锁可以让当前客户端对数据进行排他性访问。一般步骤为先获取锁,然后执行操作,最后释放锁。
- 常见的关系型数据库在加锁时就不允许其他客户端修改了,这种锁称为“悲观锁”。
- Redis使用WATCH命令来对数据进行加锁,WATCH只会在数据被其他客户端抢先修改了的情况下才通知执行了这个命令的客户端,而不会阻止其他客户端对数据进行修改,这种加锁方式称为“乐观锁”。
- 因为在失败后会进行重试,所以这种锁在重负载的情况下可能会出现性能问题。
悲观锁实现
- SETNX指令,只会在键不存在的情况下为键设置值,用于获取锁。
- 锁占有者通过为键设置超时时间,来避免自身崩溃后一直持有锁。
- 竞争者通过检查并为没有超时时间的锁设置超时时间,来避免锁占有者在设置超时时间之前就崩溃。
- 锁占有者通过取消键的值来释放锁。
信号量
通过redis命令有多种方式来实现信号量功能,属于redis应用场景,本篇不多介绍。
性能优化
短结构
- Redis为列表、集合、散列和有序集合提供了一组配置选项,可以让Redis以更节约空间的压缩列表存储长度较短的结构,即短结构。
- 列表、散列、有序集合的配置选项由*-max-ziplist-entries和-max-ziplist-value组成,entries表示包含最大元素的数量,value表示每个节点的最大体积字节数。
- 集合的配置选项为set-max-intset-entries,但要求成员必须能够被解释为可表示的整数,这种存储方式被称为整数集合。
- 当选项设置的任意限制条件被突破的时候,redis会将编码转换为其他结构。
- 压缩列表会以序列化的方式存储数据,这些序列化数据每次被读取的时候都要进行解码,每次被写入的时候也要进行局部的重新编码,并且可能需要对内存里面的数据进行移动。
- 所以短结构本质上是拿时间换空间,当结构很长时,对时间性能的影响就会变得很大。
分片结构
- 一个通用的技巧,对数据计算哈希值后分片存储,既可以对键进行分片也可以对键的值进行分片。分片后的数据进行读写以及运算操作都需要进行相应的调整。
- 可以和短结构组合使用,让每个分片都能够被表示为短结构。
- 不同的分片可以存储在不同的数据库中从而对数据库的写性能进行扩展。
位图表示
- 一个通用的技巧,对数据进行连续编码,然后以位图的方式来存储。
- 可以和分片与短结构组合使用,将长位图进行分片。
Lua脚本编程
Redis从2.6版本开始引入使用Lua编程语言进行的服务器端脚本编程功能。
- SCRIPT LOAD命令接受一个字符串格式的Lua脚本为参数,然后把脚本存储起来并返回SHA1的校验和。
- EVALSHA命令接受脚本的SHA1校验和以及脚本所需的全部参数来执行脚本。
- Redis也把EVAL和EVALSHA看作是单个命令来处理,都是原子操作。所以通过Lua脚本可以更加简单的实现事务操作的效果。
注意事项
- Lua里面的某些数据类型是不允许进行传出的,因为脚本在返回不同类型的数据时可能会产生含糊不清的结果,所以应该尽量显式的返回字符串,然后手动的进行分析操作。
- 使用SCRIPT KILL命令可以终止正在运行的脚本,但是已经对结构进行了修改的Lua脚本将无法被中断,只能使用SHUTDOWN NOSAVE命令杀死服务器,让服务器从最近的一次快照重新启动。