5、Redis主从架构

Redis主从架构

5、Redis主从架构_第1张图片
主从架构的主要优势在于:

1)、高可用性:当主节点发生故障或不可用时,从节点可以自动接替主节点的角色,确保系统的持续可用性。这种故障转移
	的机制可以极大地减少服务中断时间,提高应用的可靠性
	
2)、数据备份:从节点复制主节点的数据,充当了主节点数据的备份。在主节点数据丢失或损坏的情况下,可以使用从节点
	的数据进行恢复,确保数据的完整性和可靠性。
	
3)、扩展性:通过添加多个从节点,可以扩展系统的读取能力。读取操作可以在多个从节点上并行执行,从而提高系统的读
	取吞吐量和响应速度。

4)、负载均衡:主节点负责写入操作,从节点负责读取操作,将读写操作分担到不同的节点上,实现负载均衡,提高系统整
	体性能。

1、redis主从架构搭建

准备两个虚拟机:
	我的主节点ip为:192.168.0.29
	    从节点ip为:192.168.0.30	
	    
1)、#编辑配置文件:
	[root@localhost redis-6.2.7]# vim redis.conf

	#修改配置
	#配置主从复制
	# 从192.168.0.29 6379的redis实例复制数据,Redis 5.0之前使用slaveof 
	replicaof 192.168.0.29 6379 

	# 配置从节点只读
	replica‐read‐only yes  

2)、#在启动主节点之后,再启动从节点 
	[root@localhost redis-6.2.7]# src/redis-server redis.conf

	#连接从节点:
	[root@localhost redis-6.2.7]# src/redis-cli

3)、#测试在192.168.0.29 6379实例上写数据,192.168.0.30 6379 实例是否能及时同步新修改数据
	#192.168.0.29 6379 写数据
	127.0.0.1:6379> set tdKey 666
	
	#192.168.0.30 6379 实例上是否能获取数据
	127.0.0.1:6379> get tdKey 

2、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。

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

5、Redis主从架构_第2张图片

数据部分复制:
	当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,
redis改用可以支持部分数据复制的命令PSYNC去master同步数据,slave与master能够在
网络连接断开重连后只进行部分数据复制(断点续传)。

	master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,
master和它所有的 slave都维护了复制的数据下标offset和master的进程id,因此,当网络
连接断开后,slave会请求master 继续进行未完成的复制,从所记录的数据下标开始。如果
master进程id变化了,或者从节点数据下标 offset太旧,已经不在master的缓存队列里了,
那么将会进行一次全量数据的复制

2.2、主从复制(部分复制,断点续传)流程图:

5、Redis主从架构_第3张图片
如果有很多从节点,为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点压力过
大),可以做如下架构,让部分从节点与从节点(与主节点同步)同步数据
5、Redis主从架构_第4张图片

3、Jedis连接代码示例

1)、引入相关依赖:

	redis.clients
	jedis
	2.9.0


2)、访问代码:
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.29", 6379, 3000, null);

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

            System.out.println(jedis.set("single", "td"));
            System.out.println(jedis.get("single"));

            //管道示例
            //管道的命令执行方式: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("td" + i, "zhuge");
            }
            List results = pl.syncAndReturnAll();
            System.out.println(results);*/

            //lua脚本模拟一个商品减库存的原子操作
            //lua脚本命令执行方式:redis‐cli ‐‐eval /tmp/test.lua , 10
            /*jedis.set("product_count_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_count_10016"), Arrays.asList("10"));
            System.out.println(obj);*/


        } catch (Exception e) {
            e.printStackTrace();

        } finally {
            //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
            if (jedis != null) {
                jedis.close();
            }
        }
    }
}


4、redis管道与调用lua脚本

4.1、管道(Pipeline)

	客户端可以一次性发送多个请求而不用等待服务器的响应,待所有命令都发送完后再一
次性读取服务的响 应,这样可以极大的降低多条命令执行的网络传输开销,管道执行多条
命令的网络开销实际上只相当于一 次命令执行的网络开销。

	需要注意到是用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起
所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多
越好。 pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此
后的响应中得到信息;也就是pipeline并不是表达“所有command都一起成功”的语义,管
道中前面命令失败,后面命令 不会有影响,继续执行

详细代码示例见上面jedis连接示例:
public class JedisSingleTest {
    public static void main(String[] args) throws IOException {

        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);
    }
}

4.2、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,架构,java)