Redis持久化主从与哨兵架构
Posted lee_curry
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis持久化主从与哨兵架构相关的知识,希望对你有一定的参考价值。
#Redis持久化
RDB快照
默认情况下,redis将内存数据库快照保存在名字为dump.rdb的二进制文件中。
也可以在redis的配置文件进行设置,如:save 60 1000 每60秒内至少有1000个键被改动过,自动保存一次。也可以手动生产rdb快照,进入客户端执行命令save或bgsave可以生产dump.rdb文件,会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件。
AOF
快照功能并不是非常耐久:如果redis因为某些原因而造成故障停机,那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。从1.1版本开始,redis增加了一种完全耐久的持久化方式:aof持久化,将修改的每一条指令记录进文件appendonly.aof中(先写入os cache,每隔一段时间fsync到磁盘)
比如执行命令“”set curry 666“”,aof文件里会记录如下数据
*3
$3
set
$5
curry
$3
666
这是一种resp协议格式数据,*后面数字代表命令有多少个参数,$号后面的数字代表这个参数有几个字符
注意,如果执行带过期时间的set命令,aof文件里记录的是并不是执行的原始命令,而是记录key过期的时间戳
需要手动打开AOF功能:
appendonly yes
也可以配置redis多久才将数据fsync到磁盘中。
appendfsync always;每次有新命令追加到AOF文件就执行一次fsync,非常慢,也非常安全。
appendfsync everysec:每秒fsync一次,足够快,并且在故障是只会丢失一秒中的数据
appendfsync no;从不fsync,将数据交给os来处理,更快,也更不安全的选择、
aof重写
AOF文件里可能有太多没有指令,所以AOF会定期根据内存的最新数据生成aof文件
重写配置
auto‐aof‐rewrite‐min‐size 64mb //aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就 很快,重写的意义不大 2
auto‐aof‐rewrite‐percentage 100 //aof文件自上一次重写后文件大小增长了100%则再次触发重写
手动重写
redis客户端执行bgrewriteaof重写aof
这个地方重写也是异步操作,会fork出一个子进程去做(与bgsave命令类似)
redis 4.0 混合持久化
结合rdb和aof各个特性:
配置文件开启混合持久化(必须先开启aof)
aof-use-rdb-preamble yes
如果开启后,AOF在重新时,不在是单纯将内存数据转换为RESP命令写入AOF文件,而是将重写这一刻之前的内存做RDB快照处理,并且将rdb快照内容和增量的aof修改内存数据的命令存在一起,都会写入新的aof文件,新的文件一开始不叫appendonly。aof,等到重写完新的aof文件才会进行改名,覆盖原有的aof文件,完成新旧两个aof文件的替换。
当redis重启的时候,可以先加载rdb的内容,然后在重放增量aof日志就可以完全替代之前的aof全量文件重放,可以提高效率。
Redis数据备份策略:
- 写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留近48 小时的备份
- 每天都保留一份当日的数据备份到一个目录中去,可以保留近1个月的备份
- 每次copy备份的时候,都把太旧的备份给删了
- 每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏
Redis主从架构
redis主从架构搭建,配置从节点步骤:
1、复制一份redis.conf文件
2、将相关配置修改为如下值:
port 6380
pidfile /var/run/redis_6380.pid # 把pid进程号写入pidfile配置的文件
logfile “6380.log”
dir /usr/local/redis‐5.0.3/data/6380 # 指定数据存放目录
#需要注释掉bind
bind 127.0.0.1(bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通 过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可)
3、配置主从复制
replicaof 192.168.0.60 6379 # 从本机6379的redis实例复制数据,Redis 5.0之前使用slaveof
replica‐read‐only yes # 配置从节点只读
4、启动从节点 16 redis‐server redis.conf
5、连接从节点 19 redis‐cli ‐p 6380
6、测试在6379实例上写数据,6380实例是否能及时同步新修改数据
7、可以自己再配置一个6381的从节点
主从工作原理
如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个PSYNC 命令给master请求复制数据。 master收到PSYNC命令后,会在后台进行数据持久化通过bgsave生成新的rdb快照文件,持久化期 间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完 毕以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后 再加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。 当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多 个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送 给多个并发连接的slave。
主从复制(全量复制)流程图:
数据部分复制
当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,redis改用可以支 持部分数据复制的命令PSYNC去master同步数据,slave与master能够在网络连接断开重连后只进行部分 数据复制(断点续传)。
master会在其内存中创建一个复制数据用的缓存队列,缓存近一段时间的数据,master和它所有的 slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master 继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标 offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制
主从复制(部分复制,断点续传)流程图:
package com.tuling.jedis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Pipeline;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* 访问redis单机
*
* @author curry
*/
public class JedisSingleTest
public static void main(String[] args) throws IOException
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(20);
jedisPoolConfig.setMaxIdle(10);
jedisPoolConfig.setMinIdle(5);
// timeout,这里既是连接超时又是读写超时,从Jedis 2.8开始有区分connectionTimeout和soTimeout的构造函数
JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.10.110", 6379, 3000, "123456");
Jedis jedis = null;
try
//从redis连接池里拿出一个连接执行命令
jedis = jedisPool.getResource();
//******* jedis普通操作示例 ********
System.out.println(jedis.set("single1", "zhuge"));
System.out.println(jedis.get("single1"));
//******* 管道示例 ********
//管道的命令执行方式:cat redis.txt | redis-cli -h 127.0.0.1 -a password - p 6379 --pipe
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<Object> results = pl.syncAndReturnAll();
System.out.println(results);
//******* lua脚本示例 ********
//模拟一个商品减库存的原子操作
//lua脚本命令执行方式:redis-cli --eval /tmp/test.lua , 10
jedis.set("product_stock_10016", "15"); //初始化商品10016的库存
String script = " local count = redis.call('get', KEYS[1]) " +
" local a = tonumber(count) " +
" local b = tonumber(ARGV[1]) " +
" if a >= b then " +
" redis.call('set', KEYS[1], a-b) " +
//模拟语法报错回滚操作
" bb == 0 " +
" return 1 " +
" end " +
" return 0 ";
Object obj = jedis.eval(script, Arrays.asList("product_stock_10016"), Arrays.asList("10"));
System.out.println(obj);
catch (Exception e)
e.printStackTrace();
finally
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
if (jedis != null)
jedis.close();
顺带讲下redis管道与调用lua脚本,代码示例上面已经给出
管道(Pipeline)
客户端可以一次性发送多个请求而不用等待服务器的响应,待所有命令都发送完后再一次性读取服务的响 应,这样可以极大的降低多条命令执行的网络传输开销,管道执行多条命令的网络开销实际上只相当于一 次命令执行的网络开销。需要注意到是用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓 存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。 pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的响应中得到信 息;也就是pipeline并不是表达“所有command都一起成功”的语义,管道中前面命令失败,后面命令 不会有影响,继续执行
Redis Lua脚本
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:
1、减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器 上完成。使用脚本,减少了网络往返时延。这点跟管道类似。
2、原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。管道不是原子的,不过 redis的批量操作命令(类似mset)是原子的。
3、替代redis的事务功能:redis自带的事务功能很鸡肋,报错不支持回滚,而redis的lua脚本几乎实现了 常规的事务功能,支持报错回滚操作,官方推荐如果要使用redis的事务功能可以用redis lua替代
**注意,**不要在Lua脚本中出现死循环和耗时的运算,否则redis会阻塞,将不接受其他的命令, 所以使用 时要注意不能出现死循环、耗时的运算。redis是单进程、单线程执行脚本。管道不会阻塞redis。
Redis哨兵高可用架构
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。 哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过 sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis 主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
redis哨兵架构搭建步骤
1、复制一份sentinel.conf文件 2 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是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 + 1),master才算真正失效
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的主从
另外两个哨兵自己再配置两个sentinel,端口26380和26381,注意上述配置文件里的对应数字都要修改
sentinel集群都启动完毕后,会将哨兵集群的元数据信息写入所有sentinel的配置文件里去(追加在文件的 下面),我们查看下如下配置文件sentinel-26379.conf,如下所示:
1 sentinel known‐replica mymaster 192.168.0.60 6380 #代表redis主节点的从节点信息
2 sentinel known‐replica mymaster 192.168.0.60 6381 #代表redis主节点的从节点信息
3 sentinel known‐sentinel mymaster 192.168.0.60 26380 52d0a5d70c1f90475b4fc03b6ce7c3c569 35760f #代表感知到的其它哨兵节点
4 sentinel known‐sentinel mymaster 192.168.0.60 26381 e9f530d3882f8043f76ebb8e1686438ba8 bd5ca6 #代表感知到的其它哨兵节点
当redis主节点如果挂了,哨兵集群会重新选举出新的redis主节点,同时会修改所有sentinel节点配置文件 的集群元数据信息,比如6379的redis如果挂了,假设选举出的新主节点是6380,则sentinel文件里的集 群元数据信息会变成如下所示
1 sentinel known‐replica mymaster 192.168.0.60 6379 #代表主节点的从节点信息 2 sentinel known‐replica mymaster 192.168.0.60 6381 #代表主节点的从节点信息 3 sentinel known‐sentinel mymaster 192.168.0.60 26380 52d0a5d70c1f90475b4fc03b6ce7c3c569 35760f #代表感知到的其它哨兵节点
4 sentinel known‐sentinel mymaster 192.168.0.60 26381 e9f530d3882f8043f76ebb8e1686438ba8 bd5ca6 #代表感知到的其它哨兵节点
同时还会修改sentinel文件里之前配置的mymaster对应的6379端口,改为6380
1 sentinel monitor mymaster 192.168.0.60 6380
当6379的redis实例再次启动时,哨兵集群根据集群元数据信息就可以将6379端口的redis节点作为从节点 加入集群
哨兵的Jedis连接代码:
package com.tuling.jedis;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* 访问redis哨兵集群
*
* @author curry
*/
public class JedisSentinelTest
public static void main(String[] args) throws IOException
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);
String masterName = "mymaster";
Set<String> sentinels = new HashSet<String>();
sentinels.add(new HostAndPort("192.168.65.60",26379).toString());
sentinels.add(new HostAndPort("192.168.65.60",26380).toString());
sentinels.add(new HostAndPort("192.168.65.60",26381).toString());
//JedisSentinelPool其实本质跟JedisPool类似,都是与redis主节点建立的连接池
//JedisSentinelPool并不是说与sentinel建立的连接池,而是通过sentinel发现redis主节点并与其建立连接
JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(masterName, sentinels, config, 3000, null);
Jedis jedis = null;
try
jedis = jedisSentinelPool.getResource();
System.out.println(jedis.set("sentinel666", "zhuge"));
System.out.println(jedis.get("sentinel666"));
catch (Exception e)
e.printStackTrace();
finally
//注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
if (jedis != null)
jedis.close();
spring boot 整合redis连接代码见实例项目
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>
2.核心配置文件
server:
port: 8080
spring:
redis:
database: 0
timeout: 3000
password: 123456
sentinel: #哨兵模式
master: mymaster #主服务器所在集群名称
nodes: 192.168.10.110:26379,192.168.10.110:26380,192.168.10.110:26381
cluster:
nodes: 192.168.50.61:8001,192.168.50.62:8002,192.168.50.63:8003,192.168.50.61:8004,192.168.50.62:8005,192.168.50.63:8006
lettuce:
pool:
max-idle: 50
min-idle: 10
max-active: 100
max-wait: 1000
访问代码
package com.redis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IndexController
private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 测试节点挂了哨兵重新选举新的master节点,客户端是否能动态感知到
*
* @throws InterruptedException
*/
@RequestMapping("/test_sentinel")
public void testSentinel() throws InterruptedException
int i = 1;
while (true)
try
stringRedisTemplate.opsForValue().set("zhuge"+i, i+""); //jedis.set(key,value);
System.out.println("设置key:"+ "zhuge" + i);
i++;
Thread.sleep(1000);
catch (Exception e)
logger.error("错误:", e);
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;
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都是采用此策略序列化保存的。
Redis主从与哨兵架构-学习
1. Redis主从工作原理
如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个PSYNC命令给master请求复制数据。
master收到PSYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完毕以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后再加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。
当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的slave。
如果文字看起来很累,咱们整个流程图来看看:
1.1 数据部分复制
当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,redis改用可以支持部分数据复制的命令PSYNC去master同步数据,slave与master能够在网络连接断开重连后只进行部分数据复制(断点续传)。
master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。
老规矩,画个图帮助理解
不过话说我们公司还是用的主从架构呢。上次找了下原始设计发现一个诡异的原型。
各位知道为啥整成这样不?
假设有很多从节点,为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点压力过大),让部分从节点与从节点(与主节点同步)同步数据。
技术这东西,适合自己的就是最好的。看官不要觉得我们架构太low哈。
2.Redis哨兵高可用架构
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。
哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
以上是关于Redis持久化主从与哨兵架构的主要内容,如果未能解决你的问题,请参考以下文章