Redis持久化、事务、主从复制、哨兵、Jedis、Spring-Data-Redis

redis持久化

所谓持久化,就是将数据保存到永久性存储介质上,在特定的时间将保存的数据进行恢复的机制。

*为什么要进行持久化*

  • 防止数据的意外丢失,保证数据是安全的。

*证明redis持久化的存在*

  • redis为了追求高效的读写速度,默认情况下所有的增删改,都是在内存中进行的,断电以后redis的数据会丢失,丢失的数据是保存在内存中的数据。但是我们会发现,在关闭了redis服务器之后,再次启动redis服务,之前的key还在!也就是说,redis是有持久化功能的!

  • redis中持久化的方式,有两种

    1. RDB(存快照)

    2. AOF(存日志)

redis持久化之RDB

*RDB启动方式:save命令*

每当执行save命令的时候,都会立即进行一次快照保存!

  • save指令相关配置
    dbfilename dump.rdb说明:设置本地数据库文件名,默认值为dump.rdb
    经验:通常设置为dump-*端口号*.rdb

  • dir说明:
    设置存储rdb文件的路径经验:通常设置为存储空间较大的目录中
    rdbcompression yes说明:设置存储至rdb文件时会否压缩数据经验:通常默认为开启状态,如果设置为no,可以节省cpu运行时间,但存储文件会很大。
    rdbchecksum yes说明:设置是否进行rdb文件格式校验,该校验过程写文件和读文件的过程中都会进行经验:通常默认为开启状态,如果设置为no,可以节约读写的时间,但会有数据损坏风险

*rdb数据的恢复时机*

  • redis启动时。

*save持久化数据的缺点*

  • redis是单线程模型,那么在数据量过大的情况下执行save指令,save指令的执行时间就会很长,这样排在save指令之后的其他指令只能长时间地阻塞,这样会拉低服务器的性能!
    所以线上环境中,不建议使用save命令。

*RDB启动方式:bgsave命令*

  • 针对于save命令的缺点,需要使用bgsave命令来在后台进行rdb数据快照备份。
    注意:bgsave命令是针对save阻塞问题做的优化,redis内容所有涉及到rdb的操作都采用bgsave,save命令可以放弃使用了。

*RDB启动方式:自动执行*

  • 配置save second changes作用在限定时间内(second),至少有指定个数(changes)的key发生变化时,就自动持久化 在redis.conf文件中有以下配置:save 900 1 表示每900秒(15分钟)至少有1个key发生变化,则dump内存快照 save 300 10表示每300秒(5分钟)至少有10个key发生变化,则dump内存快照 save 60 10000表示每60秒(1分钟)至少有1000个key发生变化,则dump内存快照

  • 注意

    1. save自动执行,执行的是bgsave命令。
    2. 在执行FLUSHALL命令的时候,无论是否满足条件,都会立即生成一个新的空的dump.rdb来覆盖以前的dump.rdb。总之,FLUSHALL很危险,工作中不要使用。

*RDB优点*

  • RDB是一个紧凑压缩的二进制文件,存储效率较高
  • RDB内部存储的是redis在某个时间点的数据快照,非常适合于数据备份,全量复制的场景
  • RDB恢复数据的速度要比AOF快很多
  • 将RDB文件单独拷贝到远程机器中,用于灾难恢复

*RDB缺点*

  • RDB方式无论是执行指令还是利用配置,都无法做到实时持久化,有丢失最新数据的风险
  • bgsave指令每次运行都要fork一个子进程,要牺牲掉一些性能
  • redis的众多版本中未进行RDB文件格式的统一,在各个版本的服务之间会有不兼容的现象
  • RDB是基于快照的思想,每次读写都是全部数据,当数据量巨大时,效率非常低

redis持久化之AOF

AOF(append only file)持久化:以独立日志的方式记录每次写命令(也就是记录数据产生的过程),重启时再重新执行AOF文件中的命令,达到数据恢复的目的。
*AOF写数据的三种策略*

  • always 每次写入操作均同步到AOF文件中,*数据零误差,性能较低*
  • everysec 每秒同步到AOF中,*数据准确性较高,性能较高*,最多丢失1秒的数据
  • no 由系统控制何时将命令同步到AOF文件,*整个过程不可控*

*AOF功能开启*

  • appendonly yes|no 是否开启AOF功能appendfsync always|everysec|no AOF写数据的策略appendfilename filename 建议配置为appendonly-端口号.aof

*AOF写数据遇到的问题*

  • 针对于右边的6个命令,如果aof都统统保存下来,是没有必要的! AOF提供了一个功能:AOF重写来解决这个问题!

*AOF重写*

  • 随着命令不断写入AOF文件,AOF文件会越来越大,为了解决这个问题,redis引入了AOF重写机制来压缩AOF文件体积。AOF文件重写是将Redis进程内的数据转化为写命令同步更新到AOF文件的过程。简单地说就是将对同一个数据的若干条命令执合并为最终结果所对应的那一条命令!这样既降低了磁盘占用量,提高磁盘利用率,又减少了数据恢复时所用的时间!

*AOF重写规则*

  1. 进程内已超时的数据不再写入文件。

  2. 忽略无效指令,重写时使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。

  3. 对同一数据的多条写命令合并为一条命令
    如lpush list1 a、lpush list1 b可以合并为lpush list a b

*AOF重写启动方式*

  1. 手动重写:bgrewriteaof
  2. 自动重写触发条件设置auto-aof-rewrite-min-size size 自动触发AOF重写的文件最小大小auto-aof-rewrite-percentage percentage 自动触发AOF重写的百分比
  3. 自动重写触发比对参数,以下两个参数通过运行info persistence来获取具体信息aof_current_size (当前大小)aof_base_size (上次重写后的大小)
  4. 自动重写触发条件,以下两个条件一个成立就会触发aof的自动重写(可以只配置一种)aof_current_size > auto-aof-rewrite-min-size(aof_current_size - aof_base_size) / aof_base_size > auto-aof-rewrite-percentage percentage

*RDB与AOF如何抉择?*

  1. 如果要求对整体数据特别敏感,建议使用AOF

    AOF持久化策略采用everyseconds的话,每秒备份一次写命令。该策略使redis保持很好的性能,当出现问题时,最多丢失1秒内的数据。

  2. 如果对某时段内的数据特别敏感,建议使用RDB

    由开发或者运维人员手工使用RDB维护,可以具有针对性的对于某时段内的数据进行灾难备份,且恢复速度较快。所以阶段数据持久化通常使用RDB。

    如果同时开启RDB和AOF时,redis优先使用AOF来恢复数据。

redis事务

redis虽然是单线程,但是也会有事务的问题。

127.0.0.1:6379> multi # multi 开始事务
OK
127.0.0.1:6379> add username tom # 添加错误命令到队列
(error) ERR unknown command `add`, with args beginning with: `username`, `tom`,
127.0.0.1:6379> set username tom 
QUEUED
127.0.0.1:6379> exec # 使用exec执行队列的命令
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get username  # 队列中有错误命令,整个队列的命令都不执行。
(nil)
127.0.0.1:6379> multi # multi 开始事务
OK
127.0.0.1:6379> set password 123456 # 添加命令到队列
QUEUED
127.0.0.1:6379> exec  # 使用exec执行队列的命令
1) OK

此时出现两个问题:

  1. 如果队列中有错误命令,则整个队列的命令都不执行。

  2. 如果队列没有错误命令,但是执行时出现异常,那么不影响其他命令的执行。(程序自行编码完成回滚处理。)

需要对数据进行监控,加watch,类似于乐观锁。

watch num
multi
set k1 v1 
incr num
# 在exec之前,如果有其他客户对num进行了操作,则这个事务整体都不执行
exec

redis主从复制

当今互联网的“三高”架构:高并发、高性能、高可用。

*单机redis的问题*

单机redis的问题很明,如果当前的redis服务宕机了,则系统的整个缓存系统瘫痪,导致灾难性的后果。另外单机redis也有内存容量瓶颈。

*redis主从复制*

一个master可以有多个slave,一个slave只能有一个master

*redis的主从复制的作用*

  1. 读写分离,提高服务读写的负载能力。
  2. 提高了整个redis服务的可用性。

*搭建redis主从复制*

  1. 准备3台虚拟机,分别装好redis

  2. 开放每台服务器的6379端口

  3. 编辑redis master服务的配置文件redis.conf

       daemonize yes
       requirepass foobared
       bind 0.0.0.0
       logfile redis.log
    
  4. 编辑redis slave服务的配置文件redis.conf

       daemonize yes
       masterauth foobared
       requirepass foobared
       bind 0.0.0.0
       logfile redis.log
    
  5. 分别启动3个redis服务,且使用redis客户端连接redis服务,键入以下命令

    info replication
    

    会发现此时3个redis服务的role都是master

  6. 使用以下命令,完成主从复制

      slaveof 192.168.1.51 6379
    

*redis主从复制流程*

  1. slave服务器配置master的连接信息(slaveof指令)
  2. slave连接上master,向master发送psync2指令
  3. master判断是否为全量复制(第一次连接),如果是全量复制,则进入下一步;否则进行增量复制(第8步)。
  4. master启动一个后台线程,执行bgsave生成一份RDB快照文件,同时将bgsave执行的过程中所接收到的写命令缓存到复制缓冲区中。
  5. RDB文件生成完毕之后,master会将RDB发送给slave。
  6. slave收到RDB文件之后,清空自己的旧数据,然后持久化到本地磁盘,再从本地磁盘加载到内存中。
  7. 当salve恢复了RDB文件中的数据到内存后,会发送命令告诉master RDB恢复已经完成。
  8. master会将内存中缓存的写命令发送给slave,slave也会执行这些命令。
  9. 如果slave node开启了AOF,那么会立即执行BGREWRITEAOF,重写AOF

我们已经知道了主从复制的流程了,而这些流程被划分为3个阶段。

*主从复制的三个阶段*

  1. 连接阶段,由slave去主动连接master,双方互相记住对方的套接字。
  2. 数据同步阶段,由master把目前的所有数据一次性同步给slave(使用RDB)
  3. 命令传播阶段,master把后期收到的写命令,发送给slave

步骤:
1、生成多个centos虚拟机(克隆)
2、开放6379端口
3、redis配置

master

reqirepass foobared
bind 0.0.0.0
logfile redis.log

slave

reqirepass foobared
bind 0.0.0.0
logfile redis.log
masterauth foobared	#连接主机时的密码

4、启动服务

如果服务已经运行
pkill redis-server
如果没有启动服务
./redis-server redis.conf

5、查看当前redis服务的信息

info replication

所有的主机都是master

6、在要作为slave的服务器中运行命令连接master

slaveof master'ip port

7、再查看master服务的信息

info replication

8、查看slave服务的信息

info replication

9、测试

# 在master中添加数据
set k1 v1
# 在slave中读取该数据
get k1

测试:

  1. 当master宕机以后,slave会怎么样? --> 原地待命,等待master

  2. 当slave宕机以后,再次重启会怎么样? --> 脱离master,自立成为master

redis哨兵模式

问题描述:当master宕机以后,所有的slave是无法正常工作的,这时需要从所有的slave中选择一个作为新的master,并通知所有的其他slave连接该新的master,这个过程完全可以由人工完成。也可以使用哨兵模式!

*哨兵是什么*

哨兵也是一个redis服务,只是不提供数据服务
*哨兵模式搭建*

  1. 再准备1个虚拟机,安装好redis(安装过程以及安装目录和之前一样)
  2. 将redis解压目录下的哨兵配置文件sentinel.conf考到/usr/local/redis/目录下
cp ~/redis/sentinel.conf  /usr/loacl/redis/
  1. 查看sentinel.conf文件的内容
 cat sentinel.conf | grep -v "^#" | grep -v "^$" 
  1. 将sentinel.conf的内容写入sentinel-26379.conf文件中
 cat sentinel.conf | grep -v "^#" | grep -v "^$" > sentinel-26379.conf

编辑sentinel-26379.conf的内容
哨兵监听的端口,记得开放端口port 26379
哨兵存放数据的位置dir .
下面标红的的数字1表示只要由1个哨兵认为master宕机了,就能确认maser确实宕机# 了,毕竟现在只有一个哨兵,还没有为哨兵做集群sentinel

monitor mymaster 192.168.43.110 6379 1

设定master在多长时间(毫秒)没响应,就认为master宕机了
sentinel down-after-milliseconds mymaster 30000
哨兵连接master也需要通过master的认证
sentinel auth-pass mymaster foobared

  1. 在哨兵模式下,任何机器都有可能成为master,所以修改3个redis的配置为以下同一个配置
    daemonize yes
    masterauth foobared  <-- 关键是为每个reids服务加上这一行,保证能够互相访问*
    requirepass foobared
    bind 0.0.0.0
    logfile redis.log
    
  2. 先保证主从复制环境搭建好,最后再启动哨兵
  3. 此时关闭master,等待一段时间,会发先哨兵选出了一个新的master,并且通知其他slave归顺新主

至此哨兵环境已经搭建成功

*下面搭建哨兵集群的环境*

  1. 为了方便,我们就不再搞出更多的虚拟机了,而是在一个虚拟机上,做出3个哨兵分别监听26379、26380、26381端口,做一个哨兵的伪集群。

    哨兵监听的端口,记得开放端口port 26379 | 26380 | 26381 # 哨兵存放数据的位置dir .# 下面标红的的数字2表示只要由1个哨兵认为master宕机了,就能确认maser确实宕机# 了,毕竟现在只有一个哨兵,还没有为哨兵做集群sentinel monitor mymaster 192.168.1.51 6379 *2* # 设定master在多长时间(毫秒)没响应,就认为master宕机了sentinel down-after-milliseconds mymaster 30000# 哨兵连接master也需要通过master的认证****sentinel auth-pass mymaster foobared****

  2. 重新搭建redis的主从复制环境:一主二从。

  3. 然后启动3个客户端分别在192.168.1.54上启动3个监听不同端口的哨兵。
    ./bin/redis-server sentinel-26379.conf --sentinel
    ./bin/redis-server sentinel-26380.conf --sentinel
    ./bin/redis-server sentinel-26381.conf --sentinel

  4. 关闭192.168.1.51的redis服务,过一会,哨兵集群会重新选出一个新主

  5. 使用客户端连接到一个哨兵服务

    ./bin/redis-cli -p 26379
    info sentinel
    
  6. 退出26379哨兵(shutdown),其他哨兵也能感知

  7. 此时再关闭新主192.168.1.53

  8. 会发现,剩下的2个哨兵能继续重新选举一个新主

  9. 再让26379哨兵回来,可以看出26379回来后,获得到了最新的信息

*哨兵工作原理*

  1. 监控:同步信息
  2. 通知:保持数据互通
  3. 故障转移:
    (1) 发现问题
    (2) 从哨兵中选出负责人
    (3) 负责人选出master
    (4) 新master上位,其他slave切换master,原master也臣服于新master

Java客户端Jedis

  1. 首先开放linux服务器6379端口(或者直接关闭防火墙),然后在redis.conf配置文件中,设置密码requirepass foobared,然后注释掉bind 127.0.0.1这行配置,重启redis服务。

  2. 创建maven项目,且在项目中导入jedis依赖

    <dependency>  
    	<groupId>redis.clients</groupId>  
    	<artifactId>jedis</artifactId>  
    	<version>3.2.0</version>
    </dependency>
    
  3. 基本使用

    @Test
    public void testJedisConn(){
        Jedis jedis = new Jedis("192.168.174.130",6379);
        jedis.auth("foobared");
        System.out.println(jedis.ping());
        System.out.println(jedis.set("k1", "abcd"));
        System.out.println(jedis.strlen("k1"));
        jedis.close();
    }
    
  4. 使用连接池

    @Test
    public void testJedisPool(){
         //jedis连接池配置
         JedisPoolConfig config = new JedisPoolConfig();
         //进行配置
         config.setMaxTotal(50);
         config.setMaxIdle(10);
         //数据库连接池
         JedisPool jedisPool = new JedisPool("192.168.174.130",6379);
         Jedis jedis = jedisPool.getResource();
         jedis.auth("foobared");
         System.out.println(jedis.ping());
     }
    
  5. 编写util类

    package com.woniu.util;
    
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    public class JedisUtils {
        private static JedisPool jedisPool;
        static{
            JedisPoolConfig config = new JedisPoolConfig();
            //进行配置
            config.setMaxTotal(50);
            config.setMaxIdle(10);
            //数据库连接池
            jedisPool = new JedisPool("192.168.174.130",6379);
        }
    
        public static Jedis getJedisConn(){
            Jedis jedis = jedisPool.getResource();
            jedis.auth("foobared");
            return jedis;
        }
    }
    
  6. 测试util类

    @Test
    public void testJedisUtils(){
        System.out.println(JedisUtils.getJedisConn().ping());
    }
    

Spring-Data-Redis

  1. 添加依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
  1. 编写redis的配置文件或者JavaConfig
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 配置连接池 -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="50"></property>
        <property name="maxIdle" value="5"></property>
    </bean>
    <!-- Spring整合Jedis(也就是redis) -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="192.168.174.132"></property>
        <property name="port" value="6379"></property>
        <property name="password" value="foobared"></property>
        <!-- 注入连接池 -->
        <property name="poolConfig" ref="jedisPoolConfig"></property>
    </bean>
    <!--配置字符串序列化 -->
    <bean id="stringSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
    <!-- 配置RedisTemplate -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory"></property>
        <property name="keySerializer" ref="stringSerializer"></property> 
        <property name="valueSerializer" ref="stringSerializer"></property>
    </bean>
</beans>
 <!--配置字符串序列化 -->
<bean id="stringSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>

<!-- 配置RedisTemplate -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="jedisConnectionFactory"></property>
    <property name="keySerializer" ref="stringSerializer"></property> 	需要对key和value进行序列化配置,否则存入redis的key和value有特殊字符.
    <property name="valueSerializer" ref="stringSerializer"></property>
</bean>
  1. 编写测试类
package com.woniu.springbootredis;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-redis.xml")
public class AppTest {
    //是redis配置文件注入的
    @Autowired
    private RedisTemplate<String, Object> rt;
    @Test
    public void test() throws Exception {
        //string类型
        ValueOperations<String, Object> string = rt.opsForValue();
        string.set("goods2","apple");
        System.out.println(string.get("goods2"));
        //list类型
        ListOperations<String, Object> list = rt.opsForList();
        //hash类型
        HashOperations<String, Object, Object> hash = rt.opsForHash();
        //set类型
        SetOperations<String, Object> set = rt.opsForSet();
        //ZSet类型
        ZSetOperations<String, Object> zSet = rt.opsForZSet();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何呵呵是大佬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值