1. 为什么要搭建集群?
a) 服务器可能因为代码原因,人为原因,或者自然灾害等造成服务器损坏。数据服务就挂掉了
b) 大公司都会有很多的服务器(华东地区、华南地区、华中地区、华北地区、西北地区、西南地区、东北地区、台港澳地区机房)
2. 什么是集群?
集群是一组相互独立的、通过高速网络互联的计算机,它们构成了一个组,并以单一系统的模式加以管理。一个客户与集群相互作用时,集群像是一个独立的服务器。集群配置是用于提高可用性和可缩放性。
3. Redis集群架构图
软件层面:只有一台电脑,在这台电脑上启动了多台redis服务。
硬件层面:存在多台实体电脑,每台电脑都启动了一个或者多个redis服务。
4. 搭建集群
4.1 环境准备
这里准备了两个主机环境,IP分别为192.168.31.20和192.168.31.21。每一个台主机启动3个redis服务,它们分别是:
192.168.31.20:7000
192.168.31.20:7001
192.168.31.20:7002
192.168.31.21:7003
192.168.31.21:7004
192.168.31.21:7005
4.2 搭建Redis集群的步骤
4.2.1 在192.168.31.20主机上配置集群
(1)在用户主目录下新建一个conf文件夹
$ mkdir ~/conf
(2)在conf文件夹下新建7000.conf配置文件
# 节点端口号
port 7000
# 节点IP
bind 192.168.31.20
# 在后台运行服务
daemonize yes
# 记录redis-server进程ID的文件,该文件是自动生成
pidfile 7000.pid
# 启用集群
cluster-enabled yes
# 集群配置文件,该文件是自动生成
cluster-config-file 7000_node.conf
# 集群节点超时时间,默认为15s。如果超过了15秒该节点没向其它节点汇报成功,则认为该节点已经挂了
cluster-node-timeout 15000
# 启用aof增量持久化策略
appendonly yes
什么是aof增量持久化?
Redis以日志的形式来记录每个写操作,它会把执行过的所有“写”指令记录到aof文件中。当Redis启动时候,它会自动读取该文件内容并重新构建数据。换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。
(3)在conf文件夹下新建7001.conf
port 7001
bind 192.168.31.20
daemonize yes
pidfile 7001.pid
cluster-enabled yes
cluster-config-file 7001_node.conf
cluster-node-timeout 15000
appendonly yes
(4)在conf文件夹下新建7002.conf
port 7002
bind 192.168.31.20
daemonize yes
pidfile 7002.pid
cluster-enabled yes
cluster-config-file 7002_node.conf
cluster-node-timeout 15000
appendonly yes
(5)启动集群节点
$ cd ~/conf
$ redis-server 7000.conf
$ redis-server 7001.conf
$ redis-server 7002.conf
4.2.2 在192.168.31.21主机上配置集群
(1)在用户主目录下新建一个conf文件夹
$ mkdir ~/conf
(2)在conf文件夹下新建7003.conf配置文件
port 7003
bind 192.168.31.21
daemonize yes
pidfile 7003.pid
cluster-enabled yes
cluster-config-file 7003_node.conf
cluster-node-timeout 1500
appendonly yes
(3)在conf文件夹下新建7004.conf
port 7004
bind 192.168.31.21
daemonize yes
pidfile 7004.pid
cluster-enabled yes
cluster-config-file 7004_node.conf
cluster-node-timeout 15000
appendonly yes
(4)在conf文件夹下新建7005.conf
port 7005
bind 192.168.31.21
daemonize yes
pidfile 7005.pid
cluster-enabled yes
cluster-config-file 7005_node.conf
cluster-node-timeout 15000
appendonly yes
(5)启动集群节点
$ cd ~/conf
$ redis-server 7003.conf
$ redis-server 7004.conf
$ redis-server 7005.conf
4.2.3 创建集群
在Redis安装包中包含redis-trib.rb文件,该文件用于创建Redis集群。下面操作在192.168.31.20主机上进行。
- 创建集群步骤:
第一步:将redis-trib.rb拷贝到/usr/local/bin目录下。
$ cp /usr/local/redis/src/redis-trib.rb /usr/local/bin
第二步:安装ruby环境。
$ sudo apt-get install ruby
在提示信息处输入y,然后继续安装,直到安装完成为止。
第三步:安装成功后,运行下面命令创建集群。
$ redis-trib.rb create --replicas 1 192.168.31.20:7000 192.168.31.20:7001 192.168.31.20:7002 192.168.31.21:7003 192.168.31.21:7004 192.168.31.21:7005
启动成功后,界面如下所示:
4.2.4 测试
方式一:在命令行访问Redis集群。
在执行redis-cli命令时候加上参数-c代表连接到集群。
方式二:在java程序中访问Redis集群。
@Test
public void test3() throws Exception{
// 定义Set集合,集合元素是HostAndPort类型,一个HostAndPort对象用于封装一个集群节点的主机与端口
Set<HostAndPort> nodes = new HashSet<>();
// 添加多个集群节点
nodes.add(new HostAndPort("192.168.31.20", 7000));
nodes.add(new HostAndPort("192.168.31.20", 7001));
nodes.add(new HostAndPort("192.168.31.20", 7002));
nodes.add(new HostAndPort("192.168.31.21", 7003));
nodes.add(new HostAndPort("192.168.31.21", 7004));
nodes.add(new HostAndPort("192.168.31.21", 7005));
// 创建JedisCluster集群对象
JedisCluster jedisCluster = new JedisCluster(nodes);
// 设置数据
jedisCluster.set("name", "joey");
// 获取数据
System.out.println(jedisCluster.get("name"));
// 关闭资源
jedisCluster.close();
}
4.3 redis集群的运行原理
- 主从复制:
redis集群主要分为两个角色:master(主)和slave(从)。redis集群中只有一个master,但是可以有多个slave。一般master负责写数据,slave负责读数据。当发生写操作时候,master会自动将数据同步到多个slave节点上。假如master节点挂了,如果启动了选举机制,那么redis集群会通过投票方式选择一个slave节点升级为master。
值得注意的是,redis集群必须要至少有3个master节点,否则在创建集群时会失败。并且当存活的master节点数小于总节点数的一半时,整个集群就无法提供服务了。
- 哨兵机制:
redis自带哨兵(sentinel)功能。哨兵其实是一个分布式系统,它主要负责管理多个redis服务。比如说:redis服务的心跳检测、报警、故障迁移等。可以同时运行多个哨兵进程,这些哨兵进程通过gossip协议
来接收master是否下线的信息,并通过投票方式来决定是否执行故障迁移,以及选择哪个slave节点来充当新的master。
5. SpringBoot整合Redis
第一步:配置pom。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
第二步:定义配置类,在该配置类中配置JedisCluster。
@Configuration
@ConfigurationProperties(prefix = "jedis.cluster")
public class JedisClusterConfig {
private String nodesString;
private Integer connectionTimeout;
private Integer soTimeout;
private Integer maxAttempts;
private String password;
public Integer getConnectionTimeout() {
return connectionTimeout;
}
public void setConnectionTimeout(Integer connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getSoTimeout() {
return soTimeout;
}
public void setSoTimeout(Integer soTimeout) {
this.soTimeout = soTimeout;
}
public Integer getMaxAttempts() {
return maxAttempts;
}
public void setMaxAttempts(Integer maxAttempts) {
this.maxAttempts = maxAttempts;
}
public String getNodesString() {
return nodesString;
}
public void setNodesString(String nodesString) {
this.nodesString = nodesString;
}
@Bean
public JedisCluster jedisCluster() {
// 获取集群节点的主机地址和端口号
String[] nodes = nodesString.split(",");
// 定义一个集合,集合元素类型为HostAndPort类型,每一个HostAndPort存储一个集群节点的IP和端口
Set<HostAndPort> nodeSet = new HashSet<HostAndPort>(nodes.length);
// 遍历所有节点,把获取到节点的IP和端口封装到一个HostAndPort对象中,并添加到Set集合
for (String node : nodes) {
String[] nodeAttrs = node.trim().split(":");
HostAndPort hap = new HostAndPort(nodeAttrs[0], Integer.valueOf(nodeAttrs[1]));
nodeSet.add(hap);
}
// 创建JedisPoolConfig对象,该对象封装了连接池的一些参数信息
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 创建并返回JedisCluster对象
JedisCluster jc = new JedisCluster(nodeSet, connectionTimeout, soTimeout, maxAttempts,password, poolConfig);
return jc;
}
}
第三步:配置redis集群。
jedis:
cluster:
nodesString: 192.168.31.20:7000,192.168.31.20:7001,192.168.31.20:7002,192.168.31.21:7003,192.168.31.21:7004,192.168.31.21:7005
connectionTimeout: 60
soTimeout: 60
maxAttempts: 1000
第四步:测试。
@Service
public class ContentServiceImpl implements IContentService {
@Autowired
private JedisCluster jedisCluster;
@Override
public String findBigAdData() {
try{
// 从redis中读取大广告数据
String jsonData = jedisCluster.get("bigAdData");
if (StringUtils.isNotBlank(jsonData)){
return jsonData;
}
// 读取数据库,并且把读取到数据保存在redis中
// ....
// 写入缓存中
jedisCluster.set("bigAdData", jsonData);
return jsonData;
}catch(Exception ex){
throw new RuntimeException(ex);
}
}
}