redis有三种持久化机制:RDB、AOF、混合模式
是redis默认的持久化机制
# 比如redis.conf文件中默认就会开启以下配置,意思是60秒内如果 有1万个key改变就会触发RDB持久化
save 900 1
save 300 10
save 60 10000
持久化的文件的文件名与文件存储路径的配置在redis.conf文件中是下面的配置
dbfilename dump.rdb
dir ./
RDB持久化的方式有两种:
save
这条命令在执行时是单线程的,在备份生成rdb文件时会阻塞用户的操作
bgsave
rdb持久化底层默认使用的是bgsave
就重新fork一个线程来进行数据备份,不会阻塞用户的操作。在备份过程中如果用户进行了更新操作,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
RDB的优点就是文件体积小、重启恢复数据快
缺点是可能会丢失一段时间的数据,因为我们不可能每秒钟去生成一个rdb文件
如果要开启AOF持久化我们需要在redis.conf文件中放开下面这一行的配置改为yes
appendonly yes
生成的aof文件名与文件生成目录的配置如下
appendfilename "appendonly.aof"
dir ./
dir这个配置项即指定rdb文件的目录也指定aof文件的目录
AOF文件的触发策略
# appendfsync always
appendfsync everysec
# appendfsync no
aof文件存储内容的格式如下所示
# 执行的原命令
set name hs# aof文件持久化内容
*3
$3
set
$4
name
$2
hs其中*3表示这个命令有三个参数:set key value
$3表示下来来这个参数的长度为3,比如set就是3位 name是4位 hs为2位
AOF文件的优点是数据安全性更高,不会丢失很多数据
缺点是文件更大、数据恢复时间慢
AOF文件重写
aof触发文件重写的配置项的
# 其中100是百分比,达到64m就会触发一次文件重写,aof文件自上一次重写后文件大小增长了100%则再次触发重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
底层其实就是相当于执行一次bgrewriteaof
命令
比如我现在执行很多次自增操作
127.0.0.1:6379> set number 1
OK
127.0.0.1:6379> incr number
(integer) 2
127.0.0.1:6379> incr number
(integer) 3
127.0.0.1:6379> incr number
(integer) 4
127.0.0.1:6379> incr number
(integer) 5
而aof文件中的内容如下所示
[root@VM-8-7-centos redis-5.0.3]# cat appendonly.aof
*3
$3
set
$6
number
$1
1
*2
$4
incr
$6
number
*2
$4
incr
$6
number
*2
$4
incr
$6
number
*2
$4
incr
$6
number
此时我手动执行bgrewriteaof命令
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
现在aof文件中的内容如下所示
[root@VM-8-7-centos redis-5.0.3]# cat appendonly.aof
*3
$3
set
$6
number
$1
5
aof文件的重写其实就是结合当前内存中的数据生成命令,然后保存在appendonly.aof文件中
如果rdb和aof两种持久化都开启后,redis服务重启时会优先使用aof文件来进行数据恢复,因为这种方式数据安全性更高。
redis4.0版本开始有混合模式,它其实本质还是aof这种方式。如果要使用混合模式,前提是aof的配置项需要开启。
开启混合模式的配置项,redis5.0的版本默认开启
aof-use-rdb-preamble yes
混合模式结合了rdb和aof两种文件的优点,在触发aof文件重写时,此时会把当前内存中的数据生成二进制文件的格式存储在appendonly.aof文件中,之后还是以之前aof文件存储的格式存储
如下所示,前面一部分是二进制内容,后面一部分还是之前那的aof文件追加格式。
[root@VM-8-7-centos redis-5.0.3]# cat appendonly.aof
REDIS0009 redis-ver5.0.3
redis-bitseR¨used-memÀ
𮤭preambleþ㭡mehsnumberȫ)!*2
$6
SELECT
$1
0
*3
$3
set
$5
name1
$7
hushang
其实当我们使用混合模式这种方式后,就可以不需要再单独触发rdb文件备份了,可以将redis.conf文件中的save配置项都注释掉
我们可以利用crontab定时任务备份aof或rdb文件
在数据恢复时只是将备份的文件放到指定目录下(dir配置项目录),然后重启redis-server即可完成数据恢复
搭建主从架构
拿一台服务器上运行多个redis-service实例来举例
首先复制一份redis.conf文件,修改下面这几个不能重复的配置项
port 6380
pidfile /var/run/redis_6380.pid # 把pid进程号写入pidfile配置的文件
logfile "6380.log"
dir /usr/local/redis-5.0.3/data/6380 # 指定数据存放目录
主从架构重点是配置下面的几行配置
replicaof 192.168.0.60 6379 # 从本机6379的redis实例复制数据,Redis 5.0之前使用slaveof
replica-read-only yes # 配置从节点只读# 如果master配置的密码,那么我们在从机上也要指定密码
masterauth
启动两个服务后,在redis-cli中可以通过info replication
命令来查看主从架构是否搭建成功
全量主从复制原理
部分数据主从复制
如果某个时间点从节点宕机了,过一段时间后有重启了,那么这个时候我想要只是把宕机这段时间的数据进行增量复制,而不进行全量复制。过程如下
主从复制风暴
如果一个master节点下有很多的slave节点,为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点压力过大),可以做如下架构,让部分从节点与从节点(与主节点同步)同步数据
redis客户端可以一次性发送多条命令给redis-service去执行,目的是减少网络传输开销。但管道不能保证多条命令的原子性,如果其中有一条命令执行失败了是不会影响到其他命令的执行,以下是一个jedis使用管道的实例
Pipeline pl = jedis.pipelined();
for (int i = 0; i < 10; i++) {pl.incr("pipelineKey");pl.set("zhuge" + i, "zhuge");//模拟管道报错// pl.setbit("zhuge", -1, true);
}
List
相关配置与启动
# 复制一份sentinel.conf文件
cp sentinel.conf sentinel-26379.conf# 将相关配置修改为如下值
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel-26379.pid"
logfile "26379.log"
dir "/usr/local/redis-5.0.3/data"
# sentinel monitor
# quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 + 1),master才算真正失效
sentinel monitor mymaster 192.168.0.60 6379 2 # mymaster这个名字随便取,客户端访问时会用到# 启动sentinel哨兵实例
src/redis-sentinel sentinel-26379.conf
我们启动了多个redis服务和redis哨兵后,在哨兵配置文件的最后会自动生成一些数据
sentinel known-replica mymaster 192.168.0.60 6380 #代表redis主节点的从节点信息
sentinel known-replica mymaster 192.168.0.60 6381 #代表redis主节点的从节点信息
sentinel known-sentinel mymaster 192.168.0.60 26380 52d0a5d70c1f90475b4fc03b6ce7c3c56935760f #代表感知到的其它哨兵节点
sentinel known-sentinel mymaster 192.168.0.60 26381 e9f530d3882f8043f76ebb8e1686438ba8bd5ca6 #代表感知到的其它哨兵节点
当主节点如果宕机后,上面的信息也会更着修改,同时sentinel monitor mymaster 192.168.0.60 6379 2
这个配置项也会跟着修改
引入相关依赖
org.springframework.boot spring-boot-starter-data-redis
org.apache.commons commons-pool2
基本配置
server:port: 8080spring:redis:database: 0timeout: 3000sentinel: #哨兵模式master: mymaster #主服务器所在集群名称nodes: 192.168.0.60:26379,192.168.0.60:26380,192.168.0.60:26381lettuce:pool:max-idle: 50min-idle: 10max-active: 100max-wait: 1000
java代码中的具体使用
@Autowired
private StringRedisTemplate stringRedisTemplate;stringRedisTemplate.opsForValue().set("name", "hushang");
RedisTemplate中定义了对5种数据结构操作
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set
SpringRedisTemplate介绍
StringRedisTemplate继承自RedisTemplate,也一样拥有上面这些操作。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。我们使用redis-cli能直接看到内容
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。我们使用redis-cli看不到明文内容
Redis客户端命令对应的RedisTemplate中的方法列表:
String类型结构 | |
---|---|
Redis | RedisTemplate rt |
set key value | rt.opsForValue().set(“key”,“value”) |
get key | rt.opsForValue().get(“key”) |
del key | rt.delete(“key”) |
strlen key | rt.opsForValue().size(“key”) |
getset key value | rt.opsForValue().getAndSet(“key”,“value”) |
getrange key start end | rt.opsForValue().get(“key”,start,end) |
append key value | rt.opsForValue().append(“key”,“value”) |
Hash结构 | |
hmset key field1 value1 field2 value2… | rt.opsForHash().putAll(“key”,map) //map是一个集合对象 |
hset key field value | rt.opsForHash().put(“key”,“field”,“value”) |
hexists key field | rt.opsForHash().hasKey(“key”,“field”) |
hgetall key | rt.opsForHash().entries(“key”) //返回Map对象 |
hvals key | rt.opsForHash().values(“key”) //返回List对象 |
hkeys key | rt.opsForHash().keys(“key”) //返回List对象 |
hmget key field1 field2… | rt.opsForHash().multiGet(“key”,keyList) |
hsetnx key field value | rt.opsForHash().putIfAbsent(“key”,“field”,“value” |
hdel key field1 field2 | rt.opsForHash().delete(“key”,“field1”,“field2”) |
hget key field | rt.opsForHash().get(“key”,“field”) |
List结构 | |
lpush list node1 node2 node3… | rt.opsForList().leftPush(“list”,“node”) |
rt.opsForList().leftPushAll(“list”,list) //list是集合对象 | |
rpush list node1 node2 node3… | rt.opsForList().rightPush(“list”,“node”) |
rt.opsForList().rightPushAll(“list”,list) //list是集合对象 | |
lindex key index | rt.opsForList().index(“list”, index) |
llen key | rt.opsForList().size(“key”) |
lpop key | rt.opsForList().leftPop(“key”) |
rpop key | rt.opsForList().rightPop(“key”) |
lpushx list node | rt.opsForList().leftPushIfPresent(“list”,“node”) |
rpushx list node | rt.opsForList().rightPushIfPresent(“list”,“node”) |
lrange list start end | rt.opsForList().range(“list”,start,end) |
lrem list count value | rt.opsForList().remove(“list”,count,“value”) |
lset key index value | rt.opsForList().set(“list”,index,“value”) |
Set结构 | |
sadd key member1 member2… | rt.boundSetOps(“key”).add(“member1”,“member2”,…) |
rt.opsForSet().add(“key”, set) //set是一个集合对象 | |
scard key | rt.opsForSet().size(“key”) |
sidff key1 key2 | rt.opsForSet().difference(“key1”,“key2”) //返回一个集合对象 |
sinter key1 key2 | rt.opsForSet().intersect(“key1”,“key2”)//同上 |
sunion key1 key2 | rt.opsForSet().union(“key1”,“key2”)//同上 |
sdiffstore des key1 key2 | rt.opsForSet().differenceAndStore(“key1”,“key2”,“des”) |
sinter des key1 key2 | rt.opsForSet().intersectAndStore(“key1”,“key2”,“des”) |
sunionstore des key1 key2 | rt.opsForSet().unionAndStore(“key1”,“key2”,“des”) |
sismember key member | rt.opsForSet().isMember(“key”,“member”) |
smembers key | rt.opsForSet().members(“key”) |
spop key | rt.opsForSet().pop(“key”) |
srandmember key count | rt.opsForSet().randomMember(“key”,count) |
srem key member1 member2… | rt.opsForSet().remove(“key”,“member1”,“member2”,…) |