随笔- 100  文章- 0  评论- 3  阅读- 30413 

一:持久化

1.RSB快照

  1.

  在默认情况下, Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中。

  可以对 Redis 进行设置, 让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次 数据集。 比如说, 以下设置会让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次 数据集:

  # save 60 1000

  

  关闭RDB只需要将所有的save保存策略注释掉即可

 

  2.save与bgsave生成文件

 

  还可以手动执行命令生成RDB快照,进入redis客户端执行命令save或bgsave可以生成dump.rdb文件, 每次命令执行都会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件。

 

  3.写时复制机制

  Redis 借助操作系统提供的写时复制技术(Copy-On-Write, COW),在生成快照的同时,依然可以正常 处理写命令。

  简单来说,bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。 bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。此时,如果主线程对这些 数据也都是读操作,那么,主线程和 bgsave 子进程相互不影响。但是,如果主线程要修改一块数据,那 么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文 件,而在这个过程中,主线程仍然可以直接修改原来的数据。

 

  4.save与bgsave对比

  

 

 

2. aof

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

   每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文 件的末尾。 这样的话, 当 Redis 重新启动时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目 的。

  配置:

  

 

 

  测试

  重启后,写入:

  

 

  查看appendonly.aof

  

 

  这是一种resp协议格式数据,星号后面的数字代表命令有多少个参数,$号后面的数字代表这个参数有几 个字符.

 

  关于过期时间,则使用时间戳的方式

set program true ex 10000

  

 

 

  AOF重写

  AOF文件里可能有太多没用指令,所以AOF会定期根据内存的最新数据生成aof文件

  可以通过下面的配置进行修改:

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

  当然AOF还可以手动重写,进入redis客户端执行命令bgrewriteaof重写AOF 注意,AOF重写redis会fork出一个子进程去做(与bgsave命令类似),不会对redis正常命令处理有太多 影响

 

3.rdb与aof

  

 

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

  

 

 

4.混合持久化

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

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

  结构:

  

 

   

  配置:

  首先保证aof开启,然后

# aof‐use‐rdb‐preamble yes

  

  测试

  

 

   

 

 

二:主从架构

1.架构

  

 

   

2.搭建

  复制redis.conf成redis_6380.conf

cp redis.conf redis_6380.conf

  

  修改文件

复制代码
port 6380
pidfile /var/run/redis_6380.pid

日志
logfile "6380.log"

持久化
dbfilename dump_6380.rdb

配置主从关系
replicaof 192.168.19.132 6379
replica-read-only yes
复制代码

 

  启动

src/redis-server conf/redis_6380.conf 

 

  测试:

  查询数据是否同步过来,6379修改数据,6380是否同步过来

  

 

 

3.主从复制原理

  新的链接:

  

 

 

  如果你为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的缓存队列里了,那么将会进行一次全量数据的复制。

 

4.java程序-Jedis连接【单机与主从的代码是一致的】

  pom

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

 

  程序:

复制代码
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单机
 */
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.19.132", 6379, 3000, null);

        Jedis jedis = null;
        try {
            //从redis连接池里拿出一个连接执行命令
            jedis = jedisPool.getResource();

            //******* jedis普通操作示例 ********
            System.out.println(jedis.set("single1", "caojun"));
            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) " +
                    "   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();
        }
    }
}
复制代码

 

  

三:哨兵架构

1.架构

  

 

 

2.配置

  先搭建主从:

  

 

 

  搭建哨兵

复制代码
拷贝一份conf
cp sentinel.conf sentinel_26381.conf 

port 26379
daemonize yes
pidfile /var/run/redis-sentinel_26379.pid
logfile "26379.log"
dir /opt/software/redis-5.0.3/data


sentinel monitor mymaster 192.168.19.132 6379 2
复制代码

 

  启动

src/redis-sentinel conf/sentinel_26379.conf 

 

  进入客户端,检查info命令

src/redis-cli -p 26379

  

 

 

  查看

  sentinel集群都启动完毕后,会将哨兵集群的元数据信息写入所有sentinel的配置文件里去(追加在文件的 最下面),我们查看下如下配置文件sentinel-26379.conf,如下所示:

more conf/sentinel_26379.conf 

  

 

   其中,6381,6380都代表redis主节点的从节点信息

 

3.java代码-jedis

复制代码
package com.jun.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哨兵集群
 */
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.19.132", 26379).toString());
        sentinels.add(new HostAndPort("192.168.19.132", 26380).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", "meighui"));
            System.out.println(jedis.get("sentinel666"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
            if (jedis != null)
                jedis.close();
        }
    }
}
复制代码

 

3.springboot 的java代码

3.1.pom

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

 

3.2.代码

复制代码
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("caojun-"+i, i+""); //jedis.set(key,value);
                System.out.println("设置key:"+ "caojun-" + i);
                i++;
                Thread.sleep(1000);
            }catch (Exception e){
                logger.error("错误:", e);
            }
        }
    }

}
复制代码

 

3.3.配置文件

复制代码
server:
  port: 8080

spring:
  redis:
    database: 0
    timeout: 3000
    sentinel:    #哨兵模式
      master: mymaster #主服务器所在集群名称
      nodes: 192.168.19.132:26379,192.168.19.132:26380,192.168.19.132: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
复制代码

 

 posted on   曹军  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示