Redis高可用
客户端分片集群
•
Jedis客户端分片访问
•
一致性哈希算法
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 启动三个redis服务,使用 --net=host 方式,让容器直接使用主机的端口:6379,6380,6381
docker run -d --name redis6379 \
--net=host \
--restart=always \
redis
docker run -d --name redis6380 \
--net=host \
--restart=always \
redis redis-server --port 6380
docker run -d --name redis6381 \
--net=host \
--restart=always \
redis redis-server --port 6381
# 进入redis,测试数据操作
docker exec -it redis6379 redis-cli
set a aaaaaaaaaaaa
set b bbbbbbbb
keys *
get a
get b
flushall
Jedis依赖:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
// jedis分片API
// 服务器的地址列表
List<JedisShardInfo> list = new ArrayList<>();
list.add(new JedisShardInfo("192.168.64.140", 6379));
list.add(new JedisShardInfo("192.168.64.140", 6380));
list.add(new JedisShardInfo("192.168.64.140", 6381));
// 连接池的配置对象
GenericObjectPoolConfig cfg = new JedisPoolConfig();
// 新建分片连接池
ShardedJedisPool pool = new ShardedJedisPool(cfg, list);
// 从连接池获取数据操作工具
ShardedJedis j = pool.getResource();
// 向集群添加100条数据
for (int i = 0; i < 100; i++) {
j.set("k"+i, "v"+i);
}
pool.close();
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 进入服务器查看数据
docker exec -it redis6379 redis-cli
keys *
docker exec -it redis6380 redis-cli -p 6380
keys *
docker exec -it redis6381 redis-cli -p 6381
keys *
Jedis 分片客户端API,使用“一致性哈希”算法:
•
环形结构
•
2^32 -1个节点
•
所有物理服务器均匀分散到哈希环上
•
数据经过哈希运算落到一个节点上,从这个节点顺时针寻找物理服务器节点
主从+哨兵
主从集群
•
存放到主服务器的数据,会向从服务器复制来作为备份
•
如果主服务器宕机,可以访问从服务器的数据
•
读写分离
•
主服务器负责写入数据
•
从服务器负责读取数据
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 清理容器
docker rm -f $(docker ps -aq) --volumes
# 启动主服务器
docker run -d --name redis6379 \
--net host \
--restart=always \
redis
# 启动两个从服务器
docker run -d --name redis6380 \
--net host \
--restart=always \
redis redis-server --port 6380 \
--slaveof 192.168.64.140 6379
docker run -d --name redis6381 \
--net host \
--restart=always \
redis redis-server --port 6381 \
--slaveof 192.168.64.140 6379
# 查看日志
docker logs redis6379
docker logs redis6380
# 查看三个服务器的角色
docker exec -it redis6379 redis-cli
info replication
docker exec -it redis6380 redis-cli -p 6380
info replication
docker exec -it redis6381 redis-cli -p 6381
info replication
# 向主服务器添加数据
docker exec -it redis6379 redis-cli
set a aaaaaa
exit
# 在从服务器查看是否有数据
docker exec -it redis6380 redis-cli -p 6380
get a
exit
# 主机宕机
docker stop redis6379
# 查看从机日志
docker logs -f redis6380
# 在从机访问数据
docker exec -it redis6380 redis-cli -p 6380
info replication
get a
set b bbbbbbbbbbbbbbbbb
# 手动将redis6380提升成主机
docker exec -it redis6380 redis-cli -p 6380
slaveof no one
info replication
exit
# 手动设置redis6381作为redis6380的从机
docker exec -it redis6381 redis-cli -p 6381
slaveof 192.168.64.140 6380
info replication
exit
# 启动6379后,也作为从机连接6380
docker restart redis6379
docker exec -it redis6379 redis-cli
slaveof 192.168.64.140 6380
info replication
exit
哨兵服务
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 准备三个哨兵的配置文件
mkdir /opt/sentinel/
cd /opt/sentinel/
cat <<EOF >5000.conf
port 5000
sentinel monitor mymaster 192.168.64.140 6380 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
EOF
cat <<EOF >5001.conf
port 5001
sentinel monitor mymaster 192.168.64.140 6380 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
EOF
cat <<EOF >5002.conf
port 5002
sentinel monitor mymaster 192.168.64.140 6380 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
EOF
# 启动三个哨兵服务
docker run -d --name sentinel5000 \
-v /opt/sentinel/5000.conf:/sentinel.conf \
--net host \
--restart=always \
redis redis-sentinel /sentinel.conf
docker run -d --name sentinel5001 \
-v /opt/sentinel/5001.conf:/sentinel.conf \
--net host \
--restart=always \
redis redis-sentinel /sentinel.conf
docker run -d --name sentinel5002 \
-v /opt/sentinel/5002.conf:/sentinel.conf \
--net host \
--restart=always \
redis redis-sentinel /sentinel.conf
# 通过哨兵查看集群信息
docker exec -it sentinel5000 redis-cli -p 5000
sentinel master mymaster
sentinel slaves mymaster
sentinel sentinels mymaster
# 测试主机宕机,哨兵自动执行主从切换
docker stop redis6380
# 查看哨兵日志
docker logs sentinel5000
# 重新启动6380
docker start redis6380
docker logs sentinel5000
Jedis客户端:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
// 哨兵服务器地址列表
Set<String> set = new HashSet<>();
set.add("192.168.64.140:5000");
set.add("192.168.64.140:5001");
set.add("192.168.64.140:5002");
// 创建哨兵连接池
JedisSentinelPool pool = new JedisSentinelPool("mymaster", set);
// 从连接池取出数据操作工具对象
Jedis j = pool.getResource();
// 放100条数据
for (int i = 0; i < 100; i++) {
j.set("k" + i, "v" + i);
}
pool.close();
Redis Cluster集群
•
可以是多主多从结构
•
自带主从切换,不需要哨兵服务
•
使用“哈希槽”算法
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 清理容器
docker rm -f $(docker ps -aq) --volumes
# 清理无主的数据卷
docker volume prune -f
# 准备配置文件
mkdir /opt/redis
cd /opt/redis
mkdir 7000 7001 7002 7003 7004 7005
cat <<EOF >7000/redis.conf
port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF
cat <<EOF >7001/redis.conf
port 7001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF
cat <<EOF >7002/redis.conf
port 7002
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF
cat <<EOF >7003/redis.conf
port 7003
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF
cat <<EOF >7004/redis.conf
port 7004
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF
cat <<EOF >7005/redis.conf
port 7005
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF
# 启动6个redis服务
docker run -d --name redis7000 --restart=always \
--net host -v /opt/redis/7000/redis.conf:/redis.conf \
redis redis-server /redis.conf
docker run -d --name redis7001 --restart=always \
--net host -v /opt/redis/7001/redis.conf:/redis.conf \
redis redis-server /redis.conf
docker run -d --name redis7002 --restart=always \
--net host -v /opt/redis/7002/redis.conf:/redis.conf \
redis redis-server /redis.conf
docker run -d --name redis7003 --restart=always \
--net host -v /opt/redis/7003/redis.conf:/redis.conf \
redis redis-server /redis.conf
docker run -d --name redis7004 --restart=always \
--net host -v /opt/redis/7004/redis.conf:/redis.conf \
redis redis-server /redis.conf
docker run -d --name redis7005 --restart=always \
--net host -v /opt/redis/7005/redis.conf:/redis.conf \
redis redis-server /redis.conf
# 进入redis添加数据
# 组成集群之前,服务不能使用
docker exec -it redis7000 redis-cli -p 7000
set a aaaaaaaaaaa
# 组成集群
docker exec -it redis7000 redis-cli --cluster create \
192.168.64.140:7000 192.168.64.140:7001 \
192.168.64.140:7002 192.168.64.140:7003 \
192.168.64.140:7004 192.168.64.140:7005 \
--cluster-replicas 1
# 添加数据
docker exec -it redis7000 redis-cli -c -p 7000
set a aaaaaaaaaaaaaaaaaaaaaaaa
set b bbbbbbbbbbbbbbbbbbbbbbb
set c cccccccccccccccccccccccccccc
get a
get b
get c
docker exec -it redis7000 redis-cli -c -p 7000
keys *
docker exec -it redis7000 redis-cli -c -p 7001
keys *
docker exec -it redis7000 redis-cli -c -p 7002
keys *
CLUSTER KEYSLOT a
CLUSTER KEYSLOT b
CLUSTER KEYSLOT c
CLUSTER KEYSLOT {user}a
CLUSTER KEYSLOT {user}b
CLUSTER KEYSLOT {user}c
CLUSTER KEYSLOT {user}
mset x xxxxxx y yyyy z zzzzzzzz
mset {user}x xxxxxx {user}y yyyy {user}z zzzzzzzz
mget x y z
mget {user}x {user}y {user}z
主从切换、故障转移:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
docker stop redis7000
docker logs redis7004
docker exec -it redis7004 redis-cli -c -p 7004
info replication
docker stop redis7004
docker exec -it redis7001 redis-cli -c -p 7001
set d dddddddddddd
docker start redis7004
docker logs redis7004
docker start redis7000
docker logs redis7000
docker exec -it redis7001 redis-cli -c -p 7001
set d dddddddddddd
Jedis客户端API:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
Set<HostAndPort> set = new HashSet<>();
set.add(new HostAndPort("192.168.64.140", 7000));
set.add(new HostAndPort("192.168.64.140", 7001));
set.add(new HostAndPort("192.168.64.140", 7002));
set.add(new HostAndPort("192.168.64.140", 7003));
set.add(new HostAndPort("192.168.64.140", 7004));
set.add(new HostAndPort("192.168.64.140", 7005));
JedisCluster j = new JedisCluster(set);
for (int i = 0; i < 100; i++) {
j.set("k" + i, "v" + i);
}
j.close();
Key操作命令
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
docker rm -f $(docker ps -aq) --volumes
redis --help
redis -l
redis -lx
redis -c
docker exec -it redis7000 redis-cli -c -p 7000
set a aaaaaaaaaa
set b bbbbbbbbbbbbb
set c ccccccccccccc
set d dddddddddd
keys *
查询所有Key
exists a
Key是否存在
type a
对应的值的数据类型
del d
删除Key,等待从所有主从服务器都删除完成
unlink c
异步删除,主机删除后直接返回,不等待从机删除
expire a 10
设置过期时间(秒)
ttl a
查看过期时间
库操作
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
config get databases
查看数据库数量
select 1
切换库,不支持集群模式
dbsize
当前库中键的数量
flushdb
清空当前库
flushall
清空所有库
Redis 5种基本数据类型
•
String
•
List
•
Set
•
Hash
•
Zset
String
•
string可以做数字运算
•
string单个字符串最大存储512M(2^32 - 1)
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
docker exec -it redis7000 redis-cli -c -p 7000
set key value
--------------------------
set a aaaa
get key
--------------------------
get a
append key value
在末尾追加字符串
--------------------------
append a xxxxx
strlen key
字符串长度
--------------------------
strlen a
setnx key value
键不存在才设置, nx -- not exsits
--------------------------
setnx c cccccccccccccccc
setnx c xxxxxxxxxxx
set key value nx
nx -- not exsits
--------------------------
set c yyyyyyyyyyyyyy nx
incr key
递增,原子操作
-------------------------
set d 1
incr d
incr d
incr d
decr key
递减,原子操作
-------------------------
decr d
decr d
decr d
decr d
incrby key step
------------------------
incrby d 10000
decrby key step
------------------------
decrby d 3000
mset key value key value ......
----------------------------------
mset a aaaa b bbbbb c cccccc d ddddd e eeeee f fffff
mset {x}a aaaa {x}b bbbbb {x}c cccccc {x}d ddddd {x}e eeeee {x}f fffff
mget key key ......
----------------------------------
mget a b c d
mget {x}a {x}b {x}c {x}d
msetnx key value key value ......
所有的键都不存在才设置,有任意一个键存在全部都失败
----------------------------------
msetnx {x}g ggggg {x}h hhhhh {x}i iiiii
msetnx {x}i iiiii {x}j jjjjj {x}k kkkkk
getrange key start end
截取字符串,包含start和end
------------------------------------
set a abcdefghijklmn
getrange a 0 3
getrange a 9 -1 # 从下标9开始,到末尾最后一个字符
setrange key start value
替换子串,从start位置开始进行替换
------------------------------------
setrange a 5 ----
get a
expire key seconds
设置键的过期时间
-------------------------
set x xxxxxxxxxxxx
expire x 10
ttl key
查看过期时间
-------------------------
ttl x
setex key seconds value
添加数据,同时指定过期时间
set + expire
-------------------------
setex x 10 xxxxxxxxxxxx
ttl x
set key value EX|PX 时间
EX以秒为单位设置过期时间
PX以毫秒为单位设置过期时间
-------------------------
set c cccccccc EX 10
ttl c
set c cccccccc ex 10 nx
getset key value
取旧值,换新值
-------------------------
set a aaaaaaaaaaaa
getset a xxxxxxxxxxxxxx
get a
List
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 清空数据
get a
flushall
get b
flushall
get c
flushall
lpush/rpush key value value ......
左侧添加/右侧添加
-------------------------------------
lpush a 1 2 3
rpush a x y z
lrange key start end
从左侧按范围取数据
-1表示末尾
-----------------------------------
lrange a 0 -1
lpop/rpop key [弹出的数量]
从左侧或右侧弹出值
-----------------------------------
lpop a
rpop a
rpop a 2
rpoplpush from_key to_key
右侧弹出,再加入左侧
-----------------------------------
rpush {x}a 1 2 3 4 5 6 7 8 9
lrange {x}a 0 -1
rpoplpush {x}a {x}b
rpoplpush {x}a {x}b
rpoplpush {x}a {x}b
lrange {x}a 0 -1
lrange {x}b 0 -1
rpoplpush {x}a {x}a
rpoplpush {x}a {x}a
rpoplpush {x}a {x}a
lindex key index
按下标取数据
------------------------------
lindex {x}a 0
lindex {x}a 1
lindex {x}a 2
lindex {x}a 3
llen
列表长度
------------------------------
llen {x}a
linsert key before/after value new_value
在指定的的值的前后添加数据
------------------------------
linsert {x}a before 5 x
lrange {x}a 0 -1
lrem key n value
从左侧找到n个给定的值删掉,n是0表示删除全部指定的值
------------------------------
lrem {x}a 2 6
lset key index new_value
替换index位置
------------------------------
lset {x}a 3 /
lrange {x}a 0 -1
Set
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 清空数据
get a
flushall
get b
flushall
get c
flushall
sadd key value value .....
-------------------------------
sadd a 11 22 33 44 55 11 22 33 66 77
smembers key
取所有值
-------------------------------
smembers a
sismember key value
值是否在Set中存在
-------------------------------
sismember a 33
scard key
元素数量
-------------------------------
scard a
srem key value value ......
删除值
-------------------------------
srem a 33 44
smembers a
spop key [count]
随机弹出
-------------------------------
spop a
spop a 4
srandmember key n
随机访问n个值(不会删除)
-------------------------------
srandmember a 2
smove from to value
把值从一个集合移动到另一个集合
-------------------------------
sadd {x}a 11 22 33 44 55 66
smove {x}a {x}b 55
smembers {x}a
smembers {x}b
sinter key key ......
多个Set集合的交集
sunion key key ......
并集
sdiff key key ......
差集
-------------------------------
sadd {x}s1 11 22 33 44
sadd {x}s2 22 44 66 88
sinter {x}s1 {x}s2
sunion {x}s1 {x}s2
sdiff {x}s1 {x}s2
Hash
•
多数情况下直接使用redis存放兼职数据
•
如果多个属性的对象中,有的属性要频繁修改,可以考虑使用Hash
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 清空数据
get a
flushall
get b
flushall
get c
flushall
hset key field value field value ......
--------------------------------------------
hset a k1 v1 k2 v2 k3 v3
hget key field
--------------------------------------------
hget a k1
hget a k2
hget a k3
hexists key field
--------------------------------------------
hexists a k1
hexists a k9
hkyes key
列出所有field
--------------------------------------------
hkeys a
hvals key
列出所有value
--------------------------------------------
hvals a
hincrby key field increment
在数字上加一个值
--------------------------------------------
hset a k4 1
hincrby a k4 10
hget a k4
hsetnx key field value
不存在才添加
--------------------------------------------
hsetnx a k1 xxxxxx
Zset
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 清空数据
get a
flushall
get b
flushall
get c
flushall
zadd key score value score value ......
---------------------------------------------
zadd a 23234 aaaa 5645 bbbb 23125345 cccc 3 ddd 6456 eee
zadd a 8 ddd
zrange key start end
取数据,end用-1表示末尾
---------------------------------------------
zrange a 0 -1
zrange key start end withscores
取数据时同时返回得分
---------------------------------------------
zrange a 0 -1 withscores
zrevrange key start end [withscores]
按得分从高到低排序
---------------------------------------------
zrevrange a 0 -1 withscores
zrevrange a 0 2 withscores
zrangebyscore key min max [withscores] [limit start count]
按分值范围取数据
---------------------------------------------
zrangebyscore a 1000 10000 withscores
zrangebyscore a 0 100000000 withscores limit 0 3
zrangebyscore a 0 100000000 withscores limit 3 3
zrevrangebyscore key min max [withscores] [limit start count]
按得分从高到低排序
--------------------------------------------
zrevrangebyscore a 10000 1000 withscores
zrevrangebyscore a 100000000 0 withscores limit 0 3
zrevrangebyscore a 100000000 0 withscores limit 3 3
zincrby key increment member
增加积分
----------------------------------------
zincrby a 1 ddd
zincrby a -1 ddd
zrem key member member ......
删除
----------------------------------------
zrem a cccc aaaa
zcount key min max
统计分数区间内的数据量
----------------------------------------
zcount a 0 1000
zrank key member
该数据的排名(从小到大)
----------------------------------------
zrank a eee
zrevrank key member
该数据的排名(从大到小)
----------------------------------------
zrevrank a eee
Bitmaps
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
setbit key offset value
只能设置0和1
--------------------------------------
setbit active-users-20220720 7457634 1
setbit active-users-20220720 4356462 1
setbit active-users-20220720 6753454 1
setbit active-users-20220720 2353464 1
setbit active-users-20220720 43526 1
setbit active-users-20220720 567345623 1
setbit active-users-20220720 452364 1
setbit active-users-20220720 1236743 1
setbit active-users-20220720 45463526 1
setbit active-users-20220720 57545 1
setbit active-users-20220720 67457432 1
setbit active-users-20220720 4280000000 1
bitcount key [start end]
值是1的数据量
start, end 指的是哪个字节,不是位
--------------------------------------------------
bitcount active-users-20220720 0 535000000
bitcount active-users-20220720 0 -1
getbit key offset
--------------------------------------------------
getbit active-users-20220720 1236743
getbit active-users-20220720 4356462
getbit active-users-20220720 57545
bitop operation destnation_key key key ......
对多个 bitmap 进行运算,运算结果保存到目标对象
operation
and
or
not
xor
HypperLogLog
基数统计:使用更小的内存,对大量数据进行统计。
使用一种特殊的统计学算法完成数据量的统计,存在误差
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 清理数据
flushall
pfadd key value value ......
--------------------------------------
pfadd {x}a aaaaaa bbbbbbbb cccc ddddd aaaaaa bbbbbbbb cccc ddddd aaaaaa aaaaaa cccc cccc cccc
pfadd {x}b xxx yyy zz zz xxx zz cccc
pfcount key key key ......
--------------------------------------
pfcount {x}a
pfcount {x}b
pfcount {x}a {x}b
pfmerge destnation_key key key ......
--------------------------------------
pfmerge {x}x {x}a {x}b
pfcount {x}x
Geospatial
保存地理信息(经纬度)
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
geoadd key 经度 纬度 地名 经度 纬度 地名 ......
geoadd city 121.47 31.23 shanghai 106.5 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.9 beijing
---------------------------------------------------
geopos key 地名 地名 ......
获取坐标位置
-------------------------------------
geopos city shenzhen chongqing
geodist key 地名 地名 [m|km|ft|mi]
两地的距离 distance
-------------------------------------
geodist city shenzhen chongqing
georadius key 经度 维度 距离 [m|km|ft|mi]
该地点周边范围内的地地点
-------------------------------------
georadius city 110 25 500 km
georadius city 110 25 700 km
georadius city 110 25 2000 km
Pub-Sub
Publish - Subscribe
发布和订阅模式
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 消费者
docker exec -it redis7000 redis-cli -c -p 7000
subscribe myChannel
# 生产者
docker exec -it redis7000 redis-cli -c -p 7000
publish myChannel 11111111111
publish myChannel 222222222
publish myChannel 333333333333333
Redis事务
•
功能弱
•
Multi 组队命令序列 类似 mysql 的 begin multi 后可以执行多步数据操作
•
Exec 执行multi的命令序列 相当于 mysql 的 commit
•
Discard 放弃执行命令序列 相当于回滚
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
get {x}
multi
set {x}k1 aaaaaaaaaaaaa
set {x}k2 bbbbbbbbbbbbb
set {x}k3 ccccccccccccccccc
del {x}k1
exec
multi
set {x}k4 ccccccccccccc
set {x}k5 gggggggggggggg
discard
Redis持久化
redis内存的数据,持久化存储到磁盘,当redis重启时,可以从持久化的数据快速回复内存数据。
RDB
Redis Database
内存数据镜像,把内存所有数据拍一个快照,把数据全部保存到一个磁盘文件:dump.rdb
默认在达到一定条件时才会自动保存,或者可以手动调用:
•
save 同步保存
•
bgsave 异步保存
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
redis -cx
redis -1
docker exec -it redis bash
# 查看文件,没有持久化文件dump.rdb
ls
# 在 redis中添加3条数据
redis-cli
set a aaaaaaaaaa
set b bbbbbbbbb
set c cccccccccccccc
# 退出redis客户端
exit
# 查看文件,仍然没有持久化文件dump.rdb
ls
# 进入redis客户端,执行持久化操作,把内存数据持久化存储到磁盘
redis-cli
save
bgsave
# 退出客户端,查看文件
exit
ls
# 退出容器
exit
# 重启redis容器,redis重启时,会从持久化文件dump.rdb恢复数据
docker restart redis
# 查看数据
docker exec -it redis redis-cli
keys *
# 添加新的数据
set d ddddddd
set e eeeeeeeeeee
# 退出redis-cli客户端
exit
# 退出redis容器
exit
# 杀掉容器进程
docker kill redis
# 重启redis容器,redis重启时,会从持久化文件dump.rdb恢复数据
docker restart redis
# 查看数据,只有旧数据,新数据会丢失
docker exec -it redis redis-cli
keys *
可以设置redis自动保存快照,redis.conf中添加配置,例如:
•
save 3600 1:1小时内,有一条数据改动
•
save 300 100:5分钟内,有100条数据改动
•
save 60 10000:1分钟,有10000条数据改动
RDB的优缺点:
•
优点: 恢复数据速度快
•
缺点:
•
持久化时存储数据效率低
•
不能实时存储
•
可能丢失少量新数据
写时复制:
•
保存快照时,先写到临时文件,完成后删除dump.rdb,把临时文件盖明成 dump.rdb
•
临时文件写入失败数据损坏,也不会影响dump.rdb
dump.rdb文件修复:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
docker exec -it redis bash
redis-check-rdb dump.rdb
AOF
AOF - Append Only File
记录数据操作指令,每执行一条增删改的命令,都会实时地追加到 aof 日志文件。
默认不开启AOF,需要在配置文件中开启AOF:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
appendonly yes #默认是no
AOF的优缺点:
•
优点
•
持久化效率高
•
实时持久化
•
不会丢失数据
•
缺点
•
恢复数据时效率低
•
aof中记录的所有命令进行重放,效率低
修复AOF文件:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
redis-check-aof --fix appendonly.aof
rewrite压缩:
对文件中记录的指令,能合并的指令合并成一条执行。
•
文件大小达到min-size时触发,每增长100%时再次触发
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
aof测试
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
0. 删除redis
docker rm -f redis --volumes
1. 上传配置文件redis.conf,其中打开了aof
2. 启动新的redis容器,挂载这个配置文件
docker run -d --name redis \
-v ~/redis.conf:/redis.conf \
redis \
redis-server /redis.conf
3. 在redis中添加数据
docker exec -it redis bash
redis-cli
set a aaaaaaaaaa
set b bbbbbbbbbbbb
set c ccccccc
exit
4. 查看aof文件
ls
cat appendonly.aof
exit
如果是新版本redis执行:
cd appendonlydir/
ls
cat appendonly.aof.1.incr.aof
exit
5. 测试数据恢复
docker restart redis
docker exec -it redis redis-cli
keys *
官方建议使用 RDB+AOF 混合模式
存储:
•
RDB完整存储内存数据
•
RDB之后的增量数据,使用AOF来记录增量数据
恢复:
•
从RDB恢复绝大多数数据
•
重放AOF,恢复少量增量数据
默认已经开启了混合模式:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
aof-use-rdb-preamble yes
需要手动打开AOF:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
appendonly yes #默认是no
高可用
主从
•
主从复制
•
读写分离
•
从服务器重启 可以自动从主服务器同步数据
•
主服务器宕机 从服务器会等待主服务器重启
•
主从复制原理 从服务器上线 全量复制 从服务器发送请求,主服务器rdb,将rdb文件发送给从服务器导入数据 主服务器更改数据时,自动向从服务器同步数据 增量复制
•
主-从-从结构 从服务器的从服务器 减轻主服务器数据同步的压力
•
命令 slaveof ip port 作为制定服务器的从服务器 slaveof no one 从服务器转为主服务器
哨兵
•
监控主从集群,自动主从切换
•
选举依据 1.优先级 replica-priority 2. 偏移量 获取主机的数据数量 3. runid 随机的40位id值
Redis Cluster 集群
•
去中心化
•
配置 cluster-enabled yes cluster-config-file node.conf cluster-node-timeout 15000 #失联时间
•
集群规划原则 主服务不在相同ip 从服务不在他的主服务ip
•
哈希槽 计算哈希槽值 cluster keyslot key cluster keyslot key{group} 哈希槽中的数据量 cluster countkeysinslot slot 只能查看当前服务器哈希槽 获得哈希槽中存储的键 cluster getkeysinslot slot count count指定获取的数量
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
cluster keyslot 5345 cluster keyslot 8542 cluster keyslot 2573 cluster keyslot 5345{news} cluster keyslot 8542{news} cluster keyslot 2573{news}
•
数据操作 set key value 使用key计算哈希槽 set key{group} value 使用group计算哈希值 mset key{group} value key{group} value ... 添加多个数据必须指定组,且是相同的组 get key get key{group} 添加时有组名,获取数据时也必须有组名
•
故障恢复 主从切换 一台主机宕机,它的从服务器自动成为主机 之前的主机重启后成为从机 一组主从全部宕机 哈希槽覆盖不全,整个集群不可用 cluster-require-full-coverage yes 默认 哈希槽覆盖不全,其他服务器仍提供服务 cluster-require-full-coverage no
•
api JedisCluster j = new JedisCluster(set); j.set("k", "kkk");
•
缺点 不支持多键操作 不支持lua脚本 旧的集群向cluster迁移不方便
击穿、穿透和雪崩
击穿
•
热门数据,突然过期,访问被打倒数据库。
•
击穿Redis,穿到数据库。
数据库访问短时激增 redis正常运行
•
可能的场景 突发热门访问 一个key正被大量访问 这个key过期
•
解决方案 预先设置热门数据 实时调整过期时间,自动续期 使用锁、限流 缓存数据不存在,先把数据存入redis
穿透
•
Redis中没有数据,数据库中也没有数据
•
大量执行这种访问,会拖慢数据库
访问量增大 redis数据命中率低 数据库也没有数据 可能的场景
•
访问的数据在redis中数据不存在
•
大量访问非正常数据,数据在数据库中也不存在 非正常访问 黑客攻击
•
解决方案
•
对不存在的数据,缓存空值
•
白名单 可访问的数据id作为偏移量存入bitmaps 访问时检查bitmaps
•
布隆过滤器
•
•
有客户端API提供,在应用中使用布隆过滤器
•
Guava
•
进行实时监控 监控攻击
雪崩
大量key集中过期,数据库访问短时激增
•
解决方案
•
多级缓存架构
•
nginx缓存
•
ehcache | guava
•
redis
•
自动续期、自动更新数据 即将过期的数据提前更新数据
•
对过期时间使用随机值 分散过期时间
•
锁 | 队列
** 分布式锁
方案1-setnx+expire
setnx - Set if Not Exists,如果锁不存在,才能添加成功
•
setnx key value 加锁 没有,可以放入 数据存在,无法放入
•
del key 解锁
•
死锁 长时间不释放锁或忘记释放锁,会发生死锁 设置过期 expire key seconds
方案2-SET的扩展命令(SET key value NX EX|PX time)
合并两个命令,数据不存在时添加数据并设置过期时间:
•
setnx
•
expire
加锁原子性 避免两步操作中间出现故障,可以一步完成 set key value nx ex 15 #15秒 set key value nx px 15000 #15000毫秒
方案3-SETNX + value值是系统时间+过期时间
防止误删其他客户端的锁
set my-lock 系统当前时间+过期时间
删除锁时:
1.
获取my-lock的值,进行比对,是不是自己设置的值
2.
如果是,才能执行删除
方案4-SET EX PX NX + 校验唯一随机值,再释放锁
例如uuid,防止误删其他客户端的锁
** 方案5-使用Lua脚本(包含get + del两条指令)
防止误删时的多步操作问题: a比对值一致 a正准备删除锁时,锁过期 b加锁 a执行删锁,这时会误删b的锁 lua脚本原子操作 解锁原子性 比对+删锁
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
if (redis.call('get', KEYS[1]) == ARGV[1])
then return redis.call('del', KEYS[1]);
else return 0;
end;
# 调用Lua脚本:
# 参数说明: 键值对数量 key value key value...
eval "脚本" 1 mylock ijfu0983u48t
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
# 加锁
set mylock 123 nx ex 30
# 解锁
eval "if (redis.call('get', KEYS[1]) == ARGV[1]) then return redis.call('del', KEYS[1]); else return 0; end;" 1 mylock 456
加锁:
set myLock u5y43t33 nx ex 60
解锁:
执行Lua脚本解锁
Jedis API:
--javascripttypescriptbashsqljsonhtmlcssccppjavarubypythongorustmarkdown
public class Test1 {
static Jedis j;
public static void main(String[] args) {
j = new Jedis("192.168.64.140", 6379);
String lock = UUID.randomUUID().toString();
if (lock("myLock", lock)) {
System.out.println("已加锁");
System.out.println("执行业务");
unlock("myLock", lock);
System.out.println("已解锁");
} else {
System.out.println("加锁失败");
}
}
static boolean lock(String lockKey, String lockVal) {
String result = j.set(lockKey, lockVal, "nx", "ex", 60);
return "OK".equals(result);
}
static void unlock(String lockKey, String lockVal) {
/*
if (redis.call('get', KEYS[1]) == ARGV[1])
then return redis.call('del', KEYS[1]);
else return 0;
end;
*/
String script =
"if (redis.call('get', KEYS[1]) == ARGV[1])\n" +
" then return redis.call('del', KEYS[1]);\n" +
" else return 0;\n" +
"end;";
j.eval(script, 1, lockKey, lockVal);
}
方案6-多机实现的分布式锁Redlock
主从切换加锁失败:
主服务器加的锁,没有成功向从机复制的情况下,主机宕机。从机上没有锁。
Redlock:
对多台无关的服务器遍历加锁、解锁,只要多数成功就算成功
** 方案7-开源框架~Redisson
Redisson是一个类似于Jedis的Redis客户端API,提供了分布式锁的API:
•
可重入锁
•
公平锁
•
联锁
•
红锁
•
读写锁