第⼀部分:⼀致性Hash算法
第⼆部分:集群时钟同步问题
第三部分:分布式ID解决⽅案
数据表A(ID),A的数据量很⼤的情况下,我们会进⾏分表操作,A(ID)表拆分成了A1表 (ID)+A2表(ID),需要⼀种在分布式集群架构中能够产⽣全局唯⼀ID的⽅案
第四部分:分布式调度问题(定时任务的分布式)
第五部分:Session共享(⼀致性)问题
session共享演示地址:http://39.101.136.208/LoginProject/
分布式和集群
分布式和集群是不⼀样的,分布式⼀定是集群,但是集群不⼀定是分布式(因为集群就是多个实例⼀起 ⼯作,分布式将⼀个系统拆分之后那就是多个实例;集群并不⼀定是分布式,因为复制型的集群不是拆分⽽是复制)
单体架构演变为分布式架构
比如上图中的用户实例(实例数目必须大于1)就是一个集群,分布式:将一个大的系统拆分成多个子系统,然后将这些子系统单独部署在各自的机器上,各个子系统之间相互的调用构建成一个整体的系统对外服务。
第⼀部分 ⼀致性Hash算法 Hash算法在分布式集群架构中的应⽤场景
Hash算法在很多分布式集群产品中都有应⽤,⽐如分布式集群架构Redis、Hadoop、ElasticSearch, Mysql分库分表,Nginx负载均衡等 主要的应⽤场景归纳起来两个 请求的负载均衡(⽐如nginx的ip_hash策略) Nginx的IP_hash策略可以在客户端ip不变的情况下,将其发出的请求始终路由到同⼀个⽬标服务 器上,实现会话粘滞,避免处理session共享问题
如果没有IP_hash策略,那么如何实现会话粘滞? 可以维护⼀张映射表,存储客户端IP或者sessionid与具体⽬标服务器的映射关系 <ip,tomcat1>
缺点
1)那么,在客户端很多的情况下,映射表⾮常⼤,浪费内存空间 2)客户端上下线,⽬标服务器上下线,都会导致重新维护映射表,映射表维护成本很⼤ 如果使⽤哈希算法,事情就简单很多,我们可以对ip地址或者sessionid进⾏计算哈希值,哈希值与服务 器数量进⾏取模运算,得到的值就是当前请求应该被路由到的服务器编号,如此,同⼀个客户端ip发送 过来的请求就可以路由到同⼀个⽬标服务器,实现会话粘滞。
分布式存储
以分布式内存数据库Redis为例,集群中有redis1,redis2,redis3 三台Redis服务器 那么,在进⾏数据存储时,<key1,value1>数据存储到哪个服务器当中呢?针对key进⾏hash处理 hash(key1)%3=index, 使⽤余数index锁定存储的具体服务器节点
第 2 节 普通Hash算法存在的问题
普通Hash算法存在⼀个问题,以ip_hash为例,假定下载⽤户ip固定没有发⽣改变,现在tomcat3出现
了问题,down机了,服务器数量由3个变为了2个,之前所有的求模都需要重新计算。
如果在真实⽣产情况下,后台服务器很多台,客户端也有很多,那么影响是很⼤的,缩容和扩容都会存
在这样的问题,⼤量⽤户的请求会被路由到其他的⽬标服务器处理,⽤户在原来服务器中的会话都会丢失。
第 3 节 ⼀致性Hash算法
⾸先有⼀条直线,直线开头和结尾分别定为为1和2的32次⽅减1,这相当于⼀个地址,对于这样⼀条
线,弯过来构成⼀个圆环形成闭环,这样的⼀个圆环称为hash环。我们把服务器的ip或者主机名求
hash值然后对应到hash环上,那么针对客户端⽤户,也根据它的ip进⾏hash求值,对应到环上某个位
置,然后如何确定⼀个客户端路由到哪个服务器处理呢?按照顺时针⽅向找最近的服务器节点
假如将服务器3下线,服务器3下线后,原来路由到3的客户端重新路由到服务器4(顺时针找到最近的一个结点),对于其他客户端没有影响只是这⼀⼩部分受影响(请求的迁移达到了最⼩,这样的算法对分布式集群来说⾮常合适的,避免了⼤量请求迁移 )
哈希偏斜:
如上面左图,客户端请求落在区间1至2之间就很少(机器2压力变大),而落在区间2至1之间的请求就很大(机器1资源浪费)
解决方案: 为了解决这种数据倾斜问题,⼀致性哈希算法引⼊了虚拟节点机制,即对每⼀个服务节点计算多个 哈希,每个计算结果位置都放置⼀个此服务节点,称为虚拟节点。
具体做法可以在服务器ip或主机名的后⾯增加编号来实现。⽐如,可以为每台服务器计算三个虚拟节 点,于是可以分别计算 “节点1的ip#1”、“节点1的ip#2”、“节点1的ip#3”、“节点2的ip#1”、“节点2的 ip#2”、“节点2的ip#3”的哈希值,于是形成六个虚拟节点,当客户端被路由到虚拟节点的时候其实是被 路由到该虚拟节点所对应的真实节点
第⼆部分 集群时钟同步问题
第 1 节 时钟不同步导致的问题
时钟此处指服务器时间,如果集群中各个服务器时钟不⼀致势必导致⼀系列问题,试想 “集群是各个服
务器⼀起团队化作战,⼤家⼯作都不在⼀个点上,岂不乱了套!”
举⼀个例⼦,电商⽹站业务中,新增⼀条订单,那么势必会在订单表中增加了⼀条记录,该条记录中应
该会有“下单时间”这样的字段,往往我们会在程序中获取当前系统时间插⼊到数据库或者直接从数据库
服务器获取时间。那我们的订单⼦系统是集群化部署,或者我们的数据库也是分库分表的集群化部署,
然⽽他们的系统时钟缺不⼀致,⽐如有⼀台服务器的时间是昨天,那么这个时候下单时间就成了昨天,
那我们的数据将会混乱!如下:
集群时钟同步思路:
分布式集群中各个服务器节点都可以连接互联⽹
从国家授时中心或时间服务器进行同步,windows有计划任务,Linux也有定时任务,crond,可以使⽤linux的定时任务,每隔10分钟执⾏⼀次ntpdate命令
第三部分 分布式ID解决⽅案
1.UUID(可以⽤),
2.独⽴数据库的⾃增ID ⽐如A表分表为A1表和A2表,那么肯定不能让A1表和A2表的ID⾃增,那么ID怎么获取呢?我们可 以单独的创建⼀个Mysql数据库,在这个数据库中创建⼀张表,这张表的ID设置为⾃增,其他地⽅ 需要全局唯⼀ID的时候,就模拟向这个Mysql数据库的这张表中模拟插⼊⼀条记录,此时ID会⾃ 增,然后我们可以通过Mysql的select last_insert_id() 获取到刚刚这张表中⾃增⽣成的ID. (这种⽅式现在基本不⽤,因为过于麻烦了)
3.SnowFlake 雪花算法:(可以⽤,推荐)
雪花算法是Twitter推出的⼀个⽤于⽣成分布式ID的策略。
雪花算法是⼀个算法,基于这个算法可以⽣成ID,⽣成的ID是⼀个long型,那么在Java中⼀个long
型是8个字节,算下来是64bit,如下是使⽤雪花算法⽣成的⼀个ID的⼆进制形式示意:
另外,⼀切互联⽹公司也基于上述的⽅案封装了⼀些分布式ID⽣成器,⽐如滴滴的tinyid(基于数
据库实现)、百度的uidgenerator(基于SnowFlake)和美团的leaf(基于数据库和SnowFlake)
等,他们在。
4.借助Redis的Incr命令获取全局唯⼀ID(推荐):
Redis Incr 命令将 key 中储存的数字值增⼀。如果 key 不存在,那么 key 的值会先被初始化为 0
,然后再执⾏ INCR 操作。
第四部分 分布式调度问题
调度—>定时任务,分布式调度—>在分布式集群环境下定时任务这件事 Elastic-job(当当⽹开源的分布式调度框架)
第 1 节 定时任务的场景 定时任务形式:每隔⼀定时间/特定某⼀时刻执⾏
例如:
1.订单审核、出库
2.订单超时⾃动取消、⽀付退款
3.礼券同步、⽣成、发放作业
4.物流信息推送、抓取作业、退换货处理作业
5.数据积压监控、⽇志监控、服务可⽤性探测作业
6.定时备份数据
7.⾦融系统每天的定时结算
8.数据归档、清理作业
9.报表、离线数据分析作业
第 2 节 什么是分布式调度 什么是分布式任务调度?有两层含义
1)运⾏在分布式集群环境下的调度任务(同⼀个定时任务程序部署多份,只应该有⼀个定时任务在执⾏)
2)分布式调度—>定时任务的分布式—>定时任务的拆分(即为把⼀个⼤的作业任务拆分为多个⼩的作业任务,同时执⾏)
第 3 节 定时任务与消息队列的区别
共同点
异步处理:⽐如注册、下单事件
应⽤解耦:不管定时任务作业还是MQ都可以作为两个应⽤之间的⻮轮实现应⽤解耦,这个⻮轮可以中转数据,当然单体服务不需要考虑这些,服务拆分的时候往往都会考虑
流量削峰:双⼗⼀的时候,任务作业和MQ都可以⽤来扛流量,后端系统根据服务能⼒定时处理订单或者 从MQ抓取订单抓取到⼀个订单到来事件的话触发处理,对于前端⽤户来说看到的结果是已经下单成功了,下单是不受任何影响的
本质不同:
定时任务作业是时间驱动,⽽MQ是事件驱动; 时间驱动是不可代替的,⽐如⾦融系统每⽇的利息结算,不是说利息来⼀条(利息到来事件)就算⼀下,⽽往往是通过定时任务批量计算;所以,定时任务作业更倾向于批处理,MQ倾向于逐条处理;
第 4 节 定时任务的实现⽅式
定时任务的实现⽅式有多种。早期没有定时任务框架的时候,我们会使⽤JDK中的Timer机制和多线程机 制(Runnable+线程休眠)来实现定时或者间隔⼀段时间执⾏某⼀段程序;后来有了定时任务框架,⽐ 如⼤名鼎鼎的Quartz任务调度框架,使⽤时间表达式(包括:秒、分、时、⽇、周、年)配置某⼀个任 务什么时间去执⾏:
第 5 节 分布式调度框架Elastic-Job
Elastic-Job是当当⽹开源的⼀个分布式调度解决⽅案,基于Quartz⼆次开发的,由两个相互独⽴的⼦项⽬Elastic-Job-Lite和Elastic-Job-Cloud组成。我们要学习的是 Elastic-Job-Lite,它定位为轻量级⽆⼼ 化解决⽅案,使⽤Jar包的形式提供分布式任务的协调服务,⽽Elastic-Job-Cloud⼦项⽬需要结合Mesos以及Docker在云环境下使⽤。Elastic-Job的github地址:github.com/elasticjob
主要功能介绍:
分布式调度协调:在分布式环境中,任务能够按指定的调度策略执⾏,并且能够避免同⼀任务多实例重复执⾏ 丰富的调度策略 基于成熟的定时任务作业框架Quartz cron表达式执⾏定时任务
弹性扩容缩: 当集群中增加某⼀个实例,它应当也能够被选举并执⾏任务;当集群减少⼀个实例时,它所执⾏的任务能被转移到别的实例来执⾏。
失效转移:某实例在任务执⾏失败后,会被转移到其他实例执⾏错过执⾏作业重触发 若因某种原因导致作业错过执⾏,⾃动记录错过执⾏的作业,并在上次作业完成后⾃动触发。
⽀持并⾏调度 ⽀持任务分⽚:任务分⽚是指将⼀个任务分为多个⼩任务项在多个实例同时执⾏。作业分⽚⼀致性 当任务被分⽚后,保证同⼀分⽚在分布式环境中仅⼀个执⾏实例。
5.2 Elastic-Job-Lite应⽤
jar包(API) + 安装zk软件
Elastic-Job依赖于Zookeeper进⾏分布式协调,所以需要安装Zookeeper软件(3.4.6版本以上),关于 Zookeeper,此处我们不做详解,在阶段三会有深度学习,我们此处需要明⽩Zookeeper的本质功能: 存储+通知。
1.安装Zookeeper(此处单例配置)【存储+通知】存储是znode,通知是客户端监听某个节点
1)我们使⽤3.4.10版本,在linux平台解压下载的zookeeper-3.4.10.tar.gz
2)进⼊conf⽬录,cp zoo_sample.cfg zoo.cfg
- 进⼊bin⽬录,启动zk服务
启动 ./zkServer.sh start
停⽌ ./zkServer.sh stop
查看状态 ./zkServer.sh status
Zookeeper的树形节点结构图
2.引⼊Jar包
3.定时任务实例
第五部分 Session共享问题
Session共享及Session保持或者叫做Session⼀致性
SpringSession源码讲解:
核心注解就是@EnableRedisHttpSession,该注解导入了RedisHttpSessionConfiguration,而这个configuration又继承于SpringHttpSessionConfiguration
//容器启动完成就创建Filter,这个filter类里面有个doFilterInternal方法会对原生request和response封装成一个wrapper。
第一次请求request.getSession()会进入SessionRepositoryFilter【impl Filter】的getSession获取session为null
创建session
保存sessionId到redis里面去,这个方法一直跟进去就会调用sessionRepository.save(session);