Redis学习笔记
- 启动redis
- 性能测试benchmark
- redis基本操作
- redis五大数据类型
- 三大特殊数据类型
- redis事务
- Jedis
- springboot整合redis
- redis.conf详解
- redis持久化
- redis发布订阅
- redis主从复制
- 哨兵模式
- redis集群
- redis缓存预热
- redis缓存雪崩
- redis缓存击穿
- redis缓存穿透
- 性能指标监控
- 热key问题
- 布隆过滤器
- springboot整合docker的redis集群
启动redis
redis中的OPS 即operation per second 每秒操作次数。意味着每秒对Redis的持久化操作。
启动redis服务
redis-server ./redis.conf
启动redis客户端
-p: 端口号
要在 redis-cli 后面加上 --raw ,避免中文乱码
redis-cli -p 6379 --raw
测试redis是否正常启动
127.0.0.1:6379 > ping
PONG
显示pong说明服务已经正常运行
查看进程中redis服务和客户端
ps -ef|grep redis
关闭redis服务
127.0.0.1:6379 > shutdown
not connected > exit
性能测试benchmark
redis-benchmark
redis基本操作
# To enable logging to the system logger, just set 'syslog-enabled' to yes,
# and optionally update the other syslog parameters to suit your needs.
# syslog-enabled no
# Specify the syslog identity.
# syslog-ident redis
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
# syslog-facility local0
# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and 'databases'-1
databases 16
有16个数据库 默认使用第0个
select 切换数据库
选择第4个数据库
127.0.0.1:6379 > select 3
OK
dbsize 查看数据库大小
数据库大小为0
127.0.0.1:6379[3]> dbsize
(integer) 0
keys 查找所有给定模式的key
查找所有的key
127.0.0.1:6379[3]> keys *
1) "name"
flush 清空数据库
清空当前数据库
127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty array)
清空所有数据库
127.0.0.1:6379[3]> flushall
OK
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379 > keys *
(empty array)
exists 查找数据库中是否存在key
127.0.0.1:6379 > EXISTS age
(integer) 1
move 把当前数据库中的数据移动到指定数据库
127.0.0.1:6379 > move age 1
(integer) 1
127.0.0.1:6379 > move name 1
(integer) 1
127.0.0.1:6379 > select 1
OK
127.0.0.1:6379[1]> keys *
1) "name"
2) "age"
expire 设置键值的到期时间
127.0.0.1:6379[1]> EXPIRE name 10
(integer) 1
ttl 查看键值的存活时间
time to live
127.0.0.1:6379[1]> ttl name
(integer) 6
127.0.0.1:6379[1]> ttl name
(integer) 5
127.0.0.1:6379[1]> ttl name
(integer) 4
127.0.0.1:6379[1]> ttl name
(integer) 2
127.0.0.1:6379[1]> ttl name
(integer) -2
127.0.0.1:6379[1]> keys *
1) "age"
del 删除指定的键值
127.0.0.1:6379[1]> keys *
1) "age"
127.0.0.1:6379[1]> del age
(integer) 1
127.0.0.1:6379[1]> keys *
(empty array)
type 查看指定值的数据类型
127.0.0.1:6379[1]> type name
string
append 在key对应值后添加
如果key存在,则在值后添加
127.0.0.1:6379[1]> APPEND name hello
(integer) 8 #显示添加后的值长度
127.0.0.1:6379[1]> get name
"zwbhello"
如果key不存在,则新建一个键值(相当于set key)
127.0.0.1:6379[1]> APPEND name1 hello
(integer) 5
127.0.0.1:6379[1]> keys *
1) "age"
2) "name"
3) "name1"
redis五大数据类型
String(字符串)
结构:key-value
set和get字符串
127.0.0.1:6379 > set name zwb
OK
127.0.0.1:6379 > get name
"zwb"
strlen 获得key对应值长度
127.0.0.1:6379[1]> STRLEN name
(integer) 13
incr 对key对应值+1(i++)
127.0.0.1:6379[1]> INCR views
(integer) 1 #显示+1后的值
127.0.0.1:6379[1]> INCR views
(integer) 2
127.0.0.1:6379[1]> get views
"2"
decr 对key对应值-1(i–)
127.0.0.1:6379[1]> DECR views
(integer) 1 #显示-1后的值
127.0.0.1:6379[1]> DECR views
(integer) 0
127.0.0.1:6379[1]> get views
"0"
incrby 对key对应值+指定值(i+=)
127.0.0.1:6379[1]> INCRBY views 10
(integer) 10
127.0.0.1:6379[1]> INCRBY views 10
(integer) 20
127.0.0.1:6379[1]> get views
"20"
decrby 对key对应值-指定值(i-=)
127.0.0.1:6379[1]> DECRBY views 10
(integer) 10
127.0.0.1:6379[1]> DECRBY views 10
(integer) 0
127.0.0.1:6379[1]> get views
"0"
getrange 获得key对应值的范围
127.0.0.1:6379[1]> GETRANGE name 0 5
"zwbhel" #0到5输出6个字符[0,5]
127.0.0.1:6379[1]> GETRANGE name 0 -1
"zwbhellohello" #0到-1输出整个字符串
setrange 设置key对应值从x开始的范围
127.0.0.1:6379[1]> SETRANGE name 3 byebye #从3开始后面的字符替换成byebye但其余不变,长度还是13
(integer) 13
127.0.0.1:6379[1]> get name
"zwbbyebyeello"
setex 设置键值并设置其过期时间
set and expire
127.0.0.1:6379[1]> SETEX key1 20 value1
OK
127.0.0.1:6379[1]> ttl key1
(integer) 14
127.0.0.1:6379[1]> keys *
1) "views"
2) "name"
3) "age"
4) "key1"
5) "name1"
127.0.0.1:6379[1]> ttl key1
(integer) -2
127.0.0.1:6379[1]> keys *
1) "views"
2) "name"
3) "age"
4) "name1"
setnx 设置键值如果键值不存在
set not exist 分布式锁中常用
127.0.0.1:6379[1]> SETNX name "zwb"
(integer) 0 #没有设置成功返回0
127.0.0.1:6379[1]> get name
"zwbbyebyeello"
127.0.0.1:6379[1]> SETNX key2 "value2"
(integer) 1 #设置成功返回1
127.0.0.1:6379[1]> get key2
"value2"
mset和mget 批量设置和获取键值
127.0.0.1:6379[1]> mset key1 value1 key2 value2 key3 value3
OK
127.0.0.1:6379[1]> mget key1 key2 key3
1) "value1"
2) "value2"
3) "value3"
msetnx 批量设置键值如果键值不存在
msetnx是原子性操作,要么都成功,要么都失败
127.0.0.1:6379[1]> MSETNX key1 value1 key4 value4
(integer) 0 #设置失败
127.0.0.1:6379[1]> keys *
1) "key3"
2) "key1"
3) "key2" #没有key4
set/mset和get/mget设置对象
127.0.0.1:6379[1]> set user:1 {name:zwb,age:20}
OK
127.0.0.1:6379[1]> get user:1
"{name:zwb,age:20}"
127.0.0.1:6379[1]> mset user:1:name zwb user:1:age 20
OK
127.0.0.1:6379[1]> mget user:1:name user:1:age
1) "zwb"
2) "20"
getset 先get值再set值
127.0.0.1:6379[1]> GETSET name zwb2
(nil) #如果不存在返回nil并设置值
127.0.0.1:6379[1]> GETSET name zwb3
"zwb2" #如果存在返回值并设置值
127.0.0.1:6379[1]> GETSET name zwb4
"zwb3"
List(列表)
结构:key value1-value2-value3
底层数据结构为双向链表,有序的
lpush 从队列左边插入元素(头插)
127.0.0.1:6379[1]> LPUSH list A
(integer) 1
127.0.0.1:6379[1]> LPUSH list B
(integer) 2
127.0.0.1:6379[1]> LPUSH list C
(integer) 3
rpush 从队列右边插入元素(尾插)
lrange 从队列中获取元素值
127.0.0.1:6379[1]> LRANGE list 0 -1 #从左开始
1) "C"
2) "B"
3) "A"
127.0.0.1:6379[1]> lrange list 0 -1
1) "A"
2) "B"
3) "A"
4) "B"
5) "C"
6) "D"
127.0.0.1:6379[1]> LRANGE list -3 -1 # 从右开始
1) "B"
2) "C"
3) "D"
lpop 从队列左边取出元素
127.0.0.1:6379[1]> LPOP list
"C"
127.0.0.1:6379[1]> LRANGE list 0 -1
1) "B"
2) "A"
rpop 从队列右边取出元素
lindex 通过索引在队列中获取元素
127.0.0.1:6379[1]> LRANGE list 0 -1
1) "D"
2) "C"
3) "A"
4) "B"
127.0.0.1:6379[1]> LINDEX list 0 #=0为第一个元素
"D"
127.0.0.1:6379[1]> LINDEX list 2 #>=0从左边开始数
"A"
127.0.0.1:6379[1]> LINDEX list -1 #<0从右边开始数
"B"
127.0.0.1:6379[1]> LINDEX list -2
"A"
127.0.0.1:6379[1]> LINDEX list -3
"C"
llen 获取队列的长度
127.0.0.1:6379[1]> llen list
(integer) 4
lrem 移除队列中指定元素
127.0.0.1:6379[1]> LRANGE list 0 -1
1) "D"
2) "D"
3) "D"
4) "D"
5) "C"
6) "A"
7) "B"
127.0.0.1:6379[1]> LREM list 1 D #count>0 从左往右移除1个
(integer) 1
127.0.0.1:6379[1]> LRANGE list 0 -1
1) "D"
2) "D"
3) "D"
4) "C"
5) "A"
6) "B"
127.0.0.1:6379[1]> LREM list -1 D #count<0 从右往左移除1个
(integer) 1
127.0.0.1:6379[1]> LRANGE list 0 -1
1) "D"
2) "D"
3) "C"
4) "A"
5) "B"
127.0.0.1:6379[1]> LREM list 0 D #count=0 移除全部符合的元素
(integer) 2
127.0.0.1:6379[1]> LRANGE list 0 -1
1) "C"
2) "A"
3) "B"
ltrim 通过索引截取队列
127.0.0.1:6379[1]> lrange list 0 -1
1) "A"
2) "B"
3) "C"
4) "D"
127.0.0.1:6379[1]> LTRIM list 1 2 #从左开始
OK
127.0.0.1:6379[1]> lrange list 0 -1
1) "B"
2) "C"
127.0.0.1:6379[1]> lrange list 0 -1
1) "D"
2) "A"
3) "B"
127.0.0.1:6379[1]> LTRIM list -2 -1 #从右开始
OK
127.0.0.1:6379[1]> lrange list 0 -1
1) "A"
2) "B"
rpoplpush 从队列右边pop元素push进另一个队列的左边
如果队列不存在,则创建队列
127.0.0.1:6379[1]> lrange list 0 -1
1) "A"
2) "B"
3) "A"
4) "B"
5) "C"
6) "D"
127.0.0.1:6379[1]> RPOPLPUSH list list2
"D"
127.0.0.1:6379[1]> RPOPLPUSH list list2
"C"
127.0.0.1:6379[1]> RPOPLPUSH list list2
"B"
127.0.0.1:6379[1]> lrange list 0 -1
1) "A"
2) "B"
3) "A"
127.0.0.1:6379[1]> lrange list2 0 -1
1) "B"
2) "C"
3) "D"
lset 设置队列中索引的元素值
如果找不到key或找不到队列中的索引值则报错
127.0.0.1:6379[1]> LRANGE list 0 -1
1) "A"
2) "B"
3) "A"
127.0.0.1:6379[1]> LSET list 2 C #从左开始
OK
127.0.0.1:6379[1]> LRANGE list 0 -1
1) "A"
2) "B"
3) "C"
127.0.0.1:6379[1]> LSET list -1 D
OK
127.0.0.1:6379[1]> LRANGE list 0 -1 #从右开始
1) "A"
2) "B"
3) "D"
linsert 通过元素值的前和后将新元素插入队列中
127.0.0.1:6379[1]> LRANGE list 0 -1
1) "A"
2) "B"
3) "D"
127.0.0.1:6379[1]> LINSERT list before D C #D的before插入C
(integer) 4
127.0.0.1:6379[1]> LRANGE list 0 -1
1) "A"
2) "B"
3) "C"
4) "D"
127.0.0.1:6379[1]> LINSERT list after A C #A的after插入C
(integer) 5
127.0.0.1:6379[1]> LRANGE list 0 -1
1) "A"
2) "C"
3) "B"
4) "C"
5) "D"
Set(集合)
结构:key value1,value2,value3
set是不可重复的,无序的
sadd 往集合中添加元素
127.0.0.1:6379[1]> SADD set A
(integer) 1
127.0.0.1:6379[1]> SADD set B C
(integer) 2
smembers 查看集合中所有元素
127.0.0.1:6379[1]> SMEMBERS set
1) "C"
2) "B"
3) "A"
scard 获取集合中元素的数量
127.0.0.1:6379[1]> scard set
(integer) 3
srem 移除集合中指定元素
127.0.0.1:6379[1]> SREM set B
(integer) 1
127.0.0.1:6379[1]> SMEMBERS set
1) "C"
2) "A"
srandmember 从集合中随机抽取指定数量元素
127.0.0.1:6379[1]> SMEMBERS set
1) "D"
2) "F"
3) "A"
4) "C"
5) "B"
6) "E"
127.0.0.1:6379[1]> SRANDMEMBER set
"B"
127.0.0.1:6379[1]> SRANDMEMBER set
"A"
127.0.0.1:6379[1]> SRANDMEMBER set
"C"
127.0.0.1:6379[1]> SRANDMEMBER set 2
1) "D"
2) "E"
127.0.0.1:6379[1]> SRANDMEMBER set 2
1) "B"
2) "C"
spop 从集合中随机删除指定数量元素
127.0.0.1:6379[1]> SMEMBERS set
1) "F"
2) "A"
3) "C"
4) "D"
5) "B"
6) "E"
127.0.0.1:6379[1]> SPOP set
"B"
127.0.0.1:6379[1]> SPOP set 2
1) "F"
2) "A"
127.0.0.1:6379[1]> SMEMBERS set
1) "C"
2) "D"
3) "E"
smove 在集合中移动指定元素到另一个集合中
如果目标集合不存在,则创建集合
127.0.0.1:6379[1]> SMOVE set set2 C
(integer) 1
127.0.0.1:6379[1]> SMEMBERS set
1) "D"
2) "E"
127.0.0.1:6379[1]> SMEMBERS set2
1) "C"
sdiff 获得两个集合的差集
127.0.0.1:6379[1]> SMEMBERS set
1) "C"
2) "D"
3) "E"
127.0.0.1:6379[1]> SMEMBERS set2
1) "A"
2) "AB"
3) "C"
127.0.0.1:6379[1]> SDIFF set set2
1) "D"
2) "E"
sinter 获得两个集合的交集
127.0.0.1:6379[1]> SMEMBERS set
1) "C"
2) "D"
3) "E"
127.0.0.1:6379[1]> SMEMBERS set2
1) "A"
2) "AB"
3) "C"
127.0.0.1:6379[1]> sinter set set2
1) "C"
sunion 获得两个集合的并集
127.0.0.1:6379[1]> SMEMBERS set
1) "C"
2) "D"
3) "E"
127.0.0.1:6379[1]> SMEMBERS set2
1) "A"
2) "AB"
3) "C"
127.0.0.1:6379[1]> sunion set set2
1) "D"
2) "E"
3) "C"
4) "A"
5) "AB"
hash(哈希)
结构:KEY key1:value1,key2:value2 ,key3:value3
使用场景: 存储对象
hset和hget/hmget元素
127.0.0.1:6379[1]> hset hash key1 value1
(integer) 1
127.0.0.1:6379[1]> HGET hash key1
"value1"
127.0.0.1:6379[1]> HMGET hash key1 key2 key3
1) "value1"
2) "value2"
3) "value3"
hgetall 获取hash中所有键值对
127.0.0.1:6379[1]> HGETALL hash
1) "key1"
2) "value1"
3) "key2"
4) "value2"
5) "key3"
6) "value3"
hdel 删除指定hash中的key值
127.0.0.1:6379[1]> HDEL hash key2
(integer) 1
127.0.0.1:6379[1]> HGETALL hash
1) "key1"
2) "value1"
3) "key3"
4) "value3"
hlen 获取指定hash中的长度
127.0.0.1:6379[1]> HGETALL hash
1) "key1"
2) "value1"
3) "key3"
4) "value3"
127.0.0.1:6379[1]> HLEN hash
(integer) 2 #有两个键值对
hexists 判断指定hash中是否存在key
127.0.0.1:6379[1]> HGETALL hash
1) "key1"
2) "value1"
3) "key3"
4) "value3"
5) "key2"
6) "value2"
127.0.0.1:6379[1]> HEXISTS hash key3
(integer) 1
hkeys 获取指定hash中所有key值
127.0.0.1:6379[1]> HKEYS hash
1) "key1"
2) "key3"
3) "key2"
hvals 获取指定hash中所有value值
127.0.0.1:6379[1]> HVALS hash
1) "value1"
2) "value3"
3) "value2"
hincrby 对指定hash的key值加一个值
127.0.0.1:6379[1]> hset hash key4 0
(integer) 1
127.0.0.1:6379[1]> HINCRBY hash key4 1
(integer) 1
127.0.0.1:6379[1]> HINCRBY hash key4 1
(integer) 2
127.0.0.1:6379[1]> HINCRBY hash key4 -1
(integer) 1
127.0.0.1:6379[1]> HINCRBY hash key4 -1
(integer) 0
hsetnx 如果存在则设置 否则不设置
127.0.0.1:6379[1]> HSETNX hash key4 3
(integer) 0
127.0.0.1:6379[1]> HSETNX hash key5 3
(integer) 1
hset 设置对象
127.0.0.1:6379[1]> HSET user:1 name zwb age 20
(integer) 2
127.0.0.1:6379[1]> HGET user:1 name
"zwb"
127.0.0.1:6379[1]> HGET user:1 age
"20"
ZSet(有序集合)
结构:key score1-value1,score2-value2,score3-value3
有序集合底层数据结构是跳跃链表
使用场景: 带权重的集合,统计班级成绩,工资
zadd 往有序集合中添加元素
XX: 仅仅更新存在的成员,不添加新成员。
NX: 不更新存在的成员。只添加新成员。
CH: 修改返回值为发生变化的成员总数,原始是返回新添加成员的总数 (CH 是 changed 的意思)。更改的元素是新添加的成员,已经存在的成员更新分数。 所以在命令中指定的成员有相同的分数将不被计算在内。注:在通常情况下,ZADD返回值只计算新添加成员的数量。
INCR: 当ZADD指定这个选项时,成员的操作就等同ZINCRBY命令,对成员的分数进行递增操作。
127.0.0.1:6379[1]> ZADD zset 8000 zwb
(integer) 1
127.0.0.1:6379[1]> ZADD zset 5000 A 6000 B
(integer) 2
zrangebyscore 根据score取出集合中的范围值
127.0.0.1:6379[1]> ZRANGEBYSCORE zset -inf +inf withscores #-inf 负无穷 +inf 正无穷
1) "A"
2) "5000"
3) "B"
4) "6000"
5) "zwb"
6) "8000"
zrange 根据索引取出集合中的范围值(从小到大)
127.0.0.1:6379[1]> zrange zset 0 -1
1) "A"
2) "B"
3) "zwb"
zrevrange 根据索引反转取出集合中的范围值(从大到小)
127.0.0.1:6379[1]> ZREVRANGE zset 0 -1
1) "zwb"
2) "A"
zrem 删除指定集合中的元素
127.0.0.1:6379[1]> ZREM zset B
(integer) 1
127.0.0.1:6379[1]> zrange zset 0 -1
1) "A"
2) "zwb"
zcard 获取集合中的元素个数
127.0.0.1:6379[1]> ZCARD zset
(integer) 2
zcount 获取集合中指定score范围的个数
127.0.0.1:6379[1]> ZREVRANGE zset 0 -1 withscores
1) "D"
2) "10000"
3) "zwb"
4) "8000"
5) "A"
6) "5000"
7) "C"
8) "4000"
9) "B"
10) "4000"
127.0.0.1:6379[1]> ZCOUNT zset 6000 10000
(integer) 2
三大特殊数据类型
geospatial 地理位置
底层用了ZSet,因此可以用ZSet的命令进行操作
使用场景: 定位,附近的人
geoadd 添加地理位置信息到key中
127.0.0.1:6379[1]> GEOADD china:city 104.065735 30.659462 chengdu
(integer) 1
geopos 从key中获得地理位置
127.0.0.1:6379[1]> GEOPOS china:city chengdu
1) 1) "104.06573742628097534"
2) "30.65946118872339099"
geodist 获得两个地理位置之间的距离
127.0.0.1:6379[1]> GEODIST china:city chengdu foshan km
"1235.2189"
georadius 获得当前点的指定半径内的地理位置
127.0.0.1:6379[1]> GEORADIUS china:city 110 30 1000 km withdist withcoord count 1
1) 1) "chengdu"
2) "574.3396"
3) 1) "104.06573742628097534"
2) "30.65946118872339099"
georadiusbymember 获得当前成员地理位置的指定半径内的地理位置
127.0.0.1:6379[1]> GEORADIUSBYMEMBER china:city chengdu 1000 km withdist withcoord
1) 1) "chengdu"
2) "0.0000"
3) 1) "104.06573742628097534"
2) "30.65946118872339099"
geohash 获得地理位置的11位geohash字符串
127.0.0.1:6379[1]> GEOHASH china:city chengdu foshan
1) "wm6n2j6k730"
2) "ws07n0m2q20"
Hyperloglog 基数统计
HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
hyperloglog不支持删除操作,因为无法判断已标记的二进制位是哪个元素生成
使用场景: 统计网页的UV(unique view:一个人访问多次但只算作一个人)
pfadd 添加任意元素到基数统计中
127.0.0.1:6379[1]> PFADD UV1 A B C D E
(integer) 1
127.0.0.1:6379[1]> PFADD UV2 C D F G H C
(integer) 1
pfcount 统计基数统计中的元素个数
127.0.0.1:6379[1]> PFCOUNT UV1
(integer) 5
127.0.0.1:6379[1]> PFCOUNT UV2
(integer) 5
pfmerge 合并多个基数统计中的值(并集)
127.0.0.1:6379[1]> PFMERGE UV3 UV1 UV2
OK
127.0.0.1:6379[1]> PFCOUNT UV3
(integer) 8
Bitmaps 位图
数据结构是操作二进制位进行记录,只有0和1两个状态,实质是string
使用场景: 保存两种状态的,比如是否登录,是否打卡,是否活跃
setbit 设置位图中的指定偏移量的位
模拟一周打卡
127.0.0.1:6379[1]> SETBIT sign 1 0
(integer) 0
127.0.0.1:6379[1]> SETBIT sign 2 0
(integer) 0
127.0.0.1:6379[1]> SETBIT sign 3 1
(integer) 0
127.0.0.1:6379[1]> SETBIT sign 4 1
(integer) 0
127.0.0.1:6379[1]> SETBIT sign 5 1
(integer) 0
127.0.0.1:6379[1]> SETBIT sign 6 1
(integer) 0
127.0.0.1:6379[1]> SETBIT sign 7 0
(integer) 0
getbit 获得位图中的指定偏移量的位
127.0.0.1:6379[1]> GETBIT sign 4
(integer) 1
127.0.0.1:6379[1]> GETBIT sign 7
(integer) 0
bitcount 统计位图中1位的个数
127.0.0.1:6379[1]> BITCOUNT sign
(integer) 4
redis事务
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
- 开始事务
- 命令入队
- 执行事务
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
multi 开启事务和exec执行事务
每个事务只能执行一次
127.0.0.1:6379 > MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED #命令入队
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) OK
3) "v2"
4) OK
discard 取消事务
127.0.0.1:6379 > MULTI
OK
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK
127.0.0.1:6379 > get k4
(nil)
编译时异常(所有命令都不会执行)
127.0.0.1:6379 > MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常(只有异常的命令不会执行)
127.0.0.1:6379 > MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> INCR k1
QUEUED
127.0.0.1:6379(TX)> mget k1 k2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range
4) 1) "v1"
2) "v2"
watch 监控key值
如果在事务执行之前key被其他命令所改动,那么事务将被打断(乐观锁,比较version值,如果被更改则失败)
127.0.0.1:6379[1]> set money 1000
OK
127.0.0.1:6379[1]> set out 0
OK
127.0.0.1:6379[1]> WATCH money
OK
127.0.0.1:6379[1]> MULTI
OK
127.0.0.1:6379[1](TX)> DECRBY money 50
QUEUED
127.0.0.1:6379[1](TX)> INCRBY out 50
QUEUED
#在这时事务没有被执行,money值在其它客户端被其他命令进行更改
127.0.0.1:6379[1]> INCRBY money 1000
(integer) 2000
#这时执行命令,发现监控的money值被更改,事务执行失败
127.0.0.1:6379[1](TX)> EXEC
(nil)
exec和discard自动解锁,因此需要重新上锁
unwatch 取消监控key值
127.0.0.1:6379[1]> WATCH money
OK
127.0.0.1:6379[1]> UNWATCH
OK
127.0.0.1:6379[1]> MULTI
OK
127.0.0.1:6379[1](TX)> INCRBY money 1000
QUEUED
127.0.0.1:6379[1](TX)> EXEC
1) (integer) 2000
Jedis
连接服务器的redis
1、开放linux防火墙端口6379和阿里云安全组端口6379
2、设置redis服务的配置文件
redis配置文件设置
设置完配置文件后记得重启redis服务
3、添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
4、测试连接
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("阿里云公网ip",6379);
jedis.auth("123456");
System.out.println(jedis.ping());
}
}
Jedis事务
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("端口号",6379);
jedis.auth("123456");
//开启事务
Transaction multi = jedis.multi();
JSONObject jsonObject = new JSONObject();
jsonObject.put("name","zwb");
jsonObject.put("age","20");
String string = jsonObject.toJSONString();
//监控name值
//jedis.watch("name");
try {
multi.set("user1",string);
//会产生运行时错误,但不影响其他语句执行
multi.incr("user1");
multi.exec();
} catch (Exception e) {
//产生编译时异常时,关闭事务
multi.discard();
e.printStackTrace();
} finally {
//执行事务后测试值
System.out.println(jedis.get("user1"));
jedis.flushAll();
jedis.close();
}
}
}
springboot整合redis
1、设置application.properties
spring.redis.host=端口号
spring.redis.port=6379
spring.redis.password=123456
2、测试
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() throws JsonProcessingException {
User user1 = new User("zwb",20);
User user2 = new User("A",18);
//将对象序列化为string类型
String s1 = new ObjectMapper().writeValueAsString(user1);
String s2 = new ObjectMapper().writeValueAsString(user2);
redisTemplate.opsForValue().set("user1",s1);
redisTemplate.opsForValue().set("user2",s2);
System.out.println(redisTemplate.opsForValue().get("user1"));
System.out.println(redisTemplate.opsForValue().get("user2"));
}
}
redis.conf详解
单位
#Note on units: when memory size is needed, it is possible to specify
#it in the usual form of 1k 5GB 4M and so forth:
#1k => 1000 bytes
#1kb => 1024 bytes
#1m => 1000000 bytes
#1mb => 10241024 bytes
#1g => 1000000000 bytes
#1gb => 10241024*1024 bytes
单位不区分大小写
#units are case insensitive so 1GB 1Gb 1gB are all the same.
INCLUDE
使用多个配置文件
#include /path/to/local.conf
#include /path/to/other.conf
NETWORK
#绑定ip
#bind 127.0.0.1
#保护模式
protected-mode no
GENERAL
#守护进程的方式运行(后台运行)
daemonize yes
#后台方式运行需要绑定pid文件
pidfile /www/server/redis/redis.pid
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
#日志输出级别
loglevel notice
#日志文件名
logfile "/www/server/redis/redis.log"
#数据库大小
databases 16
SNAPSHOTTING
#900s内如果有1个以上key进行修改,进行持久化
save 900 1
#300s内如果有10个以上key进行修改,进行持久化
save 300 10
#60s内如果有10000个以上key进行修改,进行持久化
save 60 10000
#如果持久化出错,是否停止写入
stop-writes-on-bgsave-error yes
#是否压缩rdb文件(消耗cpu资源)
rdbcompression yes
#保存rdb文件时是否检查文件
rdbchecksum yes
#保存rdb文件的名字
dbfilename dump.rdb
#文件保存目录
dir /www/server/redis/
REPLICATION
SECURITY
#设置密码
#requirepass foobared
CLIENTS
#最大客户端连接数
# maxclients 10000
#redis最大内存
# maxmemory <bytes>
#内存达到上限后的处理策略
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key among the ones with an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.
#
# LRU means Least Recently Used
# LFU means Least Frequently Used
#
# Both LRU, LFU and volatile-ttl are implemented using approximated
# randomized algorithms.
#
# Note: with any of the above policies, Redis will return an error on write
# operations, when there are no suitable keys for eviction.
#
# At the date of writing these commands are: set setnx setex append
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
# getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy noeviction
APPEND ONLY MODE
#是否开始aof模式,默认使用rdb
appendonly no
#保存aof文件的名字
appendfilename "appendonly.aof"
#每次修改都会同步
# appendfsync always
#每秒执行同步
appendfsync everysec
#不执行同步
# appendfsync no
redis持久化
RDB(Redis DataBase)
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
触发规则
1、主动Save或Bgsave(Backgroundsave)达到配置文件中被动触发规则
- SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。
- BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。 Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求。
Save是阻塞方式的;bgsave是非阻塞方式的。
2、执行flushall或flushdb
3、正常退出redis
优点
- Redis只包含一个文件,灾难恢复非常容易,并且可以十分轻松地进行备份
- 性能高,只需fork出子进程,让子进程完成持久化操作,可以避免服务进程(主进程)降低效率去进行IO操作
- 相比于AOF机制,如果数据集很大,RDB的启动效率会更高
缺点
- 系统在定时持久化的时候出现宕机的情况,最后一次持久化之后未保存的数据将会丢失,无法最大程度的避免数据丢失
- 如果当数据集较大时,可能会导致整个服务器停止服务
AOF(AppendOnlyFile)
AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
同时启用的话,redis服务器重启时优先加载AOF文件(安全性高),主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。
优点
- 带来了更高的数据安全性,可以保证每次数据的安全,如果产生宕机最多一秒内的数据丢失
- 对日志文件的写入采用append模式,因此出现宕机不会破坏日志文件已存在的内容
- AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作,导出(export) AOF 文件也非常简单:如果不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到FLUSHALL 执行之前的状态
缺点
- 在相同数量的数据集情况下,AOF文件通常大于RDB文件,RDB恢复大数据集时速度比AOF快
- AOF运行时效率比RDB低
redis发布订阅
使用场景:
1、实时消息系统
2、订阅关注
此时客户端client2、client5、client1订阅了频道channel1
对频道channel1发布消息,channel1将消息发送给订阅的三个客户端
subscribe订阅频道后,redis维护了字典,字典的键为各个频道,字典的值为一个订阅当前频道的订阅者链表,subscribe命令就是将客户端添加到频道的链表中。
publish向订阅者发布消息时,redis会以当前频道作为键,在维护的字典中查找当前频道的所有订阅者的链表,遍历该链表,将消息发布给所有客户端
为什么在 redis-cli 中使用了 subscribe 命令之后无法再执行 unsubscribe 等的命令
subscribe和publish 订阅和发布消息
A客户端上同一服务(订阅频道)
SUBSCRIBE zwb A B
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "zwb"
3) (integer) 1
1) "subscribe"
2) "A"
3) (integer) 2
1) "subscribe"
2) "B"
3) (integer) 3
1) "message"
2) "zwb"
3) "hello world!"
B客户端上同一服务(发布消息)
127.0.0.1:6379> PUBLISH zwb "hello world!"
(integer) 1
psubscribe 以*作为匹配符的模式进行订阅
A客户端同一服务(订阅频道)
127.0.0.1:6379 > PSUBSCRIBE zw*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "zw*"
3) (integer) 1
1) "pmessage"
2) "zw*"
3) "zwb"
4) "hello zwb"
B客户端上同一服务(发布消息)
127.0.0.1:6379 > PUBLISH zwb "hello zwb"
(integer) 1
redis主从复制
指将一台redis服务器数据复制到其他redis服务器,前者为主节点(master/leader),后者为从节点(slave/follower),数据复制为单向,只能从主节点到从节点。主节点以写为主,从节点以读为主。
默认每台redis服务器都是主节点,一个主节点可以有多个从节点,一个从节点只能有一个主节点。
主从复制的作用:
- 数据冗余:实现了热备份,持久化之外的另一种数据冗余的方式
- 故障恢复:主节点出现问题,可以让从节点代替主节点,实现快速故障恢复,也是服务冗余
- 负载均衡:实现了读写分离,由主节点提供写服务,从节点提供读服务,分担服务器负载。尤其读多写少的场景下,多个从节点可以分担读负载,大大提高redis服务器的并发量
- 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案
工作流程
建立连接
数据同步
命令传播
总结
心跳机制
常见问题
频繁的全量复制
频繁的网络中断
数据不一致
复制原理
从机启动成功连接到主机后会发送sync同步命令,主机接到命令后启动存盘进程,同时收集所有修改数据集命令,在后台进程执行完毕后,主机将传送整个数据文件到从机,并完成一次全量复制。
全量复制:从机在接收到数据文件后,将其存盘并加载到内存中。
增量复制:主机继续将收集到的修改命令依次传给从机,完成同步
从机重新连接到主机,一次全量复制将自动执行
slaveof 将当前主机设置为指定地址指定端口号的从机
127.0.0.1:6380 > SLAVEOF 127.0.0.1 6379
OK
info replication 获得当前主机的主从信息
主机6379信息
127.0.0.1:6379 > info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=76437,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=76437,lag=0
master_failover_state:no-failover
master_replid:bcae5c3226a40515bff5f6ef3dc647209247a663
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:76437
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:76437
从机6380信息
127.0.0.1:6380 > info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:76437
slave_priority:100
slave_read_only:1
connected_slaves:0
master_failover_state:no-failover
master_replid:bcae5c3226a40515bff5f6ef3dc647209247a663
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:76437
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:76437
slaveof no one 当前从机断开与主机的连接
如果主机断开了连接,可以用slaveof no one让自己变成主机,其他节点手动连接最新的主节点。
127.0.0.1:6380 > SLAVEOF no one
OK
127.0.0.1:6380 > info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:a8f9688e665b120d808b92060a51669d8e32b308
master_replid2:bcae5c3226a40515bff5f6ef3dc647209247a663
master_repl_offset:148089
second_repl_offset:148090
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:148089
哨兵模式
Redis哨兵的详解
哨兵的作用:
- 集群监控:负责监控redis master和slave进程是否正常工作
- 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
- 故障转移:如果master node挂掉了,会自动转移到slave node上
- 配置中心:如果故障转移发生了,通知client客户端新的master地址
后台监控主机是否断连,当主服务器宕机后,根据投票数自动将从机转换为主机。
哨兵是独立的进程,哨兵向redis服务器发送命令,等待redis服务器响应,从而监控运行的多个redis实例
当哨兵检测到主机宕机,自动投票选取一个从机变成主机,然后通过发布订阅模式通知其他从机修改配置文件,让其他从机切换主机。
一般使用多个哨兵进行监控,各个哨兵之间也会进行监控,形成多哨兵模式
如果主机宕机,哨兵1先检测到,系统并不会马上failover,仅仅是哨兵1主观认为主机不可用,称之为主观下线。当其余哨兵也检测到主机不可用,并数量达到设定的值,哨兵会进行投票,投票结果由一个哨兵发起,进行failover(故障转移)。切换成功后,通过发布订阅模式,让每个哨兵把自己监控的从机切换成主机,称之为客观下线
工作原理
监控
通知
故障转移
挑选哨兵leader
让哨兵leader选择从机当选主机,以挑选原则进行筛选抉择
sentinel.conf详解
#当至少quorum个哨兵监测到主机下线时,进行failover
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6380 1
#主机需要在多少时间后才会被哨兵主动认为不可用,默认30s
sentinel down-after-milliseconds <master-name> <milliseconds>
#指定了在发生failover主从切换时最多可以有多少个从机同时对新的主机进行同步,如果并发数量过多,会导致更多的从机因为同步而不可用
sentinel parallel-syncs <master-name> <numreplicas>
#failover-timeout 可以用在以下这些方面:
1. 同一个sentinel对同一个master两次failover之间的间隔时间。
2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
3.当想要取消一个正在进行的failover所需要的时间。
4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了。
#默认为3min
sentinel failover-timeout <master-name> <milliseconds>
启动哨兵
如果主从机中有密码,需要在从机redis.conf中配置主机密码和在sentinel.conf中配置主从机密码
在sentinel.conf文件配置(设置哨兵连接主机和从机)
sentinel auth-pass mymaster 密码
在从机的redis.conf中配置主机的密码(设置从机连接主机)
masterauth 密码
当前三台服务器结构
成功连接主机和从机
[root@iZwz99986fr8n033m5btqnZ redis]# redis-sentinel sentinel.conf
29999:X 13 Jun 2021 16:58:53.472 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
29999:X 13 Jun 2021 16:58:53.472 # Redis version=6.2.1, bits=64, commit=00000000, modified=0, pid=29999, just started
29999:X 13 Jun 2021 16:58:53.472 # Configuration loaded
29999:X 13 Jun 2021 16:58:53.473 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.1 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 29999
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
29999:X 13 Jun 2021 16:58:53.476 # Sentinel ID is 8a1e6784906f54fff8f334f20cd678e6c399b7f7
#监控主机 quorum确认客观下线的最少哨兵数为1
29999:X 13 Jun 2021 16:58:53.476 # +monitor master mymaster 127.0.0.1 6379 quorum 1
#监控到从机为6380 其主机为6379
29999:X 13 Jun 2021 16:58:53.477 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
故障转移
当6379断连时,进行故障转移,将6380设置为主机
[root@iZwz99986fr8n033m5btqnZ redis]# redis-sentinel sentinel.conf
29999:X 13 Jun 2021 16:58:53.472 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
29999:X 13 Jun 2021 16:58:53.472 # Redis version=6.2.1, bits=64, commit=00000000, modified=0, pid=29999, just started
29999:X 13 Jun 2021 16:58:53.472 # Configuration loaded
29999:X 13 Jun 2021 16:58:53.473 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.1 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 29999
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
29999:X 13 Jun 2021 16:58:53.476 # Sentinel ID is 8a1e6784906f54fff8f334f20cd678e6c399b7f7
29999:X 13 Jun 2021 16:58:53.476 # +monitor master mymaster 127.0.0.1 6379 quorum 1
29999:X 13 Jun 2021 16:58:53.477 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
#监测到主机6379主观下线
29999:X 13 Jun 2021 17:04:48.071 # +sdown master mymaster 127.0.0.1 6379
#监测到主机6379客观下线,因为达到quorum客观下线的哨兵数
29999:X 13 Jun 2021 17:04:48.071 # +odown master mymaster 127.0.0.1 6379 #quorum 1/1
#epoch纪元被更新,epoch用于给事件增加版本号(相当于投票次数)
29999:X 13 Jun 2021 17:04:48.071 # +new-epoch 1
#尝试故障转移
29999:X 13 Jun 2021 17:04:48.071 # +try-failover master mymaster 127.0.0.1 6379
#投票选举哨兵的leader
29999:X 13 Jun 2021 17:04:48.074 # +vote-for-leader 8a1e6784906f54fff8f334f20cd678e6c399b7f7 1
#已选举出哨兵leader
29999:X 13 Jun 2021 17:04:48.074 # +elected-leader master mymaster 127.0.0.1 6379
#选择6379主机下可用的从机
29999:X 13 Jun 2021 17:04:48.074 # +failover-state-select-slave master mymaster 127.0.0.1 6379
#选择到从机6380,是6379的从机
29999:X 13 Jun 2021 17:04:48.157 # +selected-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
#向从机6380发送slaveof no one命令,让6380不是从机,成为主机
29999:X 13 Jun 2021 17:04:48.157 * +failover-state-send-slaveof-noone slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
#等待从机晋升为主机
29999:X 13 Jun 2021 17:04:48.257 * +failover-state-wait-promotion slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
#从机6380成功晋升为主机
29999:X 13 Jun 2021 17:04:48.635 # +promoted-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
#对原主机6379的从机重新配置(场景下6379只有6380为从机,因此不用重新配置)
29999:X 13 Jun 2021 17:04:48.636 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6379
#对6379故障转移结束
29999:X 13 Jun 2021 17:04:48.685 # +failover-end master mymaster 127.0.0.1 6379
#原主机6379和现主机6380切换,让原主机6379变为6380的从机
29999:X 13 Jun 2021 17:04:48.685 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6380
#6380增加从机6379
29999:X 13 Jun 2021 17:04:48.685 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380
#6380增加主机6381
29999:X 13 Jun 2021 17:04:49.192 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
#监测到从机6379主观下线
29999:X 13 Jun 2021 17:05:18.753 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380
#此时6379重连
29999:X 13 Jun 2021 17:47:02.993 # -sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380
#6379已转变为6380的从机
29999:X 13 Jun 2021 17:47:12.933 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380
6379的主从信息
127.0.0.1:6379 > info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:235991
slave_priority:100
slave_read_only:1
connected_slaves:0
master_failover_state:no-failover
master_replid:f9e2117f537d8d94006700c06724dba66bb401bc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:235991
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:234065
repl_backlog_histlen:1927
6380的主从信息
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=236656,lag=0
slave1:ip=127.0.0.1,port=6379,state=online,offset=236656,lag=0
master_failover_state:no-failover
master_replid:f9e2117f537d8d94006700c06724dba66bb401bc
master_replid2:d51147c500ee4ce413195e524416d926ac150d37
master_repl_offset:236656
second_repl_offset:22641
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:236656
redis集群
集群结构设计
数据存储设计
内部通讯设计
最多两次即可命中数据库
搭建集群
Not all 16384 slots are covered by nodes
#cli集成的fix方法
redis-cli --cluster fix 127.0.0.1:6379
Node is not empty. Either the node already knows other nodes
redis6把ruby集成在cli,可以不用ruby进行搭建
集群数量至少要6个以上,因为主机数量必须为3个以上,如果每个主机都只有一个从机,那么需要6个服务器以上。
1、修改redis.conf配置
#开启cluster集群
cluster-enabled yes
#配置文件名字
cluster-config-file nodes-6379.conf
#节点无法访问被视为故障的毫秒数
cluster-node-timeout 15000
2、开启所有redis服务
[root@iZwz99986fr8n033m5btqnZ redis]# ps -ef|grep redis
root 17413 1 0 16:09 ? 00:00:04 redis-server *:6379 [cluster]
root 18050 1 0 16:14 ? 00:00:04 redis-server *:6380 [cluster]
root 18060 1 0 16:14 ? 00:00:04 redis-server *:6382 [cluster]
root 18739 1 0 16:20 ? 00:00:03 redis-server *:6381 [cluster]
root 18748 1 0 16:20 ? 00:00:04 redis-server *:6383 [cluster]
root 18966 1 0 16:22 ? 00:00:03 redis-server *:6384 [cluster]
root 21465 16310 0 16:45 pts/2 00:00:00 grep --color=auto redis
3、开启cluster集群
#redis6版本不需要ruby,直接在cli中搭建集群
#--cluster create搭建集群输入ip端口号
#--cluster-replicas集群中主机的从机数量
redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 --cluster-replicas 1 -a 密码
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:6383 to 127.0.0.1:6379
Adding replica 127.0.0.1:6384 to 127.0.0.1:6380
Adding replica 127.0.0.1:6382 to 127.0.0.1:6381
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 8f05addc7e369d5d03e3beb9964603ec486e4390 127.0.0.1:6379
slots:[0-5460] (5461 slots) master
M: 9c9b9c1a663a16b5d9cdb070a5876b343df195d0 127.0.0.1:6380
slots:[5461-10922] (5462 slots) master
M: d3e531486f42af8df14cfac926ba87442b270c7a 127.0.0.1:6381
slots:[10923-16383] (5461 slots) master
S: 6fbbe5c44b94b8c477dfbb60ad249b5c81fe5bd7 127.0.0.1:6382
replicates 9c9b9c1a663a16b5d9cdb070a5876b343df195d0
S: 17e277d0b839ae0a7eace9ec8e2030a56313bd3d 127.0.0.1:6383
replicates d3e531486f42af8df14cfac926ba87442b270c7a
S: 3669417a606673067ae338877fda119b71813e8d 127.0.0.1:6384
replicates 8f05addc7e369d5d03e3beb9964603ec486e4390
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: 8f05addc7e369d5d03e3beb9964603ec486e4390 127.0.0.1:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 17e277d0b839ae0a7eace9ec8e2030a56313bd3d 127.0.0.1:6383
slots: (0 slots) slave
replicates d3e531486f42af8df14cfac926ba87442b270c7a
S: 3669417a606673067ae338877fda119b71813e8d 127.0.0.1:6384
slots: (0 slots) slave
replicates 8f05addc7e369d5d03e3beb9964603ec486e4390
M: 9c9b9c1a663a16b5d9cdb070a5876b343df195d0 127.0.0.1:6380
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: 6fbbe5c44b94b8c477dfbb60ad249b5c81fe5bd7 127.0.0.1:6382
slots: (0 slots) slave
replicates 9c9b9c1a663a16b5d9cdb070a5876b343df195d0
M: d3e531486f42af8df14cfac926ba87442b270c7a 127.0.0.1:6381
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
4、查看nodes结点信息
显示各个主机的槽和从机信息
[root@iZwz99986fr8n033m5btqnZ redis]# cat nodes-6379.conf
17e277d0b839ae0a7eace9ec8e2030a56313bd3d 127.0.0.1:6383@16383 slave d3e531486f42af8df14cfac926ba87442b270c7a 0 1623745372000 3 connected
3669417a606673067ae338877fda119b71813e8d 127.0.0.1:6384@16384 slave 8f05addc7e369d5d03e3beb9964603ec486e4390 0 1623745372000 1 connected
9c9b9c1a663a16b5d9cdb070a5876b343df195d0 127.0.0.1:6380@16380 master - 0 1623745373000 2 connected 5461-10922
6fbbe5c44b94b8c477dfbb60ad249b5c81fe5bd7 127.0.0.1:6382@16382 slave 9c9b9c1a663a16b5d9cdb070a5876b343df195d0 0 1623745374349 2 connected
8f05addc7e369d5d03e3beb9964603ec486e4390 127.0.0.1:6379@16379 myself,master - 0 1623745371000 1 connected 0-5460
d3e531486f42af8df14cfac926ba87442b270c7a 127.0.0.1:6381@16381 master - 0 1623745374000 3 connected 10923-16383
vars currentEpoch 6 lastVoteEpoch 0
集群set数据
[root@iZwz99986fr8n033m5btqnZ redis]# redis-cli -p 6379
127.0.0.1:6379 > set name zwb
#发现计算出来key值应在5798号槽,无法set当前6379号主机,提示该槽在6380
(error) MOVED 5798 127.0.0.1:6380
#-c代表操作集群
[root@iZwz99986fr8n033m5btqnZ redis]# redis-cli -p 6379 -c -a 密码
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379 > set name zwb
#根据计算出的槽号进行重定向到6380继续进行set
-> Redirected to slot [5798] located at 127.0.0.1:6380
OK
127.0.0.1:6380 > keys *
1) "name"
集群get数据
redis原生cluster中的slave不会处理读写请求,即便get操作,客户端也会收到重定向到master的指令(不进行读写分离,从机只做备份)
[root@iZwz99986fr8n033m5btqnZ redis]# redis-cli -p 6382 -c -a 密码
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6382 > keys *
1) "name"
127.0.0.1:6382 > get name
#从机只做备份,当读取该数据的时候,数据还是会重定向到主机进行读取
-> Redirected to slot [5798] located at 127.0.0.1:6380
"zwb"
集群主机下线和主从切换
127.0.0.1:6380 > CLUSTER nodes
6fbbe5c44b94b8c477dfbb60ad249b5c81fe5bd7 127.0.0.1:6382@16382 slave 9c9b9c1a663a16b5d9cdb070a5876b343df195d0 0 1623753044000 2 connected
17e277d0b839ae0a7eace9ec8e2030a56313bd3d 127.0.0.1:6383@16383 slave d3e531486f42af8df14cfac926ba87442b270c7a 0 1623753045408 3 connected
3669417a606673067ae338877fda119b71813e8d 127.0.0.1:6384@16384 slave 8f05addc7e369d5d03e3beb9964603ec486e4390 0 1623753046410 1 connected
9c9b9c1a663a16b5d9cdb070a5876b343df195d0 127.0.0.1:6380@16380 myself,master - 0 1623753044000 2 connected 5461-10922
8f05addc7e369d5d03e3beb9964603ec486e4390 127.0.0.1:6379@16379 master - 0 1623753045000 1 connected 0-5460
d3e531486f42af8df14cfac926ba87442b270c7a 127.0.0.1:6381@16381 master - 0 1623753044000 3 connected 10923-16383
127.0.0.1:6380 > SHUTDOWN
not connected> exit
#这是6380主机已下线
[root@iZwz99986fr8n033m5btqnZ redis]# redis-cli -p 6382 -c -a 密码
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6382 > CLUSTER NODES
8f05addc7e369d5d03e3beb9964603ec486e4390 127.0.0.1:6379@16379 master - 0 1623753334978 1 connected 0-5460
3669417a606673067ae338877fda119b71813e8d 127.0.0.1:6384@16384 slave 8f05addc7e369d5d03e3beb9964603ec486e4390 0 1623753332000 1 connected
#原主机的从机6382变为主机
6fbbe5c44b94b8c477dfbb60ad249b5c81fe5bd7 127.0.0.1:6382@16382 myself,master - 0 1623753332000 7 connected 5461-10922
d3e531486f42af8df14cfac926ba87442b270c7a 127.0.0.1:6381@16381 master - 0 1623753333000 3 connected 10923-16383
#发现原主机6380下线
9c9b9c1a663a16b5d9cdb070a5876b343df195d0 127.0.0.1:6380@16380 master,fail - 1623753303866 1623753297000 2 disconnected
17e277d0b839ae0a7eace9ec8e2030a56313bd3d 127.0.0.1:6383@16383 slave d3e531486f42af8df14cfac926ba87442b270c7a 0 1623753333975 3 connected
#此时原主机6380上线
127.0.0.1:6382 > CLUSTER NODES
8f05addc7e369d5d03e3beb9964603ec486e4390 127.0.0.1:6379@16379 master - 0 1623753379187 1 connected 0-5460
3669417a606673067ae338877fda119b71813e8d 127.0.0.1:6384@16384 slave 8f05addc7e369d5d03e3beb9964603ec486e4390 0 1623753376168 1 connected
6fbbe5c44b94b8c477dfbb60ad249b5c81fe5bd7 127.0.0.1:6382@16382 myself,master - 0 1623753376000 7 connected 5461-10922
d3e531486f42af8df14cfac926ba87442b270c7a 127.0.0.1:6381@16381 master - 0 1623753378179 3 connected 10923-16383
#发现原主机6380变为主机6382的从机
9c9b9c1a663a16b5d9cdb070a5876b343df195d0 127.0.0.1:6380@16380 slave 6fbbe5c44b94b8c477dfbb60ad249b5c81fe5bd7 0 1623753377171 7 connected
17e277d0b839ae0a7eace9ec8e2030a56313bd3d 127.0.0.1:6383@16383 slave d3e531486f42af8df14cfac926ba87442b270c7a 0 1623753378000 3 connected
redis缓存预热
redis缓存雪崩
大量key在一小段时间内集中过期,导致数据库查询压力过大
redis缓存击穿
某个key访问量非常大,key在redis中某个时间过期导致大量请求直接击中数据库
redis缓存穿透
请求数据库没有的数据,大量非正常请求直接穿过redis访问数据库
性能指标监控
监控指标
监控方式
热key问题
布隆过滤器
springboot整合docker的redis集群
docker run(仅供参考)
注意:需要配置docker的容器ip为本机ip 否则springboot无法映射到docker的redis集群
--net=host
docker run --name redis1 --restart always -p 6479:6479 -v /home/redis-docker:/usr/local/etc/redis -v /home/redis-docker/data:/redis --net=host -d redis
docker run --name redis2 --restart always -p 6480:6480 -v /home/redis-docker:/usr/local/etc/redis -v /home/redis-docker/data:/redis --net=host -d redis
docker run --name redis3 --restart always -p 6481:6481 -v /home/redis-docker:/usr/local/etc/redis -v /home/redis-docker/data:/redis --net=host -d redis
docker run --name redis4 --restart always -p 6482:6482 -v /home/redis-docker:/usr/local/etc/redis -v /home/redis-docker/data:/redis --net=host -d redis
docker run --name redis5 --restart always -p 6483:6483 -v /home/redis-docker:/usr/local/etc/redis -v /home/redis-docker/data:/redis --net=host -d redis
docker run --name redis6 --restart always -p 6484:6484 -v /home/redis-docker:/usr/local/etc/redis -v /home/redis-docker/data:/redis --net=host -d redis
sed脚本修改文本
sed "s/6379/6479/g" redis79.conf > ../redis-docker/redis79.conf
sed "s/6380/6480/g" redis80.conf > ../redis-docker/redis80.conf
sed "s/6381/6481/g" redis81.conf > ../redis-docker/redis81.conf
sed "s/6382/6482/g" redis82.conf > ../redis-docker/redis82.conf
sed "s/6383/6483/g" redis83.conf > ../redis-docker/redis83.conf
sed "s/6384/6484/g" redis84.conf > ../redis-docker/redis84.conf
前台新建终端执行docker容器
docker exec -it redis1 bash
docker exec -it redis2 bash
docker exec -it redis3 bash
docker exec -it redis4 bash
docker exec -it redis5 bash
docker exec -it redis6 bash
redis配置目录
cd /usr/local/etc/redis
redis服务启动
redis-server /usr/local/etc/redis/redis79.conf
redis-server /usr/local/etc/redis/redis80.conf
redis-server /usr/local/etc/redis/redis81.conf
redis-server /usr/local/etc/redis/redis82.conf
redis-server /usr/local/etc/redis/redis83.conf
redis-server /usr/local/etc/redis/redis84.conf
redis开启集群
redis集群 Waiting for the cluster to join 一直等待
集群需要开启集群总线端口
集群总线端口为redis客户端连接的端口 + 10000
redis-cli --cluster create 47.106.175.175:6479 47.106.175.175:6480 47.106.175.175:6481 47.106.175.175:6482 47.106.175.175:6483 47.106.175.175:6484 --cluster-replicas 1 -a 密码
springboot配置yml
redis:
host: 47.106.175.175
database: 0
port: 6479
password: 密码
timeout: 1000ms
cluster:
nodes:
- 47.106.175.175:6479
- 47.106.175.175:6480
- 47.106.175.175:6481
- 47.106.175.175:6482
- 47.106.175.175:6483
- 47.106.175.175:6484
max-redirects: 6