1、集群的概念
1.1、什么是集群
同一个业务,部署在多个服务器上(不同的服务器运行同样的代码,干同一件事)。
1.2、为什么要集群
防止单点故障,高可用性,一台服务器出现故障, 集群有能力找到正常的服务器继续提供服务。
1.3、集群的能力
- 负载均衡
把请求根据某种算法相对平衡的路由到集群的应用 - 错误恢复
集群下的某个应用挂了需要找到能用的服务继续处理请求 - 主备切换
对于应用的集群,某个应用挂掉了,集群中需要有其他的应用顶上以处理请求。
1.4、Redis集群好处
- 防止单点故障
- 解决高并发
- 处理大量数据
2、集群的方案
2.1、Redis主从复制
2.1.1、什么是主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
写请求到主Redis,读请求到从Redis ,读/写的路由需要负载均衡器(主Twemproxy/从Twemproxy) ,而主从Redis的负载均衡器需要做主备切换(keeplived)
图例:
2.1.2、主从复制的作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时可以由从节点提供服务实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 读写分离:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
2.1.3、主从复制的缺点
- 不具备自动容错和恢复功能:主从宕机会影响前端的部分请求失败,需要重启机器或者手动切换前端IP才能恢复。
- 大量数据的问题没有解决。
2.2、Redis哨兵模式
2.2.1、当主服务器中断服务后,可以将一个从服务器升级为主服务器
,以便继续提供服务,但是这个过程需要人工手动来操作。 为此,Redis 2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。
图例:
2.2.2、哨兵模式的作用
哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。
另外还有:
- 监控主服务器和从服务器是否正常运行。
- 主服务器出现故障时自动将从服务器转换为主服务器。
2.2.3、哨兵模式的缺点
- 大量数据的问题依然没有解决。
2.3、Redis-Cluster集群
2.3.1、什么是Redis-Cluster
Redis-Cluster采用无中心结构,集群中的每个节点都是平等的关系,都是对等的,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。
图例:
2.3.2、为什么要Redis-Cluster
redis的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台redis服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了cluster模式,实现的redis的分布式存储,也就是说每台redis节点上存储不同的内容。
2.3.3、数据分散存储
Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用另外一种叫做哈希槽 (hash slot)
的方式来分配的。redis cluster 默认分配了 16384 个slot,当我们set一个key 时,会用CRC16
算法来取模得到所属的slot
,然后将这个key 分到哈希槽区间的节点上,具体算法就是:CRC16(key) % 16384
。
2.3.4、容错机制-投票
Redis 为了防止主节点数据丢失,可以为每个主节点可以准备特点数目的备节点,主节点挂掉从节点可以升级为主节点(哨兵模式) 。
Redis容错机制指的是,如果半数以上master节点与故障节点通信超过(cluster-node-timeout),认为该节点故障,自动触发故障转移操作. 故障节点对应的从节点自动升级为主节点 , 如果某个主挂掉,而没有从节点可以使用,那么整个Redis集群进入宕机状态
3、集群环境搭建
3.1、准备工作
3.1.1、Redis 3.2
- 需要 6 台 redis 服务器。搭建伪集群。
- 需要 6 个 redis 实例。
- 需要运行在不同的端口 6379-6384。
3.1.2、Redis 3.2 Ruby语言运行环境
我们需要使用ruby脚本来实现集群搭建
Ruby,一种简单快捷的面向对象(面向对象程序设计)脚本语言,在20世纪90年代由日本人松本行弘(Yukihiro Matsumoto)开发,遵守GPL协议和Ruby License。它的灵感与特性来自于 Perl、Smalltalk、Eiffel、Ada以及 Lisp 语言。由 Ruby 语言本身还发展出了JRuby(Java平台)、IronRuby(.NET平台)等其他平台的 Ruby 语言替代品。Ruby的作者于1993年2月24日开始编写Ruby,直至1995年12月才正式公开发布于fj(新闻组)。因为Perl发音与6月诞生石pearl(珍珠)相同,因此Ruby以7月诞生石ruby(红宝石)命名。
3.1.2、RubyGems
RubyGems简称gems,是一个用于对 Ruby组件进行打包的 Ruby 打包系统
3.1.3、Redis的Ruby驱动redis-xxxx.gem
3.1.4、创建Redis集群的工具redis-trib.rb
3.2、搭建
要让集群正常运作至少需要3个主节点,建议配置3个主节点,其余3个作为各个主节点的从节点(也是官网推荐的模式)。同一台电脑,不同端口模拟。
3.2.1、安装6个服务器
3.2.2、修改配置
每个Redis创建启动脚本start.bat,内容如下
①start.bat
title redis-6379
redis-server.exe redis.windows.conf
②redis.windows.conf
- 端口号:6379、6380、6381、6382、6383、6384。
- 开启cluster-enabled:cluster-enabled yes。
- 指定集群配置文件:
cluster-config-file nodes-6379.conf
,cluster-config-file nodes-6379.conf 是为该节点的配置信息,这里使用 nodes-端口.conf命名方法。服务启动后会在目录生成该文件。 - 指定超时:`cluster-node-timeout 15000。
- 开启持久:`appendonly yes。
3.2.3、安装Ruby
傻瓜式安装,安装完成finish即可。
3.2.4、安装Ruby驱动
①解压Ruby驱动,进入根目录,执行
ruby setup.rb
②切入到Redis目录执行(6379的Redis目录) gem install redis
3.2.5.执行集群构建脚本
- 1、启动6个Redis
- 2、拷贝redis-trib.rb到Redis目录(6379的Redis目录)
- 3、在6379根目录执行构建脚本:
redis-trib.rb create --replicas 1 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384
- 4、在出现 Can I set the above configuration? (type ‘yes’ to accept):
请确定并输入 yes 。成功后的结果如下:
3.2.6.测试集群命令
- 启动客户端:redis-cli –c –h 127.0.0.1 –p 6379 , 可以跟任何节点的端口
- 查看整个集群:cluster info
- 查看当前Redis:info replication
- 查看槽位:cluster nodes
3.2.7.集群代码测试
import org.junit.Test;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import java.util.HashSet;
import java.util.Set;
public class TestRedis {
@Test
public void test() throws Exception{
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("127.0.0.1", 6379));
nodes.add(new HostAndPort("127.0.0.1", 6380));
nodes.add(new HostAndPort("127.0.0.1", 6381));
nodes.add(new HostAndPort("127.0.0.1", 6382));
nodes.add(new HostAndPort("127.0.0.1", 6383));
nodes.add(new HostAndPort("127.0.0.1", 6384));
JedisCluster cluster = new JedisCluster(nodes);
try {
cluster.set("name","huahua");
String res = cluster.get("name");
System.out.println(res);
//cluster.quit();
} catch (Exception e) {
e.printStackTrace();
//cluster.quit();
}
}
}