Java-分布式框架-redis-2

一、Redis持久化

RDB快照(snapshot)

在默认情况下, Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中。你可以对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。比如说, 以下设置会让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次数据集:
// save 60 1000
关闭RDB只需要将所有的save保存策略注释掉即可。

还可以手动执行命令生成RDB快照,进入redis客户端执行命令save或bgsave可以生成dump.rdb文件,每次命令执行都会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件。save是同步命令,bgsave是异步命令,bgsave会从redis主进程fork(fork()是linux函数)出一个子进程专门用来生成rdb快照文件。

image.png

注意:如果设置镜像保存策略为save 60 1000,若时间55秒,操作次数999次的时候redis宕机了,那么这999次操作会丢失。

AOF(append-only file)

快照功能并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化,将修改的每一条指令记录进文件appendonly.aof中你可以通过修改配置文件来打开 AOF 功能:

  • appendonly yes

从现在开始, 每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾。这样的话, 当 Redis 重新启动时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。你可以配置 Redis 多久才将数据 fsync 到磁盘一次。有三个选项:

  • appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
  • appendfsync everysec:每秒 fsync 一次,足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据。
  • appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。
AOF重写

AOF文件里可能有太多没用指令,所以AOF会定期根据内存的最新数据生成aof文件例如,执行了如下几条命令:

127.0.0.1:6379> incr readcount
(integer) 1
127.0.0.1:6379> incr readcount
(integer) 2
127.0.0.1:6379> incr readcount
(integer) 3
127.0.0.1:6379> incr readcount
(integer) 4
127.0.0.1:6379> incr readcount
(integer) 5

重写后AOF文件里变成一条命令,而不是5条

*3
$3
SET
$9
readcount
$1
5

如下两个配置可以控制AOF自动重写频率

  • auto-aof-rewrite-min-size 64mb

aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就很快,重写的意义不大

  • auto-aof-rewrite-percentage 100

aof文件自上一次重写后文件大小增长了100%则再次触发重写当然AOF还可以手动重写,进入redis客户端执行命令bgrewriteaof重写AOF注意,AOF重写redis会fork出一个子进程去做,不会对redis正常命令处理有太多影响

命令 RDB AOF
启动优先级
体积
恢复速度
数据安全性 容易丢数 据根据策略决定

注意1:redis启动时如果既有rdb文件又有aof文件则优先选择aof文件恢复数据,因为aof一般来说数据更全一点。

注意2:redis数据库的定位并不是作为持久层数据库,而是一个内存数据库,是用来抗并发的。

Redis 4.0 混合持久化

重启 Redis 时,我们很少使用 RDB来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 RDB来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。 Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。通过如下配置可以开启混合持久化:

  • aof-use-rdb-preamble yes

如果开启了混合持久化,AOF在重写时,不再是单纯将内存数据转换为RESP命令写入AOF文件,而是将重写这一刻之前的内存做RDB快照处理,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一起,都写入新的AOF文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,原子的覆盖原有的AOF文件,完成新旧两个AOF文件的替换。于是在 Redis 重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重启效率大幅得到提升。

混合持久化AOF文件结构


image.png

Redis主从架构

Redis主从工作原理
  1. slave连接master成功,发送psync同步命令给master。
  2. master接收到同步命令,执行bgsave后台生成最新rdb快照数据文件,注意这期间master还会接收到新的写命令,master会把这些指令缓存起来。
  3. master发送rdb数据文件给slave。
  4. master发送期间缓存起来的写指令。
  5. slave清除原来的rdb数据文件。
  6. 合成接收到的rdb快照数据文件与新的写指令,并加载到内存中。
  7. master通过socket长连接持续把写指令发送给从节点,保证主从命令数据一致性。

主从复制(全量复制)流程图:

image.png

注意:repl_back_buffer容器中专门存储最近的写命令,但容量有限,默认为1MB.

主从之间的连接可能会因为网络不稳定等原因会造成连接暂时中断与重连,不可能每次重连发现主从数据不一致就采用全量复制,redis也给我们提供了部分复制的功能.

  1. slave连接中断.
  2. 期间repl_back_buffer容器存储最近的写命令.
  3. slave重连master成功.
  4. slave发送psync(offerset)同步指令给master.
  5. master收到同步指令,判断offerset是否在repl_back_buffer容器的写指令的范围内,若在,同步offerset后面的指令;若不在,全量同步.
  6. master通过socket长连接持续把写指令发送给从节点,保证主从命令数据一致性。

主从复制(部分复制)流程图:

image.png

redis主从架构搭建,配置从节点步骤
1. 复制一份redis.conf文件
2. 将相关配置修改为如下值:
port 6380
pidfile /var/run/redis_6380.pid
logfile "6380.log"
dir /usr/local/redis‐5.0.3/data/6380
3. 配置主从复制
replicaof 192.168.0.60 6379 # 从本机6379的redis实例复制数据
replica‐read‐only yes
4. 启动从节点
redis‐server redis.conf
5. 连接从节点
redis‐cli ‐p 6380
6. 测试在6379实例上写数据,6380实例是否能及时同步新修改数据
7. 可以自己再配置一个6381的从节点
单机使用
  • 添加依赖

        redis.clients
        jedis
        2.9.0

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.0.60", 6379, 3000, null);

        Jedis jedis = null;
        try {
            //从redis连接池里拿出一个连接执行命令
            jedis = jedisPool.getResource();
            //******* jedis普通操作示例 ********
            System.out.println(jedis.set("name", "value"));
            System.out.println(jedis.get("name"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
            if (jedis != null)
                jedis.close();
        }
    }
}

Redis哨兵高可用架构

sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)


image.png
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    
// quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 +1),master才算真正失效
sentinel monitor mymaster 192.168.0.60 6379 2
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,注意上述配置文件里的对应数字都要修改
哨兵使用
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 sentinels = new HashSet();
        sentinels.add(new HostAndPort("192.168.0.60",26379).toString());
        sentinels.add(new HostAndPort("192.168.0.60",26380).toString());
        sentinels.add(new HostAndPort("192.168.0.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", "666"));
            System.out.println(jedis.get("sentinel666"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
            if (jedis != null)
                jedis.close();
        }
    }
}
SpringBoot下哨兵使用

    org.springframework.boot
    spring-boot-starter-data-redis

//线程池

    org.apache.commons
    commons-pool2

spring:
  redis:
    database: 0
    timeout: 3000
    lettuce:
      pool:
        max-idle: 50
        min-idle: 10
        max-active: 100
        max-wait: 1000
     sentinel:    #哨兵模式
       master: mymaster #主服务器所在集群名称
       nodes: 192.168.0.60:26379,192.168.0.60:26380,192.168.0.60:26381
@Autowired
private StringRedisTemplate stringRedisTemplate;

@RequestMapping("/test_sentinel")
public void testSentinel() throws InterruptedException {
    int i = 1;
    while (true){
        try {
            stringRedisTemplate.opsForValue().set("name"+i, i+""); //jedis.set(key,value);
            System.out.println("设置key:"+ "name" + i);
            i++;
            Thread.sleep(1000);
        }catch (Exception e){
            logger.error("错误:", e);
        }
    }
}

你可能感兴趣的:(Java-分布式框架-redis-2)