Redis是基于内存的NoSql数据库,同时以其卓越的读写性能闻名业内,并且我在这篇博客Redis持久化机制原理分析与解惑-为什么Redis进行RDB持久化数据时,新起一个进程而不是在原进程中起一个线程中讲过Redis的两种数据持久化方式,但是如果Redis读写压力较大的情况下,将所有的数据都存在一个实例中,这将会大大降低Redis的性能,但是我们可以使用Redis提供的主从复制功能来实现数据冗余和读写分离。
Redis在2.8版本之前,对于从机器每次重连到主都会发送SYNC命令进行全量复制,在2.8版本之后从向主发送PSYNC支持断点续传的复制方式,我们接下来依次看看这两种不同的主从复制方式。
(一)我们先看2.6版本全量主从复制流程:
(1) 从节点起动起来之后主动向主节点发送SYNC命令要求同步数据
(2) 主节点收到SYNC命令之后,fork出一个子进程非阻塞主实例的执行RBD持久化机制,执行完RDB之后将rdb文件发给从节点,
在执行RDB期间主节点将写命令写入缓存,当主节点接到多个从发送SYNC命令之后只执行一次RDB
(3) 从节点收到rdb文件之后载入内存,这个过程从节点可以使用旧数据响应客户端请求,也可以返回一个错误信息
(4) 主节点将缓存中的写命令以Redis命令协议的形式发给从节点
(二)下面先使用版本redis-2.6.16.tar.gz构建一个主从架构
(1)解压 tar zxvf redis-2.6.16.tar.gz
(2)进入到解压后的文件夹redis-2.6.16 执行make命令
(3)复制redis.conf配置文件 为redis_6379.conf、redis_6380.conf、redis_6381.conf
依次修改内容如下:
#主节点配置文件redis_6379.conf
port 6379
#配置log文件
logfile master_6379.log
#主节点关闭持久化机制
#save 900 1
#save 300 10
#save 60 10000
#内存设置为100M,在实际使用中 这里内存实际数值设置要大于实际所需,原因是要为主写命令缓存区预留出空间
maxmemory 104857600
#从节点配置文件redis_6380.conf
port 6380
#配置log文件
logfile slave_6380.log
maxmemory 104857600
#设置将该节点设置为 6379端口实例的从节点
slaveof 127.0.0.1 6379
#从节点配置文件redis_6381.conf
port 6381
#配置log文件
logfile slave_6381.log
maxmemory 104857600
#设置将该节点设置为 6379端口实例的从节点
slaveof 127.0.0.1 6379
(4)进入到src目录下依次执行如下命令:
nohup ./redis-server ../redis_6379.conf &
nohup ./redis-server ../redis_6380.conf &
nohup ./redis-server ../redis_6381.conf &
(5)我们通过log来看一下主从复制过程
这份是主节点的log
这份是其中一个从节点的log
从主从的log中也可以清晰的看到主从复制的流程,其中从节点我们配置文件中设置了slave-serve-stale-data yes,这样从节点就会非阻塞的方式加载rdb文件,并且使用旧数据响应客户端读请求。
(三)登录客户端查看主从信息
使用如下命令依次登录主从节点
./redis-cli -h 127.0.0.1 -p 6379
./redis-cli -h 127.0.0.1 -p 6381
使用info命令看到主从的信息:
主节点主从复制信息如下图,可以看到主节点中有两个从节点,并且从节点都在线
从节点主从复制信息,记录了主节点的相关信息
(四)使用telnet 查看Redis主节点以Redis命令协议形式发送缓存写命令
这里提供一个连接Redis服务端的Jedis工具类给大家
/**
* 连接redis服务的工具类
* @author yujie.wang3
* @since 09/08/2017
*/
public final class RedisUtil {
//Redis服务器IP
private static String ADDR = "10.4.36.87";
//Redis的端口号
private static int PORT = 6379;
//可用连接实例的最大数目,默认值为8;
//如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
private static int MAX_ACTIVE = 100;
//控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
private static int MAX_IDLE = 20;
//等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
private static int MAX_WAIT = 10000;
private static int TIMEOUT = 10000;
//在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
private static boolean TEST_ON_BORROW = true;
private static JedisPool jedisPool = null;
/**
* 初始化Redis连接池
*/
static {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxActive(MAX_ACTIVE);
config.setMaxIdle(MAX_IDLE);
config.setMaxWait(MAX_WAIT);
config.setTestOnBorrow(TEST_ON_BORROW);
jedisPool = new JedisPool(config, ADDR, PORT);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取Jedis实例
* @return
*/
public synchronized static Jedis getJedis() {
try {
if (jedisPool != null) {
Jedis resource = jedisPool.getResource();
return resource;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 释放jedis资源
* @param jedis
*/
public static void returnResource(final Jedis jedis) {
if (jedis != null) {
jedisPool.returnResource(jedis);
}
}
}
我们使用如下代码写入和读取key
/**
* 测试Redis主节点以Redis命令协议的形式向从节点发送写命令
* @author yujie.wang
* @since 09/08/2017
*/
public class RedisTest {
public static void main(String[] args) {
String key0 = "yujie_";
String value = "";
for(int i = 0; i <= 10000; i++){
String key = key0 + String.valueOf(i);
value = String.valueOf(i);
//写入key
addKeys(key, value);
System.out.println("add key"+ key + " value: "+ value);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//读取key
System.out.println("get_Key: " + key + " : "+ getKey(key));
}
}
public static String getKey(String key) {
Jedis client = RedisUtil.getJedis();
String value = client.get(key);
RedisUtil.returnResource(client);
return value;
}
public static void addKeys(String key ,String value){
Jedis client = RedisUtil.getJedis();
client.set(key, value);
RedisUtil.returnResource(client);
}
}
启动程序之后我们使用telnet 127.0.0.1 6379 访问主节点,并发送sync命令:
从这个过程可以看到 主节点首先传过来一个包含内存中数据的rdb文件,之后会以redis命令协议的形式发送写命令,并且每写一个命令就会发送一个命令。基于此我们可以实现redis的读写分离机制。