WHCSRL 技术网

redis持久化主从与哨兵结构(学习总结)_bingtanghulu

目录

1. RDB,AOF及混合持久化

1.1 RDB持久化机制

1.1.1 配置rdb策略

1.2 aof

1.2.1 开启AOF

 1.2.2 配置aof策略

1.3 aof重写

1.3.1 配置重写策略

 1.3.2 手动重写aof

1.4 RDB 和 AOF ,我应该用哪一个?

1.5 混合持久化

1.5.1 开启混合持久化

1.6 Redis数据备份策略

2. redis主从架构原理

2.1 redis主从架构图

2.2 搭建redis主从架构

 2.2.1 主从复制全量流程图

 2.2.2 部分复制流程图

 2.2.3 平常redis主从架构

4.redis管道与lua脚本

4.1 管道

4.2 lua脚本

4.2.1 redis使用lua脚本

 4.3 jedis操作

4.3.1 引入jedis依赖

 4.3.2 模拟管道报错和操作字符串

4.3.3 模拟lua脚本

5. redis哨兵高可用架构

5.1 redis哨兵架构图

 5.2 redis哨兵搭建

5.2.1 查看哨兵集群元信息

 5.3 jedis连接哨兵

5.3.1 引入依赖

 5.3.2 springboot项目核心配置

 5.3.3 jedis连接代码

5.5 Redis客户端命令对应的RedisTemplate中的方法列表


1. RDB,AOF及混合持久化

如果有不知道怎么配置redis客户端的请看另外一篇博客

传送门!!!

1.1 RDB持久化机制

redis默认打开RDB功能,RDB是一种快照形式的redis持久化机制,他的数据都保存在dump.rdb文件中

1.1.1 配置rdb策略

1. 默认配置策略:格式:save 秒数 保存键数,整体连起来就是在多少秒内写多少键值就会把数据保存到dump.rdb文件中。默认配置如下

 2. 手动保存

进入redis客户端输入save或者bgsave命令可手动保存数据到dumb.rdb文件,

bgsave的写时复制(COW)机制
Redis 借助操作系统提供的写时复制技术(Copy-On-Write, COW),在生成快照的同时,依然可以正常处理写命令。简单来说,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。 bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。此时,如果主线程对这些 数据也都是读操作,那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据,那 么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文 件,而在这个过程中,主线程仍然可以直接修改原来的数据。
save与bgsave对比:

 

配置自动生成rdb文件后台使用的是bgsave方式

效果如下:

 

1.2 aof

aof持久化:默认不开启,这种机制会将每条修改命令不包括读的命令写入appendonly.aof文件中。

1.2.1 开启AOF

 修改appendonly yes 参数打开AOP功能

  1. vi redis.conf #修改配置文件参数appendonly yes
  2. ps -ef |grep redis #查看redis进程
  3. kill -9 pid #杀掉进程
  4. src/redis-server redis.conf # 重启服务端
  5. src/redis-cli #开启客户端
  6. set test 666 #写入命令

 在redis安装目录下能看到appendonly.aof文件代表aop功能开启成功!dump.rdb文件是RDB持久化的文件

 1.2.2 配置aof策略

appendfsync always :每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
appendfsync everysec :每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
appendfsync no :从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。

推荐appendfsync everysec-每秒 fsync 一次,可以兼顾速度和安全性。

1.3 aof重写

aof定期将一些无用且重复的命令生成一条命令。

1.3.1 配置重写策略

auto aof rewrite min size 64 mb //aof 文件至少要达到 64M 才会自动重写,文件太小恢复速度本来就
很快,重写的意义不大
auto aof rewrite percentage 100 //aof 文件自上一次重写后文件大小增长了 100%% 则再次触发重写

 1.3.2 手动重写aof

进入redis客户端使用bgrewriteaof命令重写

1.4 RDB 和 AOF ,我应该用哪一个?

 

生产环境可以都启用,redis启动时如果既有rdb文件又有aof文件则优先选择aof文件恢复数据,因为aof 一般来说数据更全一点。

1.5 混合持久化

混合持久化:重写这一刻之前的内存做内存快照,并将rdb快照和增量aof修改命令放到一起存入aof文件,重写一个新的aof文件,重写完成后覆盖原来的appendonly.aof文件。redis重启以后加载dump.rdb文件,并加载增量aof文件完成重写。结合了aof数据安全和rdb效率的优势。

1.5.1 开启混合持久化

 aofuserdbpreamble yes #开启混合持久化

1.6 Redis数据备份策略

策略如下,脚本需要自己写,如果有写过的linux大佬,麻烦给个链接,谢谢!

1. 写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48
小时的备份
2. 每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
3. 每次copy备份的时候,都把太旧的备份给删了
4. 每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏

2. redis主从架构原理

2.1 redis主从架构图

 当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的slave。架构图如下:

2.2 搭建redis主从架构

1 、复制一份 redis . conf 文件
mkdir config #创建文件夹
cp redis.conf /usr/local/redis-5.0.3/config/redis-6380.conf #复制一份文件
        
2 、将相关配置修改为如下值:
port 6380
pidfile / var / run / redis_6380 . pid # pid 进程号写入 pidfile 配置的文件
logfile "6380.log" 7 dir / usr / local / redis 5.0.3 / data / 6380 # 指定数据存放目录
# 需要注释掉 bind
# bind 127.0.0.1 bind 绑定的是自己机器网卡的 ip ,如果有多块网卡可以配多个 ip ,代表允许客户端通
过机器的哪些网卡 ip 去访问,内网一般可以不配置 bind ,注释掉即可)
3 、配置主从复制
  replicaof 192.168.1.7 6379 # 从本机 6379 redis 实例复制数据, Redis 5.0 之前使用 slaveof
replica read only yes # 配置从节点只读
4 、启动从节点
src/redis server ./config/redis-6380 . conf
5 、连接从节点
src/redis cli p 6380
6 、测试在 6379 实例上写数据, 6380 实例是否能及时同步新修改数据
7 、可以自己再配置一个 6381 的从节点

 如果能显示如下所示代表链接成功:

 2.2.1 主从复制全量流程图

 2.2.2 部分复制流程图

 2.2.3 平常redis主从架构

主从复制风暴:为了缓解主节点往多个从节点复制数据的压力,可以做如下架构:

 

4.redis管道与lua脚本

4.1 管道

客户端可以一次性发送多个请求而不用等待服务器的响应,待所有命令都发送完后再一次性读取服务的响 应,这样可以极大的降低多条命令执行的网络传输开销,管道执行多条命令的网络开销实际上只相当于一 次命令执行的网络开销。需要注意到是用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓 存起所有命令的处理结果 。打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。 pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的响应中得到信息;也就是pipeline并不是表达“所有command都一起成功”的语义,管道中前面命令失败,后面命令不会有影响,继续执行。

4.2 lua脚本

Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:
1、 减少网络开销 :本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器
上完成。使用脚本,减少了网络往返时延。 这点跟管道类似
2、 原子操作 :Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。 管道不是原子的,不过
redis的批量操作命令(类似mset)是原子的。
3、 替代redis的事务功能 :redis自带的事务功能很鸡肋,而redis的lua脚本几乎实现了常规的事务功能,
官方推荐如果要使用redis的事务功能可以用redis lua替代。

4.2.1 redis使用lua脚本

从Redis2.6.0版本开始,通过内置的Lua解释器,可以使用EVAL命令对Lua脚本进行求值。EVAL命令的格式如下:

 EVAL script numkeys key [key ...] arg [arg ...]

 

注意,不要在Lua脚本中出现死循环和耗时的运算,否则redis会阻塞,将不接受其他的命令, 所以使用
时要注意不能出现死循环、耗时的运算。redis是单进程、单线程执行脚本。管道不会阻塞redis。

 4.3 jedis操作

4.3.1 引入jedis依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

 4.3.2 模拟管道报错和操作字符串

  1. package com.tuling.jedis;
  2. import redis.clients.jedis.Jedis;
  3. import redis.clients.jedis.JedisPool;
  4. import redis.clients.jedis.JedisPoolConfig;
  5. import redis.clients.jedis.Pipeline;
  6. import java.io.IOException;
  7. import java.util.Arrays;
  8. import java.util.List;
  9. public class JedisSingleTest {
  10. public static void main(String[] args) throws IOException {
  11. JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
  12. jedisPoolConfig.setMaxTotal(20);
  13. jedisPoolConfig.setMaxIdle(10);
  14. jedisPoolConfig.setMinIdle(5);
  15. // timeout,这里既是连接超时又是读写超时,从Jedis 2.8开始有区分connectionTimeout和soTimeout的构造函数
  16. JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.1.7", 6379, 3000, null);
  17. Jedis jedis = null;
  18. try {
  19. //从redis连接池里拿出一个连接执行命令
  20. jedis = jedisPool.getResource();
  21. //******* jedis普通操作示例 ********
  22. /*System.out.println(jedis.set("single1", "genghongbo"));
  23. System.out.println(jedis.get("single1"));*/
  24. //******* 管道示例 ********
  25. //管道的命令执行方式:cat redis.txt | redis-cli -h 127.0.0.1 -a password - p 6379 --pipe
  26. Pipeline pl = jedis.pipelined();
  27. for (int i = 0; i < 10; i++) {
  28. pl.incr("pipelineKey");
  29. pl.set("test" + i, "zhuge");
  30. //模拟管道报错
  31. pl.setbit("test", -1, true);
  32. }
  33. List<Object> results = pl.syncAndReturnAll();
  34. System.out.println(results);
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. } finally {
  38. //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
  39. if (jedis != null)
  40. jedis.close();
  41. }
  42. }
  43. }

4.3.3 模拟lua脚本

  1. //******* lua脚本示例 ********
  2. //模拟一个商品减库存的原子操作
  3. //lua脚本命令执行方式:redis-cli --eval /tmp/test.lua , 10
  4. jedis.set("product_stock_10016", "15"); //初始化商品10016的库存
  5. String script = " local count = redis.call('get', KEYS[1]) " +
  6. " local a = tonumber(count) " +
  7. " local b = tonumber(ARGV[1]) " +
  8. " if a >= b then " +
  9. " redis.call('set', KEYS[1], a-b) " +
  10. //模拟语法报错回滚操作
  11. " bb == 0 " +
  12. " return 1 " +
  13. " end " +
  14. " return 0 ";
  15. Object obj = jedis.eval(script, Arrays.asList("product_stock_10016"), Arrays.asList("10"));
  16. System.out.println(obj);

5. redis哨兵高可用架构

5.1 redis哨兵架构图

 5.2 redis哨兵搭建

1 、复制一份 sentinel . conf 文件
cp sentinel . conf sentinel 26379. conf
2 、将相关配置修改为如下值:
port 26379
daemonize yes
pidfile "/var/run/redis‐sentinel‐26379.pid"
logfile "26379.log"
dir "/usr/local/redis‐5.0.3/data"
# sentinel monitor < master redis name > < master redis ip > < master redis port > < quorum >
# quorum 是一个数字,指明当有多少个 sentinel 认为一个 master 失效时 ( 值一般为: sentinel 总数 / 2 +
1 ) master 才算真正失效 12 sentinel monitor mymaster 192.168.0.60 6379 2 # mymaster 这个名字随便取,客户端访问时会用
3 、启动 sentinel 哨兵实例
src / redis sentinel sentinel 26379. conf
4 、查看 sentinel info 信息
src / redis cli p 26379
127.0.0.1 : 26379 > info
可以看到 Sentinel info 里已经识别出了 redis 的主从
5 、可以自己再配置两个 sentinel ,端口 26380 26381 ,注意上述配置文件里的对应数字都要修改

 如下图所示即可。

5.2.1 查看哨兵集群元信息

进入sential-26379.conf文件查找

 哨兵挂掉后自动从从节点选举出主节点信息更改端口号为新的主节点端口号

sentinel monitor mymaster 192.168.0.60 6380 2

 5.3 jedis连接哨兵

5.3.1 引入依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-pool2</artifactId>
</dependency>

 5.3.2 springboot项目核心配置

server :
port : 8080
spring :
redis :
database : 0
timeout : 3000
sentinel : # 哨兵模式
master : mymaster # 主服务器所在集群名称
nodes : 192.168.0.60 : 26379 , 192.168.0.60 : 26380 , 192.168.0.60 : 26381
lettuce : 12 pool :
max idle : 50
min idle : 10
max active : 100
max wait : 1000

 5.3.3 jedis连接代码

  1. package com.tuling.jedis;
  2. import redis.clients.jedis.HostAndPort;
  3. import redis.clients.jedis.Jedis;
  4. import redis.clients.jedis.JedisPoolConfig;
  5. import redis.clients.jedis.JedisSentinelPool;
  6. import java.io.IOException;
  7. import java.util.HashSet;
  8. import java.util.Set;
  9. /**
  10. * 访问redis哨兵集群
  11. *
  12. */
  13. public class JedisSentinelTest {
  14. public static void main(String[] args) throws IOException {
  15. JedisPoolConfig config = new JedisPoolConfig();
  16. config.setMaxTotal(20);
  17. config.setMaxIdle(10);
  18. config.setMinIdle(5);
  19. String masterName = "mymaster";
  20. Set<String> sentinels = new HashSet<String>();
  21. sentinels.add(new HostAndPort("192.168.1.7",26379).toString());
  22. // sentinels.add(new HostAndPort("192.168.1.7",26380).toString());
  23. // sentinels.add(new HostAndPort("192.168.1.7",26381).toString());
  24. //JedisSentinelPool其实本质跟JedisPool类似,都是与redis主节点建立的连接池
  25. //JedisSentinelPool并不是说与sentinel建立的连接池,而是通过sentinel发现redis主节点并与其建立连接
  26. JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName, sentinels, config, 3000, null);
  27. Jedis jedis = null;
  28. try {
  29. jedis = jedisSentinelPool.getResource();
  30. System.out.println(jedis.set("sentinel666", "test"));
  31. System.out.println(jedis.get("sentinel666"));
  32. } catch (Exception e) {
  33. e.printStackTrace();
  34. } finally {
  35. //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
  36. if (jedis != null)
  37. jedis.close();
  38. }
  39. }
  40. }

5.4 StringRedisTemplate与RedisTemplate详解

StringRedisTemplate与RedisTemplate详解
spring 封装了 RedisTemplate 对象来进行对redis的各种操作,它支持所有的 redis 原生的 api。在
RedisTemplate中提供了几个常用的接口方法的使用,分别是:
private ValueOperations < K , V > valueOps ;
private HashOperations < K , V > hashOps ;
private ListOperations < K , V > listOps ;
private SetOperations < K , V > setOps ; 5 private ZSetOperations < K , V > zSetOps ;

 RedisTemplate中定义了对5种数据结构操作

redisTemplate . opsForValue (); // 操作字符串
redisTemplate . opsForHash (); // 操作 hash
redisTemplate . opsForList (); // 操作 list
redisTemplate . opsForSet (); // 操作 set
redisTemplate . opsForZSet (); // 操作有序 set

 StringRedisTemplate继承自RedisTemplate,也一样拥有上面这些操作。

StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存
的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的

5.5 Redis客户端命令对应的RedisTemplate中的方法列表

 

 

 

 

推荐阅读