Redis学习之数据结构与持久化(一)

API的使用

通用命令

1)keysios

遍历全部key。redis

可使用keys he*遍历全部以he开头的键。数据库

使用方式:热备从节点,scan。编程

2)dbsize缓存

计算key的总数。安全

3)existsbash

检查key是否存在。服务器

4)del key网络

删除指定的key-value。数据结构

5)expire key seconds

设置key的过时时间。

6)ttl key

查看key剩余的过时时间。

7)persist key

去掉key的过时时间。

8)type key

查看key的类型。

速度快的缘由

1)纯内存。

2)非阻塞IO。

3)避免线程切换和静态消耗。

数据结构和内部编码

String

Redis String类型是能够与Redis键关联的最简单的值类型。它是Memcached中惟一的数据类型,所以新手在Redis中使用它也很天然。

因为Redis键是字符串,当咱们使用字符串类型做为值时,咱们将字符串映射到另外一个字符串。字符串数据类型对于许多用例颇有用,例如缓存HTML片断或页面。

set命令

set:无论key是否存在,都设置。

setnx:key不存在,才设置。

set key value xx:key存在,才设置。

mget、mset命令

mget key1 key2 key3,....:批量获取key,原子操做。

mset key1 value1 key2 value2 key3 value3:批量设置key-value。

HASH

其实是一个map->map的结构,能够很方便的存储Java类。Redis哈希看起来正是人们可能指望看到一个“哈希”,使用字段值对:

> hmset user:1000 username antirez birthyear 1977 verified 1
OK
> hget user:1000 username
"antirez"
> hget user:1000 birthyear
"1977"
> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"
5) "verified"
6) "1"
复制代码

相关命令:

hget、hset、hdel、hesists、hlen、hmget、hmset。

List

所述LPUSH命令将一个新元素到一个列表,在左侧(在头部),而RPUSH命令将一个新元素到一个列表,在右侧(在尾部)。最后, LRANGE命令从列表中提取元素范围:

> rpush mylist A
(integer) 1
> rpush mylist B
(integer) 2
> lpush mylist first
(integer) 3
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
复制代码

请注意,LRANGE须要两个索引,即要返回的范围的第一个和最后一个元素。两个索引均可以是负数,告诉Redis从结尾开始计数:因此-1是最后一个元素,-2是列表的倒数第二个元素,依此类推。

正如您所见,RPUSH附加了列表右侧的元素,而最后的LPUSH附加了左侧的元素。

Redis列表中定义的一个重要操做是弹出元素的能力。弹出元素是从列表中检索元素并同时从列表中删除元素的操做。您能够从左侧和右侧弹出元素,相似于如何在列表的两侧推送元素:

> rpush mylist a b c
(integer) 3
> rpop mylist
"c"
> rpop mylist
"b"
> rpop mylist
"a"
复制代码

Capped lists(上限列表)

在许多用例中,咱们只想使用列表来存储最新的项目,不管它们是什么:社交网络更新,日志或其余任何内容。

Redis容许咱们使用列表做为上限集合,只记住最新的N项并使用LTRIM命令丢弃全部最旧的项。

LTRIM命令相似于LRANGE,可是**,而不是显示元件的指定范围**它设置在该范围做为新的列表值。超出给定范围以外的全部元素。

一个例子将使它更清楚:

> rpush mylist 1 2 3 4 5
(integer) 5
> ltrim mylist 0 2
OK
> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"
复制代码

上面的LTRIM命令告诉Redis只从索引0到2中获取列表元素,其余全部内容都将被丢弃。这容许一个很是简单但有用的模式:一块儿执行List推操做+ List修剪操做以添加新元素并丢弃超出限制的元素:

LPUSH mylist <some element>
LTRIM mylist 0 999
复制代码

上面的组合添加了一个新元素,而且只将1000个最新元素放入列表中。使用LRANGE,您能够访问顶级项目,而无需记住很是旧的数据。

注意:虽然LRANGE在技术上是一个O(N)命令,可是访问列表的头部或尾部的小范围是一个恒定时间操做。

Set

Redis集是字符串的无序集合。该 SADD命令添加新的元素到set。对于集合执行许多其余操做也是可能的,例如测试给定元素是否已存在,执行多个集合之间的交集,并集或差别等等。

> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2
复制代码

在这里,我已经为个人集合添加了三个元素,并告诉Redis返回全部元素。正如您所看到的那样,它们没有排序 - Redis能够在每次调用时以任意顺序返回元素,由于与用户没有关于元素排序的合同。

Redis有命令来测试会员资格。例如,检查元素是否存在:

> sismember myset 3
(integer) 1
> sismember myset 30
(integer) 0
复制代码

Sorted sets

排序集是一种数据类型,相似于Set和Hash之间的混合。与集合同样,有序集合由惟一的,非重复的字符串元素组成,所以在某种意义上,有序集合也是一个集合。

可是,虽然内部集合中的元素没有排序,可是有序集合中的每一个元素都与浮点值相关联,称为分数 (这就是为何类型也相似于散列,由于每一个元素都映射到一个值)。

此外,排序集合中的元素按顺序排列(所以它们不是根据请求排序的,顺序是用于表示排序集合的数据结构的特性)。它们按照如下规则订购:

  • 若是A和B是两个具备不一样分数的元素,若是A.score> B.score,那么A> B。
  • 若是A和B具备彻底相同的分数,若是A字符串按字典顺序大于B字符串,则A> B。A和B字符串不能相等,由于有序集只有惟一元素。

让咱们从一个简单的例子开始,添加一些选定的黑客名称做为有序集合元素,其出生年份为“得分”。

> zadd hackers 1940 "Alan Kay"
(integer) 1
> zadd hackers 1957 "Sophie Wilson"
(integer) 1
> zadd hackers 1953 "Richard Stallman"
(integer) 1
> zadd hackers 1949 "Anita Borg"
(integer) 1
> zadd hackers 1965 "Yukihiro Matsumoto"
(integer) 1
> zadd hackers 1914 "Hedy Lamarr"
(integer) 1
> zadd hackers 1916 "Claude Shannon"
(integer) 1
> zadd hackers 1969 "Linus Torvalds"
(integer) 1
> zadd hackers 1912 "Alan Turing"
(integer) 1
复制代码

正如您所看到的,ZADD与SADD相似,可是须要一个额外的参数(放在要添加的元素以前),即分数。 ZADD也是可变参数,所以您能够自由指定多个得分 - 值对,即便在上面的示例中未使用它。

对于排序集,返回按出生年份排序的黑客列表是微不足道的,由于实际上它们已经排序了。

实现说明:排序集是经过包含跳过列表和散列表的双端口数据结构实现的,所以每次添加元素时,Redis都会执行O(log(N))操做。这很好,可是当咱们要求排序的元素时,Redis根本不须要作任何工做,它已经所有排序了:

> zrange hackers 0 -1
1) "Alan Turing"
2) "Hedy Lamarr"
3) "Claude Shannon"
4) "Alan Kay"
5) "Anita Borg"
6) "Richard Stallman"
7) "Sophie Wilson"
8) "Yukihiro Matsumoto"
9) "Linus Torvalds"
复制代码

若是我想以相反的方式订购它们,最小到最老的怎么办?使用ZREVRANGE而不是ZRANGE:

> zrevrange hackers 0 -1
1) "Linus Torvalds"
2) "Yukihiro Matsumoto"
3) "Sophie Wilson"
4) "Richard Stallman"
5) "Anita Borg"
6) "Alan Kay"
7) "Claude Shannon"
8) "Hedy Lamarr"
9) "Alan Turing"
复制代码

使用WITHSCORES参数也能够返回分数:

> zrange hackers 0 -1 withscores
1) "Alan Turing"
2) "1912"
3) "Hedy Lamarr"
4) "1914"
5) "Claude Shannon"
6) "1916"
7) "Alan Kay"
8) "1940"
9) "Anita Borg"
10) "1949"
11) "Richard Stallman"
12) "1953"
13) "Sophie Wilson"
14) "1957"
15) "Yukihiro Matsumoto"
16) "1965"
17) "Linus Torvalds"
18) "1969"
复制代码

在切换到下一个主题以前,只须要关于排序集的最后一点。排序集的分数能够随时更新。仅针对已经包含在已排序集合中的元素调用ZADD将使用O(log(N))时间复杂度更新其得分(和位置)。所以,当有大量更新时,排序集合是合适的。

因为这个特性,常见的用例是排行榜。典型的应用程序是一个Facebook游戏,在这个游戏中,您能够结合使用高分进行排序的用户以及获取排名操做,以显示前N个用户以及排行榜中的用户排名(例如,“你是这里#4932的最佳成绩“)。

配置建议

maxToTal

理论值=命令平均执行时间*业务总QPS数。

maxIdle&&minIdle(最大空闲数与最少空闲数)

1)maxIdle=maxTotal,减小建立新链接的开销。

2)预热minIdle,减小第一次启动后的新链接的开销。

redis经常使用功能

慢查询

生命周期

1)慢查询发生在第三阶段。

2)客户端超时不必定慢查询,但慢查询是客户端超时的一个可能因素。

两个配置

1)slowlog-max-len

1:先进先出队列

2:固定长度

3:保存在内存内

2)slowlog-log-slower-than

慢查询阈值(单位:微秒)

命令

1)slowlog get [n]:获取慢查询队列。

2)slowlog len:获取慢查询队列长度。

3)slowlog reset:清空慢查询队列。

运维经验

1)slowlog-max-len不要设置太小,一般设置1000左右。

2)slowlog-log-slower-than不要设置过大,默认10ms,一般设置1ms。

3)理解生命周期。

4)按期持久化慢查询。

Pipeline

Redis是使用客户端 - 服务器模型和所谓的请求/响应协议的TCP服务器。

这意味着一般经过如下步骤完成请求:

  • 客户端向服务器发送查询,并一般以阻塞方式从套接字读取服务器响应。
  • 服务器处理该命令并将响应发送回客户端。

例如,四个命令序列是这样的:

  • 客户: INCR X.
  • 服务器: 1
  • 客户: INCR X.
  • 服务器: 2
  • 客户: INCR X.
  • 服务器: 3
  • 客户: INCR X.
  • 服务器: 4

客户端和服务器经过网络连接链接。这样的连接能够很是快(环回接口)或很是慢(在因特网上创建的链接,在两个主机之间有许多跳)。不管网络延迟是什么,数据包都有时间从客户端传输到服务器,而后从服务器返回到客户端以进行回复。

此时间称为RTT(Round Trip Time)。当客户端须要连续执行许多请求时(例如,将多个元素添加到同一列表或使用多个键填充数据库),很容易看出这会如何影响性能。例如,若是RTT时间是250毫秒(在因特网上的链路很是慢的状况下),即便服务器可以每秒处理100k个请求,咱们也可以以每秒最多四个请求进行处理。

若是使用的接口是环回接口,则RTT要短得多(例如个人主机报告0,044毫秒,ping 127.0.0.1),但若是你须要连续执行屡次写入,它仍然不少。

幸运的是,有一种方法能够改进这个用例。

Redis Pipelining

能够实现请求/响应服务器,以便即便客户端还没有读取旧响应,它也可以处理新请求。这样就能够将多个命令发送到服务器而无需等待回复,最后只需一步便可读取回复。

这被称为流水线技术,而且是几十年来普遍使用的技术。例如,许多POP3协议实现已经支持此功能,大大加快了从服务器下载新电子邮件的过程。

Redis从很早就开始支持流水线操做,所以不管您运行什么版本,均可以使用Redis进行流水线操做。这是使用原始netcat实用程序的示例:

$ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG
复制代码

重要说明:当客户端使用流水线发送命令时,服务器将被强制使用内存对回复进行排队。所以,若是您须要使用流水线发送大量命令,最好将它们做为具备合理数量的批次发送,例如10k命令,读取回复,而后再次发送另外10k命令,依此类推。速度将几乎相同,但使用的额外内存将最大为此10k命令的回复排队所需的数量。

mset与pipeline对比

mset:原子操做。

pipeline:非原子操做。

使用建议

1)注意每次pipeline携带数据量。

2)pipeline每次只能做用在一个Redis节点上。

发布订阅

SUBSCRIBE,UNSUBSCRIBE和PUBLISH 实现了发布/订阅消息传递范例,其中(引用维基百科)发件人(发布者)没有被编程为将其消息发送给特定接收者(订阅者)。相反,发布的消息被表征为信道,而不知道可能存在什么(若是有的话)订户。订阅者表达对一个或多个频道的兴趣,而且仅接收感兴趣的消息,而不知道有哪些(若是有的话)发布者。发布者和订阅者的这种分离能够容许更大的可扩展性和更动态的网络拓扑。

例如,为了订阅频道foo,bar客户端发出提供频道名称的SUBSCRIBE:

SUBSCRIBE foo bar
复制代码

其余客户端发送到这些频道的消息将由Redis推送到全部订阅的客户端。

订阅一个或多个频道的客户端不该发出命令,尽管它能够订阅和取消订阅其余频道。对订阅和取消订阅操做的回复以消息的形式发送,以便客户端能够只读取连贯的消息流,其中第一个元素指示消息的类型。订阅客户端上下文中容许的命令是SUBSCRIBE,PSUBSCRIBE,UNSUBSCRIBE,PUNSUBSCRIBE, PING和QUIT。

请注意,redis-cli在订阅模式下不会接受任何命令,而且只能退出模式Ctrl-C。

消息是具备三个元素的Array回复

第一个元素是消息的类型:

  • subscribe:表示咱们成功订阅了做为回复中第二个元素的通道。第三个参数表示咱们当前订阅的频道数。
  • unsubscribe:表示咱们成功取消订阅做为回复中第二个元素的频道。第三个参数表示咱们当前订阅的频道数。当最后一个参数为零时,咱们再也不订阅任何通道,而且客户端能够发出任何类型的Redis命令,由于咱们在Pub / Sub状态以外。
  • message:它是由另外一个客户端发出的PUBLISH命令收到的消息。第二个元素是原始通道的名称,第三个参数是实际的消息有效负载。

Redis的持久化

Redis提供了不一样的持久性选项:

  • RDB持久性以指定的时间间隔执行数据集的时间点快照。
  • AOF持久性记录服务器接收的每一个写入操做,将在服务器启动时再次播放,重建原始数据集。使用与Redis协议自己相同的格式以仅追加方式记录命令。当Redis太大时,Redis可以重写日志。
  • 若是您愿意,只要服务器正在运行,您就能够根据须要禁用持久性。
  • 能够在同一实例中组合AOF和RDB。请注意,在这种状况下,当Redis从新启动时,AOF文件将用于重建原始数据集,由于它保证是最完整的。

最重要的是要理解RDB和AOF持久性之间的不一样权衡。让咱们从RDB开始:

RDB的优点

  • RDB是Redis数据的一个很是紧凑的单文件时间点表示。RDB文件很是适合备份。例如,您可能但愿在最近24小时内每小时归档您的RDB文件,而且天天保存RDB快照30天。这使您能够在发生灾难时轻松恢复数据集的不一样版本。
  • RDB很是适合灾难恢复,能够将单个压缩文件传输到远端数据中心,也能够传输到Amazon S3(多是加密的)。
  • RDB最大限度地提升了Redis的性能,由于Redis父进程为了坚持不懈而须要作的惟一工做就是分配一个将完成全部其他工做的孩子。父实例永远不会执行磁盘I/O或相似操做。
  • 与AOF相比,RDB容许使用大数据集更快地重启。

RDB的缺点

  • 若是您须要在Redis中止工做时(例如断电后)将数据丢失的可能性降至最低,则RDB并很差。您能够配置生成RDB的不一样保存点(例如,在对数据集进行至少五分钟和100次写入以后,但您能够有多个保存点)。可是,您一般每五分钟或更长时间建立一个RDB快照,所以若是Redis因任何缘由中止工做而没有正确关闭,您应该准备丢失最新的数据分钟。
  • RDB常常须要fork()才能使用子进程持久存储在磁盘上。若是数据集很大,Fork()可能会很是耗时,而且若是数据集很是大且CPU性能不佳,可能会致使Redis中止服务客户端几毫秒甚至一秒钟。AOF也须要fork(),但你能够调整你想要重写日志的频率而不须要对耐久性进行任何权衡。

AOF优点

  • 使用AOF Redis更持久:您可使用不一样的fsync策略:no fsync at all, fsync every second, fsync at every query。使用fsync的默认策略,每秒写入性能仍然很好(使用后台线程执行fsync,而且当没有fsync正在进行时,主线程将努力执行写入。)可是您只能丢失一秒的写入。
  • AOF日志是仅附加日志,所以若是停电,则没有搜索,也没有损坏问题。即便因为某种缘由(磁盘已满或其余缘由)日志以半写命令结束,redis-check-aof工具也可以轻松修复它。
  • 当Redis太大时,Redis可以在后台自动重写AOF。重写是彻底安全的,由于当Redis继续附加到旧文件时,使用建立当前数据集所需的最小操做集生成一个全新的文件,而且一旦第二个文件准备就绪,Redis会切换两个并开始附加到新的那一个。
  • AOF以易于理解和解析的格式一个接一个地包含全部操做的日志。您甚至能够轻松导出AOF文件。例如,即便您使用FLUSHALL命令刷新了全部错误,若是在此期间未执行重写日志,您仍然能够保存数据集,只需中止服务器,删除最新命令,而后从新启动Redis。

AOF的缺点

  • AOF文件一般比同一数据集的等效RDB文件大。
  • 根据确切的fsync策略,AOF可能比RDB慢。通常来讲,fsync设置为every second性能仍然很是高,而且在fsync禁用的状况下,即便在高负载下也应该与RDB同样快。即便在写入负载很大的状况下,RDB仍可以提供有关最大延迟的更多保证。
  • 在过去,咱们遇到了特定命令中的罕见错误(例如,有一个涉及阻塞命令,如BRPOPLPUSH)致使生成的AOF在从新加载时不会重现彻底相同的数据集。这个错误不多见,咱们在测试套件中进行测试,自动建立随机复杂数据集并从新加载它们以检查一切正常,但RDB持久性几乎不可能出现这种错误。为了更清楚地说明这一点:Redis AOF逐步更新现有状态,如MySQL或MongoDB,而RDB快照一次又一次地建立全部内容,这在概念上更加健壮。可是 —— 1)应该注意的是,每次经过Redis重写AOF时,都会从数据集中包含的实际数据开始从新建立,与老是附加的AOF文件(或者重写旧的AOF而不是读取内存中的数据)相比,对bug的抵抗力更强。2)咱们从未向用户提供过关于在现实世界中检测到的AOF损坏的单一报告。

好的,那我该怎么用?

通常的迹象是,若是您但愿必定程度的数据安全性与PostgreSQL为您提供的数据安全性至关,则应使用两种持久性方法。

若是你很是关心你的数据,可是在发生灾难的状况下仍然会有几分钟的数据丢失,你能够单独使用RDB。

有许多用户单独使用AOF,但咱们不鼓励它,由于不时有RDB快照是进行数据库备份,更快重启以及AOF引擎中出现错误的好主意。

注意:因为全部这些缘由,咱们可能最终将AOF和RDB统一为将来的单一持久性模型(长期计划)。

有关详细的服务器配置请查阅我以前的博客。

常见的持久化开发运维问题

改善fork

1)优先使用物理机或者高效支持fork操做的虚拟化技术。

2)控制Redis实例最大可用内存:maxmemory。

3)合理配置Linux内存分配策略:vm.overcommit_memory=1。

4)下降fork频率:例如放宽AOF重写自动触发时机,没必要要的全量复制。

子进程开销和优化

CPU

开销:RDB和AOF文件生成,属于CPU密集型。

优化:不作CPU绑定,不和CPU密集型部署。

内存

开销:fork内存开销,copy-on-write。

优化:echo never > /sys/kernel/mm/transparent_hugepage/enabled。

硬盘

开销:AOF和RDB文件写入,能够结合iostat,iotop分析。

优化:no-appendfsync-on-rewrite = yes

AOF阻塞

定位

1)Redis日志

2)info Persistence

3)硬盘使用状况

相关文章
相关标签/搜索