原理
利用redis 的主从复制,哨兵机制实现读写分离和高可用
1、redis 主从复制:redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。
2、Redis 哨兵机制三大功能:
监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。
提醒(Notification): 当被监控的某个Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover): 当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master
实现步骤
1、配置redis主从复制
1、准备两台服务器(192.168.138.128、192.168.138.129)分别安装上redis,(可用yum install -y redis),以128服务器为主服务器,redis的主节点,129服务器为redis的从服务器。
2、修改129从节点的redis配置文件,在配置文件中添加两项
(若是用yum源安装的redis ,配置文件默认在/etc 路径下, vim /etc/redis.conf)
slaveof 192.168.138.128 6379(主服务器的ip和端口)
masterauth 123456 (主服务器redis 密码,没有可以不写)
3、主从服务器都需求修改远程访问 (vim /etc/redis.conf)
bind 0.0.0.0(允许所有ip访问)
protected-mode no(放开远程访问)
4、主从服务器开放防火墙端口
firewall-cmd --zone=public --add-port=6379/tcp --permanent #开启端口
firewall-cmd --reload #重启firewall
5、启动启动两台服务器的redis (systemctl status redis命令启动),用redis Manager 远程工具连接,并在主服务器redis(128)上添加一个值,在从服务器的redis可以查看到一个同样的值,并且在从服务器redis上无法添加值,则主从服务配置成功!如图所示
6、进入redis客户端用info replication 命令查看redis 角色
2、哨兵模式配置(高可用)
1、在主从服务器上修改sentinel.conf 哨兵配置文件(默认在/etc/sentinel.conf),找不到可以使用find / -name "sentinel*"命令进行查找
2、修改完成之后启动哨兵模式,/usr/bin/redis-server /etc/sentinel.conf --sentinel &(/usr/bin/redis-server 为redis服务端启动文件)
用ps -ef|grep redis 查看sentinel是否启动
3、验证哨兵是否生效
首先将主服务器的redis 的进程杀死
在从服务器的redis客户端用info replication 命令查看redis 角色,这时从服务器的redis已经提升为master
java应用
// An highlighted block
package com.redis.temp.kaiwen.conf;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* The type Jedis pool factory.
*
* @author ljy
* @version 1.0
* @date 2019 -12-12 14:31:01
*/
@Configuration
public class JedisPoolFactory {
//master
@Value("${master.spring.redis.host}")
private String masterHost;
@Value("${master.spring.redis.port}")
private int masterPort;
@Value("${master.spring.redis.password}")
private String masterPassword;
@Value("${master.spring.redis.database}")
private int masterDatabase;
//node
@Value("${node.spring.redis.host}")
private String nodeHost;
@Value("${node.spring.redis.port}")
private int nodePort;
@Value("${node.spring.redis.password}")
private String nodePassword;
@Value("${node.spring.redis.database}")
private int nodeDatabase;
private static final int MAX_IDLE = 200; //最大空闲连接数
private static final int MIN_IDLE = 50; //最大空闲连接数
private static final int MAX_TOTAL = 1024; //最大连接数
private static final long MAX_WAIT_MILLIS = 10000; //建立连接最长等待时
/**
* Gets jedis pool.
*
* @return the jedis pool
*/
@Bean(name = "masterPool")
public JedisPool getMasterJedisPool() {
return getJedisPool(masterHost, masterPort, masterPassword, masterDatabase);
}
@Bean(name = "nodePool")
public JedisPool getNodeJedisPool() {
return getJedisPool(nodeHost, nodePort, nodePassword, nodeDatabase);
}
private JedisPool getJedisPool(String nodeHost, int nodePort, String nodePassword, int nodeDatabase) {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(MAX_IDLE);
config.setMinIdle(MIN_IDLE);
config.setMaxTotal(MAX_TOTAL);
config.setMaxWaitMillis(MAX_WAIT_MILLIS);
config.setMinEvictableIdleTimeMillis(60000);
config.setNumTestsPerEvictionRun(-1);
config.setTestOnReturn(true);
return new JedisPool(config, nodeHost, nodePort, 1000, nodePassword, nodeDatabase);
}
}
package com.redis.temp.kaiwen.redis;
import io.netty.channel.ChannelHandler.Sharable;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import javax.annotation.Resource;
/**
* @author: liangjinyin
* @Date: 2020-11-06
* @Description: redis 操作工具类
*/
@Component
@Sharable
public class Master {
@Resource(name = "masterPool")
private JedisPool jedisPool;
/**
* 获取String值,获取不到返回默认值
*
* @param key the key
* @param value the value
* @return value or default
*/
public String getStringValueOrDefault(String key, String value) {
if (StringUtils.isEmpty(key)) {
return value;
}
if (jedisPool == null) {
return value;
}
try (Jedis jedis = jedisPool.getResource()) {
String s = jedis.get(key);
return StringUtils.isEmpty(s) ? value : s;
} catch (Exception e) {
e.printStackTrace();
return value;
}
}
}
package com.redis.temp.kaiwen.redis;
import io.netty.channel.ChannelHandler.Sharable;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import javax.annotation.Resource;
/**
* @author: liangjinyin
* @Date: 2020-11-06
* @Description: redis 操作工具类
*/
@Component
@Sharable
public class Node {
@Resource(name = "nodePool")
private JedisPool jedisPool;
/**
* 获取String值,获取不到返回默认值
*
* @param key the key
* @param value the value
* @return value or default
*/
public String getStringValueOrDefault(String key, String value) {
if (StringUtils.isEmpty(key)) {
return value;
}
if (jedisPool == null) {
return value;
}
try (Jedis jedis = jedisPool.getResource()) {
String s = jedis.get(key);
return StringUtils.isEmpty(s) ? value : s;
} catch (Exception e) {
e.printStackTrace();
return value;
}
}
}
package com.redis.temp.kaiwen.redis;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @author: liangjinyin
* @Date: 2020-11-06
* @Description: redis 工具厂
*/
@Component
public class RedisUtils {
@Resource
private Master master;
@Resource
private Node node;
public Master getMaster() {
return master;
}
public Node getNode() {
return node;
}
}
package com.redis.temp.kaiwen.redis;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author: liangjinyin
* @Date: 2020-11-06
* @Description:
*/
@RestController
@RequestMapping("/redis")
@Slf4j
public class RedisTestController {
@Resource
private RedisUtils redisUtils;
@GetMapping("/aa")
public String getTest() {
String master = redisUtils.getMaster().getStringValueOrDefault("ff", "ee");
String node = redisUtils.getNode().getStringValueOrDefault("aa", "ee");
log.info("redis主节点获取值{}, redis 次节点获取值{}", master, node);
return master + node;
}
}
参考文献
[1]: https://blog.csdn.net/qq_40742298/article/details/104255152?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight
[2]: https://www.jianshu.com/p/7d5fbf90bcd7