redis系列二:主从搭建与jedis的三种使用方式

主从原理

在主从模式中,从节点slave会在连接上master时,会发送一个PSYNC命令给master请求复制数据。
master接收到PSYNC命令,用持久化命令bgsave生成最新的rbd快照文件发送给slave节点,该命令是后台方式启动,所以这期间不会影响客户端的请求,如果持久化期间还有其他数据修改,master会将该数据集缓存到一个缓冲区中发送个slave。
由于某些原因,master和slave断开时,slave会自动重连master,如果master收到多个slave的并发连接请求,master也只会持久化一次,然后把这一份持久化数据分别发送给slave节点。
大致复制流程如下图
image

主从配置

在redis目录下创建一个文件夹master-slave,存放三个redis的配置文件

  1. redis_6379.conf的主要配置属性
bind 0.0.0.0
protected-mode no
port 6379
timeout 3000
daemonize yes
supervised no
pidfile /usr/data/6379/redis_6379.pid
loglevel notice
logfile "/usr/data/6379/6379.log"
dbfilename dump.rdb
dir /usr/data/6379/
replica-read-only yes
requirepass 123456
  1. redis_6380.conf的主要配置属性
bind 0.0.0.0
protected-mode no
port 6380
timeout 3000
daemonize yes
supervised no
pidfile /usr/data/6380/redis_6380.pid
loglevel notice
logfile "/usr/data/6380/6380.log"
dbfilename dump.rdb
dir /usr/data/6380/
replica-read-only yes
masterauth 123456
requirepass 123456
replicaof 192.168.2.190 6379
  1. redis_6381.conf的主要配置属性
bind 0.0.0.0
protected-mode no
port 6381
timeout 3000
daemonize yes
supervised no
pidfile /usr/data/6381/redis_6381.pid
loglevel notice
logfile "/usr/data/6381/6381.log"
dbfilename dump.rdb
dir /usr/data/6381/
replica-read-only yes
masterauth 123456
requirepass 123456
replicaof 192.168.2.190 6379

6379端口的实例作为master主节点;
6380端口和6381端口作为从节点,所以需要配置replicaof属性(如需配置不同机子的节点,只需将该处的ip修改成对应的即可),replicaof属性指向主节点redis实例的地址和端口;
由于6379设置了密码,所以还需要设置masterauth属性

启动和测试

分别运行redis的启动命令(启动顺序没有要求)

[root@localhost redis-5.0.14]# ./src/redis-server ./master-slave/redis_6379.conf 
[root@localhost redis-5.0.14]# ./src/redis-server ./master-slave/redis_6380.conf 
[root@localhost redis-5.0.14]# ./src/redis-server ./master-slave/redis_6381.conf 

如果在6380和6381中插入数据会报错:

127.0.0.1:6380> set test 001
(error) READONLY You can't write against a read only replica.

因为配置文件中指定了只是做作为6379的副本,并且是只读
所有只能在6379中设置

127.0.0.1:6379> set test 001
OK

在6380和6381中获取

127.0.0.1:6380> get test
"001"
----------------------------------------------------
127.0.0.1:6381> get test
"001"

jedis测试

maven导包

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

例子1-普通demo

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.IOException;

public class Demo1 {



    public static void main(String[] args) throws Exception {

        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(10);
        jedisPoolConfig.setMaxIdle(5);
        jedisPoolConfig.setMinIdle(3);
        //设置连接到主节点
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.2.190", 6379, 3000, "123456");

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

例子2-管道模式

管道(Pipeline):将多个操作命令通过一个请求发送出去,等全部执行完成再将所有的结果返回,如果某个操作失败不影响其他操作,所以管道操作不具备原子性。

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Pipeline;

import java.util.List;

public class Demo2 {



    public static void main(String[] args) throws Exception {

        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(10);
        jedisPoolConfig.setMaxIdle(5);
        jedisPoolConfig.setMinIdle(3);
        //设置连接到主节点
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.2.190", 6379, 3000, "123456");

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

            //管道示例
            //管道的命令执行方式: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("sch" + i, i+"");
            }
            List<Object> results = pl.syncAndReturnAll();
            //结果显示:[1, OK, 2, OK, 3, OK, 4, OK, 5, OK, 6, OK, 7, OK, 8, OK, 9, OK, 10, OK]
            System.out.println(results);
            

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

例子3-lua脚本模式

可以将一个或多个操作指令用lua脚本规范写好,通过一次请求发送到服务端执行,具有原子性,执行过程不会被其他操作命令干扰,可以作为事务功能的技术支持。

注意:
不要在Lua脚本中出现死循环和耗时的运算,否则redis会阻塞,将不接受其他的命令, 所以使用时要注意不能出现死循环、耗时的运算。redis是单进程、单线程执行脚本。管道不会阻塞redis。

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Pipeline;

import java.util.Arrays;
import java.util.List;

public class Demo3 {



    public static void main(String[] args) throws Exception {

        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(10);
        jedisPoolConfig.setMaxIdle(5);
        jedisPoolConfig.setMinIdle(3);

        //设置连接到主节点
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.2.190", 6379, 3000, "123456");

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

            //lua脚本模拟一个商品减库存的原子操作
            //lua脚本命令执行方式:redis-cli --eval /tmp/test.lua , 10
            jedis.set("iphone", "99");  //初始化商品iphone的库存
            //KEYS[1] 用来表示在redis 中用作键值的参数占位,主要用來传递在redis 中用作keyz值的参数。
            //ARGV[1] 用来表示在redis 中用作参数的占位,主要用来传递在redis中用做 value值的参数。
            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("iphone"), Arrays.asList("10"));
            //结果显示:1;此时,iphone库存本来是99,执行一次a-b,即99-10操作,剩余89
            System.out.println(obj);

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

posted @ 2022-03-01 22:42  匿名用户甲  阅读(682)  评论(0)    收藏  举报