文章内容输出来源:拉勾教育Java高薪训练营。
本篇文章是 分布式集群架构场景方案 学习课程中的一部分笔记
分布式与集群
分布式
- 把一个系统拆分为多个子系统,每个子系统负责各自的那部分功能,独立部署,各司其职
集群
- 多个实例共同工作,最简单/最常见的集群是把一个应用复制多份部署
一致性Hash算法
Hash算法在分布式集群中应用场景
Hash算法,⽐如说在安全加密领域MD5、SHA等加密算法,在数据存储和查找⽅⾯有Hash表等, 以上都应⽤到了Hash算法。
普通Hash算法存在的问题(Hash冲突审时)设计不好时,效率低下
-
开放寻址:向前或者向后找空闲位置存放
-
拉链法:元素存储位置放了⼀个链
-
应用场景
Hash算法在很多分布式集群产品中都有应⽤,⽐如分布式集群架构Redis、Hadoop、ElasticSearch,Mysql分库分表,Nginx负载均衡等
-
请求的负载均衡(比如nginx的ip_hash策略)
Nginx的IP_hash策略可以在客户端ip不变的情况下,将其发出的请求始终路由到同⼀个⽬标服务器上,实现会话粘滞,避免处理session共享问题
-
不使用ip_hash时处理session共享
-
维护ip,tomcat映射表
缺点
1)那么,在客户端很多的情况下,映射表⾮常⼤,浪费内存空间
2)客户端上下线,⽬标服务器上下线,都会导致重新维护映射表,映射表维护成本很⼤
-
-
使用hash算法,对ip地址或者sessionid进⾏计算哈希值,哈希值与服务器数量进⾏取模运算
-
-
分布式存储
- redis集群中:针对key进⾏hash处理hash(key1)%n=index, n为服务器数量
-
-
问题: 某个节点挂掉或者扩容时,会导致访问节点变化,发生会话丢失等问题
一致性Hash算法
-
原理
⾸先有⼀条直线,直线开头和结尾分别定为为1和2的32次⽅减1,这相当于⼀个地址,对于这样⼀条 线,弯过来构成⼀个圆环形成闭环,这样的⼀个圆环称为hash环。我们把服务器的ip或者主机名求 hash值然后对应到hash环上,那么针对客户端⽤户,也根据它的ip进⾏hash求值,对应到环上某个位 置,然后如何确定⼀个客户端路由到哪个服务器处理呢?按照顺时针⽅向找最近的服务器节点
- 请求的迁移达到了最⼩,这样的算法对分布式集群来说⾮常合适的,避免了⼤量请求迁移
-
问题:⼀致性哈希算法在服务节点太少时,容易因为节点分部不均匀⽽造成数据倾斜问题(访问分布不均)
-
解决方案:
引⼊了虚拟节点机制,即对每⼀个服务节点计算多个
哈希,每个计算结果位置都放置⼀个此服务节点,称为虚拟节点。
具体做法可以在服务器ip或主机名的后⾯增加编号来实现。⽐如,可以为每台服务器计算三个虚拟节
点,于是可以分别计算 “节点1的ip#1”、“节点1的ip#2”、“节点1的ip#3”、“节点2的ip#1”、“节点2的
ip#2”、“节点2的ip#3”的哈希值,于是形成六个虚拟节点,当客户端被路由到虚拟节点的时候其实是被
路由到该虚拟节点所对应的真实节点
-
手写实现一致性Hash算法
Nginx配置一致性Hash负载均衡策略
-
ngx_http_upstream_consistent_hash 模块是⼀个负载均衡器,使⽤⼀个内部⼀致性hash算法来选择合适的后端节点
该模块可以根据配置参数采取不同的⽅式将请求均匀映射到后端机器,consistent_hash $remote_addr:可以根据客户端ip映射
consistent_hash $request_uri:根据客户端请求的uri映射
consistent_hash $args:根据客户端携带的参数进⾏映-
过程
1)github下载nginx⼀致性hash负载均衡模块 https://github.com/replay/ngx_http_consistent_hash
2)将下载的压缩包上传到nginx服务器,并解压
3)我们已经编译安装过nginx,此时进⼊当时nginx的源码⽬录,执⾏如下命令
./confifigure —add-module=/root/ngx_http_consistent_hash-master
make
make install
4)Nginx就可以使⽤啦,在nginx.conf⽂件中配置
-
集群时钟同步问题
时钟不同步导致的问题:
- 新增⼀条订单,那么势必会在订单表中增加了⼀条记录,该条记录中应该会有“下单时间”这样的字段,往往我们会在程序中获取当前系统时间插⼊到数据库或者直接从数据库服务器获取时间。那我们的订单⼦系统是集群化部署,或者我们的数据库也是分库分表的集群化部署,然⽽他们的系统时钟缺不⼀致,⽐如有⼀台服务器的时间是昨天,那么这个时候下单时间就成了昨天,那我们的数据将会混乱
时钟同步思路:
-
各个服务器节点都可以访问互联网
#使⽤ ntpdate ⽹络时间同步命令
ntpdate -u ntp.api.bz #从⼀个时间服务器同步时间windows有计划任务
Linux也有定时任务,crond,可以使⽤linux的定时任务,每隔10分钟执⾏⼀次ntpdate命令 -
分布式集群中某一个服务器节点可以访问互联网或者所有节点都不能访问
选取集群中的⼀个服务器节点A(172.17.0.17)作为时间服务器(整个集群时间从这台服务 器同步,如果这台服务器能够访问互联⽹,可以让这台服务器和⽹络时间保持同步,如果不能就⼿动设置⼀个时间
-
解决
⾸先设置好A的时间
把A配置为时间服务器(修改/etc/ntp.conf⽂件)
1、如果有 restrict default ignore,注释掉它
2、添加如下⼏⾏内容
restrict 172.17.0.0 mask 255.255.255.0 nomodify notrap # 放开局
域⽹同步功能,172.17.0.0是你的局域⽹⽹段
server 127.127.1.0 # local clock
fudge 127.127.1.0 stratum 10
3、重启⽣效并配置ntpd服务开机⾃启动
service ntpd restart
chkconfig ntpd on
集群中其他节点就可以从A服务器同步时间了
ntpdate 172.17.0.17
-
Session共享问题
解决方案
-
Nginx的 IP_Hash 策略(可以使⽤)
优点:
配置简单,不⼊侵应⽤,不需要额外修改代码缺点:
服务器重启Session丢失
存在单点负载⾼的⻛险
单点故障问题 -
Session复制(不推荐)
优点:
不⼊侵应⽤
便于服务器⽔平扩展
能适应各种负载均衡策略
服务器重启或者宕机不会造成Session丢失
缺点:性能低
内存消耗
不能存储太多数据,否则数据越多越影响性能
延迟性- 也即,多个tomcat之间通过修改配置⽂件,达到Session之间的复制
-
Session共享,Session集中存储(推荐)
优点:
能适应各种负载均衡策略
服务器重启或者宕机不会造成Session丢失
扩展能⼒强
适合⼤集群数量使⽤缺点:
对应⽤有⼊侵,引⼊了和Redis的交互代码
分布式调度问题
场景
订单审核、出库
订单超时⾃动取消、⽀付退款
礼券同步、⽣成、发放作业
物流信息推送、抓取作业、退换货处理作业
数据积压监控、⽇志监控、服务可⽤性探测作业
定时备份数据
⾦融系统每天的定时结算
数据归档、清理作业
报表、离线数据分析作业
含义
- 1)运⾏在分布式集群环境下的调度任务(同⼀个定时任务程序部署多份,只应该有⼀个定时任务在执⾏)
- 2)分布式调度—>定时任务的分布式—>定时任务的拆分(即为把⼀个⼤的作业任务拆分为多个⼩的作业任务,同时执⾏)
定时任务与消息队列区别
-
共同点
异步处理
⽐如注册、下单事件应⽤解耦
不管定时任务作业还是MQ都可以作为两个应⽤之间的⻮轮实现应⽤解耦,这个⻮轮可以中转 数据,当然单体服务不需要考虑这些,服务拆分的时候往往都会考虑流量削峰
双⼗⼀的时候,任务作业和MQ都可以⽤来扛流量,后端系统根据服务能⼒定时处理订单或者从MQ抓取订单抓取到⼀个订单到来事件的话触发处理,对于前端⽤户来说看到的结果是已经下单成功了,下单是不受任何影响的 -
不同点
定时任务作业是时间驱动,⽽MQ是事件驱动;
时间驱动是不可代替的,⽐如⾦融系统每⽇的利息结算,不是说利息来⼀条(利息到来事件)就算⼀下,⽽往往是通过定时任务批量计算;
所以,定时任务作业更倾向于批处理,MQ倾向于逐条处理;
定时任务
-
实现方式
-
quartz
- 1,创建任务调度器
- 2, 创建一个任务
- 3, 创建人物的时间触发器
- 4, 使用任务调度器根据时间触发器执行我们的任务
-
Elastic-Job
-
主要功能
分布式调度协调
在分布式环境中,任务能够按指定的调度策略执⾏,并且能够避免同⼀任务多实例重复执⾏丰富的调度策略
基于成熟的定时任务作业框架Quartz cron表达式执⾏定时任务弹性扩容缩容 当集群中增加某⼀个实例,它应当也能够被选举并执⾏任务;当集群减少⼀个实例时,它所执⾏的任务能被转移到别的实例来执⾏。失效转移
某实例在任务执⾏失败后,会被转移到其他实例执⾏ 错过执⾏作业重触发 若因某种原因导致作业错过执⾏,⾃动记录错过执⾏的作业,并在上次作业
完成后⾃动触发。⽀持并⾏调度 ⽀持任务分⽚,任务分⽚是指将⼀个任务分为多个⼩任务项在多个实例同时执⾏。
作业分⽚⼀致性 当任务被分⽚后,保证同⼀分⽚在分布式环境中仅⼀个执⾏实例。
-
应用
-
需要zookeeper
1)我们使⽤3.4.10版本,在linux平台解压下载的zookeeper-3.4.10.tar.gz
2)进⼊conf⽬录,cp zoo_sample.cfg zoo.cfg- 进⼊bin⽬录,启动zk服务
-
-
轻量级,去中心化
- 仅使用一个jar包+zookeeper
不需独立部署,就是一个jar程序 - 执行节点对等(程序和jar)一样,唯一不一样的可能是分片
定时调度自触发(没有中心调度节点分配)
服务自发现
主节点非固定
- 仅使用一个jar包+zookeeper
-
任务分片与扩容缩容
1)分⽚项也是⼀个JOB配置,修改配置,重新分⽚,在下⼀次定时运⾏之前会重新调⽤分⽚算法,那么 这个分⽚算法的结果就是:哪台机器运⾏哪⼀个⼀⽚,这个结果存储到zk中的,主节点会把分⽚给分好放到注册中⼼去,然后执⾏节点从注册中⼼获取信息(执⾏节点在定时任务开启的时候获取相应的分
⽚)。2)如果所有的节点挂掉值剩下⼀个节点,所有分⽚都会指向剩下的⼀个节点,这也是ElasticJob的⾼可
⽤。
-
-
分布式ID解决方案
UUID
java.util.UUID.randomUUID().toString()
独⽴数据库的⾃增ID
单独的创建⼀个Mysql数据库,在这个数据库中创建⼀张表,这张表的ID设置为⾃增,其他地⽅需要全局唯⼀ID的时候,就模拟向这个Mysql数据库的这张表中模拟插⼊⼀条记录,此时ID会⾃增,然后我们可以通过Mysql的select last_insert_id() 获取到刚刚这张表中⾃增⽣成的ID
-
问题
- 1)这⾥的createtime字段⽆实际意义,是为了随便插⼊⼀条数据以⾄于能够⾃增id。
- 2)使⽤独⽴的Mysql实例⽣成分布式id,虽然可⾏,但是性能和可靠性都不够好,因为你需要代码连接到数据库才能获取到id,性能⽆法保障,另外mysql数据库实例挂掉了,那么就⽆法获取分布式id了。
- 3)有⼀些开发者⼜针对上述的情况将⽤于⽣成分布式id的mysql数据库设计成了⼀个集群架构, 那么其实这种⽅式现在基本不⽤,因为过于麻烦了。
SnowFlake 雪花算法(可以⽤,推荐)
雪花算法是Twitter推出的⼀个⽤于⽣成分布式ID的策略。
雪花算法是⼀个算法,基于这个算法可以⽣成ID,⽣成的ID是⼀个long型,那么在Java中⼀个long型是8个字节,算下来是64bit
Redis的Incr命令获取全局唯⼀ID(推荐)
- Redis Incr 命令将 key 中储存的数字值增⼀。如果 key 不存在,那么 key 的值会先被初始化为 0,然后再执⾏ INCR 操作
XMind - Trial Version