Redis的使用
安装
使用源安装的方式
- 下载源文件
wget https://download.redis.io/redis-stable.tar.gz
- 编译redis
tar -zxvf redis-stable.tar.gz
- cd目录进入到redis目录
cd redis-stable
- 安装二进制文件
make install
如果编译安装成功,会有看到 src文件夹、redis.conf文件等
其中src文件夹中又包含:redis-server、redis-cli等文件
- 运行启动 Redis 服务
redis-server
注意:此方法是在前台启动,如果成功,将看到Redis的启动日志,要停止Redis,使用ctrl + c
修改Redis配置
要修改Redis配置,找到Redis的配置文件redis.conf
即可。
# 如果要修改Redis默认端口,找到以下配置即可,Redis默认端口为:6379。
port 6379
# 如果要设置Redis的密码,找到以下配置即可,Redis默认启动是没有密码的。
# requirepass foobared
# 如果想要以守护进程模式启动Redis,找到以下配置即可,默认为 no,改为 yes 即可。
daemonize yes
使用修改后的配置文件启动Redis
./src/redis-server ./redis.conf
以上述方式启动后可以看到没有像上面所说的前台方式启动,而是在后台运行,原因是设置了以守护进程模式启动,如果将 daemonize 改为 no,又会以前台方式运行
Redis命令行界面
- 进入Redis命令行界面,使用以下命令:
redis-cli
注意:如果没有设置密码,这则用过此命令可直接进入Redis命令行界面,如果设置了密码需要使用密码进入:
auth [密码]
# 此外,可以使用ping查看是否接通,如果成功会出现 PONG 的提示
# 如果没有输入密码,ping会出现提示:(error) NOAUTH Authentication required.
# 如果输入密码错误,ping会出现提示:(error) WRONGPASS invalid username-password pair or user is disabled.
ping
Linux下Redis环境变量的配置
- 首先需要找到redis-server所在的目录
# 例如:我的redis安装在 /redis/redis-stable目录中,所以redis-server所在目录即是:/redis/redis-stable/src 目录下,可通过以下命令查看当前所在目录
pwd
- 配置环境,只需在
/etc/profile
中添加配置即可
# 进入文件编辑
vim /etc/profile
# 在文件末尾添加以下配置即可,注意:输入 i 即可进行编辑
export PATH=$PATH:/redis/redis-stable/src
# 编辑完成后,按Esc 然后输入 :wq 保存退出
# 然后输入以下命令,刷新配置
source /etc/profile
配置完成后,就无需再进入指定目录才能运行redis相关指令了
Linux客户端的远程连接
- 如果要远程连接其它的Redis,使用以下命令即可
# 本地使用 redis-cli 其实是省略了一些内容的,完整的为以下命令,远程连接只需改为需要远程地址和端口即可。
redis-cli -h host -p port -a "[密码]"
# 也可以使用以下命令,先连接到远程,再输入密码,这样相对于上面直接输入密码更安全一点
redis-cli -h 127.0.0.1 -p 6379
auth [密码]
Redis 命令
# redis 默认16个库,默认使用0号数据库
set name 0
# 使用1号数据库
select 1
# 是否成功连接redis
ping
# 退出命令行操作
quit
# 查看数据库中有多少个键
dbsize
# 查看所有服务器状态信息
info
# 查看配置信息 config get * 以下举例:查看密码
config get requirepass
# 清空当前数据库
flushdb
# 清空所有数据库
flushall
Redis keys 命令
# 设置key
set name redis
# 获取key
get name
# 查看所有的key
keys *
# 删除key
del name
# 查看key是否存在
exists name
# 序列化给定key,并返回被序列化的值。
dump name
# 为给定 key 设置过期时间,以秒计。
expire name 20
# 为给定 key 设置过期时间,以 UNIX 时间戳计。
expireat name 1000
# 设置 key 的过期时间以毫秒计。
pexpire name 10000
# 以秒为单位,返回给定 key 的剩余生存时间,注意:如果未设置过期时间,则为-1,如果设置了过期时间过期后为-2
ttl name
# 以毫秒为单位返回 key 的剩余的过期时间。
pttl name
# 移除 key 的过期时间,key 将持久保持
persist name
# 将当前数据库的 key 移动到给定的数据库 db 当中。使用select可切换数据库,redis默认使用0号数据库
move name 1
# 从当前数据库中随机返回一个 key 。
randomkey
# 修改 key 的名称
rename name name2
# 仅当 newkey 不存在时,将 key 改名为 newkey 。
renamenx name2 name
# 返回 key 所储存的值的类型。
type name
# 迭代数据库中的数据库键 SCAN cursor [MATCH pattern] [COUNT count]
scan cursor [match patter] [count count]
scan
127.0.0.1:6379> keys *
1) "user4"
2) "user2"
3) "user3"
4) "user1"
127.0.0.1:6379> scan 0 match user*
1) "0"
2) 1) "user3"
2) "user1"
3) "user4"
4) "user2"
127.0.0.1:6379> scan 0 match user* count 2
1) "1"
2) 1) "user3"
2) "user1"
127.0.0.1:6379> scan 1
1) "0"
2) 1) "user4"
2) "user2"
127.0.0.1:6379>
Redis数据类型
Redis支持5种数据类型:String、Hash、List、Set、Zset
String
# 设置指定key的值
set key value
# 获取指定key的值
get key
# 返回key中字符串值的子字符(相当于截取字符串),注意:起始下标从0开始。例如:redis ==> getrange name 3 5 ==> "is"
getrange key 0 1
# 将给定 key 的值设为 value ,并返回 key 的旧值。
getset key newValue
# 对 key 所储存的字符串值,获取指定偏移量上的位(bit)。当偏移量 OFFSET 比字符串值的长度大,或者 key 不存在时,返回 0
getbit key 1
# 获取所有(一个或多个)给定 key 的值
mget key1 key2
# 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)
setbit key 5 1
# 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)
setex key1 10 value1
# 只有在 key 不存在时设置 key 的值
setnx key2 value2
# 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
setrange key2 0 x
# 返回 key 所储存的字符串值的长度
strlen key1
# 同时设置一个或多个 key-value 对
mset key1 value1 key2 value2
# 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
msetnx key3 value3 key4 value4
# 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位
psetex key5 10000 value5
# 将 key 中储存的数字值增一
incr key
# 将 key 所储存的值加上给定的增量值(increment)
incrby key 4
# 将 key 所储存的值加上给定的浮点增量值(increment)
incrbyfloat key 2.2
# 将 key 中储存的数字值减一(不能是浮点数值)
decr key
# key 所储存的值减去给定的减量值(decrement)(不能是浮点数值)
decrby key 1
# 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾
append key newValue
Hash
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。
# 同时将多个 field-value (域-值)对设置到哈希表 key 中,根据Redis 4.0.0,HMSET被视为已弃用。请在新代码中使用HSET
hmset users user1 u1 age 22
# 将哈希表 key 中的字段 field 的值设为 value
hset user username redis age 11
# 获取存储在哈希表中指定字段的值
hget user username
# 获取在哈希表中指定 key 的所有字段和值
hgetall user
# 查看哈希表 key 中,指定的字段是否存在
hexists user username
# 删除一个或多个哈希表字段
hdel user username age
# 为哈希表 key 中的指定字段的整数值加上增量 increment
hincrby key field increment
# 为哈希表 key 中的指定字段的浮点数值加上增量 increment
hincrbyfloat key field increment
# 获取哈希表中的所有字段
hkeys key
# 获取哈希表中字段的数量
hlen key
# 获取所有给定字段的值
hmget key field1 field2
# 只有在字段 field 不存在时,设置哈希表字段的值
hsetnx key field value
# 获取哈希表中所有值
hvals key
# 迭代哈希表中的键值对
hscan users 0 match user* count 1
List
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
# 将一个或多个值插入到列表头部
lpush user user1 user2 user3
# 将一个值插入到已存在的列表头部
lpushx user user4
# 获取列表指定范围内的元素 lrange [列表] [开始下标] [结束下标]
lrange user 0 -1
# 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 lpush key1[key2] timeout 此处超时时间是以秒为单位
blpop user 10
# 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 brpop key1[key2] timeout 此处超时时间是以秒为单位
brpop user 10
# 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 brpoplpush [需要弹出值的列表] [接收弹出值得列表] [超时时间] 超时时间是以秒为单位
brpoplpush user users 10
# 通过索引获取列表中的元素 lindex [列表] [元素下标]
lindex user 1
# 在列表的元素前或者后插入元素 linsert [列表] before|after [列表中的元素] [需要插入的值]
linsert user before user3 user4
linsert user after user2 user1
# 获取列表长度
llen user
# 移出并获取列表的第一个元素 lpop [列表] [需要移除的元素个数]
lpop user # 移除第一个元素
lpop user 2 # 移除前两个元素
# 移除列表元素 lrem [列表] [需要移除的元素个数] [需要移除的元素] 注意:如果[需要移除的元素为0,则移除所有指定元素]
lrem user 0 user5
# 通过索引设置列表元素的值 lset [列表] [下标索引] [设置的值]
lset user 1 user1
# 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除 lrange [列表] [起始下标] [结束下标]
lrange user 0 -1
# 移除列表的最后一个元素,返回值为移除的元素 rpop [列表] [需要移除的元素个数]
rpop user # 移除第一个元素
rpop user 2 # 移除最后两个元素
# 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 rpoplpush [当前列表] [目标列表]
rpoplpush user users
# 在列表中添加一个或多个值到列表尾部 rpush [列表] [值...]
rpush user user2 user1
# 为已存在的列表添加值,如果不存在列表则不添加值不新建列表 rpushx [列表] [值]
rpushx user user0
Set
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
集合对象的编码可以是 intset 或者 hashtable。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
# 向集合添加一个或多个成员
sadd user1 u1 u2 u3 u4 u5
# 返回集合中的所有成员
smembers user1
# 获取集合的成员数
scard user1
# 返回第一个集合与其他集合之间的差异
sdiff user1 user2
# 返回给定所有集合的差集并存储在 destination 中。 sdiffstore destination key1 [key2]
sdiffstore users user1 user2
# 返回给定所有集合的交集
sinter user1 user2
# 返回给定所有集合的交集并存储在 destination 中。 sinterstore destination key1 [key2]
sinterstore users user1 user2
# 判断 member 元素是否是集合 key 的成员
sismember user1 u1
# 将 member 元素从 source 集合移动到 destination 集合。 smove source destination member
smove user1 u u1
# 移除并返回集合中的一个随机元素
spop user1
# 返回集合中一个或多个随机数 srandmember [集合] [返回的个数]
srandmember user1 2
# 移除集合中一个或多个成员。srem key member1 [member2]
srem user1 u1 u2
# 返回所有给定集合的并集 sunion key1 [key2]
sunion user1 user2
# 所有给定集合的并集存储在 destination 集合中。sunionstore destination key1 [key2]
sunionstore users user1 user2
# 迭代集合中的元素。sscan key cursor [MATCH pattern] [COUNT count]
sscan user1 0 match u* count 1
Zset
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
# 向有序集合添加一个或多个成员,或者更新已存在成员的分数。zadd key score1 member1 [score2 member2]
zadd score 100 u1 95 u2 68 u3 84 u4
# 获取有序集合的成员数。zcard key
zcard score
# 计算在有序集合中指定区间分数的成员数。zcount key min max
zcount score 95 100
# 有序集合中对指定成员的分数加上增量 increment。zincrby key increment member
zincrby score 5 u2
# 通过索引区间返回有序集合指定区间内的成员。如果不加 [withscores],则只返回成员,加了则返回 成员和分数。zrange key start stop [WITHSCORES]
zrange score 0 -1
zrange score 0 -1 withscores
# 返回有序集中指定区间内的成员,通过索引,分数从高到低,如果不加 [withscores],则只返回成员,加了则返回 成员和分数
zrevrange score 0 -1
zrevrange score 0 -1 withscores
# 返回有序集中指定分数区间内的成员,分数从高到低排序。zrevrangebyscore key max min [WITHSCORES]
zrevrangebyscore score 100 80
zrevrangebyscore score 100 80 withscores
# 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序。zrevrank key member
zrevrank score u1
# 返回有序集中,成员的分数值。zscore key member
zscore score u1
# 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中。zinterstore destination numkeys key [key ...] 注意:给定 key 的数量必须以 numkeys 参数指定
zinterstore scores 3 score5 score6 score7
# 在有序集合中计算指定字典区间内成员数量。zlexcount key min max
# [min 表示返回的结果中包含 min 值。[max 表示返回的结果中包含 max 值。
# (min 表示返回的结果中不包含 min 值。(max 表示返回的结果中不包含 max 值。
# [MIN, [MAX 可以用-,+ 代替。- 表示得分最小值的成员。+ 表示得分最大值的成员。
zlexcount score1 [u1 [u4
zlexcount score1 - +
# 通过字典区间返回有序集合的成员。
zrangebylex key min max [LIMIT offset count]
# 通过分数返回有序集合指定区间内的成员
zrangebyscore key min max [WITHSCORES] [LIMIT]
# 返回有序集合中指定成员的索引。zrank key member
zrank score u1
# 移除有序集合中的一个或多个成员。
zrem key member [member ...]
# 移除有序集合中给定的字典区间的所有成员
zremrangebylex key min max
# 移除有序集合中给定的排名区间的所有成员
zremrangebyrank key start stop
# 移除有序集合中给定的分数区间的所有成员
zremrangebyscore key min max
# 计算给定的一个或多个有序集的并集,并存储在新的 key 中
zunionstore destination numkeys key [key ...]
# 迭代有序集合中的元素(包括元素成员和元素分值)
zscan key cursor [MATCH pattern] [COUNT count]
Geo
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。
Redis GEO 操作方法有:
- geoadd:添加地理位置的坐标。
# geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中
geoadd key 经度 纬度 位置名称 [longitude latitude member ...]
- geopos:获取地理位置的坐标。
# 从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。
geopos key member [member ...]
- geodist:计算两个位置之间的距离。
# 用于返回两个给定位置之间的距离 (m:米,默认单位; km:千米; mi:英里; ft:英尺;)
geodist key member1 member2 [m|km|ft|mi]
- georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
# 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。(m:米,默认单位; km:千米; mi:英里; ft:英尺;)
# WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
# WITHCOORD: 将位置元素的经度和纬度也一并返回。
# WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
# COUNT 限定返回的记录数。
# ASC: 查找结果根据距离从近到远排序。
# DESC: 查找结果根据从远到近排序。
georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
- georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
# 找出位于指定范围内的元素, 但是 georadiusbymember 的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点。(m:米,默认单位; km:千米; mi:英里; ft:英尺;)
# WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
# WITHCOORD: 将位置元素的经度和纬度也一并返回。
# WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
# COUNT 限定返回的记录数。
# ASC: 查找结果根据距离从近到远排序。
# DESC: 查找结果根据从远到近排序。
georadiusbymember key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
- geohash:返回一个或多个位置对象的 geohash 值。
# 使用 geohash 来保存地理位置的坐标。geohash 用于获取一个或多个位置元素的 geohash 值
geohash key member [member ...]
HyperLogLog
- Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
- Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
- 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
- 但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
什么是基数
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
HyperLogLog命令的使用
# 添加指定元素到 HyperLogLog 中。pfadd key element [element ...]
pfadd userlogin 1001
# 返回给定 HyperLogLog 的基数估算值。pfcount key [key ...] 如果是多个key,则返回的是多个key的总和,而不会分开返回值。
pfcount userlogin
# 将多个 HyperLogLog 合并为一个 HyperLogLog。PFMERGE destkey sourcekey [sourcekey ...]
pfmerge userAll userlogin userreg
Redis的事务
事务的目的是方便用户一次执行多个命令,执行Redis事务可分为三个阶段:
- 开始事务
- 命令入队
- 执行事务
Redis事务的特性
- 单独的隔离操作
事务中所有命令都会被序列化,它们将按照顺序执行,且执行过程中不会被其它客户端发来的命令打断。 - 不保证原子性
在Redis事务中,如果存在命令执行失败的情况,那么其它命令仍然会被执行,不支持事务的回滚机制。
注意:Redis不支持事务的回滚,是因为Redis是基于内存的存储系统,内部结构比较简单,若支持回滚机制,则会变得冗余,并且会损耗性能,这就与Redis简单、快速的概念相违背。
事务的命令
命令 | 说明 |
---|---|
MULTI | 标记一个事务块的开始。 |
EXEC | 执行所有事务块内的命令。 |
DISCARD | 取消事务,放弃执行事务块内的所有命令。 |
UNWATCH | 取消 WATCH 命令对所有 key 的监视。 |
WATCH key [key …] | 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
Redis事务的应用
127.0.0.1:6379> set name user1
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> set name user2
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> discard # 取消了事务
OK
127.0.0.1:6379> get name # 取消事务,可以发现值没有发送改变
"user1"
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> set name user2
QUEUED
127.0.0.1:6379(TX)> set age 11
QUEUED
127.0.0.1:6379(TX)> incr age
QUEUED
127.0.0.1:6379(TX)> incr name
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> get age
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务
1) OK
2) OK
3) (integer) 12
4) (error) ERR value is not an integer or out of range # 可以发现执行命令失败,事务也不会终止、回滚,会继续执行
5) "user2"
6) "12"
Redis如何实现乐观锁
- 什么是乐观锁
乐观锁总是假设最好的情况,即认为别的线程不会同时修改数据,所以不会上锁,只是在提交修改的时候去验证是否有别的线程更新过此数据。(具体方法可以使用版本号机制) watch
监听
watch指令类似于乐观锁,在事务提交时,如果watch监控的多个key中任何key的值已经被其它客户端更改,则使用exec执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。
# 线程1
# 例:一个用户在银行拥有1000元,下面是第一个线程在进行操作
127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379> get money
"1000"
127.0.0.1:6379> watch money # 监听money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 100
QUEUED
# 线程2
# 此时,事务还未执行,另一个线程插入,扣掉了100,即修改了数据。下面是第二个线程在进行操作
127.0.0.1:6379> decrby money 100
(integer) 900
# 线程1
# 这时money值发生了变动,watch会告诉事务,money被改变,那么第一个线程的事务会提交失败
# 下面是提交第一个事务,可以发现返回了nil,说明提交事务失败,即修改失败
127.0.0.1:6379(TX)> exec
(nil)
127.0.0.1:6379> get money
"900"
Redis持久化RDB
- Redis是一款基于内存的非关系型数据库,它会将数据全部存储在内存中。但如果Redis服务器出现一些意外,如宕机、断电等,那么内存中的数据会全部丢失。所以需要一种机制保证Redis储存的数据不会因故障而丢失,这就是Redis的持久化机制。
- 数据的持久化存储是Redis的重要特性之一,它能将内存中的数据保存到本地磁盘中,实现数据的持久存储。这样即使服务器发生意外故障,之后也能通过本地磁盘恢复数据。
- Redis提供了两种持久化机制:
- RDB(快照模式)
- AOF(追加模式)
RDB快照模式原理
RDB即快照模式,是Redis默认的数据持久化方式,它会将Redis数据库的快照保存在dump.rdb这个二进制文件中。
快照就是将内存数据以二进制文件的形式保存起来
Redis是单线程的,也就是说一个线程要同时负责多个客户端套接字的并发读写,以及内存数据结构的逻辑读写。
Redis服务器不仅需要服务线上请求,同时还要备份内存快照。在备份过程中Redis必须进行IO读写,而IO操作会严重影响服务器的性能。所以如何实现既不影响客户端的请求,又实现快照备份的操作?这就需要使用多线程。
Redis使用操作系统的多线程 COW(Copy On Write)机制来实现快照的持久化操作。
RDB实际上是Redis内部的一个定时器事件,它每隔一段固定时间就去检查当前数据发生改变的次数和改变的时间频率,看它们是否满足配置文件中规定的持久化触发条件。当满足条件时,Redis就会通过操作系统调用 fork() 来创建一个子进程,该子进程与父进程享有相同的地址空间。
Redis通过子进程遍历整个内存空间来获取存储的数据,从而完成数据持久化操作。注意:此时的主进程仍可以对外提供服务,父子进程之间通过操作系统的COW机制实现了数据段分离,从而保证了父子进程之间互不影响。
RDB持久化触发策略
RDB持久化提供了两种触发策略:
- 手动触发
- 自动触发
配置文件定义
# 数据库备份的文件
dbfilename dump.rdb
# 默认是: dir ./
dir /lhail/redis/data
注意授权: chmod 777 /lhail/redis/data
自动触发策略
# 设置备份时机
# 3600 1 : 至少执行一次更改,3600秒(1小时)备份一次
# 300 100 : 至少执行100次更改,300秒(5分钟)备份一次
# 60 10000 : 至少执行10000次更改,60秒(1分钟)备份一次
save 3600 1 300 100 60 10000
注意:
- 只要上述三个条件任意满足一个,服务器就会自动执行
bgsave
命令。 - 每次创建RDB文件之后,Redis服务器为实现自动持久化而设置的时间计数和次数计数就会被清零,并重新开始计数,因此多个策略的效果不会叠加。
手动触发策略
手动触发时通过save
命令或者bgsave
命令将内存数据保存到磁盘文件中。如下所示:
127.0.0.1:6379> save
OK
127.0.0.1:6379> bgsave
Background saving started
127.0.0.1:6379> lastsave
(integer) 1686383325
上述命令:bgsave
从后台执行数据保存操纵,其可用性要优于执行save
命令。
save
会阻塞Redis服务进程,直到 dump.rdb 文件创建完毕,这个过程中服务器不能执行处理任何的命令请求
bgsave
是非阻塞式的,即在该命令执行的过程中,不影响Redis服务器处理客户端的其它请求。原因是Redis服务器会 fork() 一个子进程来进行持久化操作(比如创建新的 dump.rdb 文件),而父进程则继续处理客户端请求。当子进程处理完后会向父进程发送一个信号,通知它已经处理完毕。此时,父进程会用新的dump.rdb文件覆盖掉原来的旧文件。
注意:lastsave
命令用于查看bgsave
命令是否执行成功
RDB的错误处理
stop-writes-on-bgsave-error yes
后台存储发送错误时,禁止写入,默认值是yes,默认情况下,Redis在后台生成的快照文件时失败,就会停止接收数据,目的是让用户能知道没有持久化功能。
RDB的数据压缩
rdbcompression yes
指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大,不建议关闭。
RDB的数据校验
rdbchecksum yes
对RDB的数据进行校验,会消耗CPU资源,默认值是yes。即执行过程中如果有一些失败的命令就会丢失。
RDB持久化的优劣势
优势:
- RDB数据持久化适合大规模的数据恢复,还原速度快。对数据的完整性不是特别敏感(可能存在最后一次丢失的情况),那么RDB持久化方式是不错的选择。
劣势:
- 消耗资源、费时。RDB持久化过程中,子进程会把Redis的所有数据都保存到新建的dump.rdb文件中,这是一个耗时耗资源的操作。因此Redis服务器不能过于频繁地创建rdb文件,否则会严重影响服务器的性能。
- 存在数据丢失的情况。在持久化进行的过程中,服务器如果突然宕机,此时存储的数据可能并不完整,比如子进程已经生成了rdb文件,但主进程还未来得及用它覆盖掉原来旧的rdb文件,如此,就丢失了最后一次持久化的数据。
Redis持久化AOF
AOF被称为追加模式,或日志模式,是Redis提供的另一种持久化策略,它能够存储Redis服务器已经执行过的命令,并且只记录对内存有过修改的命令,这种数据记录方法,被叫做增量复制
,其默认存储文件为appendonly.aof
。
Redis客户端 ===> 发送写命令 ===> Redis服务器 ===> 同步命令 ===> AOF文件
开启AOF持久化
AOF机制默认处于未开启状态,可以通过修改Redis配置文件开启AOF。如下所示:
Windows系统
# 修改配置文件,把no改为yes
appendonly yes
# 确定存储文件名是否正确
appendfilename "appendonly.aof"
# 重启服务器
redis-server --service-stop
redis-server --service-start
Linux系统
# 修改配置文件
appendonly yes # 把no改为yes
# 确定存储文件名是否正确
appendfilename "appendonly.aof"
# 存储路径名, 注意此处的完整路径与前面的数据库目录有关,前面的 dir路径如果如下:
# dir /lhail/redis/data
# 那么AOF文件的路径即是:/lhail/redis/data/appendonlydir
appenddirname "appendonlydir"
# 重启Redis服务
# 查看redis服务进程号
ps -ef | grep redis
# 关闭redis服务
kill -9 [redis进程号]
AOF持久化机制
每当有一个修改数据库的命令被执行时,服务器就将命令写入到appendonly.aof文件中,该文件存储了服务器执行过程的所有修改命令,所以,只要服务器重新执行一次 .aof 文件,就可以实现还原数据的目的,这个过程被形象的称为 “命令重演”。
写入机制
Redis在收到客户端修改命令后,先进行相应的校验,如果无误,就立即将该命令追加到 .aof 文件中,即先存到磁盘,然后服务器再执行命令,这样即使遇到了突发的宕机情况,也只需将存储到 .aof 文件中的命令,进行一次 “命令重演” 就可以恢复到宕机前的状态。
上述执行过程中,有一个重要环节就是命令的写入,只是一个IO操作。Redis为了提高写入效率,不会将内容直接写入到磁盘,而是将其放到一个内存缓存区(buffer)中,等到缓存区被填满时才真正将缓存区的内容写入到磁盘中。
重写机制
Redis在长期运行的过程中,aof文件会越变越长。如果机器宕机重启,"重演"整个aof文件会十分耗时,导致Redis长时间无法对外提供服务。此时该怎么办呢?
手动触发AOF重写
为了让aof文件的大小控制在合理的范围内,Redis提供了AOF重写机制,手动执行 bgrewriteaof
命令,开始重写aof文件,如下所示:
127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started
通过上述操作,服务器会生成一个新的aof文件,该文件具有以下特点:
- 新的 aof 文件记录的数据库数据和原 aof 文件记录的数据库数据完全一致
- 新的 aof 文件会使用尽可能少的命令来记录数据库数据,因此新的 aof 文件的体积会小很多
- AOF 重写期间,服务器不会被阻塞,它可以正常处理客户端发送的命令
自动触发AOF重写
Redis为自动触发AOF重写功能,提供了相应的配置策略。如下所示:修改Redis配置文件,让服务器自动执行bgrewriteaof
命令。
# 默认配置项
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb # 表示触发AOF重写的最小文件体积,大于或等于64MB自动触发。
该配置项表示:触发重写所需要的aof文件体积百分比,只有当aof文件的增量大于100%时才进行重写,也就是大一倍。比如,第一次重写时文件大小为64M,那么第二次触发重写的体积为128M,第三次重写为256M,以此类推。如果将百分比值设置为0就表示关闭AOF自动重写功能。
AOF策略配置
在上述介绍的写入机制过程中,如果遇到宕机前,缓存内的数据未能写入到磁盘中,那么数据仍会有丢失的风险。服务器宕机时,丢失命令的数量,取决于命令被写入磁盘的时间,越早将命令写入磁盘,发送意外时丢失的数据就越少,反之越多。
Redis为数据的安全性考虑,同样为AOF持久化提供了策略配置,打开Redis配置文件,如图所示:
上述策略配置说明:
- always:服务器每写入一个命令就调用一次 fsync 函数,将缓冲区里面的命令写入到硬盘。这种模式下,服务器出现故障,也不会丢失任何已经成功执行的命令数据,但执行速度较慢
- everysec(默认):服务器每一秒调用一次 fsync 函数,将缓冲区里面的命令写入到硬盘。这种模式下,服务器出现故障,最多只丢失一秒钟内执行的命令数据,通常都使用它作为AOF配置策略
- no:服务器不主动调用 fsync 函数,由操作系统决定何时将缓冲区里的命令写入到硬盘。这种模式下,服务器出现故障,丢失命令的数量时不确定的,所以此策略,不确定性较大,不安全
注意:Linux系统的 fsync() 函数可以将指定文件的内容从内核缓存刷到硬盘中。
由于 fsync 是磁盘的IO操作,所以很慢!如果Redis执行一条命令就 fsync一次(always),那么Redis的高性能将严重受损。
RDB和AOF对比
RDB持久化 | AOF持久化 |
---|---|
全量备份,一次保存整个数据库 | 增量备份,一次只保存一个修改数据库的命令 |
每次执行持久化操作的间隔时间较长 | 保存的间隔默认为一秒钟 |
数据保存为二进制格式,还原速度快 | 使用文本格式还原数据,所以数据还原速度一般 |
执行save 命令时会阻塞服务器,但手动或自动触发bgsave 不会阻塞服务 | AOF持久化任何时候都不会阻塞服务器 |
**如果进行数据恢复,既有 dump.rdb 文件,又有 appendonly.aof 文件,应该先通过 appendonly.aof 恢复数据,这能最大程度保证数据的安全性。 **
Redis主从复制
主从复制
也称为:主从模式
,当用户向 Master 写入数据时,Master 通过 Redis同步机制将数据文件发送至 Slave。Slave 也会通过Redis的同步机制将数据文件发送至 Master 以确保数据一致,从而实现Redis的主从复制。
- 如果 Master 和 Slave 之间的连接中断,Slave 可以自动重连 Master,但连接成功后,将自动执行一次完全同步。
- 配置主从复制后,Master可以负责读写服务,Slave只负责读服务。
- Redis复制在 Master 端也是非阻塞的,即在和 Slave同步数据的时候,Master仍可以执行客户端的命令而不受影响。
在软件架构中,主从模式(Master-Slave)是使用较多的一种架构。主(Master)和从(Slave)分别部署在不同的服务器上,当主节点服务器写入数据时,同时也会将数据同步至从节点服务器,通常情况下,主节点负责写入数据,而从节点负责读取数据。
Redis主从复制的特点
- 同一个master可以拥有多个slave节点
- master下的slave可以接受同一个架构中其它的slave连接和同步请求,实现数据的级联复制,即 master-slave-slave
- master以非阻塞的方式同步数据至master,这意味着master会继续处理一个或多个slave的读写请求
- 主从复制不会阻塞master,当一个或多个slave与master进行初次同步数据时,master可以继续处理客户端发来的请求
- 通过配置禁止master数据持久化机制,将其数据持久化操作交给slave完成,避免master中有独立的进程来完成此操作
Redis主从复制的好处
- 避免Redis的单点故障
- 做到读写分离,构建读写分离架构,满足读多写少的应用场景
主从模式的实现
- 命令实现
# 使用命令在服务器搭建主从模式,其语法格式如下:
redis-server --port <slave-port> --slaveof <master-ip> <master-port>
# 开启一个port为6300的从机,它依赖的主机为port=6379
redis-server --port 6300 --slaveof 127.0.0.1 6379
# 命令启动
redis-server --port 6300 --slaveof 127.0.0.1 6379 --masterauth usrename --requirepass password --daemonnize yes
使用info replication
查看主从关系
-
主节点
-
从节点
-
从节点
# 设置从服务器(指向master服务器 ip 端口)
slaveof ip port
# 服务器切换为独立主机,注意:从服务器切换为独立主机后,之前已同步的数据不会消失
slaveof no one
- 配置文件实现
找到Redis的配置文件redis.conf
修改相关配置
# 修改端口号
port 6300
# 设置从机密码
requirepass password
# 修改 dump.rdb文件,此文件一般不共用
dbfilename dump6300.rdb
# 修改 aof文件
appendfilename "appendonly6300.aof"
# 将主机密码添加上
masterauth <master-password>
# 指定从机的主节点地址
# 注意:以下两个选一个设置即可,效果相同,但注意replicaof在redis的命令中是没有此命令的,而slaveof在redis中是有这个命令的,即设置从机的主节点除了在配置文件中使用以下两种方式配置,还可以使用 slaveof 命令来设置
slaveof 127.0.0.1 6379
replicaof 127.0.0.1 6379
建议使用命令搭建主从模式,简单快捷
主从模式的不足
- Redis主从模式不具备自动容错和恢复功能,如果主节点宕机,Redis集群将无法工作,此时需要人为的将从节点提升为主节点
- 如果主机宕机前有一部分数据未能及时同步到从机,即使切换主机后也会造成数据不一致,从而降低系统的可用性
- 因为只有一个主节点,所以其写入能力和存储能力都受到一定程度的限制
- 在进行数据全局同步时,若同步的数据量较大可能会造成卡顿现象
Redis哨兵
在Redis主从复制模式中,因为系统不具备自动恢复的功能,所以当主服务器(master)宕机后,需要手动把一台从服务器(slave)切换为主服务器。此过程,不仅需要人为干预,且会造成一段时间内服务器处于不可用状态,同时数据安全性也得不到保障,因此主从模式的可用性较低,不适用于线上生产环境。
Redis官方推荐一种高可用的方式:哨兵模式(Redis Sentinel),它弥补了主从模式的不足。Sentinel通过监控的方式获取主机的工作状态是否正常,当主机发生故障时,Sentinel会自动进行Failover(即故障转移),并将其监控的从机提升主服务器(master),从而保证了系统的高可用性。
哨兵模式的原理
哨兵模式是一种特殊的模式,Redis为其提供了专属的哨兵命令,它是一个独立的进程,能够独立运行。
哨兵模式原理就是通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵模式的应用
哨兵模式-单机
1、搭建主从模式
- 在本地环境使用主从模式搭建一个拥有三台服务器的Redis集群节点
redis-server redis.conf
redis-server redis6301.conf
redis-server redis6302.conf
- 配置 master6379 ---- slave6301 / slave6302
# 分别在 6301 和 6302 的节点下执行
slaveof 127.0.0.1 6379
2、配置sentinel哨兵
- 先找到redis安装目录,备份一份 sentinel.conf, 以备不时之需
cp sentinel.conf sentinelCopy.conf
- 编辑配置文件 sentinel.conf
# 配置主节点地址 6379后面的1代表当前为主节点
sentinel monitor mymaster 127.0.0.1 6379 1
# 配置主节点密码
sentinel auth-pass mymaster 123456
# 主节点宕机以后选举的间隔时间10s 单位是毫秒
sentinel down-after-milliseconds mymaster 10000
3、启动哨兵
/lhail/redis/redis-stable/src/redis-sentinel /lhail/redis/redis-stable/sentinel.conf
注意:如果主节点宕机,哨兵模式会选举出新的主节点,当之前宕机的主节点重启恢复后,不会恢复为主节点,而是成为从节点。
哨兵模式-集群
1、配置sentinel哨兵
- sentinel.conf
# 配置端口
port 26379
sentinel monitor mymaster 127.0.0.1 6379 1
# 配置主节点密码
sentinel auth-pass mymaster 123456
# 主节点宕机以后选举的间隔时间10s 单位是毫秒
sentinel down-after-milliseconds mymaster 10000
- sentinel26380.conf
# 配置端口
port 26380
sentinel monitor mymaster 127.0.0.1 6379 1
# 配置主节点密码
sentinel auth-pass mymaster 123456
# 主节点宕机以后选举的间隔时间10s 单位是毫秒
sentinel down-after-milliseconds mymaster 10000
- sentinel26381.conf
# 配置端口
port 26381
sentinel monitor mymaster 127.0.0.1 6379 1
# 配置主节点密码
sentinel auth-pass mymaster 123456
# 主节点宕机以后选举的间隔时间10s 单位是毫秒
sentinel down-after-milliseconds mymaster 10000
2、启动哨兵
/lhail/redis/redis-stable/src/redis-sentinel /lhail/redis/redis-stable/sentinel.conf
/lhail/redis/redis-stable/src/redis-sentinel /lhail/redis/redis-stable/sentinel26380.conf
/lhail/redis/redis-stable/src/redis-sentinel /lhail/redis/redis-stable/sentinel26381.conf
Redis集群
Redis集群是一个由多个主从节点组成的分布式服务器群,它具有复制,高可用和分片特性。Redis的集群将所有的数据存储区域划分为16384个槽(slot)。每个节点负责一部分槽。槽的信息存储于每个节点中。Redis集群要将每个节点设置成集群模式,它没有中心节点的概念,可以水平扩展,它的性能和高可用性均优越于主从模式和哨兵模式。且集群配置非常简单。如图:
Redis集群,首先将数据根据散列规则分配到6个槽中,然后根据循环冗余校验CRC算法和取模算法将6个槽分别存储到3个不同的master节点中,每个master节点又配套部署一个slave节点,当一个master节点出现问题后,slave节点会自动顶上,相比于哨兵模式,这个方案的优点是:提供了读写的并发率,分散了I/O,在保障高可用的前提下提供了性能。
Redis集群部署
- 主节点不能少于总节点的一半
- 主节点至少要有3个
- 1、解压安装Redis
- 2、将 redis.conf 复制6份
mkdir /lhail/redis/cluster
cd /lhail/redis/cluster
mkdir -p 6001 6002 6003 6004 6005 6006
cp /lhail/redis/redis-stable/redis.conf /lhail/redis/cluster/6001/redis.conf
cp /lhail/redis/redis-stable/redis.conf /lhail/redis/cluster/6002/redis.conf
cp /lhail/redis/redis-stable/redis.conf /lhail/redis/cluster/6003/redis.conf
cp /lhail/redis/redis-stable/redis.conf /lhail/redis/cluster/6004/redis.conf
cp /lhail/redis/redis-stable/redis.conf /lhail/redis/cluster/6005/redis.conf
cp /lhail/redis/redis-stable/redis.conf /lhail/redis/cluster/6006/redis.conf
- 3、每个对应的配置如下,以6001为例:
# 1、以保护模式启动
daemonize yes
# 2、修改端口
port 6001
# 3、修改数据存储的目录,这里必须知道不同的目录位置,否则会造成数据的丢失
dir /lhail/redis/cluster/6001
# 4、开启集群模式
cluster-enabled yes
# 5、集群节点信息文件,这里最好与端口保持一致
cluster-config-file nodes-6001.conf
# 6、集群节点的超时时限,单位是毫秒
cluster-node-timeout 10000
# 7、修改为主机的ip地址,默认地址是:127.0.0.1,需要修改成其它节点计算机可以访问的ip地址,否则创建集群的时候无法访问对应节点的端口,无法创建集群
# 单机建议设置:本机IP
# 如果是多机设置,阿里云的内网IP或者注释掉或者bind:0.0.0.0
bind 0.0.0.0
# 8、受保护的模式,关闭,否则也会造成集群无法创建成功
protected-mode no
# 9、开启aof的数据持久化
appendonly yes
# 10、aof持久化的文件名
appendfilename "appendonly6001.aof"
# 11、aof文件所放位置,它是与dir一起拼接而成,例如:/lhail/redis/cluster/6001/aof/appendonly6001.aof
appenddirname "aof"
# 12、当前服务节点的密码
requirepass 123456
# 13、如果自身作为从节点后,如果要连接master节点,一定要配置,建议密码都保持一致
masterauth 123456
- 4、把修改后的6001的redis.conf配置文件,分别复制到6002、6003、6004、6005、60006,每个文件只需修改(2)(3)(5)(10)里的端口信息即可
- 5、分别将6个redis节点启动,然后检查是否启动成功
redis-server /lhail/redis/cluster/6001/redis.conf
redis-server /lhail/redis/cluster/6002/redis.conf
redis-server /lhail/redis/cluster/6003/redis.conf
redis-server /lhail/redis/cluster/6004/redis.conf
redis-server /lhail/redis/cluster/6005/redis.conf
redis-server /lhail/redis/cluster/6006/redis.conf
- 检查是否启动成功
ps -ef|grep redis
- 6、使用 redis-cli创建redis集群
- redis-cli --cluster create --cluster-replicas 1 这个数字1代表的是未来节点都会有一个从节点,代表一主一从,如果写2,就代表一主二从
- -a 每个节点的密码,建议保持一致
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:6001 127.0.0.1:6002 127.0.0.1:6003 127.0.0.1:6004 127.0.0.1:6005 127.0.0.1:6006 -a password
- 7、查看效果以及它们的映射关系,进入任意一个节点都可
# -c 表示以集群方式登录
redis-cli -c -h 127.0.0.1 -p 6001
redis-cli -c -h 127.0.0.1 -p 6002
redis-cli -c -h 127.0.0.1 -p 6003
redis-cli -c -h 127.0.0.1 -p 6004
redis-cli -c -h 127.0.0.1 -p 6005
redis-cli -c -h 127.0.0.1 -p 6006
127.0.0.1:6001> cluster info
# 集群状态正常
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
# 6个节点
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:2183
cluster_stats_messages_pong_sent:2121
cluster_stats_messages_sent:4304
cluster_stats_messages_ping_received:2116
cluster_stats_messages_pong_received:2183
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:4304
total_cluster_links_buffer_limit_exceeded:0
127.0.0.1:6001> cluster nodes
d1899066dbf39d3e2d97d3aa33fb05e4868b716c 127.0.0.1:6004@16004 slave bc088fb7ec42bf7eef936334df9c843bd56eb3c2 0 1686572806881 3 connected
1895133298a4dda4b115e2304d0922f2b78672da 127.0.0.1:6001@16001 myself,master - 0 1686572807000 1 connected 0-5460
d18298717f1661bd281905004b2a761fabe26039 127.0.0.1:6002@16002 master - 0 1686572808000 2 connected 5461-10922
74c2d3de1399c1c8f6c3ed2a855e407d5882e4a7 127.0.0.1:6006@16006 slave d18298717f1661bd281905004b2a761fabe26039 0 1686572808889 2 connected
d5d12f9963e15d70e2b1d3cddc2aa01ea8eab5ff 127.0.0.1:6005@16005 slave 1895133298a4dda4b115e2304d0922f2b78672da 0 1686572806000 1 connected
bc088fb7ec42bf7eef936334df9c843bd56eb3c2 127.0.0.1:6003@16003 master - 0 1686572807885 3 connected 10923-16383
127.0.0.1:6001>
- 3主3从成功搭建后,由上查询可知:
- 主:6001 ---- 从:6005
- 主:6002 ---- 从:6006
- 主:6003 ---- 从:6004
在 6002master节点添加数据—>在6006slave节点可以获取到数据,说明成功。如果6002停止,6006节点会瘫痪在此处
# 主节点 6002
> redis-cli -c -h 127.0.0.1 -p 6002
> set name 6002
> get name
6002
# 从节点 6006
> redis-cli -c -h 127.0.0.1 -p 6006
> get name
6002
- 关闭所有的集群节点可以使用以下命令:
redis-cli -c -h 127.0.0.1 -p 6001 -a password shutdown
redis-cli -c -h 127.0.0.1 -p 6002 -a password shutdown
redis-cli -c -h 127.0.0.1 -p 6003 -a password shutdown
redis-cli -c -h 127.0.0.1 -p 6004 -a password shutdown
redis-cli -c -h 127.0.0.1 -p 6005 -a password shutdown
redis-cli -c -h 127.0.0.1 -p 6006 -a password shutdown
Redis集群扩容
- 新增Redis主节点
# 新增redis主节点6007,并启动
> cp /lhail/redis/cluster/6006/redis.conf /lhail/redis/cluster/6007/redis.conf
> redis-server /lhail/redis/cluster/6007/redis.conf
# 增加集群节点,此处6001是示例,也可以在集群中找其它的节点
> redis-cli --cluster add-node 127.0.0.1:6007 127.0.0.1:6001 -a 123456
# 查看集群节点
> redis-cli -p 6007
> auth 123456
> cluster info
# 给新增主节点分配hash槽
> redis-cli --cluster reshard 127.0.0.1:6007 -a 123456
# 给主节点6007分配槽位
How many slots do you want to move (from 1 to 16384)? 1000
# 6007的ID
What is the receiving node ID? 1895133298a4dda4b115e2304d0922f2b78672da
# all 代表重新分配全部hash槽位
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node #1: all
# yes 开始进行重新分配
Do you want to proceed with the proposed reshard plan (yes/no)?
- 新增Redis从节点
# 添加一个从节点6008
> cp /lhail/redis/cluster/6007/redis.conf /lhail/redis/cluster/6008/redis.conf
# 启动从节点
> redis-server /lhail/redis/cluster/6008/redis.conf
> redis-cli --cluster add-node 127.0.0.1:6008 127.0.0.1:6007 -a 123456
> redis-cli -p 6008
# 查看节点信息
> cluster nodes
# 绑定节点间关系,6008设置为6007的从节点,此命令须进入到6008的客户端执行
# 命令 cluster replicate [主节点ID]
> cluster replicate 62105098ad623660c617bd7e297d03b209000e5c
Redis删除集群节点
- Redis集群节点的删除分为两个方面:
- 一是删除主节点,那么需要先备份主节点数据,还需要注意删除主节点后的空出的插槽的重新分配问题
- 二是删除从节点,直接删除即可
# 删除从节点
redis-cli --cluster del-node 127.0.0.1:6008 [从节点ID] -a [密码]
# 删除主节点
redis-cli --cluster reshard 127.0.0.1:6007 -a 123456 --cluster-from [需要删除的节点ID]
# 移动多少槽位
How many slots do you want to move (from 1 to 16384)? 1000
# 槽位分配给谁?
What is the receiving node ID? e331d7940cc175716d70933003daca9a57070992
# 重新分配槽位
Do you want to proceed with the proposed reshard plan (yes/no)? yes
# 槽位分配完成,删除主节点
redis-cli --cluster del-node 127.0.0.1:6007 [节点ID] -a 123456
Redis的缓存穿透和雪崩
在实际业务中,Redis一般配合其它数据库使用,从而达到减轻数据库压力的作用。
例如:Redis和MySQL关系型数据库配合使用。
Redis会将MySQL中经常被查询的数据缓存起来,等下一次用户访问时,就不需要查询MySQL了,而是直接查询Redis缓存数据,从而降低了MySQL数据库的读取压力。如果Redis中没有查询到数据,再从MySQL中查询,将MySQL的数据返回给客户端,同时将数据缓存到Redis。
在使用Redis做缓存的时候,也会遇到一些问题,如缓存穿透、缓存击穿和缓存雪崩等。
缓存穿透
缓存穿透: 指用户在查询某个数据时,Redis中不存在该数据
,也就是缓存未命中,就会将请求转向持久层数据库MySQL,但结果显示MySQL中也不存在该数据
,此时MySQL只能返回空对象,查询失败。偶尔出现几次这种类似情况,并不会有太大影响,但如果此类请求量十分巨大,或者有人恶意攻击,就会给MySQL数据库造成巨大的压力,甚至使MySQL崩溃,这种现象就叫做缓存穿透
解决缓存穿透有以下几种方案:
- 缓存空对象
- 当MySQL返回空对象时,Redis将该对象缓存起来,同时设置一个过期时间。用户再次发起相同请求,就会从缓存中拿到空对象。此方案虽解决了缓存穿透的问题,但同时会占用Redis的缓存空间。
- 布隆过滤器
- 布隆过滤器判定不存在的数据,那么该数据一定不存在,利用此特点可以防止缓存穿透
- 可以
先将用户可能会访问的热点数据存储在布隆过滤器中
(也称缓存预热),当有用户请求时,会先经过布隆过滤器,如果请求数据在布隆过滤器中不存在
,那么将直接拒绝该请求
,否则继续执行查询。此方法相对于缓存空对象,更为高效、实用。 - 缓存预热
- 系统启动时,提前将相关数据加载到Redis缓存中。这样就避免了用户请求时再去加载数据。
布隆过滤器
点击进入引用地址: http://c.biancheng.net/redis/bloom-filter.html
概述
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
布隆过滤器不会像使用缓存那么浪费空间,但也存在一个小问题就是不太精确。即说存在不一定存在,但说不存在就一定不存在
相比于Set集合,布隆过滤器在空间上能节省90%以上,但它的不足在于去重率大约在99%左右,即是说有1%左右的误判率。
应用场景
- 百度爬虫
- 百度爬虫系统每天都面临海量的URL数据,我们希望它每次只爬取最新的页面,而对于没有更新过的页面则不爬取,所以,爬虫系统必须对抓取过的URL去重,否则执行效率会大大降低。但若使用Set集合去装载这些URL地址,会造成资源空间的严重浪费。
- 抖音
- 以抖音为例,如果我们把用户浏览过的视频用 T 表示,没有浏览过的视频用 F 表示,现在给用户推荐视频,先去 T 中判断是否推荐过,如果没有推荐过(T中无记录),那就一定没有推荐过。如果有推荐过(T中也可能没有这个视频),这时候该视频不会推荐给用户,导致用户没有看到这个视频,但此概率极低。这也正是为什么刷抖音看到的视频没有过多重复视频的原因。毕竟如果刷来刷去都是那几个视频,用户体验会相当差。
工作原理
布隆过滤器是一个高空间利用率的概率性数据结构,它实际上是一个很长的二进制向量(即位数组)和一系列随机映射函数(即哈希函数)。
布隆过滤器使用exists()
来判断某个元素是否存在于自身结构中。当它判定某个值存在时,其实这个值只是可能存在;当判断某个值不存在时,那这个值肯定不存在,此误判概率大约在1%左右。
- 1、布隆过滤器主要是由位数组和一系列hash函数组成,其中位数组的初始状态都为0。下图所示是布隆过滤器的原理:
当使用布隆过滤器添加 key 时,会使用不同的 hash 函数对 key 存储的元素值进行哈希计算,从而会得到多个哈希值。根据哈希值计算出一个整数索引值,将该索引值与位数组长度做取余运算,最终得到一个位数组位置,并将该位置的值变为 1。每个 hash 函数都会计算出一个不同的位置,然后把数组中与之对应的位置变为 1。通过上述过程就完成了元素添加(add)操作。 - 2、工作流程-判定元素是否存在
当我们需要判断一个元素是否存时,其流程如下:首先对给定元素再次执行哈希计算,得到与添加元素时相同的位数组位置,判断所得位置是否都为 1,如果其中有一个为 0,那么说明元素不存在,若都为 1,则说明元素有可能存在。 - 3、为什么是可能“存在”
您可能会问,为什么是有可能存在?其实原因很简单,那些被置为 1 的位置也可能是由于其他元素的操作而改变的。比如,元素1 和 元素2,这两个元素同时将一个位置变为了 1(图1所示)。在这种情况下,我们就不能判定“元素 1”一定存在,这是布隆过滤器存在误判的根本原因。
安装与使用
在 Redis 4.0 版本之后,布隆过滤器才作为插件被正式使用。布隆过滤器需要单独安装,下面介绍安装 RedisBloom 的几种方法:
1.Docker安装
docker 安装布隆过滤器是最简单、快捷的一种方式:
docker pull redislabs/rebloom:latest
docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
docker exec -it redis-redisbloom bash
redis-cli
#测试是否安装成功
127.0.0.1:6379> bf.add www.biancheng.net hello
2.直接编译安装
如果您对 docker 不熟悉,也可以采用直接编译的方式来安装。
Github地址:
https://github.com/RedisBloom/RedisBloom
下载地址:
https://github.com/RedisBloom/RedisBloom/archive/refs/tags/v2.2.18.tar.gz
解压文件:
tar -zxvf v2.2.18.tar.gz
进入目录:
cd RedisBloom-2.2.18
执行编译命令,生成redisbloom.so 文件:
make
拷贝至指定目录:
cp redisbloom.so /usr/local/redis/bin/redisbloom.so
在redis配置文件里加入以下配置:
loadmodule /usr/local/redis/bin/redisbloom.so
配置完成后重启redis服务:
sudo /etc/init.d/redis-server restart
#测试是否安装成功
127.0.0.1:6379> bf.add www.biancheng.net hello
常用命令汇总
命令 | 说明 |
---|---|
bf.add | 只能添加元素到布隆过滤器。 |
bf.exists | 判断某个元素是否在于布隆过滤器中。 |
bf.madd | 同时添加多个元素到布隆过滤器。 |
bf.mexists | 同时判断多个元素是否存在于布隆过滤器中。 |
bf.reserve | 以自定义的方式设置布隆过滤器参数值,共有 3 个参数分别是 key、error_rate(错误率)、initial_size(初始大小)。 |
- 命令应用
127.0.0.1:6379> bf.add spider:url www.biancheng.net
(integer) 1
127.0.0.1:6379> bf.exists spider:url www.biancheng.net
(integer) 1
127.0.0.1:6379> bf.madd spider:url www.taobao.com www.123qq.com
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> bf.mexists spider:url www.jd.com www.taobao.com
1) (integer) 0
2) (integer) 1
缓存击穿
缓存击穿: 指用户查询的数据在缓存中不存在
,但后端数据库中存在
,造成此现象的原因一般是缓存中的key过期导致。例如,一个热点数据key
,它无时无刻都在接受大量并发的访问,但某一时刻,这个key突然过期失效
,会导致大量并发请求进入到后端数据库
,导致后端数据库压力瞬间剧增
。这种现象称为缓存击穿
。
解决方案:
- 改变过期时间
- 设置热点数据永不过期
- 将过期时间全部错开
- 分布式锁
- 上锁:当通过key查询数据时,先查询缓存,若没有数据,就通过分布式锁进行加锁,第一个获取锁的进程进入后端数据库查询,并将查询结果缓存到Redis中。
- 解锁:当其它进程发现锁被某个线程占用时,进入等待状态,直到解锁后,其余进程再依次访问被缓存的key。
缓存雪崩
缓存雪崩: 指缓存中大批量的key同时过期
,或者Redis直接宕机
。而此时数据访问量又十分巨大,从而导致后端数据库的压力一瞬间暴增,甚至使后端数据库挂掉,这种现象称为缓存雪崩
。它与缓存击穿不同,缓存击穿是再并发量特别大的时候,某一个热点key突然过期,而缓存雪崩是大量的key同时过期。
解决方案:
- 热点数据设置永不过期
- key设置随机过期时间,使失效点尽可能均匀分布
- 针对Redis发生故障的情况,在部署Redis时,可以使用主从复制、哨兵、集群