Redis 的基本数据类型
- string:可以存储字符串、正数、浮点数
- list:链表,链表上的每个节点都包含一个字符串
- set:集合,包含字符串的无序收集器,并且包含的字符串都要独一无二
- hash:包含键值对的无序散列表
- zset:字符串成员与浮点数之间的有序映射,元素的排列顺序由分值的大小决定,默认按照从小到大排列
Redis 结构相关命令
String 自增自减相关的命令
只有可以被解释为整数或者浮点数的值才可以使用一下命令
- INCR
将键储存的值加 1
INCR KEY-NAME
- DECR
将键储存的值减 1
DECR KEY-NAME
- INCRBY
将键储存的值加上一个整数
INCRBY KEY-NAME int_number
- DECRBY
将键储存的值减去一个整数
DECRBY KEY-NAME int_number
- INCRBYFLOAT
将键存储的值加上一个浮点数
INCRBYFLOAT KEY-NAME float_number
划重点:
当将一个值存储为 string 时,redis 会判断这个值是否可以被解释为整数或者浮点数,则可以使用自增自减等相关方法。
当对一个 redis 中不存在的值自增自减是,redis 会将这个值的初始值当做 0 处理
如果用户对一个无法解释为正数或者浮点数的值自增自减操作,会报错。
String 处理子串和二进制位的命令
- APPEND
向一个值末尾追加一个子串
APPEND KEY-NAME "WANG"
- GETRANGE
获取某个值的一个子串
GETRANGE KEY-NAME SRART END
// START 和 END 都包含在内
- GETBIT
将字节串看做是二进制位串,并返回位串中偏移量为 offset 的二进制位的值
GETBIT KEY-NAME offset
- SETBIT
将字节串看做是二进制位串,并将位串中偏移量为 offset 的值设置为 value
SETBIT KEY-NAME offset value
- BITCOUNT
统计二进制位串中值为 1 的二进制位的数量,可以接受 start 和 end 值
BITCOUNT KEY-NAME start end
- BITOP
对一个或多个二进制位串执行并(AND)、或(OR)、异或(XOR)、非(NOT)在内的任意一种按位运算操作。并将结果的值保存在 des-key 中
BITOP operation des-key KEY-NAME [KEY-NAME ...]
List 操作常用命令
- RPUSH: 右插入
RPUSH KEY-NAME VALUE
- LPUSH
LPUSH KEY-NAME VALUE
- RPOP
RPOP KEY-NAME
- LPOP
LPOP KEY-NAME
- LINDEX
LINDEX KEY-NAME OFFECT
- LRANGE
LRANGE KEY-NAME START END
// START 和 END 是包含在其中的
//例如:取所有
LRANGE KEY-NAME 0 -1
- LTRIM
LTRIM KEY-NAME START END
// 对 LIST 进行修剪,只会保留 START - END 的元素,START 和 END 也都包含在内
List 阻塞式的列表弹出命令及在列表中移动元素的命令
- BLPOP
BLPOP KEY-NAME [KEY-NAME ...] TIMEOUT
// 从第一个非空列表中左弹出一个元素,若全空,则等待 TIMEOUT 秒
- BRPOP
BRPOP KEY-NAME [KEY-NAME ...] TIMEOUT
// 从第一个非空列表中右弹出一个元素,若全空,则等待 TIMEOUT 秒
- RPOPLPUSH
RPOPLPUSH source-key dest-key
// 从 source-key 右弹出一个元素 左插入 dest-key 中,并返回该元素
- BRPOPLPUSH
BRPOPLPUSH source-key dest-key timeout
// 从 source-key 右弹出一个元素 左插入 dest-key 中,并返回该元素。若 source-key 为空,则等到 timeout 秒。
Set 的常用命令
- SADD
// 将一个元素或读个元素添加到集合中,并返回添加元素中原本集合中不存在的元素的数量
SADD key-name item [item ...]
- SREM
// 从集合中删除一个或多个元素,并返回被移除元素的数量
SREM key-name item [item ...]
- SISMEMBER
// 检查元素是否存在于集合中
SISMEMBER key-name item
- SCARD
// 查看集合中元素的数量
SCARD key-name
- SMEMBERS
// 返回集合中的所有元素
SMEMBERS key-name
- SRANDMEMBER
// 从集合中随机的返回 count 个元素,当 count 为正数时,返回的元素不重复,当 count 为负数时,返回的元素可能重复
SRANDMEMBER key-name count
- SPOP
// 随机从集合中移除一个元素,并返回被移除的元素
SPOP key-name
- SMOVE
SMOVE source-key dest-key item
// 从集合 cource-key 中移除元素 item 增加到集合 dest-key 中。如果成功移除返回 1 ,否则返回 0 。
用户组合或处理多个集合的命令
- SDIFF (差集运算)
// 集合的差集运算,返回存在第一个集合,但不存在后续结合中的元素
SDIFF key-name [key-name ...]
- SDIFFSTORE
SDIFFSTORE dest-key key-name [key-name ...]
// 差集运算,保存在 dest-key 中。将存在于第一个 key-name 中,但是不存在后续 key-name 中的元素保存在 dest-key 中。
- SINTER(交集运算)
// 返回所有的 key-name 中共同存在的元素
SINTER key-name [key-name ...]
- SINTERSTORE
// 返回所有的 key-name 中共同存在的元素,并保存在 dest-key 中
SINTERSTORE dest-key key-name [key-name ...]
- SUNION(并集运算)
// 返回所有 key-name 中的所有元素
SUNION key-name [key-name ...]
- SUNIONSTORE
// 返回所有 key-name 中的所有元素并保存在 dest-key 中
SUNIONSTORE dest-key key-name [key-name ...]
Hash 散列的相关操作
- HMGET
// 从散列 key-name 中获取一个或多个键的值
HMGET key-name key [key ...]
- HMSET
// 为散列 key-name 中的一个键或多个键设置值
HMSET key-name key value [key value ...]
- HDEL
// 删除散列中一个或多个键值对,返回删除键值对的数量
HDEL key-name key [key ...]
- HLEN
// 获取散列中键值对的数量
HLEN key-name key
- HEXISTS
// 检查给定键是否存在于散列中
HEXISTS key-name key
- HKEYS
//获取散列中所有键
HKEYS key-name
- HVALS
//获取散列中所有值
HVALS key-name
- HGETALL
//获取散列中所有键值对
HGETALL key-name
- HINCRBY
// 将散列 key-name 的 key 的值增加整数 increment
HINCRBY key-name key increment
- HINCRBYFLOAT
// 将散列 key-name 的 key 的值增加浮点数 increment
HINCRBYFLOAT key-name key increment
即使存在 HGETALL 为什么换有 HKEYS 和 HVALS 呢?
如果使用散列中有大量的键值对,直接使用 HGETALL 可能会到值服务器阻塞,可以先使用 HKEYS 取出所有的 key,然后在通过 HGET 一个一个的取出值
ZSet 有序结合的一些常用命令
- ZADD
// 将带有给定分值的成员添加到有序集合中
ZADD key-name score member [score member ...]
- ZREM
// 从有序集合中移除给定的成员,并返回移除的数量
ZREM key-name member [member ...]
- ZCARD
// 返回有序集合包含的成员数量
ZCARD key-name
- ZINCRBY
// 将 member 成员的分值加上 increment
ZINCRBY key-name increment member
- ZCOUNT
// 返回分值介于 min 到 max 之间的成员数量
ZCOUNT key-name min max
- ZRANK
// 返回成员 member 在有序结合中的排名
ZRANK key-name member
- ZSCORE
// 返回成员 member 的分值
ZSOCRE key-name member
- ZRANGE
// 返回有序集合中排名介于 start 到 stop 之间的成员,如果指定了 WITHSCORES 参数,则连同分值一并返回
ZRANGE key-name start stop [WITHSCORES]
有序集合的范围型整数获取命令和范围型数据删除命令,并集命令和交集命令
- ZREVRANK
// 返回有序集合中 member 的排名,顺序按照分值的从大到小排列
ZREVRANK key-name member
- ZREVRANGE
// 返回有序集合中排名(按照从大到小排列)介于 start 到 stop 之间的成员,如果指定了 WITHSCORES 参数,则连同分值一并返回
ZREVRANGE key-name start stop [WITHSCORES]
- ZRANGEBYSCORE
// 返回有序集合中,分值介于 min 到 max 之间的所有成员
ZRANGEBYSCORE key-name min max [WITHSCORES] [LIMIT offset count]
- ZREVRANGEBYSCORE
// 返回有序集合中,分值介于 min 到 max 之间的所有成员(分值按从大到小排列)
ZREVRANGEBYSCORE key-name min max [WITHSCORES] [LIMIT offset count]
- ZREMRANGEBYRANK
// 移除有序集合中分值位于 min 到 max 之间的所有成员
ZREMRANGEBYRANK key-name min max
- ZREMRANGEBYSCORE
// 移除有序集合中分值位于 min 到 max 之间的所有成员(分值按从大到小排列)
ZREMRANGEBYSCORE key-name min max
- ZINTERSTORE
// 对给定的有序集合执行类似于集合的交集运算
ZINTERSTORE dest-key key-count key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
// AGGREGATE 默认为 SUM
- ZUNIONSTORE
// 对给定的有序集合执行类似于集合的并集运算
ZUNIONSTORE dest-key key-count key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
// AGGREGATE 默认为 SUM
Redis 发布与订阅
如果一个客户端订阅了多个频道,但是让读取消息不够及时话可能会造成阻塞->redis 变慢、redis 奔溃、redis 被系统杀死、甚至导致系统奔溃。
在新版的 redis 中不会发生这种状况,因为他会自动的断开不符合 client-output-buffer-limit pubsub 配置选项要求的客户端。
所以直接使用 SUBSCRIBE 和 PUBLISH 可能会丢失部分数据。所以并不推荐直接使用它,稍后会有更好的解决方案
相关命令
- SUBSCRIBE
订阅给定的一个或多个频道
SUBSCRIBE channel [channel ...]
- UNSUBSCRIBE
退订给定的一个或多个频道,如果不给定任何频道,则退订所有频道
UNSUBSCRIBE [channel [channel ...]]
- PUBLISH
向给定的频道发送消息
PUBLISH channel message
- PSUBSCRIBE
订阅与给定模式匹配的所有频道
PSUBSCRIBE pattern [pattern ...]
- PUNSUBSCRIBE
退订给定的模式
PUNSUBSCRIBE [pattern [pattern ...]]
一些其他命令
SORT 排序
根据给定的选项,对输入的列表、集合、有序集合进行排序,然后返回或存储排序的结果(默认为升序)
SORT source-key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern...]] [ASC|DESC] [STORE desc-key]
基于 Redis 的事务
Redis 事务不同于关系型数据库那种可以在执行的过程中进行回滚的事务不同。在 Redis 中,被 MULIT 和 EXEC 包围的命令会一个接一个的执行,只有全部执行完。期间 Redis 不会处理其他客户端的命令。
Redis 有五个可以让用户在不被打断的情况下对多个键执行操作的命令:WATCH、MULTI、EXEC、UNWATCH、DISCARD。Redis 事务需要用到 MULIT 和 EXEC 命令。
在 Python 中使用:
// 在 Python 中使用 redis-py 来操纵 Reids
import redis
// 创建 redis 链接
redis_conn = redis.Redis(host='localhost', port=6379, db=0)
// 创建一个事务流
pipeline = redis_conn.pipeline()
// 向事务中添加命令
pipeline.incr('trans')
pipeline.incr('trans')
pipeline.incr('trans',-1)
// 终止事务
pipeline.execute()
键的过期时间
对于列表、集合、散列、有序集合来说,过期时间只能为整个结构设置,不能对结构中的某个元素设置过期时间。(要对单个元素设置过期时间,可以采用使用存储了时间戳的有序集合来实现)
相关命令:
- PERSIST
移除键的过期时间
PERSIST key-name
- TTL
查看给定键距离过期还有多久
TTL key-name
- EXPIRE
让给定键在指定的秒数后过期
EXPIRE key-nam seconds
- EXPIREAT
将给定键的过期时间设置为 UNIX 时间戳
EXPIREAT key-name timestamp
- PTTL
查看给定键距离过期还有多少毫秒
PTTL key-name
- PEXPIRE
让给定键在指定的毫秒数后过期
PEXPIRE key-nam seconds
- PEXPIREAT
将给定键的过期时间设置为毫秒级精确的 UNIX 时间戳
PEXPIREAT key-name timestamp
持久化存储
Redis 提供了两种不同的持久化方法来讲数据存储到硬盘。
- 快照(snapshotting)
- 只追加文件(append-only-file AOF)
# 快照持久化配置
save 60 100
stop-writes-on-bgsave-error no
rdbcompression yes
dbfilename dumo.rdb
# AOF 持久化配置
appendonly no
appendfsync everysec
no-appendfsync-no-rewrite no
auto-aof-rewirte-percentage 100
auto-aof-rewirte-min-size 64mb
dir ./
快照存储
当客户端向 redis 发送 BGSAVE 时,redis 将创建一个子进程来完成将内存中的数据以快照的形式存储到硬盘的操作。当子进程开始执行时与主进程共享数据部分占用的内存,当有新的数据更改时(包括新增)主进程会在新的内存空间上存储。
触发创建快照的几种方式:
- 客户端向 redis 发送 BGSAVE 命令(windows 平台不支持),之后 redis 创建子进程来创建快照,主进程继续处理命令请求
- 客户端向 redis 发送 SAVE 命令。redis 接收到 SAVE 命令时将开始创建快照,直至快照创建完成,redis 不再处理新的命令。这种方式会使 redis 服务挂起,所以在业务无法承受时不能采用这种方式。其通常在没有更多的内存来调用 BGSAVE 时使用。
- 通过配置文件:save 900 1。这一条配置命令指定了。当距离上次创建快照的时间大于 900 秒,而且在此期间有至少 1 次的写入操作的情况下,redis 服务自动的触发 BGSAVE 命令。(可以配置多个 save 命令,在满足任意一个的条件下都会触发)
- 当通过 SHUTDOWN 命令来关闭 redis 时,或者 redis 接收到 TERM 信号时,redis 会触发 SAVE 命令,不再执行客户端命令,开始创建快照,在快照创建结束后关闭 redis 服务。
- 当从 redis 连接到主 redis 并向主 redis 发送 SYNC 命令时,如果这时主 redis 没有正在创建快照或者刚刚创建完快照,将立即触发 BGSAVE 操作。
SAVE 相对于 BGSAVE 的优点:
由于不用创建子进程,而且没有与子进程之间的内存竞争(当 redis 占用内存越大是越明显),所以其相对于 BGSAVE 来说处理的更快一些。
BGSAVE 相对于 SAVE 的优点:
SAVE 会导致 redis 服务挂起,但 BGSAVE 不会。
经验之谈:
-
对于真实的硬件、VMWare 虚拟机、KVM虚拟机 redis 进程每占用 1 GB 内存,其创建子进程的时间将增加 10-20 毫秒。对于 Xen 虚拟机来说,redis 进程占用内存每增加 1 GB,创建子进程的时间将增加 200-300 毫秒。
-
当 redis 中需要存放大量的数据时,为了避免 redis 自动触发 BGSAVE 导致 redis 频繁的停顿,可以考虑关闭自动 BGSAVE,通过手动的方式来触犯 BGSAVE 或 SAVE,这样可以控制 redis 在何时停顿。
AOF 持久化
何时同步的 AOF?
对于 AOF 持久化配置文件中的 appendfsync 配置项,有三个可选的配置选项:
- always
当配置 appendfsync always 时,每一天 redis 写命令都会写入到硬盘,虽然这样可以将在系统奔溃是导致的数据丢失减小到最少,但是z这种同步策略会降低 redis 的速度。(机械硬盘大约每秒可以处理 200 个写命令,SSD 大约每秒可以处理几万个命令,而且会大大降低 SSD 的寿命) - everysec
每秒同步一次,比较推荐的一种同步策略,发生故障时不会丢失大量数据。 - no
有操作系统来决定何时对 AOF 文件同步,不确定会丢失的数据量,不推荐的一种同步策略。
优化 AOF 文件
随着时间的推移,AOF 文件会越来越大,可以通过向 redis 发送 GBREWRITEAOF 命令来移除冗余的 AOF 文件。
也可通过配置文件来使 redis 自动的触发 GBREWRITEAOF 命令。可以通过两个配置项:
- auto-aof-rewrite-percentage
- auto-aof-rewrite-min-size
举例:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 60mb
// 当 AOF 文件大于 60mb,而且比上次重写 AOF 时体积大 100% 时调用 GBREWRITEAOF
分布式的 Redis
在 redis 中可以通过配置 slaveof ip port 命令来配置其复制的主服务器的 ip 和 port,其被称为从服务器。从服务器还可以作为其他 redis 服务器的主服务器。在配置分布式服务器的时比较推荐的方案是采用树形结构,尽量不要使用链型结构或点状结构。
对于已经配置了主服务器的从服务器,更改主服务器相关的两个命令:
// 终止复制,不在接收主服务器发送来的更新
SLAVEOF no one
// 配置一个新的主服务器。会清楚已有的所有数据
SLAVEOF host port
主从服务器的链接过程
当从服务器链接至主服务器时,主服务器会调用 BGSAVE 命令来生成快照,然后将快照发送给从服务器来共从服务创建数据。在主服务器开始创建快照到从服务器创建完成的这段时间内,主服务器收到的新的写命令会放入缓冲区,当从服务器创建完毕后主服务器将缓冲区的命令发送给同服务器,至此完成主从链接。在此之后对主服务器的每一个写命令都会实时的得到更新。
步骤 | 主服务器操作 | 从服务器操作 |
---|---|---|
1 | (等待命令进入) | 连接主服务器,发送 SYNC 命令 |
2 | 开始执行 BGSAVE ,并使用缓冲区记录 BGSAVE 之后执行的所有写命令 | 根据配置选项来决定是继续使用现有数据来处理客户端的命令请求,还是向发送请求的客户端返回错误 |
3 | BGSAVE 完毕,向从服务器发送快照文件,并在发送期间继续使用缓冲区记录被执行的命令 | 丢弃所有旧的数据,开始载入主服务器发送来的快照文件 |
4 | 快照文件发送完毕,开始向从服务器发送缓冲器的命令 | 完成对快照文件的解释操作,像往常一样开始接受命令请求 |
5 | 缓冲区的命令发送完毕;从此时起,没执行一个写命令,就像从服务器发送相同的写命令 | 执行主服务器发来的所有存在缓冲区的写命令,并从此时起,接受并执行主服务器传来的每个写命令 |
当多个从服务器连接到同意主服务器时
当多个从服务器连接到同意主服务器时,当主服务器接受到第一个从服务器的 SYNC 命令时,如果未完成了快照文件的创建时接受到新的从服务器 SYNC ,会向这些从服务器发送这一相同的快照文件。如果主服务器已经完成了快照文件的创建,将对后续的 SYNC 执行一个完成的连接步骤。
检验硬盘写入
检查从服务器是否同步到主服务器的命令
在向主服务器写入真正的数据之后,再向主服务器写入一个唯一的虚构值,然后通过检查虚构值是否存在于从服务器中来判断是否完成同步。
相对主从同步的检查,判断是否已经存入到硬盘要困难一些。
1、对于每秒同步一次 AOF 文件的 redis 服务器来说,总是可以通过等待 1 秒来确保数据已经存入了硬盘。但是这种方法总是需要 1 秒的时间。
2、可以通过检查 INFO 命令的输出结果中 aof_pending_bio_fsync 的属性值是否为 0 来判断
def wait_for_sync(mconn,sconn):
identifier = srt(uuid.uuid4)
# 将数据添加至主服务器
mconn.zadd('synv:wait',identifier,time.time())
# 确保从服务器已经连接到主服务器
while not sconn.info()['master_link_status'] != 'up':
time.sleep(.001)
# 确保从服务器接收到数据
while not sconn.zscore('synv:wait',identifier):
time.sleep(.001)
# 设置最大等待时间
deadline = time.time() + 1.01
while time.time() < deadline:
# 检查 info 中的 aof_pending_bio_fsync 是否为 0
# INFO 命令提供了大量的与 Redis 服务器当前状态有关的信息,比如内存占用,客户端连接数,每个数据库包含的键的数量,上一次创建快照文件后执行的命令数量等
if sconn.info()['aof_pending_bio_fsync'] == 0:
break
time.sleep(.001)
mconn.rem('synv:wait',identifier)
mconn.zremrangebyscore('synv:wait',0,time.time()-900)
###处理系统故障
当系统发生故障时,redis 提供了两个命令来检查快照文件或 AOF 文件的状态,并在需要的时候对其进行修复。
# redis-check-aof 和 redis-check-dump,单独输入命令可以查看其用法
redis-check-aof
Usage: redis-check-aof [--fix] <file.aof>
# 如果提供了 --fix 参数,程序将尝试对 aof 文件进行修复(查找到出错的行,删除之后的)
redis-check-dump
Usage: redis-check-dump <dump.rdb>
目前并没有办法修复出错的快照文件。
更换故障主服务器
1、从未故障的服务器 SAVE 一个快照文件,发送的新的服务器,使新的服务器从此快照文件启动。然后将新服务器作为主服务器(快照文件目录参考:/var/local/redis/dump.rdb)
2、将原有的从服务器升级(turn)为主服务器
可以使用 Redis Sentinel,其可以监视指定的 Redis 主服务器及其属下的从服务器,并在主服务器下线时自动进行故障转移。
###Redis 事务
Redis 的锁不同与关系型数据库的事务锁。Redis 的锁主要只有在 WATCH 监视的数据被其他客户端修改后才会通知 WATCH,这种锁被称为乐观锁
Redis 的事务以 MULTI 开始,以 EXEC 结束。其间输入的命令不会立即提交到服务器,客户端会在接收到 EXEC 命令时,将命令打包发送给服务器,这样做性能更高。但是导致了,在事务中通过 读取到的数据来做决定是不可靠的,因为它并不是最终提交时的状态。
因为事务的处理需要过程,可能在事务执行过程中,数据的状态会由于其他客户端的操作发生改变,所有导致一个意外的结果。所以在执行的过程中,需要使用 WATCH 来对数据进行监视。在使用 WATCH 对数据进行监视后,直到用户执行 EXEC 命令的这段时间,如果数据发生数据发生改变,在执行 EXEC 时,事务将失败并抛出一个错误。(通过 WATCH 监视的数据,也可以通过 UNWATCH、DISCORD 来取消监视)
非事务性流水线
在 redis-py 中通过调用 pipeline() 会发起一个事务,期间的命令 在 redis 端会通过 MULIT 和 EXEC 包裹,如果使用 pipeline(False) 之前的命令不会通过 MULIT 和 EXEC 包裹,但是也会一次性提交到 redis 服务器。
为什么不总是用 pipeline(Ture)?
因为 pipeline(Ture) 间的命令会被 MULIT 和 EXEC 包裹,在服务器端连续执行时会阻塞其他的客户端,影响性能。所以对于没有不需要连续执行的命令采用 非事务性流水线,只是一次性的提交到服务器,然后顺序执行,并不阻塞其他的客户端,这样性能更高。
关于性能方面的注意
redis 提供了 redis-benchmark 命令来测试 redis 的速度
redis-benchmark -c 1 -q
# -c 1 限定只使用一个客户单来测试
# -q 简化输出结果
在使用 redis-benchmark 测试时,切记不要讲输出结果看做应用程序的实际性能。
性能或者错误 | 可能的原因 | 解决方法 |
---|---|---|
单个客户端的性能到达 redis-benchmark 的50%~60% | 不使用流水线时的预期性能 | 无 |
单个客户端的性能到达 redis-benchmark 的25%~30% | 对于每个命令或者每组命令都建立的新的连接 | 重用已有的 redis 连接 |
客户端返回错误“Cannot assign requested address”(无法分配指定的地址) | 对于每个命令或者每组命令都建立的新的连接 | 重用已有的 redis 连接 |
至此结束,欢迎一起浏览探讨~