集群模式相关知识
一、一致性Hash算法
分布式和集群
分布式一定是集群,但是集群不一定是分布式,集群是多个实例一起工作,分布式是将一个系统拆分成多个,就是多个实例,而集群的满足条件不一定是系统拆分,也可以是复制,相同服务多部署,提高程序性能。
理解:比如一个电商网站,里面的业务复杂,于是就进行了拆分,把每个模块拆成了一个个子系统,然后一起协调工作运行,这就是分布式,而集群呢,可以是另一种,当一个服务器已经不能满足访问要求的时候,我可以这个用户子系统,我复制部署在多个服务器,通过Nginx进行负载均衡,可以对用户请求进行分流,这就没有拆分系统,所以他只能是集群。
Hash算法简介
Hash算法应用广泛,如加密算法MD5、SHA,数据储存和查找的Hash表等,而平常我们接触最多的就是Hash表,比如有一个数组,需要对他的数据进行查找,常用的一些查找法比如顺序查找、二分查找,如果是数据量大了,效率就非常低下,因此又有一种直接寻址法,把数组下标和值直接绑定在一起,通过下标来直接查找,但是如果两个值中间值差大,对空间又非常浪费;接着又有开放寻址法,对每一个数据进行取模,然后放入对应的下标中,但是如果是相同的取模值,就会产生hash冲突,他就把冲突值往前或后移,但是也有问题,无法扩展,长度为5的数组只能放5个值,因此数组加链表的Hash表就出现了,也就是我们常用的HashMap的设计结构。
数组:数组存储空间是连续的,占用内存严重,空间复杂度大,二分法时间复杂度小,查找容易,插入和删除困难。
链表:链表存储空间离散,占用内存比较宽松,空间复杂度小,时间复杂度大,查找困难,插入和删除简单。
Hash表又称为哈希表:由数组和链表构成,既满足了数据查找方便,又不用占用太多内存,通过key获取hashcode值,再取模的规则存储到数组中。Hashmap是一个线性数组实现,里面实现了一个静态内部类entry(键值对对象),其重要属性有key、value、next,从属性key、value就可以看出来entry是hashmap键值对实现的一个继承bean(类)。这个线性数组被保存在entry[]中。
Hash算法应用场景
Hash算法在分布式集群架构Redis、Hadoop、ElasticSearch,Mysql分库分表,Nginx负载均衡都有应用。
主要应用场景
1)请求的负载均衡(如Nginx的Ip_hash策略)
Nginx的Ip_hash策略可以在客户端ip不变的情况下,将其发出的请求始终路由到同⼀个⽬标服务器上,实现会话粘滞(会话保持),避免处理多服务器之间的session共享问题,
如果没有Ip_hash策略就需要自己维护一张映射表,存储客户端IP或者sessionid与具体目标服务器的映射关系。
缺点:
针对大体量级系统,客户端众多,这个映射表需要储存的数据就非常多,浪费内存空间。
客户端上下线,目标服务器上下线都会导致重新维护映射表,维护成本高。
如果使用hash算法,可以直接对ip地址或者sessionid进行计算哈希值,哈希值与服务器数量进行取模运算,得到的值就是当前请求应该被路由到的服务器编号,如此,一个客户端ip发送的请求就可以路由到同一个目标服务器,实现会话粘带。
2)分布式存储
以分布式内存数据库Redis为例,集群中有redis1,redis2,redis3 三台Redis服务器,在数据储存时,<key,value>数据,根据key进行hash处理,hash(key)%3=index,使用余数来锁定存储的具体服务节点。
一致性Hash算法
普通hash算法存在一个问题,如果服务器宕机了,服务器的数量就会减少,之前的所有求模运算都将重新来,缩容和扩容也是一样的,大量用户的请求都将被重新分发,原来服务器会话都会丢失,因此诞生了一致性hash算法。
一致性hash算法实现原理
首先有一条直线,他的开头和结尾分别定为为0和2的32次⽅减1,对应IP地址的长度,服务器的IP地址是32位,所以是2^32-1次方的数值空间,对于这样⼀条线,弯过来构成⼀个圆环形成闭环,这样的⼀个圆环称为hash环。我们把服务器的ip或者主机名求hash值然后对应到hash环上,那么针对客户端⽤户,也根据它的ip进⾏hash求值,对应到环上某个位置。客户端路由服务器就按照顺时针找离他最近的服务器节点。
如果某台服务器宕机或者需要进行扩容之类,客户端就按照顺时针接着往下顺延或者缩短定位最近服务器节点。
每台服务器负责⼀段,⼀致性哈希算法对于节点的增减都只需重定位环空间中的⼀⼩部分数据,具有较好的容错性和可扩展性。但是,⼀致性哈希算法在服务节点太少时,容易因为节点分部不均匀⽽造成数据倾斜问题。例如系统中只有两台服务器,其环分布如下,节点2只能负责⾮常⼩的⼀段,⼤量的客户端请求落在了节点1上,这就是数据(请求)倾斜问题
为了解决这种数据倾斜问题,⼀致性哈希算法引⼊了虚拟节点机制,即对每⼀个服务节点计算多个哈希,每个计算结果位置都放置⼀个此服务节点,称为虚拟节点。
具体实现:以在服务器ip或主机名的后面增加编号来实现。⽐如,可以为每台服务器计算三个虚拟节点,于是可以分别计算 “节点1的ip#1”、“节点1的ip#2”、“节点1的ip#3”、“节点2的ip#1”、“节点2的ip#2”、“节点2的ip#3”的哈希值,于是形成六个虚拟节点,当客户端被路由到虚拟节点的时候其实是被路由到该虚拟节点所对应的真实节点。
手写三种一致性hash算法
普通hash算法实现
一致性hash算法实现 不含虚拟节点
一致性hash算法实现 含虚拟节点(就在前面的基础上引入虚拟节点就好了)
Nginx配置一致性Hash负载均衡策略
ngx_http_upstream_consistent_hash 模块是⼀个负载均衡器,使⽤⼀个内部⼀致性hash算法来选择合适的后端节点。
1) github下载nginx⼀致性hash负载均衡模块 https://github.com/replay/ngx_http_consistent_hash
2) 将下载的压缩包上传到nginx服务器,并解压
3) 进⼊nginx的源码⽬录,顺序执⾏如下命令./configure —add-module=/root/ngx_http_consistent_hash-master
è Make
è make install
4) Nginx就可以使⽤啦,在nginx.conf⽂件中配置
二、集群时钟同步问题
时钟不同步导致的问题
时钟此处指服务器时间,如果集群中各个服务器时钟不⼀致势必导致⼀系列问题。电商⽹站业务中,新增⼀条订单,那么势必会在订单表中增加了⼀条记录,该条记录中应该会有“下单时间”这样的字段,往往我们会在程序中获取当前系统时间插⼊到数据库或者直接从数据库服务器获取时间。那我们的订单子系统是集群部署,或者我们的数据库也是分库分表的集群部署,然⽽他们的系统时钟不⼀致,比如有⼀台服务器的时间是昨天,那么这个时候下单时间就成了昨天,那我们的数据将会混乱。
集群时钟同步配置
第一种:分布式集群中各个服务器节点都可以连接互联网直接通过互联网国家授时中心获取正确时间并制定定时计划,按时间计划进行刷新同步。
操作⽅式:
第二种:只有一台服务器可以连接互联网或者所有服务器都不能连接互联网。
选取一台服务器作为时间服务器,整个集群时间从这台服务器同步,如果这台服务器能够访问互联⽹,可以让这台服务器和⽹络时间保持同步,如果不能就⼿动设置⼀个时间。
实现步骤:
1)设置服务器的时间
2)把当前服务器设置为时间服务器(修改/etc/ntp.conf文件)
3)集群中其他节点通过命令从A服务器同步时间
三、分布式DI解决方案
需求场景
当单表已经不能满足数据储存要求的时候,就需要对单表进行分表,以Mysql为准,单表储存量最佳500万,超过性能就会慢慢下降,因此搭建Mysql集群进行分表就势在必行,而分表带来了一个无法回避的问题,多表之间数据的唯一序列号该如何处理。
UUID解决方案
UUID 是指Universally Unique Identifier,翻译为中文是通⽤唯⼀识别码,他的生成规则客户端地址+时间戳+一个随机数,重复的概率是非常低的,不过他有一个缺点,没有规律,作为主键建立索引的话,查询效率不高。
独立数据库的自增ID解决方案
创建⼀个Mysql数据库,在这个数据库中创建⼀张表,这张表的ID设置为⾃增,其他地⽅需要全局唯⼀ID的时候,就模拟向这个Mysql数据库的这张表中模拟插⼊⼀条记录,此时ID会⾃增,然后我们可以通过Mysql的select last_insert_id() 获取到刚刚这张表中⾃增⽣成的ID。
缺点:性能和可靠性都不够好,因为你需要代码连接到数据库才能获取到id,性能⽆法保障,另外mysql数据库实例挂掉了,那么就⽆法获取分布式id了。
SnowFlake 雪花算法解决方案
雪花算法是Twitter(推特)推出的⼀个⽤于⽣成分布式ID的策略。
雪花算法是⼀个算法,基于这个算法可以⽣成ID,⽣成的ID是⼀个long型,那么在Java中⼀个long型是8个字节,算下来是64bit,由符号位加时间戳(毫秒级)加机器id最后拼接一个随机序列号(一毫秒产生四千多个不重复得序列号,)生成,换算成秒也就是一秒内能产生四百万个不重复id,而且在一台机子上可以运行七十多年生成不重复得ID,可靠性非常高。
雪花算法原理理解:机器ID给了10位,而在实现得时候,拆分成了两部分,一部分集群编号占5位,另一部分集群机器编号占5位,组合起来拼接成完整的机器ID,接着获取当前得时间戳,如果获取当前时间戳小于上次时间戳,违背雪花算法核心原则,直接抛出异常,不能生成ID。如果当前时间戳等于当前时间戳,就代表当前请求还在同一毫秒内,进入同一毫秒生成规则,但是里面有一个限制,一毫秒内最大生成唯一ID数量是4095,进行位与运算判断,如果超了4095边界,就等待,里面有一个方法在疯狂刷新时间戳,当下个时间戳到来,取下一毫秒的时间戳来生成唯一ID,这一毫秒又可以生成4095个唯一ID.
Redis的Incr命令获取全局唯⼀ID解决方案
Redis Incr 命令将 key 中储存的数字值增⼀。如果 key 不存在,那么 key 的值会先被初始化为 0,然后再执⾏ INCR 操作。
Redis安装
1)官⽹下载redis-3.2.10.tar.gz http://download.redis.io/releases/redis-3.2.10.tar.gz
2)上传到linux服务器解压 tar -zxvf redis-3.2.10.tar.gz
3)cd 解压⽂件⽬录,对解压的redis进⾏编译
4)make
5)然后cd 进⼊src⽬录,执⾏make install
6)修改解压⽬录中的配置⽂件redis.conf,关掉保护模式
7)在src⽬录下执⾏ ./redis-server ../redis.conf 启动redis服务
四、分布式调度问题解决方案
定时任务的场景
调度—>定时任务,分布式调度—>在分布式集群环境下定时任务
定时任务就是每隔一段时间或者特定时间执行
比如:订单审核、物流信息推送、日志监控、定时备份数据、报表数据分析、数据积压检测等等
分布式调度
1) 运行在分布式集群环境下的调度任务(多个实例同时运行,但是同一时间只有一个实例生效,比如统计报表定时任务多实例部署,调用的时候只需要一个就行了,另外一个相当于备用)
2) 分布式调度->进行任务的分布式->定时任务拆分(比如我一个单体应用有很多定时任务,我对他进行拆分,为了提高性能,进行集群部署,比如拆分出来的统计报表定时任务,数据备份定时任务等等,执行的时候应该是一起执行,而不是一个个执行)
定时任务与消息队列的区别
共同点
1)异步处理:比如注册、下单事件,比如下单完成后台只是做了一个标记,并不是同步就完成了整个订单的处理流程,你看到的订单完成只是显示的一个标记状态,如果是消息队列,后台就按消息顺序处理这些订单,而定时任务的话,就按间隔时间去扫描订单表,获取标记进行订单处理。
2)应用解耦:定时任务和MQ都可以作为两个应用之间的齿轮实现应用解耦,比如两个系统需要数据交互,就可以通过mq当做中转;定时任务的话,就是把数据存储到一张中间表,进行定时扫描获取数据。
3)流量削峰:双十一的时候,流量很大,定时任务作业和MQ都可以用来扛流量,如果直接全部发送到后台,后台是处理不过来的,会直接崩掉,有了他们,后台就可以根据服务能力定时处理订单或者从MQ抓取订单事件触发处理,而前端用户看到的结果是下单成功。
本质不同
定时任务是时间驱动,消息队列是事件驱动
时间驱动不可代替,比如金融系统每日利息结算,不是利息到来事件就算一下,而是批量处理,所以定时任务作业更倾向于批量处理,MQ倾向于逐条处理。
定时任务Quartz实现方式
定时任务的实现⽅式有多种。早期没有定时任务框架的时候,我们会使⽤JDK中的Timer机制和多线程机制(Runnable+线程休眠)来实现定时或者间隔⼀段时间执⾏某⼀段程序;后来有了定时任务框架,⽐如Quartz任务调度框架,使⽤时间表达式(包括:秒、分、时、⽇、周、年)配置某⼀个任务什么时间去执⾏。
引入Quartz的jar包
编写定时任务作业主调度程序分为四步:
1)创建任务调度器 如公交调度站
2)创建一个任务 如公交车出行
3)创建任务的时间触发器 如公交车出行时间表
cron表达式由七个位置组成,空格分隔
* 1、Seconds(秒) 0~59
* 2、Minutes(分) 0~59
* 3、Hours(⼩时) 0~23
* 4、Day of Month(天)1~31,注意有的⽉份不⾜31天
* 5、Month(⽉) 0~11,或者JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC
* 6、Day of Week(周) 1~7,1=SUN或者 SUN,MON,TUE,WEB,THU,FRI,SAT
* 7、Year(年)1970~2099 可选项
4)使用任务调度器根据时间触发器执行我们的任务
Elastic-Job简介
Elastic-Job是当当⽹开源的⼀个分布式调度解决⽅案,基于Quartz⼆次开发的,由两个相互独⽴的⼦项⽬Elastic-Job-Lite和Elastic-Job-Cloud组成,Elastic-Job-Lite,它定位为轻量级⽆中⼼化解决⽅案,使⽤Jar包的形式提供分布式任务的协调服务,⽽Elastic-Job-Cloud⼦项⽬需要结合Mesos以及Docker在云环境下使⽤。
主要功能:
1)分布式调度协调:在分布式环境中,任务能够按指定的调度策略执⾏,并且能够避免同⼀任务多实例重复执⾏。
2)丰富的调度策略:基于成熟的定时任务作业框架Quartz cron表达式执⾏定时任务
3)弹性扩容缩容:当集群中增加某⼀个实例,它应当也能够被选举并执⾏任务;当集群减少⼀个实例时,它所执⾏的任务能被转移到别的实例来执⾏。
4)失效转移:某实例在任务执⾏失败后,会被转移到其他实例执⾏
5)错过执⾏作业重触发:若因某种原因导致作业错过执⾏,⾃动记录错过执⾏的作业,并在上次作业完成后⾃动触发。
6)⽀持并⾏调度:⽀持任务分⽚,任务分⽚是指将⼀个任务分为多个⼩任务项在多个实例同时执⾏。
7)作业分⽚⼀致性:当任务被分⽚后,保证同⼀分⽚在分布式环境中仅⼀个执⾏实例。
安装Zookeeper
Elastic-Job依赖于Zookeeper进⾏分布式协调,Zookeeper的本质功能:存储+通知。
1)我们使⽤3.4.10版本,在linux平台解压下载的zookeeper-3.4.10.tar.gz
2)进⼊conf⽬录,cp zoo_sample.cfg zoo.cfg
3)修改目录地址dataDir=/usr/local/zookeeper-3.4.10/data
- 进⼊bin⽬录,启动zk服务
启动 ./zkServer.sh start
停⽌ ./zkServer.sh stop
查看状态 ./zkServer.sh status
Elastic-job应用
需求:每隔两秒钟执⾏⼀次定时任务(resume表中未归档的数据归档到resume_bak表中,每次归档1条记录)
1) 定义定时任务业务逻辑处理类ArchivieJob实现SimpleJob接口,重写execute方法,封装核心逻辑
2)配置注册中心,连接云主机上的zookeeper,进行任务配置(时间事件10秒执行、调用定时任务逻辑处理类、任务调度器开启任务)
3)测试结果,每隔十秒执行一次逻辑处理,完成单例定时任务执行。如果多个应用启动,多个实例同时创建一个leader节点,谁创建了谁就是领导者,其他就不能创建了,而当前领导者就负责执行对应实例定时任务,另外的相当于备份,当集群服务扩容或者缩减,zookeeper的领导者leader会重新选举,找出新的执行者。
Elastic-job轻量级和去中⼼化
轻量级:
不需要引入或安装其他服务或者组件,把jar包引入调用即可。
不需要独立部署,就是一个jar程序。
去中心化:
执行节点对等。
定时调度自触发,不需要中心节点分配,谁该执行什么任务等等。
服务自发现,通过注册中心服务发现。
主节点非固定,通过leader选举产生。
Elastic-job任务分片及扩容
ElasticJob可以把作业分为多个的task(每⼀个task就是⼀个任务分⽚),每⼀个task交给具体的⼀个机器实例去处理(⼀个机器实例是可以处理多个task的),但是具体每个task执⾏什么逻辑由我们⾃⼰来指定,比如把批量处理的数据按类型分开,然后多个实例各自执行不同类型的数据,提高并发处理效率。
设置3个分片,设置3个分片对应逻辑参数
获取参数,在业务逻辑类进行处理
扩容:
新增加⼀个运⾏实例,它会⾃动注册到注册中⼼,注册中⼼发现新的服务上线,注册中⼼会通知ElasticJob 进⾏重新分⽚,总分⽚项有多少,就可以搞多少个实例机器。
1)分⽚项也是⼀个JOB配置,修改配置,重新分⽚,在下⼀次定时运⾏之前会重新调⽤分⽚算法,那么这个分⽚算法的结果就是:哪台机器运⾏哪⼀个分⽚,这个结果存储到zk中的,主节点会把分⽚给分好放到注册中⼼去,然后执⾏节点从注册中⼼获取信息(执⾏节点在定时任务开启的时候获取相应的分⽚)。
2)如果所有节点挂掉只剩下⼀个节点时,所有分⽚都会指向剩下的⼀个节点,这就是ElasticJob的⾼可⽤。
五、Session共享问题
Session问题原因分析
Session共享及Session保持或者叫做Session⼀致性
http协议是无状态的协议,而页面具有动态的内容,就需要有状态,保持http状态的技术就是cookie和session,但是客户端和服务器又不会把会话数据保留,因此当nginx进行集群tomcat登录请求的时候,由于nginx的轮询策略,就会产生第一次登录请求分发到第一台tomcat服务器,登录成功,当前服务器session保存了当前登录信息,第二次请求来了,获取业务数据,跳转数据列表,对页面渲染,但是第二次缺请求到了第二台服务器,而第二台服务器是没得session的,于是又重定向到了登录界面。
解决Session⼀致性的⽅案
Nginx的 IP_Hash 策略
前面Nginx有讲述,同一个客户端的IP的请求都会被路由到同一个目标服务器,也叫会话保持。
优点:
配置简单,不⼊侵应⽤,不需要额外修改代码
缺点:
服务器重启Session丢失
存在单点负载⾼的⻛险
单点故障问题
Session复制
优点:
不侵入应用
便于服务器水平扩展
能适应各种负载均衡策略
服务器重启或者宕机不会造成session丢失
缺点:
性能低
内存消耗
不能存储太多数据,数据越多性能越低
延迟性
Session共享,Session集中储存
优点
能适应各种负载均衡策略
服务器重启或者宕机不会造成session丢失
扩展能力强
适合大集群数量使用
缺点
对应用有入侵,引入了和Redis的交互代码