redis 是什么?
- redis 是 "Remote Dictionary Server" 的缩写,它以字典的结构存储数据
- redis 的所有数据都存储在内存中,在性能上相对其它硬盘存储的数据库有很大的优势
- redis 可以对内存中的数据进行持久化
- redis 支持主从复制功能,实现高可用
- redis 键值支持的数据类型有:字符串类型、散列类型、列表类型、集合类型、有序集合类型
- redis 的主要用于实现缓存、队列、消息订阅等功能
- redis 提供了如下一些可执行程序:
redis-server:redis 服务端,用于启动 redis 服务
redis-cli:redis 命令行客户端,可以直接连接到 redis 服务,提供了丰富的键值操作的命令
redis-benchmark: redis 提供的性能测试工具
redis-check-aof:aof 文件修复工具
redis-check-dump:adb 文件检查工具
redis-sentinel:哨兵服务器,可以启动哨兵用于监控 redis 服务的运行情况
- 一个 redis 可以配置多个数据库:
redis 默认支持 16 个数据库,可以通过启动参数 databases 来修改该值
数据库编号从 0 开始,不支持自定义名称
在客户端可以通过 select 来随时切换数据库
数据库之间的数据不是完全隔离的,我们可以通过 flushAll 命令清空一个实例下所有数据库的数据
这些数据库更像一个命名空间,不适合存放不同应用下的数据
redis-cli 中常用的命令
- 字符串类型
字符串类型的键允许存放的最大数据类型为 512 M,常用命令如下:
SET key value # 设置键值
GET key # 获取键值
INCR numKey # 让键值递增,返回递增后的数值,键值非数字会报错,不存在时默认为 0
INCRBY numKey num # 和 INCR 类似,支持每次动态加指定数值的数据
DECR numKey # 自减
DECRBY numKey num # 减少指定数值
INCRBYFLOAT numKey num # 增加指定的浮点数值
APPEND key value # 向尾部追加值,返回追加后的字符长度
STRLEN key # 返回键值的字符串长度
MSET/MGET # 和 GET/SET 类似,批量设置和获取
GETBIT key offset # 获取一个字符串指定位置的二进制值
SETBIT key offset value # 设置指定位置的二进制的值
BITCOUNT key # 统计键值对应的二进制位数
BITOP operation destkey key1... # 对多个键 key1,key2... 对应的值进行位运算,并存储在 destkey 键对应的值中
- 散列类型
散列类型的键值是也是一种字典类型,存储了字段和字段值的映射关系,字段值只支持字符串,散列类型适合存储对象,可以每次获取或者更新某个属性的值,减少解析整个对象的时间,常用命令如下:
HSET key field value # 设置散列类型 key 对应的对象的某个属性
HGET key field # 获取散列类型 key 对应的对象的某个属性
HMSET key field value ... # 批量设置散列类型 key 对应的多个属性的值
HMGET key field ... # 批量获取散列类型 key 对应的多个属性的值
HGETALL key # 获取散列类型中某个 key 对应的所有属性及值,也就是整个对象
HEXISTS key field # 判断散列类型中某个字段是否存在
HSETNX key field value # 判断散列类型中某个字段是否存在,不存在则赋值
HDEL key field ... # 删除散列类型中对象里的一个或者多个字段
HKEYS key # 获取散列类型中对象里的所有字段属性名
HVALS key # 获取散列类型中对象里的所有字段属性对应的值
HLEN # 获取散列类型中对象里的字段数量
- 列表类型
列表类型可以存储一个有序的字符串列表,常用的操作是向列表两头添加元素或者列表某个片段,列表类型使用双向链表实现,访问首尾元素的速度非常快,常用命令如下:
LPUSH key value1 value2... # 向列表左边增加多个元素
RPUSH key value1 value2... # 向列表右边增加多个元素
LPOP key # 从列表左边弹出一个元素
RPOP key # 从列表右边弹出一个元素
BRPOP key # 与 RPOP 类似,只是如果列表中没有元素时会一直等待直到新元素加入,可以接受多个 key 值,优先使用第一个 key
BLPOP key... # 与 BRPOP 类似
LLEN key # 获取列表中元素个数,时间复杂度为O(1)
LRANGE key start stop # 获取列表中从 start 到 stop 之间的元素,从 0 开始索引
LREM key count value # 删除列表中前 count 个值为 value 的元素,count>0 从左边删除,count<0 从右边删除,count=0 全部删除
LINDEX key index # 获取列表中指定索引的元素
LSET key index value # 设置列表中指定索引的元素
LTRIM key start end # 删除列表中指定范围以外的所有元素
LINSERT key BEFORE/AFTER pivot value # 从列表中查找值等于 pivot 的第一个元素,然后在其前面或者后面插入 value 元素
RPOPLPUSH source destination # 从 source 列表右边弹出一个元素,插入到 destination 列表左边
- 集合类型
集合类型的元素是无序的、唯一的,常用的操作是向集合中加入某个元素或者删除某个元素、判断某个元素是否存在,大部分操作的时间复杂度基本都为 O(1),常用命令如下:
SADD key value1 value2... # 向集合中加入多个元素
SREM key value1 value2... # 从集合中删除多个元素
SMEMBERS key # 获取集合中的所有元素
SISMEMBER key value # 判断元素是否存在集合中
SDIFF key1 key2... # 对多个集合求差集运算
SDIFFSTORE destination key1 key2... # 对多个集合求差集运算,并将结果存储在 destination 集合中
SINTER key1 key2... # 对多个集合求交集运算
SINTERSTORE destination key1 key2...# 对多个集合交差集运算,并将结果存储在 destination 集合中
SUNION key1 key2... # 对多个集合求并集运算
SUNIONSTORE destination key1 key2...# 对多个集合并差集运算,并将结果存储在 destination 集合中
SCARD key # 获取集合中的元素个数
SRANDMEMBER key count # 随机从集合中获取 count 个不相同的元素
SPOP key # 随机从集合中弹出一个元素
- 有序集合类型
有序集合类型使用散列表和跳跃表实现的,比列表类型更消耗内存,有序集合通过给每个元素存放一个 score 进行排序,对于需要排序分页的场景非常适用,常用命令如下:
ZADD key score member ... # 向有序集合中添加元素,如果存在则更新元素对应的 score
ZSCORE key member # 获取有序集合中某个元素的分数
ZRANGE key start stop # 获取有序集合中排名在某个范围的元素,结果集从小到大排序
ZREVRANGE key start stop # 获取有序集合中排名在某个范围的元素,结果集从大到小排序
# 获取有序集合中分数在某个范围的元素,WITHSCORES 表示是否把分数返回,LIMIT 表示是否需要分页
# 默认情况下是包含最大值和最小值,如果想不包好最大值最小值,可以加个左括号:ZRANGEBYSCORE hello 3 (5
# -inf 和 +inf 表示无穷小和无穷大
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
# ZREVRANGEBYSCORE 和 ZRANGEBYSCORE 相似,只是从大到小排序
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
ZINCBY key increment member # 增加有序集合中某个元素的分数
ZCARD key # 获取有序集合中元素的数量
ZCOUNT key min max # 指定分数范围内元素的个数
ZREM key member... # 删除有序集合中一个或者多个元素
ZREMRANGEBYRANK key start stop # 删除有序集合中某个排名范围的元素
ZREMRANGEBYSCORE key min max # 删除有序集合中某个score范围的元素
ZRANK key member # 获取某个元素的排名
# 计算有序集合的交集,可以通过 AGGREGATE 指定聚合类型,默认是求和,最终将key1,key2 集合中的元素通过不同的聚合方式放入 destinationKey 集合中
ZINTERSTORE destinationKey numkeys key1 key2... [AGGREGATE SUM | MIN | MAX]
- 通用的命令
CONFIG set loglevel warning # 动态修改 redis 的部分配置
CONFIG get loglevel # 获取 redis 的部分配置
SELECT 1 # 更改数据库编号,默认从 0 开始
KEYS pattern # 获取满足 glob 风格的所有键值,当键值较多时会影响性能,生产不建议这样做
EXISTS key # 判断某个键值是否存在,存在返回 1, 否则返回 0
DEL key1 key2 # 可以删除一个或者多个键,返回删除的个数
KEYS "user*" | xargs | del # 实现通配符匹配删除多个键值
TYPE key # 获取键值的数据类型,string | hash | list | set | zset(有序集合)
redis 中事务
- redis 中事务是一组命令的集合
- 事务以 MULTI 命令开始,以 EXEC 命令结束
- 事务的原理是先将属于一个事务的所有命令暂时存起来,等到 EXEC 命令再以此执行
- redis 的事务没有提供关系型数据库事务的回滚功能
- redis 中的事务的执行结果是一起返回的,因此无法将前一个命令的结果作为下一个命令的参数
- WATCH 命令用于监控一个或者多个键,一旦其中一个键被修改,之后的事务就不会执行,监控一直持续到 EXEC 命令
- WATCH 命令主要用于防止竞态条件,保证一个事物执行前某些键值不被修改
> MULTI
> SADD "user:1" 2
> SADD "user:2" 1
> EXEC
redis 中的过期时间
- EXPIRE key seconds 用于设置键的过期时间,单位为秒
- PEXPIRE key milliSeconds 单位为毫秒
- TTL key 用于获取键的过期时间,返回 -1 表示过期时间为永久,-2 表示键值不存在
- PTTL key 获取过期时间,单位为毫秒
- PERSIST key 用于清空过期时间,设为永久
- SET 命令同样会清空过期时间
排序
- SORT 命令可以对列表类型,集合类型和有序集合类型键进行排序
- 利用排序可以完成与关系型数据库中连接查询相类似的任务
- SORT 命令默认是对键值为数字的序列进行排序,非数字的排序会报错
- ALPHA 参数,可以按照字典顺序排序字符串类型的键值
- DESC 参数,实现元素从大到小排序
- LIMIT offset count 参数,实现排序分页
- “BY 参考值” 参数,参考值可以是字符串类型的键,也可以是散列类型的某个键对应的字段
# 通过 BY 我们可以实现使用其他参考键值进行排序
# 使用列表中的每个元素替换参考值中的第一个 *,并获取对应的散列类型的 time 字段进行排序
SORT tag:ruby:posts BY post:*-> time DESC
- GET 参数,通过给 SORT 命令加 GET 参数,返回不再是元素自身的值,而是 GET 参数中指定的键值
# 通过 post:* 中 time 字段进行排序,并返回 post:* 中的 title 字段,同时返回原始列表中的信息(#)
# 同一个 SORT 命令可以返回使用多个 GET 参数
SORT tag:ruby:posts BY post:*-> time DESC GET post:*->title GET #
- STORE 参数,排序后的结果不直接返回排序结果,存储在对应的键中(列表类型)
- 使用 SORT 命令,关于性能方面注意的几点:
- 尽量减少排序键中元素的个数
- 使用 LIMIT 参数获取必要的数据
- 如果排序结果较大,使用 STORE 参数进行缓存,以免多次排序
redis 中的消息通知
- 使用列表类型实现任务队列(LPUSH/RPOP/BRPOP)
- 使用 BLPOP/BRPOP 实现优先级队列
- 实现发布订阅机制(PUBLISH/SUBSCRIBE/UNSUBSCRIBE/PSUBSCRIBE/PUNSUBSCIBE)
redis 中编码存储优化
redis 对不同的数据类型都提供了多种编码方式,以节省空间和加快查询效率,通过命令 OBJECT ENCODING key 命令可以查看某个键值对应的编码方式
以下是每种数据类型可能才用的内部编码方式:
| 数据类型 | 内部编码方式 | OBJECT ENCODING 命令结果 |
|---|---|---|
| 字符串类型 | REDIS_ENCODING_RAW | "raw" |
| REDIS_ENCODING_INT | "int" | |
| REDIS_ENCODING_EMBSTR | "embstr" | |
| 散列类型 | REDIS_ENCODING_HT(散列表) | "hashtable" |
| REDIS_ENCODING_ZIPLIST(紧凑顺序表) | "ziplist" | |
| 列表类型 | REDIS_ENCODING_LINKEDLIST(双向链表) | "linkedlist" |
| REDIS_ENCODING_ZIPLIST | "hashtable" | |
| 集合类型 | REDIS_ENCODING_HT | "hashtable" |
| REDIS_ENCODING_INTSET | "intset" | |
| 有序集合类型 | REDIS_ENCODING_ZIPLIST | "ziplist" |
| REDIS_ENCODING_SKIPLIST(跳跃表) | “skiplist” |
- 每种类型的内部编码方式一般至少有两种以上
- 当元素较少时,redis 一般会采用一种更为紧凑但性能较差的编码方式
- redis 启动后会预先建立一个 0 到 9999 这些数字的共享对象,因此如果要设置的键值在这些数字范围内时,redis 就直接引用他们不需要再建立对象
- 当字符串的键值内容不超过 39 字节时,redis 采用 REDIS_ENCODING_EMBSTR 编码方式
- 当散列类型键的字段个数少于 hash-max-ziplist-entries 参数值且每个字段名和字段值的长度都小于 hash-max-ziplist-value 参数值时,采用 REDIS_ENCODING_ZIPLIST 编码方式,否则使用 REDIS_ENCODING_HT 编码
- 当列表类型键的元素个数少于 list-max-ziplist-entries 参数值且每个元素的长度都小于 list-max-ziplist-value 参数值时,采用 REDIS_ENCODING_ZIPLIST 编码方式,否则使用 REDIS_ENCODING_LINKEDLIST 编码
- 当集合类型中的所有元素为整数且元素的个数小于配置 set-max-insert-entries 时,采用 REDIS_ENCODING_INTSET 编码方式,否则采用 REDIS_ENCODING_HT 编码方式
- 同样有序集合类型可以通过参数 zset-max-ziplist-value 和 zset-max-ziplist-entries 来控制使用 REDIS_ENCODING_ZIPLIST 编码方式还是 REDIS_ENCODING_SKIPLIST 编码方式
redis 与 Lua 脚本
- 在 Lua 脚本中可以通过 redis.call('get', 'foo') 的方式调用 redis 命令
- redis.pcall 和 redis.call 类似,不同之处是执行失败时记录失败消息并继续执行
- 在 redis 中使用 Lua 脚本时禁止使用全局变量和文件系统调用相关函数,为了保持状态安全一致性
- 通过 eval 命令可以让开发者像调用其它 redis 命令一样调用 Lua 脚本
# 第一个参数是要执行的脚本,第二个参数表示后面传入 key 的数量,后面是 key 和 arg 传入的参数,可以在脚本里直接使用
eval 脚本内容 key参数数量 [key...] [arg...]
- evalsha 命令
- 和 eval 一样是执行 Lua 脚本,只不过每次执行时是将脚本的摘要传递给 redis 服务端
- redis 服务端会检查缓存是否存在该摘要的脚本并执行,不存在则抛出异常
- 因为 eval 命令每次都会将脚本发到远端,带宽消耗大,所以一般 evalsha 命令执行失败时再使用 eval 命令并设置缓存
- SCRIPT lOAD:将脚本的 SHA1 摘要加入到脚本缓存中
- SCRIPT EXSITS: 判断某个脚本的 SHA1 摘要是否被缓存
- SCRIPT FLUSH:清除 SHA1 脚本本地缓存
- SCRIPT KILL: 停止脚本的执行
- 使用脚本开发的优点:
- 减少网络开销
- 原子操作
- 代码的复用
持久化
redis 提供两种持久化的方式,一种是 RDB 方式,一种是 AOF 方式:
- RDB 的方式是定时将内存中的数据存储到磁盘,以下几种情况会进行数据快照:
- 配置指定规则自动同步,比如 save 300 10, 表示每 300 内至少有 10 条数据被修改则进行快照
- 用户执行 SAVE 或者 BGSAVE 命令,SAVE 命令同步执行会阻塞其它客户端的请求,BGSAVE 异步同步数据
- 执行 FLUSHALL 命令
- 主从复制时
- AOF 的方式是每次将更新数据的命令记录到磁盘
- 通过参数 appendonly 开启 AOF 数据备份
- 通过参数 appendfsync 设置磁盘缓存刷新机制
- 因为每条更新数据的命令都会被记录到磁盘,所以磁盘数据会越来越大,因此 redis 提供了重写 AOF 的功能
- 通过 auto-aof-rewrite-percentage 参数和 auto-aof-rewrite-min-size 参数可以设置自动重写 AOF 文件策略
主从复制
- 通过在启动 redis 服务端时加入 slaveof 参数设置为其它服务器的从数据库
- 默认情况下,从数据库是只读的
- 通过 info replication 命令查看 redis 的主从状态
- 可以通过参数 slave-read-only 参数设置为可写,但是不建议这样设置会导致数据错乱
- 一个主数据库可以关联多个从数据库
- 通过 SLAVEOF NO ONE 命令使当前从数据库停止接收其它数据库的同步并转换为主数据库
- redis 采用乐观复制策略,主数据库在执行完客户端请求后立即返回给客户端,而不回等待同步到从数据库才返回
- 乐观复制策略保证了主数据库性能不受影响,但是会使主从数据库有一定时间的数据不一致的窗口
- 为了尽量保证主从数据的一致性,redis 提供了两个参数来限制主数据库的可写状态:
- min-slaves-to-write 3:表示只有当 3 个或者 3 个以上的从数据库连接到主数据库时,主数据库才是可写的
- min-slaves-max-lag 10:表示允许从数据库最长失去连接的时间(秒),超过该时间,主数据库变为只读。
- 从数据库可以作为另外一台数据库的主数据库
- 当主数据库发生故障时,一定不要立即重启,重启后因为主数据库没有开启持久化,所以数据库中的数据会被清空,然而主从数据又保持的联系,致使从数据库中的数据也被清空,使从数据库的持久化失去意义。这种情况下建议先使用 SLAVE NO ONE 使从数据库变为主数据库再重启主数据库
- 在从数据库与主数据库重新建立连接时,既可以选择全量同步数据,也可以选择增量复制同步数据
哨兵
Redis 2.8 以后提供了哨兵工具来实现自动化监控和故障恢复功能:
- 哨兵系统用户监控系统运行,当主数据库出现故障时自动将从数据库升级转换为主数据库
- 一个主从的 redis 系统可以部署多个哨兵来进行监控,避免哨兵系统的单点问题
- 一个哨兵实例也可以监控多个主从 redis 系统
- 配置哨兵系统时只需要配置主数据库的信息,哨兵系统会自动通过 info replication 命令发现其从数据库的信息
- 哨兵相关参数:
- slvae-priority:从数据库的优先级设置,当主数据库挂掉后,选择优先级最高的从数据库升级为主数据库
- down-after-milliseconds: 指定时间后,如果 ping 的数据库节点仍然未回复,则认为该数据库实例主观下线
- quorum:多多个哨兵系统都认为某个数据库节点主观下线且超过 quorum 个数,则认为该数据库实例客观下线
- 认为客观下线的前提下,需要保证只有一个哨兵节点来执行故障恢复操作,选举哨兵头领的过程使用 Raft 算法。
- 哨兵部署建议:
- 为每个节点部署一个哨兵,避免单点问题
- quorum 的值至少设置为 N/2 + 1, 保证大部分哨兵节点同意后才进行故障恢复
管理工具
- 记录耗时命令日志
- 和 mysql 一样, redis 也可以记录慢查询日志
- 通过 slowlog-log-slower-than 参数设置多长时间的执行时间会被记录到耗时日志里
- 通过参数 slowlog-max-len 来限制耗时日志记录的条数
- SLOWLOG GET 命令可以获取当前的耗时命令日志
- 命令监控:MONITOR 命令可以监控到所有 REDIS 执行的命令,对系统性能影响比较大
- phpRedisAdmin 工具,支持以树形结构查看键列表,编辑键值,查看数据库信息和键信息等功能,是 PHP 开发的。
- Rdbtools 工具,是 Python 工具开发的一个 Redis 快照文件解析器,根据快照文件导出的 JSON 数据,分析 Redis 每个键的占用空间等
- Redis 命令属性,每个命令都有不同的属性,一个命令同时可以拥有多个属性:
- REDIS_CMD_WRITE:拥有该属性的命令会修改数据库的数据
- REDIS_CMD_DENYOOM:拥有该属性的命令可能会增加 Redis 占用的存储空间
- REDIS_CMD_NOSCRIPT:拥有该属性的命令无法再 Redis 脚本中被执行
- REDIS_CMD_RANDOM:一个脚本执行了拥有了 REDIS_CMD_RANDOM 属性的命令后,就不能执行拥有 REDIS_CMD_WRITE 的命令了
- REDIS_CMD_SORT_FOR_SCRIPT:拥有该属性的命令会产生随机结果,在脚本中调用这些命令时 Redis 会对结果结果进行排序
- REDIS_CMD_LOADING:Redis 会在启动时执行拥有该属性的命令
redis 主要配置参数
- databases:设置 redis 支持的数据库的个数
- hash-max-ziplist-entries: 散列类型使用 REDIS_ENCODING_ZIPLIST 编码方式时对应的字段个数临界值
- hash-max-ziplist-value: 散列类型使用 REDIS_ENCODING_ZIPLIST 编码方式时对应的字段名和字段值的长度临界值
- list-max-ziplist-entries: 列表类型使用 REDIS_ENCODING_ZIPLIST 编码方式时对应的元素个数临界值
- list-max-ziplist-value: 列表类型使用 REDIS_ENCODING_ZIPLIST 编码方式时对应的元素的长度临界值
- set-max-ziplist-entries: 散列类型使用 REDIS_ENCODING_INTSET 编码方式时对应的元素个数的临界值
- zset-max-ziplist-value:有序集合使用编码方式的元素长度临界值
- zset-max-ziplist-entries:有序集合使用编码方式的元素个数临界值
- maxmemory: 限制最大可用内存大小(单位为字节)
- maxmemory-policy 设置在超出最大内存限制时,删除键值的处理方法:
- volatile-lru: 使用 LRU 算法删除一个设置了过期时间的键
- allkeys-lru: 使用 LRU 算法删除一个键
- volatile-random:随机删除一个设置了过期时间的键
- allkeys-random:随机删除一个键
- volatile-ttl:删除过期时间最近的一个键
- noeviction:不删除键值,插入数据时返回错误
- appendonly:等于 = true 开启 AOF 备份模式
- auto-aof-rewrite-percentage: 当目前 AOF 文件的大小超过上一次重写时 AOF 文件大小的多少比例时进行再次重写
- auto-aof-rewrite-min-size: 设置最小的 AOF 重写文件大小
- appendfsync:设置 AOF 缓存刷新机制
everysec: 默认策略,每秒执行一次刷新
always:每次执行写操作都会刷新磁盘缓存
no:不主动刷新磁盘缓存,交个操作系统,即每 30 秒一次
- slaveof:redis-server 启动参数,将当前 redis 实例设置为另外一台 redis 实例的备份实例,做主从同步
- slave-read-only:设置从数据库的读写状态
- requirepass:给 redis 服务器设置一个密码