一、Redis介绍
Redis 是一款开源的,基于 BSD 许可的,高级键值 (key-value) 缓存 (cache) 和存储 (store) 系统。由于 Redis 的键包括 string,hash,list,set,sorted set,bitmap 和 hyperloglog,所以常常被称为数据结构服务器。
你可以在这些类型上面运行原子操作,例如,追加字符串,增加哈希中的值,加入一个元素到列表,计算集合的交集、并集和差集,或者是从有序集合中获取最高排名的元素。
为了满足高性能,Redis 采用内存 (in-memory) 数据集 (dataset)。根据你的使用场景,你可以通过每隔一段时间转储数据集到磁盘,或者追加每条命令到日志来持久化。
Redis 还支持主从异步复制,非常快的非阻塞初次同步、网络断开时自动重连局部重同步。 特性包括:
- 事务
- 持久化
- 主从异步复制
- 非常快的非阻塞初次同步
- 网络断开时自动重连局部重同步
- 订阅/发布
- Lua 脚本
- 带 TTL 的键
- LRU 回收键
- 自动故障转移 (failover)
- 等等
你可以通过多种语言来使用 Redis。
Redis 是由 ANSI C 语言编写的,在无需额外依赖下,运行于大多数 POSIX 系统,如 Linux、*BSD、OS X。Redis 是在 Linux 和 OS X 两款操作系统下开发和充分测试的,我们推荐 Linux 为部署环境。Redis 也可以运行在 Solaris 派生系统上,如 SmartOS,但是支持有待加强。没有官方支持的 Windows 构建版本,但是微软开发和维护了一个 64 位 Windows 的版本。
二、数据类型
2.1 key-value
1、关于键:
api文档: http://www.redis.cn/commands.html#generic
- 二进制安全
- 包含空格
- 最大512M
- 不要太长,太长可以考虑md5或者其他方式提取特征码
- 不要太短而失去可读性: 例如user:1000:follower就比较合适
- 命名风格应该统一
- 支持键过期策略,精确到毫秒
- 支持键空间
2、关于value
支持以下几种:
- 二进制安全 (binary-safe) 的字符串。
- 列表:按照插入顺序排序的字符串元素 (element) 的集合 (collection)。通常是链表。
- 集合:唯一的,无序的字符串元素集合。
- 有序集合:和集合类似,但是每个字符串元素关联了一个称为分数 (score) 的浮点数。元素总是按照分数排序,所以可以检索一个范围的元素 (例如,给我前 10,或者后 10 个元素)。
- 哈希:由字段 (field) 及其关联的值组成的映射。字段和值都是字符串类型。这非常类似于 Ruby 或 Python 中的哈希 / 散列。
- 位数组 (位图):使用特殊的命令,把字符串当做位数组来处理:你可以设置或者清除单个位值,统计全部置位为 1 的位个数,寻找第一个复位或者置位的位,等等。
- 超重对数 (HyperLogLog):这是一个用于估算集合的基数 (cardinality,也称势,译者注) 的概率性数据结构。不要害怕,它比看起来要简单,稍后为你揭晓。
2.2 字符串String
http://www.redis.cn/commands.html#string
- 二进制安全的value
- 数值类型支持原子增原子减
- 支持创建时指定过期时间
- ...
2.3 列表List
http://www.redis.cn/commands.html#list
- 是一种双端链表,因此可以是栈,也可以是队列
- 支持阻塞操作
- 支持上限列表: Capped
什么时候用列表:
以例子来说明:
- 比如可以要记录用户最近提交的更新,把列表当做栈使用
- 2种流行的Ruby库,resqu和sidekiq,都是使用Redis的列表当做钩子,来实现后台作业,思路是把List当做一个中间件,生成者往里面添加项,而消费者消费并且执行任务。
我们以一些例子来说明list结构是如何自动创建key和删除的。
插入3个元素:自动创建了mylist
lpush mylist 1 2 3
全部删除,自动删除了 mylist
192.168.0.211:6379> lpop mylist "3" 192.168.0.211:6379> lpop mylist "2" 192.168.0.211:6379> lpop mylist "1" 192.168.0.211:6379> lpop mylist (nil) 192.168.0.211:6379> exists mylist (integer) 0
2.4 哈希Hash
http://www.redis.cn/commands.html#hash
- 哈希就是字段值对(fields-values pairs)的集合,即看成Map。
- 拥有少量字段 (少量指的是大约 100) 的哈希会以占用很少存储空间的方式存储,所以你可以在一个很小的 Redis 实例里存储数百万的对象。
- 每个哈希可以存储多达多于 40 亿个字段值对 (field-value pair)。
作为最常用的redis结构,一般有2种设计的思路,多个key和单个key。
多个Key的大概设计:
hmset user:1000 username antirez birthyear 1977 verified 1 hmset user:1001 username jerry birthyear 1971 verified 1
单个Key的大概设计:
hset users 1000 "{username:antirez,birthyear:1977,verified:1}" hset users 1001 "{username:jerry,birthday:1971,verified:2}"
2.5 无序集合Set
http://www.redis.cn/commands.html#set
- 无序字符串
- 不允许重复
- 支持集合操作,交集、并集、差集
2.6 有序集合
http://www.redis.cn/commands.html#sorted_set
关于顺序:
- 如果 A 和 B 是拥有不同分数的元素,A.score > B.score,则 A > B。
- 如果 A 和 B 是有相同的分数的元素,如果按字典顺序 A 大于 B,则 A > B。A 和 B 不能相同,因为排序集合只能有唯一元素。(2.8新特性)
特点:
- 通过双端数据结构实现,包括了跳表(skiplist)和哈希表(hashtable),所以每次添加时候执行O(log(N))的操作。
- 有序,不是请求时才排序的,顺序是依赖于表示有序集合的数据结构。
三、使用twitter演示上述结构
这是一个简单的例子,只包含4张表:用户(user)、关注(following)、粉丝(followers)、帖子(updates)
1. 用户:
#1. 使用字符串产生唯一user_id INCR next_user_id => 1000 #2. 使用hash结构的第一种设计来存储用户名密码 HMSET user:1000 username antirez password p1pp0
如果要支持getUserIdByUsername(String username)类似的操作:
# 使用hash结构一个key方式存储username->id HSET users antirez 1000
2. 粉丝和关注:
每个用户可能有多个粉丝,用户->粉丝的映射关系可以使用有序集合来存储,使用时间来作为score
# user1000的粉丝增加了一个是:user1234,时间是...ZADD followers:1000 1401267618 1234
关注也一样:
# user1000也可以关注对方1234 ZADD followering:1000 1401267618 1234
3. 帖子
这里不搞的太麻烦,帖子就是唯一的更新,也就是posts
这里最适合的结构是列表List,使用栈,把最新的放在栈顶
再利用列表的LLEN和LRange进行排序,这里不演示:
posts:1000 => a List of post ids - every new post is LPUSHed here.
四、持久化机制
redis和memcached最大不同之处就在于redis既可以做缓存,又可以做存储。存储便依赖于redis的持久化机制。
支持2种:rdb和aof。
4.1 rdb
1、什么是rdb:
- rdb就是使用快照(Snapshotting)技术保存数据集。
- 也就是说rdb是基于时间点的数据备份!
- 默认情况下,redis使用rdb的机制进行持久化,名为dump.rdb的二进制文件。
- 可以设置N秒之内至少M次改动时保存数据集。设置如"
save 60 1000
",亦可手动SAVE或者BGSAVE命令。
2、工作原理:
- redis调用fork()创建子进程c1。
- 利用copy-on-write机制,c1开始将数据集写入一个临时rdb文件。
- c1完成之后替换掉旧文件。
3、rdb优点:
- 基于时间点,适合用于备份
- 写入之后不能修改,因此可以复制,适合作为容灾技术传递给远程数据中心,例如S3
- RDB性能不错,因为只需要fork()一个子进程
- redis实例重启时基于RDB比基于AOF更快
4、rdb缺点:
- 基于时间点,有数据间隙,间隙数据有丢失的可能性
- fork()子进程如果太频繁、如果数据集较大且CPU性能不强的话,会出事故,甚至停止服务。
4.2 aof
1、什么是aof:
- Append only file,即只追加文件,顺序io
- 将修改redis的数据集命令不断追加
- aof支持每个命令都追加、每s追加、以及不主动追加依赖于操作系统三种方式
- 建议是每秒1次fsync操作
2、aof原理:
- redis进程,假设是p进程,调用fork,创建了一个子进程c
- c开始向一个临时文件写aof文件
- p在内存缓冲区积累这段时间内的新变更(同时将新的变更写入旧的aof文件以确保安全)
- 当c写完之后,给p发送一个信号,p把缓冲区的内容添加到aof文件的末尾
- redis自动重命名aof文件,替换老的为新的
3、aof优点:
- 更安全,如果使用每秒的策略,那最多就丢失1s的数据
- 追加文件,容易修复,安全,redis还有专门的修复工具
- aof内容容易理解
- aof文件支持自动重写以控制文件size
4、aof缺点:
- 作为备用策略,aof文件通常比rdb大的多
- aof安全,但是慢
- aof针对特殊命令有罕见bug
4.3 如何选择
- 如果你同时使用2种,可以达到和PostgreSQL提供的数据安全程度!
- 如果完全不关注,可以把AOF和RDB都关闭了,单纯作为缓存而不是存储!
- 如果你关注数据,需要存储,但是可以接收几分钟的数据丢失,可以只使用RDB!
- 如果你想要单独使用AOF,不鼓励,因为数据必须要备份,备份就需要RDB,而且避免了AOF的BUG,宁愿2个都用!
4.4 如何备份
Redis 对数据备份非常友好,因为你可以在数据库运行时拷贝 RDB 文件:RDB 文件一旦生成就不会被修改,文件生成到一个临时文件中,当新的快照完成后,将自动使用 rename(2) 原子性的修改文件名为目标文件。
这意味着,在服务器运行时拷贝 RDB 文件是完全安全的。以下是我们的建议:
- 创建一个定时任务(cron job),每隔一个小时创建一个 RDB 快照到一个目录,每天的快照放在另外一个目录。
- 每次定时脚本运行时,务必使用 find 命令来删除旧的快照:例如,你可以保存最近 48 小时内的每小时快照,一到两个月的内的每天快照。注意命名快照时加上日期时间信息。
- 至少每天一次将你的 RDB 快照传输到你的数据中心之外,或者至少传输到运行你的 Redis 实例的物理机之外。
4.5 如何修复AOF文件
有可能在写 AOF 文件时服务器崩溃(crash),文件损坏后 Redis 就无法装载了。如果这个发生的话,你可以使用下面的步骤来解决这个问题:
- 创建 AOF 的一个拷贝用于备份。
- 使用 Redis 自带的 redis-check-aof 工具来修复原文件:
- $ redis-check-aof --fix
- 使用 diff -u 来检查两个文件有什么不同。用修复好的文件来重启服务器。
五、复制
1、redis复制的特性:
一种使用和配置都非常简的主动(master-slave)复制,允许Redis从服务器成本主服务器的精确副本。
- 异步复制:2.8开始,从服务器会周期性的报告从复制流中处理的数据里的数据量。一个主服务器可以拥有多个从服务器。
- 支持复杂结构,master->s1->s2....
- 主服务器复制的时候master是非阻塞的。
- Redis的复制在slave上也是非阻塞的。
- 通过在slave上redis.conf中进行相应的配置,slave也能够继续使用旧版本的数据集处理请求。可以配置当复制流down掉的时候,从服务器返回给客户端一个error。然后,初始化同步结束后,旧的数据集需要被删除,新的数据集需要被载入。在这个简短的窗口期内,从服务器会阻塞到来的连接。
- 复制可以用来支持可伸缩性,用多个slave处理只读查询(例如,繁重的SORT操作可以分配到从服务器上),也可以仅仅用来作为数据冗余。
- 可以使用复制来避免主服务器将全部数据集磁盘的开销:只需要配置你的主服务器的redis.conf来防止保存(所有的"保存"指令),然后连接一个不断复制的从服务器。但是,这种设置要确保主服务器不会自动重启。
2、使用redis主从要注意安全性:即数据不丢失
(1).我们设置节点 A 作为主服务器,关闭了持久化,节点 B 和节点 C 从节点 A 复制。
(2).A 崩溃了,但是它拥有某个自动重启系统,重启了这个进程。但是,由于持久化是被关闭的,这个节点以空的数据集重启。
(3). 节点 B 和节点 C 从空的 A 复制,于是它们完全销毁了他们的数据拷贝。
当开启了哨兵时,关闭主服务器的自动重启就明智的,防止哨兵没有检测到Master的自动重启。
3、redis复制的原理
- 当slave连接master的时候,不管是第一次还是重新连接上,连接发送一个SYNC命令。
- 主服务器开始在后台保存,并且开始缓冲所有新收到的会修改数据集的命令。
- 主服务器保存完之后,传送一份数据库文件给从服务器,从服务器保存到磁盘并且加载到内存。
- 主服务器发送缓冲命令给从服务器。
- 是通过命令流完成的,和Redis协议是一样的格式。
- 可以用telnet给一台正在工作的Redis端口发送 SYNC命令,然后会看到大量的传输。
- 当主从链路断开,从服务器可以自动重连。如果主服务器收到多个并发的同步请求,只会执行一个后台来保存服务所有的服务器。
- 当重连后,总是执行一个全量同步,在2.8之后,可以选择执行部分同步.(partial resynchronization)
4、支持部分重同步(partial resynchronization)
即在复制中途断开,再重连之后,可以继续复制过程,而不需要一次完整的重新同步。
通过在主服务器上创建一个复制流的内存缓冲区(in-memory backlog)实现。主服务器和所有从服务器记录一个复制偏移量(offset)和一个主服务器的ID(run id),当链接断掉时,从服务器会重链接,并且请求服务器继续恢复复制。
如果满足以下条件:
- 主服务器的运行ID一样
- 指定的偏移量offset在复制缓冲区中可用
就可以继续增量复制,否则执行完整重同步。
5、无盘复制,试验性:(Diskless replication)
完整的全量同步需要在磁盘上创建一个RDB文件,然后从磁盘加载同一个RDB给从服务器,性能一般。
因此在2.8.18版本后试验性的支持无盘复制。
原理是:子进程通过线路(write)发送RDB给从服务器,而且不需要使用磁盘作为中间存储。
6、 默认slave 是read-only的:
在2.6版本在在之后默认从服务器默认开启只读模式。
这个行为由conf中的slave-read-only所控制
而且支持运行时通过CONFIG SET修改,在某些failover策略中关闭read-only使用有意义的。
举个例子:可以考虑使用传统的高可用模型来建立架构,比如说使用keep-alived来保持高可用,这就需要slave也支持写模式了。
7、支持密码验证
8、支持N个副本才可以写的机制:
即在master上配置一个健康监测机制,当成功检测到有N个健康的slave才允许写操作。
在Redis主服务器上设置:
min-slaves-to-write <number of slaves>
min-slaves-max-lag <number of seconds>
(1) Redis主服务器每秒钟Ping主服务器,上报处理完的复制流的数据量。
(2) Redis主服务器记录上一次从每一个从服务器中收到ping 的时间,健康监测
(3) 用户配置最小的从服务器数量,每台从服务器拥有一个不大于最大秒数的滞后lag.