一文总结之Redis
Redis
标签(空格分隔): 一文总结
学习资料:
《赠送:2015年-某播客Redis3.0新特性、主从复制、集群视频教程》
《赠送:小象学院-Redis从入门到精通》
《Redis 3.0 中文版 - v1.1.epub》
手册
http://www.runoob.com/redis/redis-tutorial.html
http://www.redis.cn/commands.html#
当前学习进度:D:\学习资料\Redis\赠送:小象学院-Redis从入门到精通\第4章:Redis 服务器的配置与管理—完整\第2节:RDB 持久化
目标
常用数据结构的基本命令
分布式部署
使用Redis与第三方工具管理Redis
了解Redis的实现原理
在应用程序中使用Redis
Redis简介
什么是Redis
Redis是基于BSD许可的,开源、高级键值缓存和存储系统
键包括:string,hash,list,set,sorted set,bitmap 和 hyperloglog
可以在这些类型上面运行原子操作,例如,追加字符串,增加哈希中的值,
加入一个元素到列表,计算集合的交集、并集和差集,或者是从有序集合中获取最高排名的元素。
为了满足高性能,Redis 采用内存 (in-memory) 数据集 (dataset)。
根据你的使用场景,你可以通过每隔一段时间转储数据集到磁盘,或者追加每条命令到日志来持久化。
持久化也可以被禁用,如果你只是需要一个功能丰富,网络化的内存缓存。
诞生过程
刚开始出现的时候,是为了解决LLOOGG.com网站的负载问题,MySQL扛不住,于是作者开发了Redis
特性
支持主从异步复制,非常快的非阻塞初次同步、网络断开时自动重连局部重同步。
相比于其他数据库的特色
-
独特的键值对模型:SQL-表格,Memcached-字符串键值对,MongoDB-JSON文档,Redis虽然也是键值对数据库,但是支持多种类型的值。通过不同类型的值处理不同的问题
-
内存数据库,读写数据不受I/O影响,速度极快
-
持久化功能:支持将内存中的数据保存到硬盘中,方便快速备份和恢复
-
发布与订阅:将消息同时分发给多个客户端,用于构建广播系统
-
过期键功能:为键设置过期时间,过期后自动删除
-
事务功能:原子地执行多个操作,并提供乐观锁,保证处理数据时的安全性
-
脚本功能
-
复制:为指定的Redis服务器创建一个或多个复制品,用于提升数据安全性,并分担读请求的负载
-
Sentinel:监控Redis服务器的状态,并在服务器发生故障时,自动故障迁移
-
集群:创建分布式数据库,为每个服务器分别执行一部分写操作和读操作
Redis当前应用情况
- Twitter使用Redis存储用户时间线
- StackOverflow使用Redis进行缓存和消息分发
- Pinterest使用Redis构建关注模型和兴趣图谱
- Flicker使用Redis构建队列
- Github使用Redis作为持久化的键值对数据库,并使用Resque来实现消息队列
- 新浪微博使用Redis来实现计数器、反向索引、排行榜、消息队列、存储客户关系
- 知乎使用Redis来进行计数、缓存、消息分发和任务调度
安装
从dashboard下载安装Docker镜像
docker pull daocloud.io/library/redis:3.2.9
直接使用Docker的Redis镜像
docker run --name redis -d daocloud.io/library/redis:3.2.9
初期,为了演示方便,直接进入容器进行访问,对于Redis的高级配置,后续详细说明
docker exec -it 1c8a9884a7d2 bash
这个镜像包含EXPOSE 6379 (Redis默认端口),所以可以通过link容器的方式访问Redis
基本使用
- 登陆
root@1c8a9884a7d2:/# redis-cli
127.0.0.1:6379>
redis-cli
是客户端登陆命令,默认登陆本机的6379
端口
更多登陆选项参见 redis-cli -h
- set and get
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
键
Redis是一个键值对数据库服务器,每当我们调研命令创建一个键的时候。这个键值对都会被存放到Redis的某个数据库里
在设计Redis键的时候,最好遵循以下原则
- 不要使用太长的键
占用更大的内存,查找也会变慢 - 不要使用太短的键
太短的键不具备可读性 - 坚持使用一种模式
例如:obj-type:id,user:1000 - 最大大小512M
exists判断键存在性、del删除键、type键类型
127.0.0.1:6379> exists k1
(integer) 1
127.0.0.1:6379> del k1
(integer) 1
127.0.0.1:6379> exists k1
(integer) 0
127.0.0.1:6379> type k2
string
expire key的时效性设置
可以为key设置时效,超时后key被删除,好像调用了del
命令一样
如下,设置key的时效性为5秒
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> expire k1 5
(integer) 1
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k1
(nil)
检查时效: ttl key
127.0.0.1:6379> expire k1 100
(integer) 1
127.0.0.1:6379> ttl k1
(integer) 98
127.0.0.1:6379> ttl k1
(integer) 97
127.0.0.1:6379> ttl k1
(integer) 94
如果想要用毫秒作为单位的话,pexpire和pttl
可以在set的时候直接指定时效性
set key 100 ex 10
,使用ex参数指定时效性
基本数据类型操作
string 字符串
二进制安全 (binary-safe) 的字符串
字符串是Redis最基本的类型,可以存储文字、数字(整型、浮点型)、还可以存储二进制数据
Redis字符串是二进制安全的,就是说一个Redis字符串可以包含任意类型的数据,
如:JPGE图像,序列化对象
字符串最大限制512M
set/get、getset、mset/mget
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
set
执行的是赋值操作,如果键已存在,会发生覆盖,哪怕这个键关联的是一个非字符串值
要求键不存在:set k1 v1 nx
,当k1
作为键存在的时候,set
执行失败
要求键存在:set k1 v1 xx
,当k1
作为键不存在的时候,set
执行失败
默认情况下,键存在就覆盖,不存在就创建
getset:给键设置一个新值,同时返回旧值
127.0.0.1:6379> getset k1 kk1
"v1"
127.0.0.1:6379> get k1
"kk1"
mset:同时设置多个键值对
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 k4 v4
OK
127.0.0.1:6379> mget k1 k2 k3 k4
1) "v1"
2) "v2"
3) "v3"
4) "v4"
incr、incrby、decr、decrby原子性增长/下降
127.0.0.1:6379> set counter 1
OK
127.0.0.1:6379> incr counter
(integer) 2
127.0.0.1:6379> incr counter
(integer) 3
127.0.0.1:6379> incr counter
(integer) 4
incr
会将获取到的字符串值解析为整数,并+1
,把结果赋值给counter
127.0.0.1:6379> get counter
"4"
incrby:按照指定间隔增加
decr:-1
decrby:按照指定间隔下降
127.0.0.1:6379> incrby counter 2
(integer) 5
127.0.0.1:6379> incrby counter 2
(integer) 7
127.0.0.1:6379> decr counter
(integer) 6
127.0.0.1:6379> decrby counter 2
(integer) 4
127.0.0.1:6379> decrby counter 2
(integer) 2
这几个命令都具有原子性,就是说即使有多个客户端对同一个key进行操作,也不会造成竞争条件
list 列表
按照插入顺序排序的字符串元素 (element) 的集合 (collection)。链表数据结构实现
Redis的列表仅仅是按照插入顺序排序的字符串列表
列表最大长度2^23-1
访问列表两端非常快,但是访问一个大列表的中间非常慢
适合存储需要快速插入,访问收尾的情形;如果需要快速访问中间部分的集合元素,推荐使用sorted set
lpush/rpush、lrange、rpop 存取、查看
- 从头部添加元素到列表 lpush
127.0.0.1:6379> lpush list1 1 2 3
(integer) 3
127.0.0.1:6379> lpush list1 4
(integer) 4
- 从尾部添加元素到列表 rpush
127.0.0.1:6379> rpush list1 5
(integer) 5
- 查看集合中的元素 lrange
索引从0开始,-1表示最后一个元素,-2表示倒数第二个元素
所以[0,-1]就是全部元素的索引
127.0.0.1:6379> lrange list1 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "5"
- 取出最后一个元素 rpop
127.0.0.1:6379> lrange list1 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "5"
127.0.0.1:6379> rpop list1
"5"
127.0.0.1:6379> lrange list1 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
ltrim 列表上限限定
限制列表中保存的元素数目,将老
的元素剔除出列表
127.0.0.1:6379> lrange list1 0 -1
1) "7"
2) "6"
3) "5"
4) "4"
5) "3"
6) "2"
7) "1"
8) "4"
9) "3"
10) "2"
11) "1"
127.0.0.1:6379> ltrim list1 0 4
OK
127.0.0.1:6379> lrange list1 0 -1
1) "7"
2) "6"
3) "5"
4) "4"
5) "3"
brpop、blpop 列表的阻塞操作
当列表为空的时候,rpop
操作返回null
brpop和blpop,会让列表为空的时候进入阻塞状态,仅当新元素添加到列表的时候,或者超时了才返回
127.0.0.1:6379> brpop list1 20
(nil)
(20.08s)
如果在brpop
空的列表的过程中,往列表中lpush
了新数据,且未超时,则blpop
会返回这个新增加的元素
与rpop
不同,blpop
返回的是数组,其中包括键(列表)的名字,因为可以阻塞等待多个列表的数据
list的典型应用场景
- 最近提交(rpop获取)
- 生产者消费者之间通讯
生产者lpush,消费者rpop
set 集合
唯一的,无序的字符串元素集合
Redis集合是没有顺序的字符串集合
不允许重复数据
集合与列表的一个区别在于,集合可以计算集合之间的关系
sadd、smembers、sismember、smember
127.0.0.1:6379> sadd myset 1 2 3
(integer) 3
127.0.0.1:6379> smembers myset
1) "1"
2) "2"
3) "3"
sismember:检查元素是否存在
127.0.0.1:6379> sismember myset 3
(integer) 1
127.0.0.1:6379> sismember myset 30
(integer) 0
smember 返回一个随机元素
127.0.0.1:6379> srandmember myset
"2"
sinter/交集、spop/随机返回并删除、sunionstore/交集并赋值
sinter
127.0.0.1:6379> sadd tag:1:news 1000
(integer) 1
127.0.0.1:6379> sadd tag:2:news 1000
(integer) 1
127.0.0.1:6379> sadd tag:5:news 1000
(integer) 1
127.0.0.1:6379> sadd tag:77:news 1000
(integer) 1
127.0.0.1:6379> sinter tag:1:news tag:2:news tag:10:news tag:27:news
(empty list or set)
127.0.0.1:6379> sinter tag:1:news tag:2:news tag:77:news
1) "1000"
spop
127.0.0.1:6379> spop news:1000:tags
"2"
127.0.0.1:6379> spop news:1000:tags
"77"
127.0.0.1:6379> spop news:1000:tags
"1"
127.0.0.1:6379> spop news:1000:tags
"5"
127.0.0.1:6379> spop news:1000:tags
(nil)
sunionstore取交集,并赋值给新的集合,单个集合的交易就是其本身,所以这个命令也有集合复制的功能
127.0.0.1:6379> sunionstore myset1(dest) myset
(integer) 1
scard 集合中元素数目
127.0.0.1:6379> scard myset
(integer) 1
sorted set 有序集合
和集合类似,但是每个字符串元素关联了一个称为分数 (score) 的浮点数。元素总是按照分数排序,所以可以检索一个范围的元素 (例如,给我前 10,或者后 10 个元素)
与Redis的set类似,但是每个元素都对应一个分数
按照分数高低排序
元素是唯一的,但是分数是可以重复的
zadd、zrang、zrevrange
zadd 有序集合名 分数 集合元素
127.0.0.1:6379> zadd hacker 1 v1
(integer) 1
127.0.0.1:6379> zadd hacker 2 v2
(integer) 1
127.0.0.1:6379> zrange hacker 0 -1
1) "v1"
2) "v2"
倒序
127.0.0.1:6379> zrevrange hacker 0 -1
1) "v2"
2) "v1"
连带分数一起返回
127.0.0.1:6379> zrange hacker 0 -1 withscores
1) "v1"
2) "1"
3) "v2"
4) "2"
zrangebyscore/获取指定分数、zremrangebyscore/删除指定分数
小于指定分数的元素
127.0.0.1:6379> zrangebyscore hacker -inf 2
1) "v1"
2) "v2"
127.0.0.1:6379> zrangebyscore hacker -inf 1
1) "v1"
删除指定分数范围的元素
127.0.0.1:6379> zremrangebyscore hacker 1 2
(integer) 2
127.0.0.1:6379> zrange hacker 0 -1
(empty list or set)
zrank 获取元素位置
从0开始计数
127.0.0.1:6379> zadd hacker 1 v1
(integer) 1
127.0.0.1:6379> zadd hacker 2 v2
(integer) 1
127.0.0.1:6379> zadd hacker 3 v3
(integer) 1
127.0.0.1:6379> zrank hacker 2
(nil)
127.0.0.1:6379> zrank hacker v2
(integer) 1
127.0.0.1:6379> zrank hacker v3
(integer) 2
127.0.0.1:6379> zrank hacker v1
(integer) 0
hash 散列
由字段 (field) 及其关联的值组成的映射。字段和值都是字符串类型。这非常类似于 Ruby 或 Python 中的哈希 / 散列
Redis哈希字符串字段与字符串之间的映射
哈希主要用来存储对象
hmset、hget、hgetall、hmget
127.0.0.1:6379> hmset user:1000 username antirez birthyear 1977 verified 1
OK
127.0.0.1:6379> hget user:1000 username
"antirez"
127.0.0.1:6379> hget user:1000 birthyear
"1977"
127.0.0.1:6379> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"
5) "verified"
6) "1"
hmget用于返回数组
127.0.0.1:6379> hmget user:1000 username birthyear
1) "antirez"
2) "1977"
hincrby 增加单个字段值
127.0.0.1:6379> hincrby user:1000 birthyear 10
(integer) 1987
127.0.0.1:6379> hincrby user:1000 birthyear 10
(integer) 1997
127.0.0.1:6379> hmget user:1000 birthyear
1) "1997"
bitmaps 位图
**使用特殊的命令,把字符串当做位数组来处理:你可以设置或者清除单个位值,统计全部置位为 1 的位个数,寻找第一个复位或者置位的位,等等 **
hyperloglogs 超重对数
使用常量空间估算大量元素的积数
这是一个用于估算集合的基数 (cardinality,也称势) 的概率性数据结构
问题:怎样记录网站每天获得的独立IP数量
第一种:用集合(set)数据结构实现,每次访问都将ip记录set数据结构,set数据结构中的每个数据都是不想同的,再调用scard命令统计集合中的元素数据即可
在设计key的时候,要注意和“每天”关联上
第一种方案的问题:随着时间的增加,消耗的内存越来越大,如果存储的是IPv6的地址,消耗更大
HyperLogLogs可以接收多个元素作为输入,并给出元素的积数估算值
基数:集合中不同元素的数量
估算值:算法给出的基数并不精确,但会控制在合理范围之内
HyperLogLogs的优点是,即使输入元素的数量或体积非常非常大,计算基数所需的空间总是固定的,并且非常小
在Redis中,每个HyperLogLogs键只需要花费12KB内存,就可以计算接近2^64个不同元素的基数
但是,HyperLogLogs只会根据输入的元素来计算基数,而不会存储元素本身,所以无法返回各个元素
sort排序
在Redis中,只有列表和有序集合是有顺序的
通过sort命令,可以对列表、集合以及有序集合,按照新的排序方式,进行重新排序
小结
对于此部分的内容,基本上集中在各种数据类型的set和get上,以及根据值类型的特点,进行相关的运算操作
这部分也是在Redis安装配置完成后,最被常用的使用功能
由于命令众多,实际使用过程中最好先看看手册,对于我们需要的数据类型提供了哪些实用的功能
Redis手册
发布与订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
- 创建频道 redisChat
SUBSCRIBE redisChat
- 重新打开一个客户端,往 redisChat 频道发送消息
PUBLISH redisChat "Redis is a great caching technique"
- 此时,第一个客户端,就是创建频道的那个客户端,就会收到消息
事务
它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED
redis 127.0.0.1:6379> GET book-name
QUEUED
redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED
redis 127.0.0.1:6379> SMEMBERS tag
QUEUED
redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
2) "C++"
3) "Programming"
Lua脚本
Redis 脚本使用 Lua 解释器来执行脚本。 Reids 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL。
Redis构建实例
微博
主要功能
- 用户账号
- 用户关系(关注与被关注)
- 发微博
- 查看微博时间线
- 对微博点赞、评论、转发
创建账号
注册微博所需信息
- 邮箱(不考虑手机号)
- 密码
- 微博名
其中,邮箱和微博名不能重复
所以,需要检查邮箱和微博名是否已被使用,检查通过的用户,注册信息需要存储起来,并且系统分配一个唯一ID
邮箱和微博名检查
所有注册过的邮箱和微博名,都存储在weibo::userd_names,和weibo::used_emails两个集合中
将需要注册的微博信息与这两个集合进行比较,看看是否是集合中的成员,
如果两者都不是集合中的成员,则校验通过,然后将信息存储到集合中。
存储用户注册信息
用Hash数据机构存储用户注册信息
HMSET weibo::user:id field1 "Hello" field2 "World"
至于id的生成,可以使用 incr key,并且处理成YYYYMMDD-ID的形式
关联email与ID的关系
使用Hash数据结构存储
HSET weibo::email_to_uid email uid
这样,当用户登录的时候,我们拿着输入的email,从 weibo::email_to_uid 散列中获取uid
127.0.0.1:6379> hmset weibo::email_to_uid nbzlnzlq@126.com 10000
OK
127.0.0.1:6379> hmget weibo::email_to_uid nbzlnzlq@126.com
1) "10000"
然后用获取到的id,去 weibo::user:id 中获取用户的注册信息,比较一下输入密码与注册密码是否一致
当然,实际情况,存储的密码都是加密过的,不会是明文
用户关系
关注集合
存储用户关注的人的id
键 weibo::user:🆔:following,值,set集合,每个元素都是用户关注账户的id
粉丝集合
存储用户的粉丝的id
键 weibo::user:🆔:fans,值,set集合,每个元素都是关注此账户的账户id
每当A关注B的时候,将A放到B的粉丝集合,B放到A的关注集合
发微博
每当用户发送一条微博的时候,程序执行两个动作
1、将微博内容、作者信息、发送时间存储起来,并且分配一个ID表示这条微博
2、将被发送的微博推入发送者本人的时间线,以及所有粉丝的时间线里面
生成微博
用set集合存储
键 :weibo::message::id,值 :微博内容、作者信息、发送时间等
时间线
时间线分为两种
1、用户本身的微博以及用户关注的账户的微博:weibo::user:🆔:custom_timeline
2、仅用户自身的微博:weibo::user:🆔:personal_timeline
以上的时间线key的设计,数据类型都是散列
广播操作
微博发送后,
1、存储微博,并生成id
2、发送微博作者的时间线
3、发送到粉丝的时间线
微博点赞
weibo::message:🆔like 集合存储为某条微博点赞的用户
微博评论
weibo::comment::id,这个用来存储每条评论
weibo::message:🆔:comments,这个用来存储每条微博对应的所有评论的id
微博转发
转发的微博与普通的微博唯一的区别,就是转发的微博会应用另一条微博
所以,转发的微博,就是在普通微博的基础上,添加 origin_message_id 属性。通过这个属性能够找到被转发的微博的详细信息
小结
散列差不多是设计中被用得最广泛的数据结构了
Redis数据库在设计应用的时候,思路与关系型数据库有很大的差异
基本上有这么一个设计思路:为每个对象设计一个散列,用新的散列维护对象间的关系
论坛
主要功能
- 用户账号
- 创建帖子、 回复帖子、为帖子投票
- 为帖子归类、查看特定类别的帖子
- 帖子的类别与标签的关系、查看特定标签的帖子
- 每日热议帖子排行榜
- 基于算法的帖子推荐
创建论坛用户账号
与微博账号的设计基本一致
创建帖子
一个帖子的构成
1、分类
2、标题
3、用户投票数
4、作者ID
5、发布时间
6、点击量
7、内容
8、tag(所属标签)
为帖子分配一个id
bbs::tipic::帖子id
查看帖子
基于分类
key - bbs::category::wordpress
value - 帖子id组成的set集合
基于标签
1、记录标签与分类之间的关系
2、记录帖子与标签的关系
bbs:tab::
bbs:tab::
回复帖子
其实和微博的评论功能相似
1、存储回复内容
2、帖子与回复内容之间关系的维护
博客
主要功能
- 账号
- 发布文章
- 在主页查看文章
- 根据分类查看文章
- 评论文章
有了博客与论坛的设计经验之后,再来设计博客就很简单了
小结
Redis的使用分为两大块
1、根据应用,使用合适的数据结构
2、Redis本身的配置
Redis配置
Redis提供了一些配置选项,通过修改这些选项的值,可以改变选项对应功能的行为
Redis提供了三种修改配置选项的方式
- 启动服务器的时候设置参数:redis-server --option1 value1 --option2 value2
- 修改配置文件:redis-server 配置文件路径
- 登陆后使用config命令修改,仅当前运行过程中有效
并不是所有值都可以在登陆后使用config命令进行设置,而第一种方式当需要设置的参数很多的时候,就非常麻烦
所以,最好是使用配置文件的方式
至于有哪些配置项,这个详见文档
缓存
当使用Redis作为缓存服务器的时候,可以通过指定最大缓存大小,当超过指定大小时,按照先进先出的原则,删除旧数据
配置文件在 redis.conf
中
maxmemory 100mb
或者登陆后使用命令设置
config set maxmemory 100mb
如果指定缓存大小为0,则表示不设限(32位操作系统最大3G)
当到达最大值后,可以指定删除旧数据或者报错,这是两种策略
回收策略
当 maxmemory 限制到达的时候,Redis 将采取的准确行为是由 maxmemory-policy 配置指令配置的。
以下策略可用:
noeviction:当到达内存限制时返回错误。当客户端尝试执行命令时会导致更多内存占用(大多数写命令,除了 DEL 和一些例外)。
allkeys-lru:回收最近最少使用(LRU)的键,为新数据腾出空间。
volatile-lru:回收最近最少使用(LRU)的键,但是只回收有设置过期的键,为新数据腾出空间。
allkeys-random:回收随机的键,为新数据腾出空间。
volatile-random:回收随机的键,但是只回收有设置过期的键,为新数据腾出空间。
volatile-ttl:回收有设置过期的键,尝试先回收离 TTL 最短时间的键,为新数据腾出空间。
当没有满足前提条件的话,volatile-lru,volatile-random 和 volatile-ttl 策略就表现得和 noeviction 一样了。
选择正确的回收策略是很重要的,取决于你的应用程序的访问模式,但是,你可以在程序运行时重新配置策略,使用 INFO 输出来监控缓存命中和错过的次数,以调优你的设置。
一般经验规则:
M 如果你期待你的用户请求呈现幂律分布(power-law distribution),也就是,你期待一部分子集元素被访问得远比其他元素多,可以使用 allkeys-lru 策略。在你不确定时这是一个好的选择。
如果你是循环周期的访问,所有的键被连续扫描,或者你期待请求正常分布(每个元素以相同的概率被访问),可以使用 allkeys-random 策略。
如果你想能给 Redis 提供建议,通过使用你创建缓存对象的时候设置的 TTL 值,确定哪些对象应该被过期,你可以使用 volatile-ttl 策略。
当你想使用单个实例来实现缓存和持久化一些键,allkeys-lru 和 volatile-random 策略会很有用。但是,通常最好是运行两个 Redis 实例来解决这个问题。
另外值得注意的是,为键设置过期时间需要消耗内存,所以使用像 allkeys-lru 这样的策略会更高效,因为在内存压力下没有必要为键的回收设置过期时间。
分片
将Redis数据拆分到多个Redis实例
这样,每个Redis只保留所有键的子集
分片的目的
允许使用很多电脑的内存总和来支持更大的数据库。没有分片,你就被局限于单机能支持的内存容量。
允许伸缩计算能力到多核或多服务器,伸缩网络带宽到多服务器或多网络适配器。
分片基础
1、键到服务器的映射关系
2、哈希分片
计算每个键的哈希值,对这个值按照服务器数量做模运算
谁实现分片
- 客户端
- 代理协助分片:Twemproxy
- 查询路由
分片缺点
- 无法对多个实例上的键的集合直接进行集合的运算
- 涉及多个键的事务不能用
- 数据处理变得更复杂
- 添加和删除容量也变得复杂
持久化
因为Redis服务器将数据存储在内存中,所以一旦服务器关闭,存储在内存中的数据就会消失不见
如果仅将Redis作为缓存,那么这是没问题的,而如果将Redis作为数据库,则必须有一个持久化方案
Redis提供RDB与AOF两种持久化功能,这两种功能都可以将内存中的数据以文件的形式存储到硬盘里
服务器可以在启动时,通过载入持久化文件来还原服务器关闭前的数据库数据
持久化文件也可以用于数据的备份、迁移等工作
RDB持久化
RDB持久化功能将数据库包含的所有数据以二进制文件的形式保存到硬盘里面
通过服务器启动时载入RDB文件,服务器根据RDB文件的内容,还原服务器原有的数据库数据
Redis服务器何时才会创建RDB文件?
- 服务器执行客户端发送的save命令
- 服务器执行客户端发送的bgsave命令
- 使用save配置选项设置的字段保存条件被满足,服务器字段执行bgsave命令
前两种是用户手动执行的,第三种情况是Redis服务器自动执行的
执行save命令过程中,Redis服务器将被阻塞,无法处理客户端发送的命令请求
save命令的复杂度为O(N),N为服务器中键值对数量之和
bgsave命令不会造成阻塞
bgsave不会造成服务器阻塞的原因在于,他不会自己创建RDB文件,而是fork()生成子进程,由子进程创建RDB文件,而自己继续处理客户端请求
当子进程创建好RDB文件并退出后,通知父进程,Redis服务器接收RDB文件
bgsave命令的复杂度为O(N),N为服务器中键值对数量之和
AOF持久化
RDB持久化有一个缺点,就是创建RDB文件需要将服务器所有数据库的数据都保存起来,非常消耗资源和时间
所以一般使用RDB方式进行持久化,都是间隔一段时间执行,不能过于频繁,否则严重影响服务器的性能
AOF持久化保存数据到数据文件的原理是,每当有修改数据库的命令被执行,服务器就会将被执行的命令写入到AOF文件的末尾
因为AOF文件存储的是服务器执行过的修改数据库的命令,所以只需要再次执行AOF文件中的命令,就能够达到还原数据库的目的
AOF也并不是完全不会丢失数据,现代操作系统都是先将数据写到缓冲区的,这样做是为了提高写入效率
对于AOF持久化来说,当一条命令真正被写入到磁盘时,这条命令才不会因为停机而意外丢失
AOF重写
随着服务器不断运行,越来越多的命令被写入到AOF文件,使得AOF文件体积越来越大
AOF重写将文件大小控制在合理范围内,通过这个功能,产生一个新的AOF文件
- 新的AOF文件与原AOF文件记录的数据库完全一样
- 新的AOF文件使用较少的命令来记录数据库数据
- AOF重写期间,服务器不会被阻塞,正常处理客户端请求
触发AOF重写
- 客户端发送bgrewriteaof命令
- 服务器配置 auto-aof-rewrite-min-size,auto-aof-rewrite-percentage
Redis服务器管理
RedisLive,使用Python编写的Redis实时监控工具
Redis Commander,使用Node.js编写的Redis管理工具
Redmon,Ruby编写的Redis管理工具
现有工具不够强大、不够稳定,目前还没有强大且稳定的工具被开源出来
重度使用Redis的公司一般都会构建自己的管理工具。
Redis分布式集群环境
单机服务器的限制
1、内存容量不足(存储能力)
2、处理能力不足(并发能力)
Redis提供的多机功能
1、复制(replication),扩展系统处理读请求的能力
2、Sentinel(哨兵),为系统提供高可用特性,减少故障停机出现
3、集群(cluster),扩展系统的数据库容量以及系统处理读写请求的能力,并提供高可用特性
复制
只要主从服务器的网络连接正常,主服务器上的数据就会不断同步给从服务器
保证主从服务器数据的一致性
因为数据是一致的,Redis允许从服务器接收客户端的查询命令,降低了主服务器的负载
通过添加从服务器,可以线性的扩展系统的处理能力
创建从服务器
- 使用 SLAVEOF
命令,比如向一个服务器发送
SLAVEOF 127.0.0.1 6379 ,可以让接收到该命令的服务器变为 127.0.0.1:6379 的从服务器。
在将一个服务器设置成从服务器之后,可以通过向它发送 SLAVEOF no one 来让它变回一个主
服务器(数据库已有的数据会被保留)。 - 在启动服务器时,通过设置 slaveof
配置选项来让服务器成为指定
服务器的从服务器。
服务器下线处理
主服务器或从服务器在运行过程中下线了怎么办?
如果下线的是从服务器,那么整个系统处理读请求的性能将有所下降,但整个系统仍然可以继续
处理写请求和读请求,所以这种下线不会导致系统停机;
如果下线的是主服务器,那么整个系统将只能处理读请求而无法处理写请求,导致系统停机。
让系统重新上线的方法
所谓让系统重新上线,不是说把下线的服务器再次启动,而是让整个Redis集群能够继续处理读写
用户需要向系统中的某一个从服务器发送 SLAVEOF no one 命令,让它变为新的主服务器,并
向其他从服务器发送 SLAVEOF 命令,让它们去复制新的从服务器
以上是手动操作,Redis 提供了 Sentinel 程序,用户可以使用
Sentinel 来自动检测主从服务器的状态,并在主服务器下线时,自动
执行故障转移操作(failover),让系统重新上线
Sentinel
作用:监视主从服务器,并在主服务器下线时自动进行故障转移
通过执行 Redis 安装文件夹中的 redis-sentinel 程序,可以启动一个 Sentinel 实例:
$ redis-sentinel sentinel.conf
因为 Redis 的 Sentinel 实际上就是一个运行在 Sentienl 模式下的 Redis 服务器,所以我们同样
可以使用以下命令来启动一个 Sentinel 实例:
$ redis-server sentinel.conf --sentinel
每个 Sentinel 实例可以监视任意多个主服务器,以及被监视的主服务器属下的所有从服务器。
多个 Sentinel 实例可以监视同一个主服务器,监视相同主服务器的这些 Sentinel 们会自动地互相连
接,组成一个分布式的 Sentinel 网络,互相通信并交换彼此关于被监视服务器的信息。
当一个 Sentinel 认为被监视的服务器已经下线时,它会向网络中的其他 Sentinel 进行确认,判断
该服务器是否真的已经下线。
如果下线的服务器为主服务器,那么 Sentinel 网络将对下线主服务器进行自动故障转移:通过将下
线主服务器的某个从服务器提升为新的服务器,并让其他从服务器转为复制新的主服务器,以此来让
系统重新回到上线状态。
twemproxy
使用分片来扩展性能:
将整个数据库分为多个部分,使用不同的服务器来储存不同部分,并负责处理相应部分的命令请求(包括读请求和写请求)。
安装、配置和运行 twemproxy
使用 twemproxy
Redis集群
上述属于Redis分布式功能,将分布式中的机器打包成一个节点,当做集群中的一个节点
多个这种节点,就是一个集群