1.集群概念
1.1.为什么要集群
- 防止单点故障
单节点的机器(应用)总会有单点故障的问题,当你的机器宕机,整个系统不可被访问,通常我们可以通过集群的方式来解决单节点故障,提高系统的可用性。
- 提升作业能力
单个机器(应用)的作业能力是有限的,在某些高业务量(并发)的应用中,我们也可以通过集群的手段来提升应用的作业能力。
1.2.什么是集群
应用架构的演变:https://editor.csdn.net/md/?articleId=104487950
- 集群概念
为了提升应用的并发作业能力我们通常会对应用做集群,这里的集群指的是把应用进行复制多个相同的应用一起工作来提高作业能力,多个应用做的是相同的事情。就如同我们生活中的场景"收银",一个收银台不足以支撑超市大量客户的结账流程,所以需要多个收银台同时工作,多个收银台都是做的相同的收银工作,这就是集群。集群可以分为数据库集群,应用集群和功能集群等
总结一下:集群就是多个应用分散在不同的服务器,每个应用跑的是同一套代码做的是相同的工作,以提高系统的整体性能,简单理解"多个人在一起做相同的事情,每个人做的事情都是完整的,有点1+1=2的感觉"。如下图:
- 图解集群
- 谈谈负载均衡
当应用做了集群之后作业能力得到提升,能够处理更高的并发请求,同时产生了一个新的问题,就是客户端的请求应该如何相对平局的分发到集群中的多个应用呢?这就需要负载均衡器了。
为了方便理解我们来再看我们的收银台案例:假如收银台排队的客户比较多,收银台可能也比较多,后面继续加入排队的客户可能无法选择人数最少的那个收银台进行排队,为了解决这一情况我们增加一个大显示屏来显示每个收银台的排队结账人数,那么结账客户可以就可以根据屏幕显示的人数来选择人数最少的收银台队列,如下图:
- 图解负载均衡
在我们的应用程序中也是这样,当我们的应用做了集群,那么就会存在多个应用节点,多个应用将会暴露多个访问地址(ip:port),那客户端是不知道该访问哪个应用节点的,这个时候我们就需要有一个请求分发的功能的组件(负载均衡器)将客户端的请求相对平均的分发多个应用节点上,这就是负载均衡,这个做请求分发的组件就是负载均衡器,如下图:
- 做个小结
总之,当我们的单体应用没办法支撑较高的并发请求时,我们可以对应用做集群,同时加入负载均衡器来提升应用的作业能力,甚至我们也可以在数据库层也做集群(如主从复制),这样的架构有一定的并发处理能力,也能满足一定的复杂业务需求,但是就应用本身而言任然是单体结构,仍然有其不足之处,如:
- 项目本身任然是单体,代码依然臃肿
- 面对海量数据数据库会成为性能瓶颈(需要分库分表)
- 持续交互能力差,代码臃肿,业务复杂,开发维护时间长,新人入手成本高。
所以当单体应用不能满足复杂的业务和海量数据的系统时,我们需要考虑其他的架构方式。
1.3.什么是分布式
- 分布式概念
我们知道,单体应用包含了整个项目的所有代码和服务逻辑,也就是说一个Tomcat需要把整个系统跑起来,那么Tomcat总有处理不过来的时候,我们是否可以为Tomcat减轻一些压力呢?当然可以。我们拿生活中的案例"厨师炒菜",来说,当饭店规模小的时候我们一个厨师就可以完成所有厨房的工作如"洗菜",“切菜”,“炒菜”三件事情,当饭店规模增加,一个厨师就忙不过来,这个时候可能要再招聘两个厨师,然后进行分工,厨师A负责洗菜,厨师B负责切菜,厨师C负责炒菜,这样一来每个厨师的压力减轻,并且每个厨师就只需要专注一件事情即可。
将上面的案例映射到我们的应用程序中也是一样的道理,当一个Tomcat负担不起系统中的所有业务的时候,我们可以按照业务把应用进行拆分成多个小的应用,每个小应用都用一个Tomcat去运行,每个小应用都是单体架构,每个应用只需要关系自己的业务即可,应用之间通过API相互调用,如图:
- 图解分布式
- 做个小结
分布式就是将应用按照业务进行拆分成多个子应用,多个子应用部署在不同的服务器中,多个子应用组成一个完整的系统,所有的子系统一起工作相互通信相互协调才能完成最终的业务流程,缺一不可,简单理解:“多个人在一起做不同的事情,多人和在一起才是一件完整的事情,有点0.5+0.5=1的感觉”。
- 集群和分布式的区别
简单说,集群就是“复制”,多个应用代码一样,功能一样,每个应用都是完整的系统,集群中的某个应用宕机也不影响。而分布式就是“拆分”,一个大的应用拆分成多子应用,每个应用代码不一样,功能也不一样,多个应用组合起来才是完整的系统,缺一不可。某个子应用宕机了,整个系统就不健康。
1.5.集群的特性
- 可扩展性
集群的性能不限于单一的服务实体,新的服务实体可以动态地加入到集群,从而增强集群的性能,或者减少服务器实现集群缩容 - 动态扩容缩容
- 高可用性
集群通过服务实体冗余使客户端免于轻易遇到out of service的警告。在集群中,同样的服务可以由多个服务实体提供。如果一个服务实体失败了,另一个服务实体会接管失败的服务实体。集群提供的从一个出错的服务实体恢复到另一个服务实体的功能增强了应用的可用性。
当访问的服务器挂了时,集群要有能力找可以正常使用额服务器继续提供服务器。
1.6.集群的能力
- 负载均衡
负载均衡能根据负载均衡算法把任务比较均衡地分布到集群环境下的计算和网络资源。
- 错误恢复
由于某种原因,执行某个任务的资源出现故障,另一服务实体中执行同一任务的资源接着完成任务。这种由于一个实体中的资源不能工作,另一个实体中的资源透明的继续完成任务的过程叫错误恢复。
当访问的服务器挂了时,集群要有能力找可以正常使用额服务器继续提供服务器。
1.7.图解Tomcat集群与负载均衡
通常情况下我们的应用(Tomcat)集群后需要做负载均衡(Nginx),而对于Nginx可以使用Keepalived做主备切换解决单节点故障问题。如下图:
2.Redis集群方案
2.1.Redis为什么做集群
- 采用Redis集群方案解决单点故障问题
- 对于高并发读写请求,采用集群方案提升Redis作业能力
- 处理大量数据,多个Redis节点实现数据的分布式存储
2.2.Redis主从复制
- 什么是主从复制
Redis有三种集群方案,主从赋值,哨兵,cluster集群,主从复制是指将一台Redis服务器的数据,复制到其他的Redis服务器
。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
写请求到主Redis,读请求到从Redis
,读/写的路由需要负载均衡器(主Twemproxy/从Twemproxy) ,而主从Redis的负载均衡器需要做主备切换(keeplived)
- 图解Redis主从
- 主从复制的作用
数据冗余
:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
故障恢复
:当主节点出现问题时可以由从节点提供服务实现快速的故障恢复;实际上是一种服务的冗余
负载均衡
:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
读写分离
:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量。
高可用基石
:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
- 主从的缺点
不具备自动容错和恢复功能
:主从宕机会影响前端的部分请求失败,需要重启机器或者手动切换前端IP才能恢复
主机宕机数据丢失
:宕机前部分有部分数据未同步到从机,切换IP后会引入数据不一致降低系统可用性
数据大的问题
:数据量特别大一个主是存储不了
2.3.Redis哨兵模式
https://cloud.tencent.com/developer/article/1409270
- 什么是哨兵模式(Redis-Sentinel)
当主服务器中断服务后,可以将一个从服务器升级为主服务器
,以便继续提供服务,但是这个过程需要人工手动来操作。 为此,Redis 2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。
Redis-Sentinel是用于管理Redis集群,该系统执行以下三个任务
监控(Monitoring)
Sentinel会不断地检查你的主服务器和从服务器是否运作正常;
提醒(Notification)
当被监控的某个Redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知;
自动故障迁移(Automatic failover)
当主服务器不可用,Sentinel会自动选举一个从服务器作为新的主服务器,并让其它的从服务器从新的主服务器复制数据,用户的请求会被变更为新的主服务器。
- 图解Redis哨兵
- 主观下线&客观下线&故障转移
主观下线
每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。
如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线
(SDOWN)
如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态
客观下线
当有足够数量(半数以上)的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线
(ODOWN)
若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。
故障转移
当前哨兵虽然发现了主数据客观下线,需要故障恢复,但故障恢复需要由领头哨兵来完成。这样来保证同一时间只有一个哨兵来执行故障恢复,选出领头哨兵后,领头哨兵将会开始对主数据库进行故障恢复。
首先是从主服务器的从服务器中选出一个从服务器作为新的主服务器。选出之后通过slaveif no ont将该从服务器升为新主服务器。
所有在线的从数据库中,选择优先级最高的从数据库。优先级通过replica-priority参数设置,优先级相同,则复制的命令偏移量越大(复制越完整)越优先 , 如果以上都一样,则选择运行ID较小的从数据库
选出一个从数据库后,领头哨兵将向从数据库发送SLAVEOF NO ONE命令使其升格为主数据库,而后领头哨兵向其他从数据库发送 SLAVEOF命令来使其成为新主数据库的从数据库,最后一步则是更新内部的记录,将已经停止服务的旧的主数据库更新为新的主数据库的从数据库,使得当其恢复服务时自动以从数据库的身份继续服务。
- 哨兵的优缺点
哨兵的优点
哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。主从可以自动切换,系统更健壮,可用性更高。
哨兵的缺点
主从服务器的数据要经常进行主从复制,这样造成性能下降。当主服务器宕机后,从服务器切换成主服务器的那段时间,服务是不能用的。
Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂
2.4.Redis-Cluster集群
- 为什么要Redis-Cluster
redis的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台redis服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了cluster模式,实现的redis的分布式存储,也就是说每台redis节点上存储不同的内容
。
- 什么是Redis-Cluster
Redis-Cluster采用无中心结构,集群中的每个节点都是平等的关系,都是对等的,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接
,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据
。
- 数据分散存储
Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用另外一种叫做哈希槽 (hash slot)
的方式来分配的。redis cluster 默认分配了 16384 个slot,当我们set一个key 时,会用CRC16
算法来取模得到所属的slot
,然后将这个key 分到哈希槽区间的节点上,具体算法就是:CRC16(key) % 16384
。
- 容错机制-投票
为了防止主节点数据丢失,可以为每个主节点可以准备特点数目的备节点,主节点挂掉从节点可以升级为主节点(哨兵模式) 。
容错机制指的是,如果半数以上master节点与故障节点通信超过(cluster-node-timeout),认为该节点故障,自动触发故障转移操作. 故障节点对应的从节点自动升级为主节点 , 如果某个主挂掉,而没有从节点可以使用,那么整个Redis集群进入宕机状态
十一.Redis集群搭建
1.环境准备
1.1.Redis 3.2
- 需要 6 台 redis 服务器。搭建伪集群。
- 6 台 redis 服务器需要运行在不同的端口 6379-6384
1.2.Ruby语言运行环境
我们需要使用ruby脚本来实现集群搭建
- Ruby 打包系统RubyGems
RubyGems简称gems,是一个用于对 Ruby组件进行打包的 Ruby 打包系统 - Redis的Ruby驱动redis-xxxx.gem
- 创建Redis集群的工具redis-trib.rb
2.Redis集群安装
2.1.安装6台Redis
- 在Window搭建6个伪集群每个Redis创建启动脚本start.bat,内容如下
title redis-3679 redis-server.exe redis.windows.conf • 1 • 2
2.2.Redis配置
- 修改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
2.3.安装Ruby
https://rubyinstaller.org/downloads/
- rubyinstaller-2.6.3-1-x64.exe 傻瓜式安装即可
2.4.安装Ruby驱动
https://rubygems.org/pages/download
- 解压Ruby驱动(rubygems-2.7.7) , 进入根目录,执行
ruby setup.rb • 1
- 切入到Redis目录执行(6379的Redis目录) gem install redis
2.5.执行集群构建脚本
下载Redis源码 http://www.redis.net.cn/download/
,拷贝 redis-trib.rb :redis-trib 位于 Redis 源码的 src 文件夹中,将其复制到任一 redis 节点目录下
- 启动6个Redis
- 拷贝redis-trib.rb到Redis目录(6379的Redis目录)
- 在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 • 1
- 在出现 Can I set the above configuration? (type ‘yes’ to accept): 请确定并输入 yes 。成功后的结果如下:
2.6.测试集群命令
- 启动客户端:redis-cli –c –h 127.0.0.1 –p 6379 , 可以跟任何节点的端口
- 查看整个集群:cluster info
- 查看当前Redis:info replication
- 查看槽位:cluster nodes
2.7.集群代码测试
- 使用JedisCluster链接集群
@Test public void testCluster() throws IOException, InterruptedException { 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 { String res = cluster.get("name"); System.out.println(res); //cluster.quit(); } catch (Exception e) { e.printStackTrace(); //cluster.quit(); } }