Redis
一、简介
企业缓存产品介绍
- Tair
# 优点:
高性能读写、支持三种存储引擎(ddb、rdb、ldb)、支持高可用、支持分布式分片集群、支撑了几乎所有淘宝业务的缓存。
# 缺点:
单机情况下,读写性能较其他两种产品较慢
- Memcached
# 优点:
高性能读写、单一数据类型、支持客户端式分布式集群、一致性hash多核结构、多线程读写性能高。
# 缺点:
无持久化、节点故障可能出现缓存穿透、分布式需要客户端实现、跨机房数据同步困难、架构扩容复杂度高
- Redis
# 优点:
高性能读写、多数据类型支持、数据持久化、高可用架构、支持自定义虚拟内存、支持分布式分片集群、单线程读写性能极高
# 缺点:
多线程读写较Memcached慢。一般是新浪、京东、直播类平台、网页游戏使用
# memcache与redis在读写性能的对比:
memcached:多核的缓存服务,更加适合于多用户并发访问次数较少的应用场景
Redis:单核的缓存服务,单节点情况下,更加适合于少量用户,多次访问的应用场景。Redis一般是单机多实例架构,配合Redis集群出现。
Redis功能介绍
- 高速读写
- 数据类型丰富 (笔试、面试)
- 支持持久化 (笔试、面试)
- 多种内存分配及回收策略
- 支持事务 (面试)
- 消息队列、消息订阅
- 支持高可用
- 支持分布式分片集群 (面试)
- 缓存穿透\\雪崩(笔试、面试)
- Redis API
二、Redis安装
# 下载:
wget http://download.redis.io/releases/redis-3.2.12.tar.gz
# 解压:
上传至 /data
tar xzf redis-3.2.12.tar.gz
mv redis-3.2.12 redis
# 安装:
cd redis
# 编译
make
# 启动:
src/redis-server &
# 配置环境变量:
vim /etc/profile
export PATH=/data/redis/src:$PATH
source /etc/profile
# 开启服务端
redis-server &
# 开启客户端
redis-cli
# 测试
127.0.0.1:6379> set num 10
OK
127.0.0.1:6379> get num
10
三、Redis基本管理操作
1.基础配置文件介绍
# 配置
mkdir /data/6379
cat >>/data/6379/redis.conf <<EOF
daemonize yes # 是否后台运行
port 6379 # 默认端口
logfile /data/6379/redis.log # 日志文件位置
dir /data/6379 # 持久化文件存储位置
dbfilename dump.rdb # RDB持久化数据文件
EOF
# 重启redis
# 关闭客户端连接
redis-cli shutdown
# 以配置文件启动
redis-server /data/6379/redis.conf
# 检测端口
netstat -lnp|grep 63
# 开启客户端
redis-cli
# 测试
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> get name
"zhangsan"
2.客户端命令常用参数说明
# redis-cli 刚装完,可以在redis服务器上直接登录redis
-p 6379 指定端口号
-h 指定链接地址
-a 指定链接密码
# 无交互执行redis命令,不用进入redis也可操作,但是如果由密码便需要密码
redis-cli set num 10
3.远程连接
# redis默认开启了保护模式,只允许本地回环地址登录并访问数据库。
protected-mode yes/no (保护模式,是否只允许本地访问)
# 1.Bind :指定IP进行监听
echo "bind 10.0.0.200 127.0.0.1" >>/data/6379/redis.conf
# 2.增加requirepass {password}
echo "requirepass 123" >>/data/6379/redis.conf
# 重启redis
redis-cli shutdown
redis-server /data/6379/redis.conf
# 验证密码
# 方法一:
[root@db03 ~]# redis-cli -a 123
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 123
OK
127.0.0.1:6379> set a b
# 在线查看和修改配置
CONFIG GET *
CONFIG GET requirepass
CONFIG SET requirepass 123
四.redis持久化(内存数据保存到磁盘)
1.作用
可以有效防止,在redis宕机后,缓存失效的问题.
2.持久化方式
- RDB
可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。
优点:
速度快,适合于用做备份,主从复制也是基于RDB持久化功能实现的。
缺点:
会有数据丢失
- AOF
记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。
AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。
优点:
可以最大程度保证数据不丢
缺点:
日志记录量级比较大,会显得结构臃肿
3.RDB
# 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个更改
# 也就是说一分钟之内10000个变动
# 或者5分钟10次
# 或15分钟1次才会持久化一次
# 这样就会导致一个问题,数据落地会不及时,有延迟,数据丢失
4.AOF
# AOF持久化配置
vim /data/6379/redis.conf
appendonly yes
appendfsync everysec # evertsec/always/no
# everysec:异步操作,每秒记录,如果一秒钟内宕机,有数据丢失
# always:同步持久化,每次发生数据变更会被立即记录到磁盘,性能差但数据完整性比较好
# no:将缓存回写的策略交给系统,linux 默认是30秒将缓冲区的数据回写硬盘的
# AOF的Rewrite(重写) :
定义:AOF采用文件追加的方式持久化数据,所以文件会越来越大,为了避免这种情况发生,增加了重写机制
当AOF文件的大小超过了配置所设置的阙值时,Redis就会启动AOF文件压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof
触发机制:Redis会记录上次重写时的AOF文件大小,默认配置时当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
auto-aof-rewrite-percentage 100 (一倍)
auto-aof-rewrite-min-size 64mb
# 面试:
redis 持久化方式有哪些?有什么区别?
rdb:基于快照的持久化,速度更快,一般用作备份,主从复制也是依赖于rdb持久化功能
aof:以追加的方式记录redis操作日志的文件。可以最大程度的保证redis数据安全,类似于mysql的binlog
五、Redis数据类型
1.介绍
# string:字符型
键 值
key value
# hash:字典类型
key col1 value1 col2 value2
# list:列表
key [a,b,c,d]
0 1 2 3
# set:集合
key1 (a,b,c,d)
# sorted set:有序集合
10 20 30 40
key1 ( a, b, c, d)
0 1 2 3
2.通用操作
# 查看
# 查所有,数据量大时一般不推荐使用
KEYS *
# 查单个
keys 健名
# 模糊查询
keys a*
# 返回键所存储值的类型
TYPE
# 检查是否存在
EXISTS
# 以秒\\毫秒设定生存时间
EXPIRE\\ PEXPIRE
# 以秒\\毫秒为单位返回生存时间
TTL\\ PTTL
# 取消生存实现设置
PERSIST
# 删除一个key
DEL
# 变更KEY名
RENAME
3.string
# 应用场景
常规计数:
微博数,粉丝数等。
订阅、礼物、页游
key:value
----------
#(1)
set name zhangsan
#(2)
MSET id 101 name zhangsan age 20 gender m
# 等价于以下操作:
SET id 101
set name zhangsan
set age 20
set gender m
mget id name age gender
#(3)计数器
每点一次关注,都执行以下命令一次
127.0.0.1:6379> incr fans_count
(integer) 10003
127.0.0.1:6379> get fans_count
"10003"
127.0.0.1:6379> incrby fans_count 1000
(integer) 11003
127.0.0.1:6379> decr fans_count
(integer) 11002
127.0.0.1:6379> decrby fans_count 1000
# 详细的例子
# 增
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 # 批量获取键
4.hash
# 应用场景:
存储部分变更的数据,如用户信息等。
最接近mysql表结构的一种类型
--------------
# 存数据:
hmset stu id 101 name zs age 18
id name age
101 zs 18
# 同等与MySQL的
insert into stu(id,name,age) values (101,\'zs\',18);
# 取数据:
HMGET stu id name age -----> select id,name,age from stu;
hgetall stu ------> select * from stu;
# 将mysql中world.city表前10行数据导入redis
select concat("hmset city_",id," id ",id," name ",name," countrycode ",countrycode," district ",district," population ",population) from world.city into outfile \'/tmp/hmset1.txt\'
cat /tmp/hmset.txt |redis-cli -a 123
hmset city_1 id 1 name Kabul contrycode AFG district Kabol population 1780000
# 例子
# 增
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 键中所有字段的值
5.list
# 应用场景
消息队列系统
比如sina微博:在Redis中我们的最新微博ID使用了常驻缓存,这是一直更新的。
但是做了限制不能超过5000个ID,因此获取ID的函数会一直询问Redis。
只有在start/count参数超出了这个范围的时候,才需要去访问数据库。
系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。
SQL数据库(或是硬盘上的其他类型数据库)只是在用户需要获取“很远”的数据时才会被触发,
而主页或第一个评论页是不会麻烦到硬盘上的数据库了。
# 微信朋友圈:
127.0.0.1:6379> LPUSH wechat "today is nice day !"
127.0.0.1:6379> LPUSH wechat "today is bad day !"
127.0.0.1:6379> LPUSH wechat "today is good day !"
127.0.0.1:6379> LPUSH wechat "today is rainy day !"
127.0.0.1:6379> LPUSH wechat "today is friday !"
[e,d,c,b,a]
0 1 2 3 4
127.0.0.1:6379> lrange wechat 0 0
"today is friday !"
127.0.0.1:6379> lrange wechat 0 1
"today is friday !"
"today is rainy day !"
127.0.0.1:6379> lrange wechat 0 2
"today is friday !"
"today is rainy day !"
"today is good day !"
127.0.0.1:6379> lrange wechat 0 3
127.0.0.1:6379> lrange wechat -2 -1
"today is bad day !"
"today is nice day !"
-----------------
# 增
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
------------
6.set
# 应用场景:
案例:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。
Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,
对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
-------------
# lxl的好友
127.0.0.1:6379> sadd lxl pg1 pg2 songlaoban oldnie oldchen marong
(integer) 6
# jnl的好友
127.0.0.1:6379> sadd jnl baoqiang yufan oldchen songzhe oldguo alexdsb
(integer) 2
# lxl jnl所有的好友
127.0.0.1:6379> SUNION lxl jnl
1) "marong"
2) "pg2"
3) "pg1"
4) "oldchen"
5) "alexdsb"
6) "yufan"
7) "songlaoban"
8) "baoqiang"
9) "oldnie"
10) "songzhe"
11) "oldguo"
# 共同的好友
127.0.0.1:6379> SINTER lxl jnl
1) "oldchen"
# lxl的但不是jnl的好友
127.0.0.1:6379> SDIFF lxl jnl
1) "songlaoban"
2) "oldnie"
3) "pg1"
4) "pg2"
5) "marong"
# jnl的但不是lxl的好友
127.0.0.1:6379> SDIFF jnl lxl
1) "alexdsb"
2) "yufan"
3) "songzhe"
4) "oldguo"
5) "baoqiang"
-----------------
# 增
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中
7.SortedSet
# 应用场景:
排行榜应用,取TOP N操作
这个需求与上面需求的不同之处在于,前面操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,
这时候就需要我们的sorted set出马了,将你要排序的值设置成sorted set的score,将具体的数据设置成相应的value,
每次只需要执行一条ZADD命令即可。
--------------
127.0.0.1:6379> zadd topN 0 smlt 0 fskl 0 fshkl 0 lzlsfs 0 wdhbx 0 wxg
(integer) 6
127.0.0.1:6379> ZINCRBY topN 100000 smlt
"100000"
127.0.0.1:6379> ZINCRBY topN 10000 fskl
"10000"
127.0.0.1:6379> ZINCRBY topN 1000000 fshkl
"1000000"
127.0.0.1:6379> ZINCRBY topN 100 lzlsfs
"100"
127.0.0.1:6379> ZINCRBY topN 10 wdhbx
"10"
127.0.0.1:6379> ZINCRBY topN 100000000 wxg
"100000000"
127.0.0.1:6379> ZREVRANGE topN 0 2
1) "wxg"
2) "fshkl"
3) "smlt"
127.0.0.1:6379> ZREVRANGE topN 0 2 withscores
1) "wxg"
2) "100000000"
3) "fshkl"
4) "1000000"
5) "smlt"
6) "100000"
127.0.0.1:6379>
--------------
# 增
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发布消息
1.介绍
Redis发布消息通常有两种模式:
-
队列模式(queuing)
任务队列的好处:
松耦合。
易于扩展。
-
发布-订阅模式(publish-subscribe)
其实从Pub/Sub的机制来看,它更像是一个广播系统,多个Subscriber可以订阅多个Channel,多个Publisher可以往多个Channel中发布消息。可以这么简单的理解:
Subscriber:收音机,可以收到多个频道,并以队列方式显示
Publisher:电台,可以往不同的FM频道中发消息
Channel:不同频率的FM频道
客户端在执行订阅命令之后进入了订阅状态,只能接收 SUBSCRIBE 、PSUBSCRIBE、 UNSUBSCRIBE 、PUNSUBSCRIBE 四个命令。 开启的订阅客户端,无法收到该频道之前的消息,因为 Redis 不会对发布的消息进行持久化。 和很多专业的消息队列系统(例如Kafka、RocketMQ)相比,Redis的发布订阅略显粗糙,例如无法实现消息堆积和回溯。但胜在足够简单,如果当前场景可以容忍的这些缺点,也不失为一个不错的选择。
2.发布订阅
# 将信息 message 发送到指定的频道 channel
PUBLISH channel msg
# 订阅频道,可以同时订阅多个频道
SUBSCRIBE channel [channel ...]
# 取消订阅指定的频道, 如果不指定频道,则会取消订阅所有频道
UNSUBSCRIBE [channel ...]
# 订阅一个或多个符合给定模式的频道,每个模式以 * 作为匹配符,比如 it* 匹配所 有以 it 开头的频道( it.news 、 it.blog 、 it.tweets 等等), news.* 匹配所有 以 news. 开头的频道( news.it 、 news.global.today 等等),诸如此类
PSUBSCRIBE pattern [pattern ...]
# 退订指定的规则, 如果没有参数则会退订所有规则
PUNSUBSCRIBE [pattern [pattern ...]]
# 查看订阅与发布系统状态
PUBSUB subcommand [argument [argument ...]]
# 注意:使用发布订阅模式实现的消息队列,当有客户端订阅channel后只能收到后续发布到该频道的消息,之前发送的不会缓存,必须Provider和Consumer同时在线。
# 发布订阅例子:
窗口1:
# 先要订阅频道baodi
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 "
七、redis事务
redis的事务是基于队列实现的。
mysql的事务是基于事务日志实现的。
----------
# 开启事务功能时(multi)
multi
command1
command2
command3
command4
# 4条语句作为一个组,并没有真正执行,而是被放入同一队列中。
如果,这是执行discard,会直接丢弃队列中所有的命令,而不是做回滚。
exec
# 当执行exec时,对列中所有操作,要么全成功要么全失败
----------
127.0.0.1:6379> set a b
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set a b
QUEUED
127.0.0.1:6379> set c d
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
八、redis主从复制
1.简介
持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障
2.原理
- 从服务器向主服务器发送 SYNC 命令。
- 接到 SYNC 命令的主服务器会调用BGSAVE 命令,创建一个 RDB 文件,并使用缓冲区记录接下来执行的所有写命令。
- 当主服务器执行完 BGSAVE 命令时,它会向从服务器发送 RDB 文件,而从服务器则会接收并载入这个文件。
- 主服务器将缓冲区储存的所有写命令(广播形式)发送给从服务器执行。
主服务器会一直持续第四步,只要有新的命令,主服务器会监视
3.主从的持久化
当配置Redis复制功能时,强烈建议打开主服务器的持久化功能。 否则的话,由于延迟等问题,部署的服务应该要避免自动拉起。
为了帮助理解主服务器关闭持久化时自动拉起的危险性,参考一下以下会导致主从服务器数据全部丢失的例子:
- 假设节点A为主服务器,并且关闭了持久化。 并且节点B和节点C从节点A复制数据
- 节点A崩溃,然后由自动拉起服务重启了节点A. 由于节点A的持久化被关闭了,所以重启之后没有任何数据
- 节点B和节点C将从节点A复制数据,但是A的数据是空的, 于是就把自身保存的数据副本删除。
在关闭主服务器上的持久化,并同时开启自动拉起进程的情况下,即便使用Sentinel来实现Redis的高可用性,也是非常危险的。 因为主服务器可能拉起得非常快,以至于Sentinel在配置的心跳时间间隔内没有检测到主服务器已被重启,然后还是会执行上面的数据丢失的流程。
无论何时,数据安全都是极其重要的,所以应该禁止主服务器关闭持久化的同时自动拉起。
# 给主服务器加上持久化
appendonly yes
appendfsync everysec
4.复制的一致性
- 在读写分离环境下,客户端向主服务器发送写命令 SET n 10086,主服务器在执行这个写命令之后,向客户端返回回复,并将这个写命令传播给从服务器。
- 接到回复的客户端继续向从服务器发送读命令 GET n ,并且因为网络状态的原因,客户端的 GET命令比主服务器传播的 SET 命令更快到达了从服务器。
- 因为从服务器键 n 的值还未被更新,所以客户端在从服务器读取到的将是一个错误(过期)的 n值。
4.1解决方案
# 主从数据一致性保证:
# 在配置文件中配置
min-slaves-to-write 1 # 从服务器的数量
min-slaves-max-lag 2 # 网络延迟的最大秒数
这个特性的运作原理:
从服务器以每秒一次的频率 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 所指定的条件, 那么写操作就不会被执行
主服务器会向请求执行写操作的客户端返回一个错误。
5.主从复制实现
# 1、环境:
# 准备两个或两个以上redis实例
mkdir /data/638{0..2}
# 配置文件示例:
cat >> /data/6380/redis.conf << EOF
port 6380
daemonize yes
pidfile /data/6380/redis.pid
loglevel notice
logfile "/data/6380/redis.log"
dbfilename dump.rdb
dir /data/6380
requirepass 123
masterauth 123
appendonly yes
appendfsync everysec
EOF
cp /data/6380/redis.conf /data/6381/redis.conf
cp /data/6380/redis.conf /data/6382/redis.conf
sed -i \'s#6380#6381#g\' /data/6381/redis.conf
sed -i \'s#6380#6382#g\' /data/6382/redis.conf
# 启动:
redis-server /data/6380/redis.conf
redis-server /data/6381/redis.conf
redis-server /data/6382/redis.conf
# 查看端口
netstat -lnp|grep 638
主节点:6380
从节点:6381、6382
# 2、开启主从:
6381/6382命令行:
redis-cli -p 6381 -a 123 SLAVEOF 127.0.0.1 6380
redis-cli -p 6382 -a 123 SLAVEOF 127.0.0.1 6380
# 3、查询主从状态
redis-cli -p 6380 -a 123 info replication
redis-cli -p 6381 -a 123 info replication
redis-cli -p 6382 -a 123 info replication
# 模拟主库故障
redis-cli -p 6380 -a 123 shutdown
# 1.从库切为主库
redis-cli -p 6381 -a 123
info replication
slaveof no one
# 2.6382连接到6381:
redis-cli -p 6382 -a 123
127.0.0.1:6382> SLAVEOF no one
127.0.0.1:6382> SLAVEOF 127.0.0.1 6381
6.redis-sentinel(哨兵)
6.1 功能
- 监控
- 自动选主,切换(6381 slaveof no one)
- 2号从库(6382)指向新主库(6381)
- 应用透明
当主服务器挂掉时,可以快速切换主从,尽快恢复服务
6.2 sentinel搭建过程
# 1.创建哨兵文件夹
mkdir /data/26380
# 2.配置哨兵
cat >> /data/26380/sentinel.conf << EOF
port 26380
dir "/data/26380"
sentinel monitor mymaster 127.0.0.1 6380 1
sentinel down-after-milliseconds mymaster 5000
sentinel auth-pass mymaster 123
EOF
# 3.启动:
redis-sentinel /data/26380/sentinel.conf &
# 配置详解
# 格式:sentinel <option_name> <master_name> <option_value>#该行的意思是:监控的master的名字叫做mymaster(自定义),地址为127.0.0.1:6380,行尾最后的一个1代表在sentinel集群中,多少个sentinel认为masters死了,才能真正认为该master不可用了。
sentinel monitor mymaster 127.0.0.1 6380 1
#sentinel会向master发送心跳PING来确认master是否存活,如果master在“一定时间范围”内不回应PONG 或者是回复了一个错误消息,那么这个sentinel会主观地(单方面地)认为这个master已经不可用了(subjectively down, 也简称为SDOWN)。而这个down-after-milliseconds就是用来指定这个“一定时间范围”的,单位是毫秒,默认30秒。
sentinel down-after-milliseconds mymaster 5000
注:此时一个哨兵就正式开启了,每次就通过哨兵访问即可
redis-cli -p 26380
哨兵会自动指向主库,就算主库挂了,哨兵也会在内部根据实际情况选择一个主库,让剩下的从库指向新的主库,而且挂了的主库也会重新启动变成从库
6.3 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集群
1.基本介绍
Redis
集群是一个可以在多个 Redis
节点之间进行数据共享的设施installation
。
Redis
集群不支持那些需要同时处理多个键的 Redis
命令, 因为执行这些命令需要在多个 Redis
节点之间移动数据, 并且在高负载的情况下, 这些命令将降低Redis
集群的性能, 并导致不可预测的行为。
Redis
集群通过分区partition
来提供一定程度的可用性availability
: 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
Redis
集群提供了以下两个好处:
- 将数据自动切分
split
到多个节点的能力。 - 当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。
2.Redis Cluster集群
Redis集群搭建的方式有多种,例如使用zookeeper等,但从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有 节点连接。
特点:
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
- 节点的fail是通过集群中超过半数的节点检测失效时才生效。
- 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
- redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value。
- Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。
3. Redis Cluster节点分配
现在我们是三个主节点分别是:A, B, C 三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽 (hash slot)的方式来分配16384个slot 的话,它们三个节点分别承担的slot 区间是:
节点A覆盖0-5460;
节点B覆盖5461-10922;
节点C覆盖10923-16383.
获取数据:
如果存入一个值\'data\'=\'1024-4-12\',按照redis cluster哈希槽的算法: CRC16(\'data\')%16384 = 2022。 那么就会把这个key 的存储分配到 A 上了。同样,当我连接(A,B,C)任何一个节点想获取\'key\'这个key时,也会这样的算法,然后内部跳转到A节点上获取数据
新增一个主节点:
新增一个节点D,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到D上,我会在接下来的实践中实验。大致就会变成这样:
节点A覆盖1365-5460
节点B覆盖6827-10922
节点C覆盖12288-16383
节点D覆盖0-1364,5461-6826,10923-12287
4.Redis Cluster主从模式
redis cluster 为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉。
上面那个例子里, 集群有ABC三个主节点, 如果这3个节点都没有加入从节点,如果B挂掉了,我们就无法访问整个集群了。A和C的slot也无法访问。
所以我们在集群建立的时候,一定要为每个主节点都添加了从节点, 比如像这样, 集群包含主节点A、B、C, 以及从节点A1、B1、C1, 那么即使B挂掉系统也可以继续正确工作。
B1节点替代了B节点,所以Redis集群将会选择B1节点作为新的主节点,集群将会继续正确地提供服务。 当B重新开启后,它就会变成B1的从节点。
不过需要注意,如果节点B和B1同时挂了,Redis集群就无法继续正确地提供服务了。
5.redis集群的搭建
# 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 https://rubygems.org/
gem sources -l
gem install redis -v 3.3.3
# 2.集群节点准备
mkdir /data/700{0..5}
cat >> /data/7000/redis.conf << EOF
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
EOF
cp /data/7000/redis.conf /data/7001/redis.conf
cp /data/7000/redis.conf /data/7002/redis.conf
cp /data/7000/redis.conf /data/7003/redis.conf
cp /data/7000/redis.conf /data/7004/redis.conf
cp /data/7000/redis.conf /data/7005/redis.conf
sed -i \'s#7000#7001#g\' /data/7001/redis.conf
sed -i \'s#7000#7002#g\' /data/7002/redis.conf
sed -i \'s#7000#7003#g\' /data/7003/redis.conf
sed -i \'s#7000#7004#g\' /data/7004/redis.conf
sed -i \'s#7000#7005#g\' /data/7005/redis.conf
# 启动节点:
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
# 查看端口是否启动
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、集群节点管理
# 增加新的节点7006/7007
# 1.添加主节点:
# add-node是加入集群节点
# 127.0.0.1:7006为要加入的节点
# 127.0.0.1:7000 表示加入的集群的一个节点,用来辨识是哪个集群,理论上那个集群的节点都可以。
redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000
# 2.转移slot(重新分片)
# redis-cluster在新增节点时并未分配卡槽,需要我们手动对集群进行重新分片迁移数据
redis-trib.rb reshard 127.0.0.1:7000
How many slots do you want to move (from 1 to 16384)? 4096
# 它提示我们需要迁移多少slot到7006上,我们平分16384个哈希槽给4个节点:16384/4 = 4096,我们需要移动4096个槽点到7006上。
What is the receiving node ID? 1c98b2b2ce18f88c76821cdb82dba4defaa5eb48
# 需要输入7006的节点id
Source node #1:all
# 如果我们不打算从特定的节点上取出指定数量的哈希槽,那么可以向redis-trib输入 all,这样的话, 集群中的所有主节点都会成为源节点,redis-trib从各个源节点中各取出一部分哈希槽,凑够4096个,然后移动到7006节点上
# 添加一个从节点
# 1c98b2b2ce18f88c76821cdb82dba4defaa5eb48为要加到master主节点的node id,127.0.0.1:7007为新增的从节点,127.0.0.1:7000为集群的一个节点(集群的任意节点都行),用来辨识是哪个集群;如果没有给定那个主节点--master-id的话,redis-trib将会将新增的从节点随机到从节点较少的主节点上。
redis-trib.rb add-node --slave --master-id 1c98b2b2ce18f88c76821cdb82dba4defaa5eb48 127.0.0.1:7007 127.0.0.1:7000
# 6.删除节点
# 将需要删除节点slot移动走
redis-trib.rb reshard 127.0.0.1:7000
# 删除一个节点
# 删除master节点之前首先要使用reshard移除master的全部slot,然后再删除当前节点
redis-trib.rb del-node 127.0.0.1:7006 1c98b2b2ce18f88c76821cdb82dba4defaa5eb48
redis-trib.rb del-node 127.0.0.1:7007 00185d1cf069b23468d5863202ac651f0d02a9f8
---------------------
# 设置redis最大内存
config set maxmemory 102400000
---------------------
十、python实现redis多种API
1、对redis的单实例进行连接操作
# redis单例提供了一种数据缓存方式和丰富的数据操作api,但是将数据完全存储在单个redis中主要存在两个问题:数据备份和数据体量较大造成的性能降低。这里redis的主从模式为这两个问题提供了一个较好的解决方案。
# 安装
unzip redis-py-master.zip
cd redis-py-master
python3 setup.py install
# 使用
>>>import redis
>>>r = redis.StrictRedis(host=\'10.0.0.200\', port=6379, db=0,password=\'123\')
>>>r.set(\'foo\', \'bar\')
True
>>>r.get(\'foo\')
\'bar\'
2.python使用redis的哨兵sentinel
# redis主从模式解决了数据备份和单例可能存在的性能问题,但是其也引入了新的问题。
# 由于主从模式配置了三个redis实例,并且每个实例都使用不同的ip(如果在不同的机器上)和端口号。
# 根据前面所述,主从模式下可以将读写操作分配给不同的实例进行从而达到提高系统吞吐量的目的,但也正是因为这种方式造成了使用上的不便,因为每个客户端连接redis实例的时候都是指定了ip和端口号的,如果所连接的redis实例因为故障下线了,而主从模式也没有提供一定的手段通知客户端另外可连接的客户端地址,因而需要手动更改客户端配置重新连接。
# 另外,主从模式下,如果主节点由于故障下线了,那么从节点因为没有主节点而同步中断,因而需要人工进行故障转移工作。
# 为了解决这两个问题,在2.8版本之后redis正式提供了sentinel(哨兵)架构。
# 1.启动redis的实例以及sentinel
redis-server /data/6380/redis.conf
redis-server /data/6381/redis.conf
redis-server /data/6382/redis.conf
redis-sentinel /data/26380/sentinel.conf &
# 使用
# 2.导入redis sentinel包
>>>from redis.sentinel import Sentinel
# 指定sentinel的地址和端口号
>>> sentinel = Sentinel([(\'localhost\', 26380)], socket_timeout=0.1)
# 测试,获取以下主库和从库的信息
>>> sentinel.discover_master(\'mymaster\')
>>> sentinel.discover_slaves(\'mymaster\')
# 配置读写分离
# 写节点
>>> master = sentinel.master_for(\'mymaster\', socket_timeout=0.1,password="123")
# 读节点
>>> slave = sentinel.slave_for(\'mymaster\', socket_timeout=0.1,password="123")
# 读写分离测试 key
>>> master.set(\'oldboy\', \'123\')
>>> slave.get(\'oldboy\')
\'123\'
2.redis cluster集群的连接并操作
# redis集群是在redis 3.0版本推出的一个功能,其有效的解决了redis在分布式方面的需求。
# 当遇到单机内存,并发和流量瓶颈等问题时,可采用Cluster方案达到负载均衡的目的。
# 并且从另一方面讲,redis中sentinel有效的解决了故障转移的问题,也解决了主节点下线客户端无法识别新的可用节点的问题,但是如果是从节点下线了,sentinel是不会对其进行故障转移的,并且连接从节点的客户端也无法获取到新的可用从节点,而这些问题在Cluster中都得到了有效的解决。
# 安装redis-cluser的客户端程序
# 下载压缩包
cd redis-py-cluster-unstable
python3 setup.py install
# 或pip
pip install redis-py-cluster
# 使用
python3
>>> from rediscluster import StrictRedisCluster
>>> startup_nodes = [{"host": "127.0.0.1", "port": "7000"},{"host": "127.0.0.1", "port": "7001"},{"host": "127.0.0.1", "port": "7002"}]
>>> rc = StrictRedisCluster(startup_nodes=startup_nodes, decode_responses=True)
>>> rc.set("name", "zs")
True
>>> print(rc.get("name"))
\'zs\'
十一、redis常见问题以及解决方案
# 缓存穿透
概念
访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时DB会挂掉。
# 解决方案
采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;
访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间。
# 缓存雪崩
概念
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
# 解决方案
可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
# 缓存击穿
概念
一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。
# 解决方案
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。