Redis简介
Redis是一款开源的,ANSI C语言编写的,高级键值(key-value)缓存和支持永久存储NoSQL数据库产品。 Redis采用内存(In-Memory)数据集(DataSet) 。 支持多种数据类型。 运行于大多数POSIX系统,如Linux、*BSD、OS X等。 作者: Salvatore Sanfilippo
Redis.io 官网
Download/redis.io 下载
Redisdoc.com 文档
redis.cn 中文网
核心特性
- 高速读写
- 数据类型丰富:常见5种
- 支持持久化
- 多种内存分配及回收策略
- 支持事务-弱事务
- 消息队列、消息订阅
- 支持高可用
- 支持分布式分片集群
和Memcache的区别:支持事务、数据类型丰富、支持高可用、支持分布式分片集群
优点
优势: 透明性:分布式系统对用户来说是透明的,一个分布式系统在用户面前的表现就像一个传统的单处理机分时系统,可让用户不必了解内部结构就可以使用。 扩展性:分布式系统的最大特点就是可扩展性,他可以根据需求的增加而扩展,可以通过横向扩展使集群的整体性能得到线性提升,也可以通过纵向扩展单台服务器的性能使服务器集群的性能得到提升。 可靠性:分布式系统不允许单点失效的问题存在,它的基本思想是:如果一台服务器坏了,其他服务器接替它的工作,具有持续服务的特性 高性能:高性能是人们设计分布式系统的一个初衷,如果建立了一个透明,灵活,可靠的分布式系统,但他运行起来像蜗牛一样慢,那这个系统就是失败的。 分布式系统的缺点: 1.在节点通信部分的开销比较大,线程安全也变得复杂,需要保证在完整数据的同时兼顾性能。 2.过分依赖网络,网络信息的丢失或饱和将会抵消分布式的大部分优势。 3.有潜在的数据安全和网络安全问题。
企业缓存数据库解决方案对比
Memcached:
优点:高性能读写、单一数据类型、支持客户端式分布式集群、一致性hash
多核结构、多线程读写性能高。
缺点:无持久化、节点故障可能出现缓存穿透、分布式需要客户端实现、跨机房数据同步困难、
架构扩容复杂度高
Redis:
优点:高性能读写、多数据类型支持、数据持久化、高可用架构、支持自定义虚拟内存、
支持分布式分片集群、单线程读写性能极高
缺点:多线程读写较Memcached慢
Tair:
优点:高性能读写、支持三种存储引擎(ddb、rdb、ldb)、支持高可用、支持分布式分片集群、
支撑了几乎所有淘宝业务的缓存。
缺点:单机情况下,读写性能较其他两种产品较慢
对比测试
总结:
对三款产品的单用户和多用户的操作的性能的对比,差异
Memcache是多核的处理模式,可以支持更多的并发能力
Redis支持少量的并发,并且发起多次修改或者读取的能力
在单台主机上装单个redis,并不具备优势!可以在单台机器上装多实例实现更高的并发!!
redis应用场景
数据高速缓存
web会话缓存(Session Cache)
排行榜应用
消息队列
发布订阅
等
Redis安装及基本配置
安装
有两个要求:1、去官网下载;2、下载6-12月的版本
下载: 解压: 上传至 /data 或者你喜欢的目录 tar xzf redis-3.2.12.tar.gz mv redis-3.2.12 redis 安装: cd redis make 修改环境变量,将src加入到文件中 vim /etc/profile export PATH=/application/mysql/bin/:/data/redis/src:$PATH
使生效 source /etc/profile
启动和简易连接: redis-server & redis-cli
配置文件
手动创建一个目录,创建配置文件
mkdir /data/6379 vim /data/6379/redis.conf daemonize yes port 6379 logfile /data/6379/redis.log dir /data/6379 dbfilename dump.rdb 127.0.0.1:6379> shutdown 关闭 not connected> exit [root@db01 6379]# redis-server /data/6379/redis.conf 重启,带上配置文件 [root@db03 ~]# netstat -lnp|grep 63 redis-cli shutdown 在命令行关闭 +++++++++++配置文件说明++++++++++++++ redis.conf 是否后台运行: daemonize yes 默认端口: port 6379 日志文件位置 logfile /var/log/redis.log 持久化文件存储位置 dir /data/6379 RDB持久化数据文件: dbfilename dump.rdb +++++++++++++++++++++++++ ------------------------- redis安全配置 (1)redis没有用户概念,redis只有密码 (2)redis默认工作在保护模式下。不允许远程任何用户登录的(protected-mode) protected-mode yes/no (保护模式,是否只允许本地访问,远程可以登录,但是无法操作) ---------------------- DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1)Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. 3) If you started the server manually just for testing, restart it with the '--protected-mode no' option. 4) Setup a bind address or an authentication password. NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside. ---------------------- (1)Bind :指定IP进行监听 vim /data/6379/redis.conf bind 10.0.0.52 127.0.0.1 (2)增加requirepass {password} vim /data/6379/redis.conf requirepass root 重启生效: redis-cli shutdown 关闭 redis-server /data/6379/redis.conf 带上配置文件启动 ----------验证--------- 方法一: [root@db03 ~]# redis-cli -a root 127.0.0.1:6379> set name zhangsan OK 127.0.0.1:6379> exit 方法二: [root@db03 ~]# redis-cli 127.0.0.1:6379> auth root OK 127.0.0.1:6379> set a b ------------------------ 在线查看和修改配置 CONFIG GET * 获取所有配置信息 ,两行是一个参数 在先修改,不用重启redis,下次登录就生效,但也不是所有的参数都能在线修改 CONFIG GET requirepass CONFIG SET requirepass 123456 ------------------
Redis持久化
RDB 持久化 可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。
优点:速度快,适合于用做备份,主从复制也是基于RDB持久化功能实现的。
缺点:会有数据丢失 AOF 持久化 记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。
AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。
优点:可以最大程度保证数据不丢
缺点:日志记录量级比较大
RDB的优点
RDB是一种表示某个即时点的Redis数据的紧凑文件。
RDB文件适合用于备份。
例如,你可能想要每小时归档最近24小时的RDB文件,每天保存近30天的RDB快照。
这允许你很容易的恢复不同版本的数据集以容灾。
RDB非常适合于灾难恢复,作为一个紧凑的单一文件,可以被传输到远程的数据中心。
RDB最大化了Redis的性能,因为Redis父进程持久化时唯一需要做的是启动(fork)一个子进程,
由子进程完成所有剩余工作。父进程实例不需要执行像磁盘IO这样的操作。
RDB在重启保存了大数据集的实例时比AOF要快。
RDB缺点
当你需要在Redis停止工作(例如停电)时最小化数据丢失,RDB可能不太好。
你可以配置不同的保存点(save point)来保存RDB文件(例如,至少5分钟和对数据集100次写之后,
但是你可以有多个保存点)。然而,你通常每隔5分钟或更久创建一个RDB快照,
所以一旦Redis因为任何原因没有正确关闭而停止工作,你就得做好最近几分钟数据丢失的准备了。
RDB需要经常调用fork()子进程来持久化到磁盘。
如果数据集很大的话,fork()比较耗时,
结果就是,当数据集非常大并且CPU性能不够强大的话,Redis会停止服务客户端几毫秒甚至一秒。
AOF也需要fork(),但是你可以调整多久频率重写日志而不会有损(trade-off)持久性(durability)。
RDB普通配置参数
vim /data/6379/redis.conf dir /data/6379 dbfilename dump.rdb save 900 1 save 300 10 save 60 10000 配置分别表示: 900秒(15分钟)内有1个更改 300秒(5分钟)内有10个更改 60秒内有10000个更改
如果不设置上面的条件配置,什么时候使用save命令,什么时候才会持久化
RDB持久化高级配置
stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb dir ./
以上配置分别表示: 后台备份进程出错时,主进程停不停止写入? 主进程不停止容易造成数据不一致 导出的rdb文件是否压缩 如果rdb的大小很大的话建议这么做 导入rbd恢复时数据时,要不要检验rdb的完整性 验证版本是不是一致 导出来的rdb文件名 rdb的放置路径
AOF持久化配置
appendonly yes
appendfsync always
appendfsync everysec
appendfsync no
是否打开aof日志功能
每1个命令,都立即同步到aof
每秒写1次
写入工作交给操作系统,由操作系统判断缓冲区大小,统一写入到aof.
vim /data/6379/redis.conf
appendonly yes
appendfsync everysec
AOF持久化高级配置
no-appendfsync-on-rewrite yes/no auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb 配置分别表示: 正在导出rdb快照的过程中,要不要停止同步aof aof文件大小比起上次重写时的大小,增长率100%时重写,缺点:业务开始的时候,会重复重写多次。 aof文件,至少超过64M时,重写
RDB到AOF的切换
在 Redis 2.2 或以上版本,可以在不重启的情况下,从 RDB 切换到 AOF : 1、为最新的 dump.rdb 文件创建一个备份。 2、将备份放到一个安全的地方。 3、执行以下两条命令: redis-cli> CONFIG SET appendonly yes redis-cli> CONFIG SET save "" 4、确保命令执行之后,数据库的键的数量没有改变。 5、确保写命令会被正确地追加到 AOF 文件的末尾
步骤 3 执行的第一条命令开启了 AOF 功能,Redis 会阻塞直到初始 AOF 文件创建完成为止, 之后 Redis 会继续处理命令请求,并开始将写入命令追加到 AOF 文件末尾。
步骤 3 执行的第二条命令用于关闭 RDB 功能。这一步是可选的,如果你愿意的话,也可以同时使用 RDB 和 AOF 这两种持久化功能。
注意:别忘了在 redis.conf 中打开 AOF 功能! 否则的话, 服务器重启之后, 之前通过 CONFIG SET 设置的配置就会被遗忘, 程序会按原来的配置来启动服务器。
面试题
redis 持久化方式有哪些?有什么区别?
rdb:基于快照的持久化,速度更快,一般用作备份,主从复制也是依赖于rdb持久化功能
aof:以追加的方式记录redis操作日志的文件。可以最大程度的保证redis数据安全,
类似于mysql的binlog
Redis多数据类型开发(了解)
数据类型(value)种类
- String
- Hash
- List
- Set
- Sorted set
常用的key(自主定制名字)操作
KEYS * 查看KEY支持通配符
DEL 删除给定的一个或多个key
EXISTS 检查是否存在
TYPE 返回键所存储值的类型
EXPIRE PEXPIRE 以秒毫秒设定生存时间
TTL PTTL 以秒毫秒为单位返回生存时间
PERSIST 取消生存实现设置
删
flushdb 清空当前选择的数据库
del mykey mykey2 删除了两个 Keys
改
move mysetkey 1 将当前数据库中的 mysetkey 键移入到 ID 为 1 的数据库中
rename mykey mykey1 将 mykey 改名为 mykey1
renamenx oldkey newkey 如果 newkey 已经存在,则无效
expire mykey 100 将该键的超时设置为 100 秒
persist mykey 将该 Key 的超时去掉,变成持久化的键
查
keys my* 获取当前数据库中所有以my开头的key
exists mykey 若不存在,返回0;存在返回1
select 0 打开 ID 为 0 的数据库
ttl mykey 查看存货时间还剩下多少秒
type mykey 返回mykey对应的值的类型
randomkey 返回数据库中的任意键
string(字符串)
形式比喻:name “张山”
定义:
string是redis最基本的类型,一个key对应一个value。
一个键最大能存储512MB。
String类型有如下基本操作(官网):
set
get
incr 计数+1
incrby 可以指定数量,一次性添加
decr 计数-1
decrby 指定数量
mset 同时设置多个键值对
mget
append
getset
setex
setnx
del
setrange
strlen
getrange
应用场景
基本的键值对存储
常规计数:微博数,粉丝数等
游戏应用,如血量、魔法值
增
set mykey "test" 为键设置新值,并覆盖原有值
getset mycounter 0 设置值,取值同时进行
setex mykey 10 "hello" 设置指定 Key 的过期时间为10秒,在存活时间可以获取value
setnx mykey "hello" 若该键不存在,则为键设置新值
mset key3 "zyx" key4 "xyz" 批量设置键
删
del mykey 删除已有键
改
append mykey "hello" 若该键并不存在,返回当前 Value 的长度
该键已经存在,返回追加后 Value的长度
incr mykey 值增加1,若该key不存在,创建key,初始值设为0,增加后结果为1
decrby mykey 5 值减少5
setrange mykey 20 dd 把第21和22个字节,替换为dd, 超过value长度,自动补0
查
exists mykey 判断该键是否存在,存在返回 1,否则返回0
get mykey 获取Key对应的value
strlen mykey 获取指定 Key 的字符长度
ttl mykey 查看一下指定 Key 的剩余存活时间(秒数)
getrange mykey 1 20 获取第2到第20个字节,若20超过value长度,则截取第2个和后面所有的
mget key3 key4 批量获取键
Hash(字典)
最接近MySQL表结构的数据类型
形式比喻:stu:{id:101,name:zhangsan}
定义:
我们可以将Redis中的Hashes类型看成具有String Key和String Value的map容器。
所以该类型非常适合于存储值对象的信息。如Username、Password和Age等。
如果Hash中包含很少的字段,那么该类型的数据也将仅占用很少的磁盘空间。
每一个Hash可以存储4294967295个键值对。
hash类型有如下操作:
hset
hsetnx
hmset
hdel
del
hincrby
hget
hmget
hlen
hexists
hgetall
hkeys
hvals
应用场景:
存储部分变更的数据,如用户信息等。
127.0.0.1:6379> hset zhangsan name zs 插入
(integer) 1
127.0.0.1:6379> hmset student id 101 name zs age 20 gender male 插入多个
OK
127.0.0.1:6379> hmset stu id 102 name lisi age 21 gender male
OK
127.0.0.1:6379> hmget stu id name age gender 获取指定值
1) "102"
2) "lisi"
3) "21"
4) "male"
127.0.0.1:6379> hgetall stu 获取全部键值对
1) "id"
2) "102"
3) "name"
4) "lisi"
5) "age"
6) "21"
7) "gender"
8) "male"
作业:
MySQL 中 city表中前10行数据,灌入到redis
mysql> select concat("hmset stu_",name," id ",id," name ",name) from t1;
+---------------------------------------------------+
| concat("hmset stu_",name," id ",id," name ",name) |
+---------------------------------------------------+
| hmset stu_zhang3 id 1 name zhang3 |
| hmset stu_li4 id 2 name li4 |
| hmset stu_wang5 id 3 name wang5 |
+---------------------------------------------------+
增
hset myhash field1 "s"
若字段field1不存在,创建该键及与其关联的Hashes, Hashes中,key为field1 ,并设value为s ,若存在会覆盖原value
hsetnx myhash field1 s
若字段field1不存在,创建该键及与其关联的Hashes, Hashes中,key为field1 ,并设value为s, 若字段field1存在,则无效
hmset myhash field1 "hello" field2 "world 一次性设置多个字段
删
hdel myhash field1 删除 myhash 键中字段名为 field1 的字段
del myhash 删除键
改
hincrby myhash field 1 给field的值加1
查
hget myhash field1 获取键值为 myhash,字段为 field1 的值
hlen myhash 获取myhash键的字段数量
hexists myhash field1 判断 myhash 键中是否存在字段名为 field1 的字段
hmget myhash field1 field2 field3 一次性获取多个字段
hgetall myhash 返回 myhash 键的所有字段及其值
hkeys myhash 获取myhash 键中所有字段的名字
hvals myhash 获取 myhash 键中所有字段的值
LIST(列表)
形式比喻:webchat [m1,m2,m3]
定义:
List类型是按照插入顺序排序的字符串链表。
和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。
在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。
与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。
List中可以包含的最大元素数量是4294967295。
常用操作: lpush lpushx linsert rpush rpushx rpoplpush del lrem ltrim lset rpoplpush lrange lpop lindex 应用场景
微信朋友圈
首先看到的是最新的 消息队列系统 比如sina微博: 在Redis中我们的最新微博ID使用了常驻缓存,这是一直更新的。
但是做了限制不能超过5000个ID,因此获取ID的函数会一直询问Redis。
只有在start/count参数超出了这个范围的时候,才需要去访问数据库。
系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。
SQL数据库(或是硬盘上的其他类型数据库)只是在用户需要获取“很远”的数据时才会被触发,
而主页或第一个评论页是不会麻烦到硬盘上的数据库了。
127.0.0.1:6379> LPUSH wechat "today is 1"
(integer) 1
127.0.0.1:6379> LPUSH wechat "today is 2"
(integer) 2
127.0.0.1:6379> LPUSH wechat "today is 3"
(integer) 3
127.0.0.1:6379> LPUSH wechat "today is 4"
(integer) 4
127.0.0.1:6379> LPUSH wechat "today is 5"
127.0.0.1:6379> LRANGE wechat 0 -1 第一个到最后一个
1) "today is 5"
2) "today is 4"
3) "today is 3"
4) "today is 2"
5) "today is 1"
增
lpush mykey a b 若key不存在,创建该键及与其关联的List,依次插入a ,b, 若List类型的key存在,则插入value中
lpushx mykey2 e 若key不存在,此命令无效, 若key存在,则插入value中
linsert mykey before a a1 在 a 的前面插入新元素 a1
linsert mykey after e e2 在e 的后面插入新元素 e2
rpush mykey a b 在链表尾部先插入b,在插入a
rpushx mykey e 若key存在,在尾部插入e, 若key不存在,则无效
rpoplpush mykey mykey2 将mykey的尾部元素弹出,再插入到mykey2 的头部(原子性的操作)
删
del mykey 删除已有键
lrem mykey 2 a 从头部开始找,按先后顺序,值为a的元素,删除数量为2个,若存在第3个,则不删除
ltrim mykey 0 2 从头开始,索引为0,1,2的3个元素,其余全部删除
改
lset mykey 1 e 从头开始, 将索引为1的元素值,设置为新值 e,若索引越界,则返回错误信息
rpoplpush mykey mykey 将 mykey 中的尾部元素移到其头部
查
lrange mykey 0 -1 取链表中的全部元素,其中0表示第一个元素,-1表示最后一个元素。
lrange mykey 0 2 从头开始,取索引为0,1,2的元素
lrange mykey 0 0 从头开始,取第一个元素,从第0个开始,到第0个结束
lpop mykey 获取头部元素,并且弹出头部元素,出栈
lindex mykey 6 从头开始,获取索引为6的元素 若下标越界,则返回nil
SET(集合)
形式比喻:weibo (m1,m2,m3)
定义:
Set类型看作为没有排序的字符集合。
Set可包含的最大元素数量是4294967295。
如果多次添加相同元素,Set中将仅保留该元素的一份拷贝。
常用操作:
sadd 创建
spop
srem
smove
sismember
smembers
scard
srandmember
sdiff 差集
sdiffstore
sinter 交集
sinterstore
sunion 并集
sunionstore
应用场景:
案例: 在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。
Redis还为集合提供了求交集、并集、差集等操作,
可以非常方便的实现如共同关注、共同喜好、二度好友等功能,
对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
127.0.0.1:6379> sadd lxl pg1 pg2 baoqiang masu marong
(integer) 5
127.0.0.1:6379> sadd jnl baoqiang yufan baobeier zhouxingchi
(integer) 4
127.0.0.1:6379> SUNION lxl jnl 并集
1) "zhouxingchi"
2) "baobeier"
3) "pg2"
4) "yufan"
5) "masu"
6) "baoqiang"
7) "pg1"
8) "marong"
127.0.0.1:6379>
127.0.0.1:6379> SINTER lxl jnl 交集
1) "baoqiang"
127.0.0.1:6379> SDIFF lxl jnl 差集
1) "masu"
2) "pg1"
3) "marong"
4) "pg2"
127.0.0.1:6379> SDIFF jnl lxl 差集
1) "yufan"
2) "zhouxingchi"
3) "baobeier"
增
sadd myset a b c
若key不存在,创建该键及与其关联的set,依次插入a ,b,若key存在,则插入value中,若a 在myset中已经存在,则插入了 d 和 e 两个新成员。
删
spop myset 尾部的b被移出,事实上b并不是之前插入的第一个或最后一个成员
srem myset a d f 若f不存在, 移出 a、d ,并返回2
改
smove myset myset2 a 将a从 myset 移到 myset2,
查
sismember myset a 判断 a 是否已经存在,返回值为 1 表示存在。
smembers myset 查看set中的内容
scard myset 获取Set 集合中元素的数量
srandmember myset 随机的返回某一成员
sdiff myset1 myset2 myset3 1和2得到一个结果,拿这个集合和3比较,获得每个独有的值
sdiffstore diffkey myset myset2 myset3 3个集和比较,获取独有的元素,并存入diffkey 关联的Set中
sinter myset myset2 myset3 获得3个集合中都有的元素
sinterstore interkey myset myset2 myset3 把交集存入interkey 关联的Set中
sunion myset myset2 myset3 获取3个集合中的成员的并集
sunionstore unionkey myset myset2 myset3 把并集存入unionkey 关联的Set中
SortedSet(有序集合)
形式比喻:weibo (socre m1,score m2,score m3)
定义:
Sorted-Sets中的每一个成员都会有一个分数(score)与之关联,
Redis正是通过分数来为集合中的成员进行从小到大的排序。
成员是唯一的,但是分数(score)却是可以重复的。 常用操作 zadd zrem zincrby zrange zrank zcard zcount zscore zrangebyscore zremrangebyscore zremrangebyrank zrevrange zrevrangebyscore zrevrangebyscore 应用场景: 排行榜应用,
取TOP N操作 这个需求与上面需求的不同之处在于,前面操作以时间为权重,
这个是以某个条件为权重,比如按顶的次数排序,这时候就需要我们的sorted set出马了,
将你要排序的值设置成sorted set的score,将具体的数据设置成相应的value,
每次只需要执行一条ZADD命令即可。
127.0.0.1:6379> zadd music 0 fskl 0 fshkl 0 lzlsfs 0 tm 0 ass
(integer) 5
127.0.0.1:6379> ZINCRBY music 1000 fskl
"1000"
127.0.0.1:6379> ZINCRBY music 10000 fshkl
"10000"
127.0.0.1:6379> ZINCRBY music 100000 lzlsfs
"100000"
127.0.0.1:6379> ZINCRBY music 1000000 tm
"1000000"
127.0.0.1:6379> ZINCRBY music 100 ass
"100"
127.0.0.1:6379> ZREVRANGE music 0 -1 withscores
1) "tm"
2) "1000000"
3) "lzlsfs"
4) "100000"
5) "fshkl"
6) "10000"
7) "fskl"
8) "1000"
9) "ass"
10) "100"
127.0.0.1:6379> ZREVRANGE music 0 -1
1) "tm"
2) "lzlsfs"
3) "fshkl"
4) "fskl"
5) "ass"
增
zadd myzset 2 "two" 3 "three" 添加两个分数分别是 2 和 3 的两个成员
删
zrem myzset one two 删除多个成员变量,返回删除的数量
改
zincrby myzset 2 one 将成员 one 的分数增加 2,并返回该成员更新后的分数
查
zrange myzset 0 -1 WITHSCORES 返回所有成员和分数,不加WITHSCORES,只返回成员
zrank myzset one 获取成员one在Sorted-Set中的位置索引值。0表示第一个位置
zcard myzset 获取 myzset 键中成员的数量
zcount myzset 1 2 获取分数满足表达式 1 <= score <= 2 的成员的数量
zscore myzset three 获取成员 three 的分数
zrangebyscore myzset 1 2 获取分数满足表达式 1 < score <= 2 的成员
#-inf 表示第一个成员,+inf最后一个成员
#limit限制关键字
#2 3 是索引号
zrangebyscore myzset -inf +inf limit 2 3 返回索引是2和3的成员
zremrangebyscore myzset 1 2 删除分数 1<= score <= 2 的成员,并返回实际删除的数量
zremrangebyrank myzset 0 1 删除位置索引满足表达式 0 <= rank <= 1 的成员
zrevrange myzset 0 -1 WITHSCORES 按位置索引从高到低,获取所有成员和分数
#原始成员:位置索引从小到大
one 0
two 1
#执行顺序:把索引反转
位置索引:从大到小
one 1
two 0
#输出结果: two
one
zrevrange myzset 1 3 获取位置索引,为1,2,3的成员
#相反的顺序:从高到低的顺序
zrevrangebyscore myzset 3 0 获取分数 3>=score>=0的成员并以相反的顺序输出
zrevrangebyscore myzset 4 0 limit 1 2 获取索引是1和2的成员,并反转位置索引
生产消费者模型
消息模式
Redis发布消息通常有两种模式: 队列模式(queuing) 发布-订阅模式(publish-subscribe) 任务队列的好处: 松耦合。 易于扩展。
Redis 发布--订阅
其实从Pub/Sub的机制来看,它更像是一个广播系统,多个Subscriber可以订阅多个Channel,
多个Publisher可以往多个Channel中发布消息。可以这么简单的理解:
Subscriber:收音机,可以收到多个频道,并以队列方式显示
Publisher:电台,可以往不同的FM频道中发消息
Channel:不同频率的FM频道
一个Publisher,多个Subscriber模型
如下图所示,可以作为消息队列或者消息管道。
主要应用:通知、公告
多个Publisher,一个Subscriber模型
可以将PubSub做成独立的HTTP接口,各应用程序作为Publisher向Channel中发送消息,
Subscriber端收到消息后执行相应的业务逻辑,比如写数据库,显示等等。
主要应用:排行榜、投票、计数。
多个Publisher,多个Subscriber模型
故名思议,就是可以向不同的Channel中发送消息,由不同的Subscriber接收。
主要应用:群聊、聊天。
发布订阅实践
PUBLISH channel msg 将信息 message 发送到指定的频道 channel SUBSCRIBE channel [channel ...] 订阅频道,可以同时订阅多个频道 UNSUBSCRIBE [channel ...] 取消订阅指定的频道, 如果不指定频道,则会取消订阅所有频道 PSUBSCRIBE pattern [pattern ...] 订阅一个或多个符合给定模式的频道,每个模式以 * 作为匹配符,
比如 it* 匹配所 有以 it 开头的频道( it.news 、 it.blog 、 it.tweets 等等),
news.* 匹配所有 以 news. 开头的频道( news.it 、 news.global.today 等等),
诸如此类 PUNSUBSCRIBE [pattern [pattern ...]] 退订指定的规则, 如果没有参数则会退订所有规则 PUBSUB subcommand [argument [argument ...]] 查看订阅与发布系统状态 注意:使用发布订阅模式实现的消息队列,当有客户端订阅channel后只能收到后续发布到该频道的消息,
之前发送的不会缓存,必须Provider和Consumer同时在线。
发布订阅例子:
窗口1:
127.0.0.1:6379> SUBSCRIBE baodi
窗口2:
127.0.0.1:6379> PUBLISH baodi "jin tian zhen kaixin!"
订阅多频道:
窗口1:
127.0.0.1:6379> PSUBSCRIBE wang*
窗口2:
127.0.0.1:6379> PUBLISH wangbaoqiang "jintian zhennanshou
消息队列系统对比
客户端在执行订阅命令之后进入了订阅状态
只能接收 SUBSCRIBE 、PSUBSCRIBE、 UNSUBSCRIBE 、PUNSUBSCRIBE 四个命令。
开启的订阅客户端,无法收到该频道之前的消息,因为 Redis 不会对发布的消息进行持久化。
和很多专业的消息队列系统(例如Kafka、RocketMQ)相比,Redis的发布订阅略显粗糙,
例如:无法实现消息堆积和回溯。
但胜在足够简单,如果当前场景可以容忍的这些缺点,也不失为一个不错的选择。
redis事务管理
redis中的事务跟关系型数据库中的事务是一个相似的概念,但是有不同之处。
关系型数据库事务执行失败后面的sql语句不在执行,而redis中的一条命令执行失败,其余的命令照常执行
redis中开启一个事务是使用multi,相当于beginstart transaction,
exec提交事务,discard取消队列命令(非回滚操作)。
事务命令
DISCARD
取消事务,放弃执行事务块内的所有命令。
EXEC
执行所有事务块内的命令。
MULTI
标记一个事务块的开始。
UNWATCH
取消 WATCH 命令对所有 key 的监视。
WATCH key [key ...] -- 乐观锁
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,
那么事务将被打断。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set a 1
QUEUED 放入了队列中,并没有执行
127.0.0.1:6379> set b 2
QUEUED
127.0.0.1:6379> set c 3
QUEUED
127.0.0.1:6379> exec 提交才回执行
1) OK
2) OK
3) OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set a 10
QUEUED
127.0.0.1:6379> set b 20
QUEUED
127.0.0.1:6379> set c 30
QUEUED
127.0.0.1:6379> DISCARD 取消后,就不执行了
OK
redis事务中的锁机制
举例:我正在买票 Ticket -1 , money -100 而票只有1张, 如果在我multi之后,和exec之前, 票被别人买了,即ticket变成0了. 我该如何观察这种情景,并不再提交 悲观的想法: 世界充满危险,肯定有人和我抢, 给ticket上锁, 只有我能操作. [悲观锁] 乐观的想法: 没有那么多人和我抢,因此,我只需要注意,有没有人更改ticket的值就可以了 [乐观锁]
redis就是乐观锁的机制
服务器管理命令
Info 数据库状态信息
重点关注Memory和Replication Clinet list Client kill ip:port config get * CONFIG GET/SET 动态修改 Dbsize 查看当前库key的个数 FLUSHALL 清空所有数据 包括已经持久化的信息 select 1 根据索引进入相应的库 redis中有16个库, FLUSHDB 清空当前库 MONITOR 监控实时指令 SHUTDOWN 关闭服务器 save将当前数据保存 SLAVEOF host port 主从配置 SLAVEOF NO ONE SYNC 主从同步 ROLE返回主从角色
Redis复制及高可用
使用异步复制。
一个主服务器可以有多个从服务器。
从服务器也可以有自己的从服务器。
复制功能不会阻塞主服务器。
可以通过复制功能来让主服务器免于执行持久化操作,由从服务器去执行持久化操作即可。
但还是建议打开,防止雪崩灾难导致数据丢失
当配置Redis复制功能时,强烈建议打开主服务器的持久化功能。
否则的话,由于延迟等问题,部署的服务应该要避免自动拉起。 为了帮助理解主服务器关闭持久化时自动拉起的危险性,参考以下会导致主从服务器数据全部丢失的例子: 1. 假设节点A为主服务器,并且关闭了持久化。 并且节点B和节点C从节点A复制数据 2. 节点A崩溃,然后由自动拉起服务重启了节点A. 由于节点A的持久化被关闭了,
所以重启之后没有任何数据 3. 节点B和节点C将从节点A复制数据,但是A的数据是空的, 于是就把自身保存的数据副本删除。 在关闭主服务器上的持久化,
并同时开启自动拉起进程的情况下,即便使用Sentinel来实现Redis的高可用性,也是非常危险的。
因为主服务器可能拉起得非常快,
以至于Sentinel在配置的心跳时间间隔内没有检测到主服务器已被重启,
然后还是会执行上面的数据丢失的流程。 无论何时,数据安全都是极其重要的,所以应该禁止主服务器关闭持久化的同时自动拉起。
以下是关于 Redis 复制功能的几个重要方面:
Redis 使用异步复制。 从 Redis 2.8 开始, 从服务器会以每秒一次的频率向主服务器报告复制流(replication stream)的处理进度。
一个主服务器可以有多个从服务器。
不仅主服务器可以有从服务器, 从服务器也可以有自己的从服务器, 多个从服务器之间可以构成一个图状结构。
复制功能不会阻塞主服务器: 即使有一个或多个从服务器正在进行初次同步, 主服务器也可以继续处理命令请求。
复制功能也不会阻塞从服务器: 只要在 redis.conf 文件中进行了相应的设置, 即使从服务器正在进行初次同步,
服务器也可以使用旧版本的数据集来处理命令查询。
不过, 在从服务器删除旧版本数据集并载入新版本数据集的那段时间内, 连接请求会被阻塞。
你还可以配置从服务器, 让它在与主服务器之间的连接断开时, 向客户端发送一个错误。
复制功能可以单纯地用于数据冗余(data redundancy), 也可以通过让多个从服务器处理只读命令请求来提升扩展性(scalability):
比如说, 繁重的 SORT 命令可以交给附属节点去运行。
可以通过复制功能来让主服务器免于执行持久化操作: 只要关闭主服务器的持久化功能, 然后由从服务器去执行持久化操作即可。
主从复制原理
1. 从服务器向主服务器发送 SYNC 命令。 2. 接到 SYNC 命令的主服务器会调用BGSAVE 命令,创建一个 RDB 文件,并使用缓冲区记录接下来执行的所有写命令。 3. 当主服务器执行完 BGSAVE 命令时,它会向从服务器发送 RDB 文件,而从服务器则会接收并载入这个文件。 4. 主服务器将缓冲区储存的所有写命令发送给从服务器执行。
1、在开启主从复制的时候,使用的是RDB方式的,同步主从数据的 2、同步开始之后,通过主库命令传播的方式,主动的复制方式实现 3、2.8以后实现PSYNC的机制,实现断线重连
SYNC命令执行过程
在主从服务器完成同步之后,
主服务器每执行一个写命令,它都会将被执行的写命令发送给从服务器执行,
这个操作被称为“命令传播”(command propagate)。
命令传播是一个持续的过程:只要复制仍在继续,命令传播就会一直进行,使得主从服务器的状态可以一直保持一致。
复制中的SYNC和PSYNC
在 Redis 2.8 版本之前,
断线之后重连的从服务器总要执行一次完整重同步(full resynchronization)操作。 从 Redis 2.8 开始,Redis 使用 PSYNC命令代替 SYNC 命令。 PSYNC 比起 SYNC 的最大改进在于 PSYNC 实现了部分重同步(partial resync)
特性:在主从服务器断线并且重新连接的时候,只要条件允许,PSYNC 可以让主服务器只向从服务器同步断线期间缺失的数据,
而不用重新向从服务器同步整个数据库。
SYNC处理断线重连方式
PSYNC处理断线重连
复制的一致性的问题
为了实现主从复制,读写分离的结构,同时,也产生了数据一致性的问题
当然,不止是redis,mysql也会有这个问题
1、在读写分离环境下,客户端向主服务器发送写命令 SET n 10086,
主服务器在执行这个写命令之后,向客户端返回回复,并将这个写命令传播给从服务器。
2、接到回复的客户端继续向从服务器发送读命令 GET n
并且因为网络状态的原因,客户端的 GET命令比主服务器传播的 SET 命令更快到达了从服务器。 3、因为从服务器键 n 的值还未被更新,所以客户端在从服务器读取到的将是一个错误(过期)的 n值。
复制安全性提升
主服务器只在有至少 N 个从服务器的情况下,才执行写操作 从 Redis 2.8 开始, 为了保证数据的安全性, 可以通过配置, 让主服务器只在有至少 N 个当前已连接从服务器的情况下, 才执行写命令。 不过, 因为 Redis 使用异步复制, 所以主服务器发送的写数据并不一定会被从服务器接收到, 因此, 数据丢失的可能性仍然是存在的。 通过以下两个参数保证数据的安全: min-slaves-to-write <number of slaves>
最少写入的从库数量,默认没有开启,最少有几台从库完完整整的接收成功了,
否则阻塞 min-slaves-max-lag <number of seconds>
最小延迟时间,从库的网络延迟时间在范围内,才认定是执行成功的
这个特性的运作原理:
从服务器以每秒一次的频率 PING 主服务器一次, 并报告复制流的处理情况。
主服务器会记录各个从服务器最后一次向它发送 PING 的时间。
用户可以通过配置, 指定网络延迟的最大值 min-slaves-max-lag ,
以及执行写操作所需的至少从服务器数量 min-slaves-to-write 。
如果至少有 min-slaves-to-write 个从服务器, 并且这些服务器的延迟值都少于 min-slaves-max-lag秒,
那么主服务器就会执行客户端请求的写操作。
你可以将这个特性看作 CAP 理论中的 C 的条件放宽版本: 尽管不能保证写操作的持久性,
但起码丢失数据的窗口会被严格限制在指定的秒数中。
另一方面, 如果条件达不到 min-slaves-to-write 和 min-slaves-max-lag 所指定的条件, 那么写操作就不会被执行
主服务器会向请求执行写操作的客户端返回一个错误。
主从复制实践
1、环境: 准备两个或两个以上redis实例 mkdir /data/638{0..2} 配置文件示例: vim /data/6380/redis.conf port 6380 daemonize yes pidfile /data/6380/redis.pid loglevel notice logfile "/data/6380/redis.log" dbfilename dump.rdb dir /data/6380 protected-mode no vim /data/6381/redis.conf port 6381 daemonize yes pidfile /data/6381/redis.pid loglevel notice logfile "/data/6381/redis.log" dbfilename dump.rdb dir /data/6381 protected-mode no vim /data/6382/redis.conf port 6382 daemonize yes pidfile /data/6382/redis.pid loglevel notice logfile "/data/6382/redis.log" dbfilename dump.rdb dir /data/6382 protected-mode no 启动: redis-server /data/6380/redis.conf redis-server /data/6381/redis.conf redis-server /data/6382/redis.conf 主节点:6380 从节点:6381、6382 2、开启主从: 6381/6382命令行:
构建 redis-cli -p 6381 SLAVEOF 127.0.0.1 6380 redis-cli -p 6382 SLAVEOF 127.0.0.1 6380
Redis主从复制管理和切换
主从复制状态监控: info replication 主从切换: slaveof no one
模拟主库故障
redis-cli -p 6380 shutdown
登陆从库
redis-cli -p 6381
info replication 发现主库down了
选择最完整的一台从库,取消从库角色
slaveof no one
将另外一台重新构建
6382连接到6381:
[root@db03 ~]# redis-cli -p 6382
127.0.0.1:6382> SLAVEOF no one
127.0.0.1:6382> SLAVEOF 127.0.0.1 6381
Redis高可用
在之前,主库宕机之后,我们通过一系列的手动操作完成了主从的切换
但是,在我们手动实现的时候有很多不足的地方
1、纯手工--- 我们不能24小时盯着所有的服务器,在使用监控软件时也会有一系列的等待和延迟
2、选主的问题---不够严谨
3、手工指定新主从后,客户端还不能知道新主,要修改APP中的配置
Redis Sentinel(哨兵)
Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,
假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,
而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。
Sentinel的构造
Sentinel 是一个监视器,它可以根据被监视实例的身份和状态来判断应该执行何种动作。
Sentinel的功能
监控(Monitoring):
Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
提醒(Notification):
当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover):
当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作,
它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器;
当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
发现并连接主服务器
Sentinel 通过用户给定的配置文件来发现主服务器。
Sentinel 会与被监视的主服务器创建两个网络连接:
命令连接用于向主服务器发送命令。
订阅连接用于订阅指定的频道,从而发现监视同一主服务器的其他 Sentinel (细节马上会介绍)。
redis-sentinel sentinel.conf
############ master1 configure ##############
sentinel monitor master1 127.0.0.1 6379 2
sentinel down-after-milliseconds master1 30000
sentinel parallel-syncs master1 1
sentinel failover-timeout master1 900000
############ master2 configure ##############
sentinel monitor master2 127.0.0.1 12345 5
sentinel down-after-milliseconds master2 50000
sentinel parallel-syncs master2 5
sentinel failover-timeout master2 450000
发现并连接从服务器
Sentinel 通过向主服务器发送 INFO 命令来自动获得所有从服务器的地址。
跟主服务器一样,Sentinel 会与每个被发现的从服务器创建命令连接和订阅连接。
发现其他sentinel
多个sentinel保证系统的高可用,防止一个sentinel宕机
sentinel1 通过发送HELLO 信息来让sentinel2 和 sentinel3发现自己,其他两个sentinel 也会进行类似的操作。
Sentinel 会通过命令连接向被监视的主从服务器发送 “HELLO” 信息,该消息包含 Sentinel 的 IP、端口号、ID 等内容,
以此来向其他 Sentinel 宣告自己的存在。与此同时Sentinel 会通过订阅连接接收其他 Sentinel 的“HELLO” 信息,
以此来发现监视同一个主服务器的其他 Sentinel 。 一个 Sentinel 可以与其他多个 Sentinel 进行连接,
各个 Sentinel 之间可以互相检查对方的可用性, 并进行信息交换。 你无须为运行的每个 Sentinel 分别设置其他 Sentinel 的地址,
因为 Sentinel 可以通过发布与订阅功能来自动发现正在监视相同主服务器的其他 Sentinel ,
这一功能是通过向频道 __sentinel__:hello 发送信息来实现的。 与此类似, 你也不必手动列出主服务器属下的所有从服务器, 因为 Sentinel 可以通过询问主服务器来获得所有从服务器的信息。 每个 Sentinel 会以每两秒一次的频率, 通过发布与订阅功能,
向被它监视的所有主服务器和从服务器的 __sentinel__:hello 频道发送一条信息,
信息中包含了 Sentinel 的 IP 地址、端口号和运行 ID (runid)。 每个 Sentinel 都订阅了被它监视的所有主服务器和从服务器的 __sentinel__:hello 频道,
查找之前未出现过的 sentinel (looking for unknown sentinels)。
当一个 Sentinel 发现一个新的 Sentinel 时, 它会将新的 Sentinel 添加到一个列表中,
这个列表保存了 Sentinel 已知的, 监视同一个主服务器的所有其他 Sentinel 。 Sentinel 发送的信息中还包括完整的主服务器当前配置(configuration)。
如果一个 Sentinel 包含的主服务器配置比另一个 Sentinel 发送的配置要旧, 那么这个 Sentinel 会立即升级到新配置上。 在将一个新 Sentinel 添加到监视主服务器的列表上面之前,
Sentinel 会先检查列表中是否已经包含了和要添加的 Sentinel 拥有相同运行 ID 或者相同地址(包括 IP 地址和端口号)的 Sentinel ,
如果是的话, Sentinel 会先移除列表中已有的那些拥有相同运行 ID 或者相同地址的 Sentinel , 然后再添加新 Sentinel 。
多个sentinel之间的链接
Sentinel 之间只会互相创建命令连接,用于进行通信。因为已经有主从服务器作为发送和接收 HELLO 信息的中介,所以 Sentinel之间不会创建订阅连接。
检测实例状态
Sentinel 使用 PING 命令来检测实例的状态:如果实例在指定的时间内没有返回回复,或者返回错误的回复,那么该实例会被 Sentinel 判断为下线。 Redis 的 Sentinel 中关于下线(down)有两个不同的概念: 主观下线(Subjectively Down, 简称 SDOWN)指的是单个 Sentinel 实例对服务器做出的下线判断。 客观下线(Objectively Down, 简称 ODOWN)指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断,
并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断。
(一个 Sentinel 可以通过向另一个 Sentinel 发送 SENTINEL is-master-down-by-addr 命令来询问对方是否认为给定的服务器已下线。) 如果一个服务器没有在 master-down-after-milliseconds 选项所指定的时间内,
对向它发送 PING 命令的 Sentinel 返回一个有效回复(valid reply), 那么 Sentinel 就会将这个服务器标记为主观下线。 服务器对 PING 命令的有效回复可以是以下三种回复的其中一种: 返回 +PONG 。 返回 -LOADING 错误。 返回 -MASTERDOWN 错误。 如果服务器返回除以上三种回复之外的其他回复, 又或者在指定时间内没有回复 Ping命令, 那么 Sentinel 认为服务器返回的回复无效(non-valid)。 注意, 一个服务器必须在 master-down-after-milliseconds 毫秒内, 一直返回无效回复才会被 Sentinel 标记为主观下线。 举个例子, 如果 master-down-after-milliseconds 选项的值为 30000 毫秒(30 秒),
那么只要服务器能在每 29 秒之内返回至少一次有效回复, 这个服务器就仍然会被认为是处于正常状态的。 从主观下线状态切换到客观下线状态并没有使用严格的法定人数算法(strong quorum algorithm),
而是使用了流言协议: 如果 Sentinel 在给定的时间范围内, 从其他 Sentinel 那里接收到了足够数量的主服务器下线报告,
那么 Sentinel 就会将主服务器的状态从主观下线改变为客观下线。 如果之后其他 Sentinel 不再报告主服务器已下线, 那么客观下线状态就会被移除。 客观下线条件只适用于主服务器: 对于任何其他类型的 Redis 实例, Sentinel 在将它们判断为下线前不需要进行协商,
所以从服务器或者其他 Sentinel 永远不会达到客观下线条件。 只要一个 Sentinel 发现某个主服务器进入了客观下线状态, 这个 Sentinel 就可能会被其他 Sentinel 推选出,
并对失效的主服务器执行自动故障迁移操作。
故障转移FAILOVER
一次故障转移操作由以下步骤组成: 1、发现主服务器已经进入客观下线状态。 2、基于Raft leader election 协议 ,进行投票选举 3、如果当选失败,那么在设定的故障迁移超时时间的两倍之后,重新尝试当选。 如果当选成功, 那么执行以下步骤。 4、选出一个从服务器,并将它升级为主服务器。 5、向被选中的从服务器发送 SLAVEOF NO ONE 命令,让它转变为主服务器。 6、通过发布与订阅功能, 将更新后的配置传播给所有其他 Sentinel ,其他 Sentinel 对它们自己的配置进行更新。 7、向已下线主服务器的从服务器发送 SLAVEOF 命令,让它们去复制新的主服务器。 8、当所有从服务器都已经开始复制新的主服务器时, leader Sentinel 终止这次故障迁移操作。 每当一个 Redis 实例被重新配置(reconfigured)
—— 无论是被设置成主服务器、从服务器、又或者被设置成其他主服务器的从服务器
—— Sentinel 都会向被重新配置的实例发送一个 CONFIG REWRITE 命令, 从而确保这些配置会持久化在硬盘里。 Sentinel 使用以下规则来选择新的主服务器: 在失效主服务器属下的从服务器当中, 那些被标记为主观下线、已断线、或者最后一次回复 PING 命令的时间大于五秒钟的从服务器都会被淘汰。 在失效主服务器属下的从服务器当中, 那些与失效主服务器连接断开的时长超过 down-after 选项指定的时长十倍的从服务器都会被淘汰。 在经历了以上两轮淘汰之后剩下来的从服务器中, 我们选出复制偏移量(replication offset)最大的那个从服务器作为新的主服务器;
如果复制偏移量不可用, 或者从服务器的复制偏移量相同, 那么带有最小运行 ID 的那个从服务器成为新的主服务器。 Sentinel 自动故障迁移的一致性特质 Sentinel 自动故障迁移使用 Raft 算法来选举领头(leader) Sentinel , 从而确保在一个给定的周期(epoch)里, 只有一个领头产生。 这表示在同一个周期中, 不会有两个 Sentinel 同时被选中为领头, 并且各个 Sentinel 在同一个节点中只会对一个领头进行投票。 更高的配置节点总是优于较低的节点, 因此每个 Sentinel 都会主动使用更新的节点来代替自己的配置。 简单来说, 我们可以将 Sentinel 配置看作是一个带有版本号的状态。
一个状态会以最后写入者胜出(last-write-wins)的方式(也即是,最新的配置总是胜出)传播至所有其他 Sentinel 。 举个例子, 当出现网络分割(network partitions)时,
一个 Sentinel 可能会包含了较旧的配置, 而当这个 Sentinel 接到其他 Sentinel 发来的版本更新的配置时,
Sentinel 就会对自己的配置进行更新。 如果要在网络分割出现的情况下仍然保持一致性, 那么应该使用 min-slaves-to-write 选项,
让主服务器在连接的从实例少于给定数量时停止执行写操作, 与此同时, 应该在每个运行 Redis 主服务器或从服务器的机器上运行 Redis Sentinel 进程。 Sentinel 状态的持久化 Sentinel 的状态会被持久化在 Sentinel 配置文件里面。 每当 Sentinel 接收到一个新的配置, 或者当领头 Sentinel 为主服务器创建一个新的配置时, 这个配置会与配置节点一起被保存到磁盘里面。 这意味着停止和重启 Sentinel 进程都是安全的。 Sentinel 在非故障迁移的情况下对实例进行重新配置 即使没有自动故障迁移操作在进行, Sentinel 总会尝试将当前的配置设置到被监视的实例上面。 特别是: 根据当前的配置, 如果一个从服务器被宣告为主服务器, 那么它会代替原有的主服务器, 成为新的主服务器,
并且成为原有主服务器的所有从服务器的复制对象。 那些连接了错误主服务器的从服务器会被重新配置, 使得这些从服务器会去复制正确的主服务器。 不过, 在以上这些条件满足之后, Sentinel 在对实例进行重新配置之前仍然会等待一段足够长的时间,
确保可以接收到其他 Sentinel 发来的配置更新, 从而避免自身因为保存了过期的配置而对实例进行了不必要的重新配置。
santinel配置
mkdir /data/26380 cp /usr/local/redis/src/redis-sentinel /data/26380 cd /data/26380 Vim sentinel.conf port 26380 dir "/data/26380" sentinel monitor mymaster 127.0.0.1 6380 1 sentinel down-after-milliseconds mymaster 60000 sentinel config-epoch mymaster 0 启动 ./redis-sentinel ./sentinel.conf & 停主库测试
[root@db01 ~]# redis-cli -p 6380
shutdown
[root@db01 ~]# redis-cli -p 6381
info replication
启动原主库(6380),再看状态。
运行一个 Sentinel 所需的最少配置如下所示: sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 60000 sentinel failover-timeout mymaster 180000 sentinel parallel-syncs mymaster 1 sentinel monitor resque 192.168.1.3 6380 4 sentinel down-after-milliseconds resque 10000 sentinel failover-timeout resque 180000 sentinel parallel-syncs resque 5 第一行配置指示 Sentinel 去监视一个名为 mymaster 的主服务器,
这个主服务器的 IP 地址为 127.0.0.1 , 端口号为 6379 ,
而将这个主服务器判断为失效至少需要 2 个 Sentinel 同意 (只要同意 Sentinel 的数量不达标,自动故障迁移就不会执行)。 不过要注意, 无论你设置要多少个 Sentinel 同意才能判断一个服务器失效,
一个 Sentinel 都需要获得系统中多数(majority) Sentinel 的支持, 才能发起一次自动故障迁移,
并预留一个给定的配置节点 (configuration Epoch ,一个配置节点就是一个新主服务器配置的版本号)。 换句话说, 在只有少数(minority) Sentinel 进程正常运作的情况下, Sentinel 是不能执行自动故障迁移的。 其他选项的基本格式如下: sentinel <选项的名字> <主服务器的名字> <选项的值> 各个选项的功能如下: down-after-milliseconds 选项指定了 Sentinel 认为服务器已经断线所需的毫秒数。 如果服务器在给定的毫秒数之内, 没有返回 Sentinel 发送的 Ping 命令的回复, 或者返回一个错误,
那么 Sentinel 将这个服务器标记为主观下线(subjectively down,简称 SDOWN )。 不过只有一个 Sentinel 将服务器标记为主观下线并不一定会引起服务器的自动故障迁移:
只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 服务器才会被标记为客观下线(objectively down, 简称 ODOWN ),
这时自动故障迁移才会执行。将服务器标记为客观下线所需的 Sentinel 数量由对主服务器的配置决定。 parallel-syncs 选项指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步,
这个数字越小, 完成故障转移所需的时间就越长。 如果从服务器被设置为允许使用过期数据集(参见对 redis.conf 文件中对 slave-serve-stale-data 选项的说明),
那么你可能不希望所有从服务器都在同一时间向新的主服务器发送同步请求,
因为尽管复制过程的绝大部分步骤都不会阻塞从服务器, 但从服务器在载入主服务器发来的 RDB 文件时,
仍然会造成从服务器在一段时间内不能处理命令请求: 如果全部从服务器一起对新的主服务器进行同步,
那么就可能会造成所有从服务器在短时间内全部不可用的情况出现。
你可以通过将这个值设为 1 来保证每次只有一个从服务器处于不能处理命令请求的状态。
配置文件
指定监控master {2表示多少个sentinel同意} sentinel monitor mymaster 127.0.0.1 6370 2 安全信息 sentinel auth-pass mymaster root 超过15000毫秒后认为主机宕机 sentinel down-after-milliseconds mymaster 15000 当主从切换多久后认为主从切换失败 sentinel failover-timeout mymaster 900000 这两个配置后面的数量主从机需要一样,epoch为master的版本 sentinel leader-epoch mymaster 1 sentinel config-epoch mymaster 1
sentinel命令
PING :返回 PONG 。 SENTINEL masters :列出所有被监视的主服务器 SENTINEL slaves <master name> SENTINEL get-master-addr-by-name <master name> : 返回给定名字的主服务器的 IP 地址和端口号。 SENTINEL reset <pattern> : 重置所有名字和给定模式 pattern 相匹配的主服务器。 SENTINEL failover <master name> : 当主服务器失效时, 在不询问其他 Sentinel 意见的情况下, 强制开始一次自动故障迁移。
Redis 集群分片(cluster)
redis集群
Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施(installation)。 Redis 集群不支持那些需要同时处理多个键的 Redis 命令,
因为执行这些命令需要在多个 Redis 节点之间移动数据, 并且在高负载的情况下,
这些命令将降低 Redis 集群的性能, 并导致不可预测的行为。 Redis 集群通过分区(partition)来提供一定程度的可用性(availability):
即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。 将数据自动切分(split)到多个节点的能力。 当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。
高性能
基于KEY进行数据拆分 1、在多分片节点中,将16384个槽位,均匀分布到多个分片节点中 2、存数据时,将key做crc16(key),然后和16384进行取模,得出槽位值(0-16383之间) 3、根据计算得出的槽位值,找到相对应的分片节点的主节点,存储到相应槽位上 4、如果客户端当时连接的节点不是将来要存储的分片节点,分片集群会将客户端连接切换至真正存储节点进行数据存储
高可用
在搭建集群时,会为每一个分片的主节点,对应一个从节点,实现slaveof的功能,同时当主节点down,实现类似于sentinel的自动failover的功能。
集群数据共享
Redis 集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现:
一个 Redis 集群包含 16384 个哈希槽(hash slot),
数据库中的每个键都属于这 16384 个哈希槽的其中一个,
集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽,
其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。 节点 A 负责处理 0 号至 5500 号哈希槽。 节点 B 负责处理 5501 号至 11000 号哈希槽。 节点 C 负责处理 11001 号至 16384 号哈希槽。
Redis Cluster
运行机制
所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽. 节点的fail是通过集群中超过半数的master节点检测失效时才生效. 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可 把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->key
集群的复制
为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作,
Redis 集群对节点使用了主从复制功能:
集群中的每个节点都有 1 个至 N 个复制品(replica),
其中一个复制品为主节点(master),
而其余的 N-1 个复制品为从节点(slave)。 在之前列举的节点 A 、B 、C 的例子中, 如果节点 B 下线了, 那么集群将无法正常运行,
因为集群找不到节点来处理 5501 号至 11000 号的哈希槽。 假如在创建集群的时候(或者至少在节点 B 下线之前), 我们为主节点 B 添加了从节点 B1 ,
那么当主节点 B 下线的时候, 集群就会将 B1 设置为新的主节点, 并让它代替下线的主节点 B ,
继续处理 5501 号至 11000 号的哈希槽, 这样集群就不会因为主节点 B 的下线而无法正常运作了。 不过如果节点 B 和 B1 都下线的话, Redis 集群还是会停止运作。 集群的复制特性重用了 SLAVEOF 命令的代码,所以集群节点的复制行为和 SLAVEOF 命令的复制行为完全相同。
集群的故障转移
在集群里面,节点会对其他节点进行下线检测。
当一个主节点下线时,集群里面的其他主节点负责对下线主节点进行故障移。
换句话说,集群的节点集成了下线检测和故障转移等类似 Sentinel 的功能。
因为 Sentinel 是一个独立运行的监控程序,而集群的下线检测和故障转移等功能是集成在节点里面的,
它们的运行模式非常地不同,所以尽管这两者的功能很相似,但集群的实现没有重用 Sentinel 的代码。
在集群里面执行命令的另种情况
命令发送到了正确的节点:命令要处理的键所在的槽正好是由接收命令的节点负责,那么该节点执行命令,
就像单机 Redis 服务器一样。
命令发送到了错误的节点:接收到命令的节点并非处理键所在槽的节点,那么节点将向客户端返回一个转向(redirection)错误,
告知客户端应该到哪个节点去执行这个命令,客户端会根据错误提示的信息,重新向正确的节点发送命令。
转向错误的实现
1、集群中的节点会互相告知对方,自己负责处理哪些槽。
2、集群中的每个节点都会记录 16384 个槽分别由哪个节点负责,从而形成一个“槽表”(slot table)。
节点在接收到命令请求时,会通过槽表检查键所在的槽是否由本节点处理:
- 如果是的话,那么节点直接执行命令;
- 如果不是的话,那么节点就从槽表里面提取出正确节点的地址信息,然后返回转向错误
Redis Cluter配置插件安装
EPEL源安装ruby支持 yum install ruby rubygems -y 使用国内源 gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/ gem install redis -v 3.3.3 gem sources -l 如果无法使用,可以使用aliyun gem sources -a http://mirrors.aliyun.com/rubygems/ gem sources --remove http://rubygems.org/
配置文件
Redis 集群由多个运行在集群模式(cluster mode)下的 Redis 实例组成, 实例的集群模式需要通过配置来开启, 开启集群模式的实例将可以使用集群特有的功能和命令。 以下是一个包含了最少选项的集群配置文件示例: port 7000 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes 文件中的 cluster-enabled 选项用于开实例的集群模式,
而 cluster-conf-file 选项则设定了保存节点配置文件的路径, 默认值为 nodes.conf 。 节点配置文件无须人为修改, 它由 Redis 集群在启动时创建, 并在有需要时自动进行更新。 要让集群正常运作至少需要三个主节点, 不过在刚开始试用集群功能时,
强烈建议使用六个节点: 其中三个为主节点, 而其余三个则是各个主节点的从节点。 cd /data mkdir cluster-test cd cluster-test mkdir 7000 7001 7002 7003 7004 7005
创建应用
mkdir cluster-test cd cluster-test mkdir 7000 7001 7002 7003 7004 7005 拷贝应用 cp redis.conf redis-server ./7000 启动应用 cd 7000 ./redis-server ./redis.conf
创建集群
{redis_src_home}/src/redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 给定 redis-trib.rb 程序的命令是 create , 这表示我们希望创建一个新的集群。 选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
集群客户端
redis-cli -c -p 7000 set foo bar get foo 重新分片 ./redis-trib.rb reshard 127.0.0.1:7000
集群管理
集群主节点状态 redis-cli -p 7000 cluster nodes | grep master 集群从节点状态 redis-cli -p 7000 cluster nodes | grep slave 增加新的节点 ./redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000 删除一个节点 redis-trib del-node ip:port '<node-id>' 删除master节点之前首先要使用reshard移除master的全部slot,然后再删除当前节点 添加一个从节点 ./redis-trib.rb add-node --slave --master-id $[nodeid] 127.0.0.1:7008 127.0.0.1:7000
规划和搭建的实践过程
6个redis实例,一般会放到3台硬件服务器 注:在企业规划中,一个分片的两个节点,分到不同的物理机,防止硬件主机宕机造成的整个分片数据丢失。 端口号:7000-7005 1、安装集群插件 EPEL源安装ruby支持 yum install ruby rubygems -y 使用国内源 gem sources -l gem sources -a http://mirrors.aliyun.com/rubygems/ gem sources --remove http://rubygems.org/ gem install redis -v 3.3.3 gem sources -l 或者: gem sources -a http://mirrors.aliyun.com/rubygems/ --remove http://rubygems.org/ --- 2、集群节点准备 mkdir /data/700{0..5} vim /data/7000/redis.conf port 7000 daemonize yes pidfile /data/7000/redis.pid loglevel notice logfile "/data/7000/redis.log" dbfilename dump.rdb dir /data/7000 protected-mode no cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes vim /data/7001/redis.conf port 7001 daemonize yes pidfile /data/7001/redis.pid loglevel notice logfile "/data/7001/redis.log" dbfilename dump.rdb dir /data/7001 protected-mode no cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes vim /data/7002/redis.conf port 7002 daemonize yes pidfile /data/7002/redis.pid loglevel notice logfile "/data/7002/redis.log" dbfilename dump.rdb dir /data/7002 protected-mode no cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes vim /data/7003/redis.conf port 7003 daemonize yes pidfile /data/7003/redis.pid loglevel notice logfile "/data/7003/redis.log" dbfilename dump.rdb dir /data/7003 protected-mode no cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes vim /data/7004/redis.conf port 7004 daemonize yes pidfile /data/7004/redis.pid loglevel notice logfile "/data/7004/redis.log" dbfilename dump.rdb dir /data/7004 protected-mode no cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes vim /data/7005/redis.conf port 7005 daemonize yes pidfile /data/7005/redis.pid loglevel notice logfile "/data/7005/redis.log" dbfilename dump.rdb dir /data/7005 protected-mode no cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes 启动节点: redis-server /data/7000/redis.conf redis-server /data/7001/redis.conf redis-server /data/7002/redis.conf redis-server /data/7003/redis.conf redis-server /data/7004/redis.conf redis-server /data/7005/redis.conf [root@db01 ~]# ps -ef |grep redis root 8854 1 0 03:56 ? 00:00:00 redis-server *:7000 [cluster] root 8858 1 0 03:56 ? 00:00:00 redis-server *:7001 [cluster] root 8860 1 0 03:56 ? 00:00:00 redis-server *:7002 [cluster] root 8864 1 0 03:56 ? 00:00:00 redis-server *:7003 [cluster] root 8866 1 0 03:56 ? 00:00:00 redis-server *:7004 [cluster] root 8874 1 0 03:56 ? 00:00:00 redis-server *:7005 [cluster] 3、将节点加入集群管理 redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 4、集群状态查看 集群主节点状态 redis-cli -p 7000 cluster nodes | grep master 集群从节点状态 redis-cli -p 7000 cluster nodes | grep slave 5、集群节点管理 5.1 增加新的节点 mkdir /data/7006 mkdir /data/7007 vim /data/7006/redis.conf port 7006 daemonize yes pidfile /data/7006/redis.pid loglevel notice logfile "/data/7006/redis.log" dbfilename dump.rdb dir /data/7006 protected-mode no cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes vim /data/7007/redis.conf port 7007 daemonize yes pidfile /data/7007/redis.pid loglevel notice logfile "/data/7007/redis.log" dbfilename dump.rdb dir /data/7007 protected-mode no cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes redis-server /data/7006/redis.conf redis-server /data/7007/redis.conf 5.2 添加主节点: redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000 5.3 转移slot(重新分片) redis-trib.rb reshard 127.0.0.1:7000
执行之后,会交互提示,输入想移动的槽位:通过自己重新计算获得数值,然后输入
提示输入nodeID (唯一的):
输入从哪些节点上分配:输入all 5.4 添加从节点 redis-trib.rb add-node --slave --master-id bff7d6e603578033f53865de3e55fb2b8c526b60 127.0.0.1:7007 127.0.0.1:7000 6.删除节点 6.1 将需要删除节点slot移动走 redis-trib.rb reshard 127.0.0.1:7000 交互提示输入
1、数量
2、接收者node
3、源节点node
4、输入done,结束 删除一个节点 删除master节点之前首先要使用reshard移除master的全部slot,然后再删除当前节点 主节点删除: redis-trib.rb del-node 127.0.0.1:7006 bff7d6e603578033f53865de3e55fb2b8c526b60 从节点删除: redis-trib.rb del-node 127.0.0.1:7007 2af3da4252ad1a7334d476e1b56498b85a1b488c
Redis API支持(Python篇)
注:不同的架构需要不同的API支持,所以要先了解使用的是哪种架构
Redis提供了各类开发语言的API,方便开发语言连接使用Redis。 https://redis.io/clients 官方网站提供了不同开发语言的API程序。
python链接redis
https://redis.io/clients#python 官网中,给我们提供了很多种Python连接redis的API,我们通常选择有“笑脸”并且带有“星号”的使用 这里我们推荐使用redis-py.
redis-py提供的,python 连接及操作redis方式:
redis-py提供两个类Redis和StrictRedis用于实现Redis的命令。
StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令(比如,SET命令对应与StrictRedis.set方法)。
Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py。
简单说,官方推荐使用StrictRedis方法。不推荐Redis类,原因是他和咱们在redis-cli操作有些不一样,主要不一样是下面这三个方面。
redis-py单机驱动安装
安装python3
tar xf Python-3.5.2.tar.xz cd Python-3.5.2 ./configure make && make install https://redis.io/clients 下载redis-py-master.zip unzip redis-py-master.zip cd redis-py-master python3 setup.py install 安装redis-cluser的客户端程序 cd redis-py-cluster-unstable python3 setup.py install
单实例链接操作
python3 >>>import redis >>>r = redis.StrictRedis(host='localhost', port=6379, db=0,password='root') >>>r.set('lufei', 'guojialei') True >>>r.get('lufei') 'bar'
sentinel集群链接并操作
[root@db01 ~]# redis-server /data/6380/redis.conf [root@db01 ~]# redis-server /data/6381/redis.conf [root@db01 ~]# redis-server /data/6382/redis.conf [root@db01 ~]# redis-sentinel /data/26380/sentinel.conf & -------------------------------- ## 导入redis sentinel包 >>> from redis.sentinel import Sentinel ##指定sentinel的地址和端口号 >>> sentinel = Sentinel([('localhost', 26380)], socket_timeout=0.1) ##测试,获取以下主库和从库的信息
## mymaster 是我们在配置文件中指定的名字
>>> sentinel.discover_master('mymaster')
>>> sentinel.discover_slaves('mymaster')
##配置读写分离
#写节点
>>> master = sentinel.master_for('mymaster', socket_timeout=0.1)
#读节点
>>> slave = sentinel.slave_for('mymaster', socket_timeout=0.1)
###读写分离测试 key
>>> master.set('oldboy', '123')
>>> slave.get('oldboy')
'123'
cluster分片集群链接测试
(1) redis-py并没有提供redis-cluster的支持,去github找了一下,有个叫redis-py-cluster的源码, 但是和redis-py不是一个作者,地址为:https://github.com/Grokzen/redis-py-cluster watch,star,fork还算可以。 (2) 安装 Latest stable release from pypi $ pip install redis-py-cluster or from source $ python setup.py install (3) 使用 >>> from rediscluster import StrictRedisCluster >>> startup_nodes = [{"host": "127.0.0.1", "port": "7000"}] >>> # Note: decode_responses must be set to True when used with python3 >>> rc = StrictRedisCluster(startup_nodes=startup_nodes, decode_responses=True) >>> rc.set("foo", "bar") True >>> print(rc.get("foo")) 'bar' 官方的创建redis的时候,都可以添加什么参数。 class redis.StrictRedis(host=‘localhost‘, port=6379, db=0, password=None, socket_timeout=None, connection_pool=None, charset=‘utf-8‘, errors=‘strict‘, decode_responses=False, unix_socket_path=None) connection pool方式 使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。 默认,每个Redis实例都会维护一个自己的连接池。 可以直接建立一个连接池,然后作为参数 Redis,这样就可以实现多个Redis实例共享一个连接池 StrictRedis Pipeline 是 StrictRedis 类的子类,支持在一个请求里发送缓冲的多个命令。 通过减少客户端和服务器之间往来的数据包,可以大大提高命令组的性能
作业
1、实现Redis Cluseter并实现Python链接集群
实现Redis Cluseter并实现Python链接集群 要求:画出实验拓扑图,标准清楚IP/hostname/作用 (1)实现redis Cluseter高可用分片集群 (2)实现python链接redis Cluster