蹊源的Java笔记—分布式
前言
现在的互联网的架构中往往会面对这数以万计的并发访问,为了让系统在这样的访问压力下可以平稳运行,有这限流算法、分布式事务、分布式锁等分布式技术,本篇博客将针对其中几个经常被使用的技术和各位同学介绍介绍。
消息队列可参考我的博客蹊源的Java笔记—消息队列
微服务可参考我的博客蹊源的Java笔记—微服务
正文
限流算法
一般来说服务降级分为两种
- 故障降级:当发生网络故障或者
RPC
服务返回异常,这种情况我们通常采用关联fallback
方法,为客户端设置兜底数据,从而避免造成服务依赖的“雪崩”现象。 - 限流降级:无论系统运行在什么样的服务器,它都会有一定的流量带宽极限,我们可以采用设置请求阈值的方式来避免过多的请求涌入从而造成系统的崩溃。
不同的RPC
框架都有着不同的配置故障降级的方式,故而我们这里注重讲解一下限流降级这种方式。
我们常见的限流算法有:
- 漏桶算法
- 令牌桶算法
- 滑动窗口算法
漏桶算法
nginx
的limit_conn_zone
模块底层就是漏桶算法,它的特点是:
- 它可以简单的比作就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。
- 它能够强行限制数据的传输速率,但是由于流速是恒定的,对突发特性的流量是无法处理的 。
令牌桶算法
- 能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。
- 令牌桶算法是最常用的限流算法,通常配合redis来实现。
令牌桶算法的组件
- 计数器:实时地统计接口的调用速率,记录单位时间内调用了多少次。
- 限流器:当令牌桶中令牌已经无法放入时,使用限流器进行限流。
- 阈值配置:用于配置时间窗口,平均速率以及突发情况下允许的最大速率
- 阈值更新:通过一个参数来实时调整对阈值的配置
- 令牌桶:通过一个工具类根据配置的时间窗口、平均速率、突发最大速率来模拟向桶中放置令牌的操作,如果溢出了,则触发限流器操作。
它的工作原理可以简单概括为:
- 所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
- 根据限流大小,设置按照一定的速率往桶里添加令牌;
- 桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
- 请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
- 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流;
知识点:
a.在某种极端情况下存在请求量两倍进入:
- 令牌桶比如以 1个/1秒 速率进入时,请求又是以 2个/1秒进入,这个时候令牌桶并没有满,请求数就是令牌数的两倍,
- 如果超过平均速率的突发流量持续的时间过长,并且令牌桶的令牌没有得到释放,那么存在请求数是令牌桶大小的两倍。
滑动窗口算法
我们熟知的Sentinel
高可用流量控制框架采用的就是滑动窗口的方式进行流量控制。
- 它将时间窗口划分为更小的时间片段,每过一个时间片段,我们的时间窗口就会往右滑动一格,每个时间片段都有独立的计数器。
- 我们在计算整个时间窗口内的请求总数时会累加所有的时间片段内的计数器。
- 时间窗口划分的越细,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。
简单举个例子:我们设置一个窗口有两个方案
- 设置 A :10s 100条请求
- 设置 B :1s 10条请求
相对而言设置B的系统相对而言更加稳定。
总得来说:
- 令牌桶算法:能够保证自身系统的流量均匀,令牌桶的填满时间,是由桶的自身容量、令牌漏出速率(桶下面的水管)、超过平均速率的突发流量持续的时间三个方面共同决定的。
- 漏桶算法:保证被调用系统(目标系统)流量均匀,适合请求到达频率稳定、需要严格控制处理速率的场景,而令牌桶适合允许突发请求的情况。
实际的生产中通常采用 漏桶算法+令牌桶算法的方式来对网络流量进行高效地控制。
负载均衡
负载均衡是网站必不可少的基础技术手段,不但可以实现网站的伸缩性,同时还改善网站的可用性。
负载均衡与反向代理的差异:
- 负载均衡时基于反向代理来实现的.
- 反向代理每一种应用服务器只有一个,它会负责把请求完成。(点对点模式)
- 负载均衡是一种应用服务器可能有多个,它只负责把请求分发到特定的应用服务器中。(它不会告诉前端请求是否响应 只负责传输数据)
四层负载均衡与七层负载均衡的区别
- 四层交换机通过解析
TCP
头等协议的内容,来决定分流的目的地; - 七层交换机则通过解析软件应用层的内容来决定分流的目的地。
- 四层是基于
IP+端口号
进行负载均衡的,七层是基于URL
进行负载均衡的。 - 七层对负载均衡的设备要求更高,性能方面比四层弱。
- 七层负载均衡更加智能化,安全性上更加有保障。
硬件负载均衡实现方案
常见负载均衡的方法有:
- 利用Http重定向负载均衡:即利用
nginx
中的rewirte
模块来实现。(客户端行为,不需要服务器转发响应,这种方法性能比较差) - DNS域名解析负载均衡:
DNS
负载均衡的控制权在域名服务商中,通常作为第一级负载均衡手段。(不需要服务器转发响应) - 反向代理负载均衡:即利用
nginx
中的upstream
模块来实现(配置简单,但性能也有局限性,响应要通过反向代理服务器返回给客户端) - IP负载均衡:即利用网关服务器实现负载均衡(性能有提升,响应要通过网关服务器,受限于其网卡带宽)
- 数据链路层负载均衡:在通讯协议的数据链路层修改
mac
地址进行负载均衡。(不需要服务器转发响应,最常见的是LVS
)
通过硬件方式实现
F5服务器
- 优点:能够直接通过智能交换机实现,处理能力更强,而且与系统无关,负载性能强更适用于一大堆设备、大访问量、简单应用。
- 缺点:成本高,除设备价格高昂,而且配置冗余.很难想象后面服务器做一个集群,但最关键的负载均衡设备却是单点配置,无法有效掌握服务器及应用状态。
通过DNS方式实现
DNS
负载均衡的实现
可以利用DNS
,进行域名解析(但是由于DNS
的缓存机制 高可用难以实现)
DNS
(Domain Name System
,域名系统),万维网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP
数串。
通过软件方式实现
软件负载均衡的四种实现方案:
-
LVS
,即Linux
虚拟服务器,即使用ipvsadm
-
Nginx
-
HAProxy
- 基于
Gossp
实现无中间点的软件负载,如Facebook
的Cassandra
知识点:
-
LVS
实施及配置还有维护过程就比较复杂 不常使用 - 对于
Http
协议,Haproxy
处理效率比Nginx
高。 - 一般场景,建议使用
Haproxy
来做Http
协议负载!但如果是Web
,那么建议使用Nginx
。
LVS
LVS
提供了三种具体的实现方式:
- LVS-NAT(IP负载均衡):请求和响应都会通过代理服务器。
NAT
为地址转换技术. - LVS-DR(直接路由,不可跨子网,最常用):只会修改链路层报文,不会更改原有的
IP
报文和TCP
报文。请求会通过arp
协议+MAC
发送到指定服务器,响应会直接发回给服务器。(不可跨子网) - LVS-TUN(IP隧道):工作机制与
DR
类似,但是采用IPIP
隧道协议,可以实现跨子网。
知识点
- arp地址解析协议:借助
arp
地址解析协议可以利用MAC
地址将子服务器之间与IP
地址进行绑定 - MAC多路访问控制 :它是一种信道划分技术, 用分布式算法决定结点如何共享信道,即决策结点何时可以传输数据。
借助代理服务器,实现请求的分发(NAT模式)
- 代理服务器:提供了 公网
IP:115.39.19.22
局域网IP:192.168.0.100
- 功能:将请求
IP
报文中的目的地 从公网IP
转换成 局域网IP
- 修改请求中的链路层报文、
IP
数据报、TCP
报文涉及了LVS
技术,即Linux
虚拟服务器技术。 - 要注意的是
IP
是网络层协议、TCP
是传输层协议、HTTP
是应用层协议
请求IP数据报
请求响应分离(DR模式)
- 借助
arp
地址解析协议可以利用MAC
地址 将子服务器之间与IP
地址进行绑定,借助代理服务器将arp
广播出去。 - 实现响应跳过代理服务器,请求的转发从网络层(跨
IP
传输) 转换成数据链路层,这样本地传输,一定程度提高了安全。
负载均衡算法
- 轮询:所有请求被依次分发到每台应用服务器上,即每台服务器需要处理的请求数目都相同,适合于所有服务器硬件都相同的场景。
- 加权轮询:在轮询的基础上,按照配置的权重将请求分发到每个服务器,高性能的服务器能分配更多的请求。
- 随机:请求被随机分配到各个应用服务器,即使应用服务器配置不同,可以加权随机算法。(这种方法最常用)
- 最小连接:记录每一个应用服务器正在处理的连接数,将新的请求分发到最少连接的服务器,也支持加权最小连接。
- Hash选择:对请求的
IP
地址进行hash
计算,实现同一个IP
地址的请求总在一个服务器上处理。(实现对有状态应用服务器高可用的一种方式)
分布式下的数据一致性问题
CAP原则
在分布式系统要满足CAP
原则,一个提供数据服务的存储系统无法同时满足:
- C数据一致性:所有应用程序都能访问到相同的数据。
- A数据可用性:任何时候,任何应用程序都可以读写访问。
- P分区耐受性:系统可以跨网络分区线性伸缩。(通俗来说就是数据的规模可扩展)
在大型网站中通常都是牺牲C,选择AP。为了可能减小数据不一致带来的影响,都会采取各种手段保证数据最终一致。
- 数据强一致:各个副本的数据在物理存储中总是一致的。
- 数据用户一致:数据在物理存储的各个副本可能是不一致的,但是通过纠错和校验机制,会确定一个一致的且正确的数据返回给用户。
- 数据最终一致:物理存储的数据可能不一致,终端用户访问也可能不一致,但是一段时间内数据会达成一致。
一致性算法
- 使一组服务器在一个值上达成一致,所以活跃的特征在于最终每个服务器都可以决定一个值。
- 通过值的一致能够实现对同一个数据的请求会让同一个服务器来处理。
-
Paxos
和Raft
都是通过选取master
来实现多节点下值的一致性,从而借助一致性hash
算法来分配请求。
一致性Hash算法可以根据不同的属性参数(通常是IP
和端口号),生成一串不相同的Hash
值,并将Hash
值转换成0-2^32-1
的整数,不同范围的值由不同服务器进行处理。(B-C之间的由B处理)。
Raft算法和Paxos算法
Raft
在Paxos
的基础上主要做了两个方向的优化:
- 将复杂的分布式共识问题拆分成领导选举、日志复制和安全性三个问题
- 压缩状态空间:相对于
Paxos
施加了更合理的限制,减少了系统状态过多而产生的不确定因素。
日志复制
- 在共识算法中,所有服务器节点都会包含一个有限状态自动机,名为复制状态机。
- 每个节点都维护着一个复制日志的队列,复制状态机会按序输入并执行该队列中的请求,执行状态转换并输出结果。
- 可见,如果能保证各个节点中日志的一致性,那么所有节点状态机的状态转换和输出也就都一致。
通过上图:
- 日志由一个个按序排列的
entry
组成。每个entry
内包含有请求的数据,还有该entry
产生时的领导任期值。每个节点上的日志队列用一个数组log[]
表示。 - 领导节点选举出来后,集群就可以开始处理客户端请求了。当客户端发来请求时,领导节点首先将其加入自己的日志队列,再并行地发送
AppendEntries RPC
消息给所有跟随节点。最终实现节点数据的一致性。
安全性
Raft
安全保障机制有5种:
- 选举安全性:节点要3个以上,避免“脑裂”的方式
- 领导者只追加:客户端发出的请求都是插入领导者日志队列的尾部,没有修改或删除的操作。
- 日志匹配:每条
AppendEntries
都会包含最新entry
之前那个entry
的下标与任期值,如果跟随节点在对应下标找不到对应任期的日志,就会拒绝接受并告知领导节点。(避免追随者故障,导致数据不一致) - 领导者完全性:如果有一条日志在某个任期被提交了,那么它一定会出现在所有任期更大的领导者日志里。(
master
会优先获取日志的更新) - 状态机安全性:如果一个节点已经向其复制状态机应用了一条日志中的请求,那么对于其他节点的同一下标的日志,不能应用不同的请求。(避免
master
宕机时,重新选举,导致部分节点数据不一致)
Raft算法和Paxos算法在分布式中的使用
说明:
- CAP: 数据一致性、数据可用性、分区耐受性
- AP: 牺牲强一致性,部分节点宕机,不会影响正常工作的节点。
- CP: 牺牲数据可用性,为了保证数据的一致性,当一台机器出现故障时,所有节点的数据都不能使用。
分布式调度
Zookeeper
是一个分布式协调服务,可用于:服务发现、分布式锁、配置管理等。
Zookeeper
提供了一给类似于Linux
文件系统的树状结构(可认为是轻量级的内存文件系统,但是适合寸少量信息,如元数据),同时提供了对于每个节点的监控与通知机制。
Zookeeper中的znode
-
ZooKeeper
命名空间内部拥有一个树状的内存模型,其中各节点被称为znode
。 - 每个
znode
包含一个路径和与之相关的元数据,以及该znode
下关联的子节点列表。 -
Zookeeper
目录树中每个节点对应一个Znode
。每个Znode
维护这一个属性,当前版本、数据版本、建立时间和修改时间等,Zookeeper
就是使用这些属性来实现特殊功能的。 - 当一个客户端要对某个节点进行修改时,必须提供该数据的版本号,当节点数据发生变化是其版本号就会增加,这里的版本号是实现
zk
乐观锁的基础。(Zookeeper
修改数据就是CAS
操作)
Zookeeper的节点类型
节点有2个维度:是否有序和是否持久化
- PERSISTENT:持久化节点
- PERSISTENT_SEQUENTIAL:顺序自动编号持久化节点,这种节点会根据当前已存在的节点数自动加 1
- EPHEMERAL :临时节点, 客户端
session
超时这类节点就会被自动删除 - EPHEMERAL_SEQUENTIAL:临时自动编号节点
znode的特性
- Watches监控通知:客户端可以在节点上设置
Watches
(可以叫做监视器)。当节点状态发生变化时,就会触发监视器对应的操作,当监视器被触发时,ZK
服务器会向客户端发送且只发送一个通知 - 数据访问:
ZK
上存储的数据需要被原子性的操作(要么修改成功要么回到原样),也是就读操作将会读取节点相关所有数据,写操作也会修改节点相关所有数据,,而且每个节点都有自己的ACL
(访问控制列表)。
Zookeeper的选举机制
Zookeeper
在选举过程中的角色:
- 领导者Leader:一个集群里面只有一个
Leader
,负责维护其它之间的心跳,所有写操作必须要经过Leader
广播到其他节点,只要有超过半数(不包括Observer
节点)写入成功,该写请求就会被提交。 - 跟随者Follower:有投票权,可以直接处理并返回客户端读请求,同时会将写请求转发给
Leader
处理。 - 观察者Observer:无投票权,可以直接处理并返回客户端读请求,同时会将写请求转发给
Leader
处理。 - 竞选者Candidate:指有资格竞选
Leader
的节点,可以在当前Leader
宕机时,选举成为Leader
.
Zab协议
-
Zookeeper
是通过Zab
协议来保证分布式事务的最终一致性的。 -
Zab
协议是Paxos
算法的一种实现。
Zxid用于标示一次更新操作的提议ID:
- 它是一个 64 位的数字。
- 高 32 位是
epoch
用来标识 leader
关系是否改变,每次一个 leader
被选出来,它都会有一个新的 epoch
。 - 低 32 位是个递增计数。
Zab协议有两种模式
- 恢复模式:当服务启动或者在领导者崩溃后,
Zab
就进入了恢复模式,当领导者被选举出来,且大多数Server
完成了和Leader
的状态同步。 - 同步模式:保证了
Leader
和其他节点具有相同的系统状态。
Zab协议的四个阶段:
- 选举阶段:节点在一开始都处于选举节点,只要有一个节点得到超半数节点的票数,它就可以当选准
Leader
. - 发现阶段:
followers
跟准Leader
进行通讯,同步followers
最近接受到的事务提议,如果存在通讯失败要重新进入选举阶段。 - 同步阶段:准
Leader
利用发现阶段获取的事务提议历史,同步集群中所有的副本。只有当大多数节点都同步完成,准Leader
才会成为真正的Leader
。follower
只会接受zxid
比自己lastZxid
大的事务提议。 - 广播阶段:
Zookeeper
集群开始真实对外提供服务,并且Leader
可以进行消息广播。如果有新的节点进入,还要对新的节点进行同步。
崩溃恢复阶段
场景:一旦 Leader
服务器出现崩溃或者由于网络原因导致 Leader
服务器失去了与过半 Follower
的联系,那么就会进入崩溃恢复模式。
Zab
协议崩溃恢复要求满足以下两个要求:
- 新选举出来的
Leader
不能包含未提交的 Proposal
(提案) - 新选举的
Leader
节点中含有最大的 zxid
(提议ID
)
Zookeeper选举基本的特性有:
-
Zookeeper
在配置集群时节点数不可小于3 - 节点只有获得半数以上的投票才能当选
Leader
-
Zookeeper
在启动时会通过广播机制来把投票结果告诉其他的节点 -
Zookeeper
在启动时首先会给自己投票,然后与其他已启动的节点进行通信,通过比较id
从而判断是否能获取其他节点的投票
场景模拟
假设有节点1、2、3、4、5
其对应的id值也是1、2、3、4、5
第一种情况:节点依次启动(中位数的节点越容易当选)
- 节点1启动,给自己投票,进行数据交互,但是没有其他节点,所以1处于竞选者
- 节点2启动,给自己投票,进行数据交互,获得1的投票,但是投票数没超过总节点的一半,所以1、2处于竞选者
- 节点3启动,给自己投票,进行数据交互,获得1、2的投票,3获取的投票数超过投票数的半数,所以3当选领导者,1、2成为跟随者
- 节点4启动,给自己投票,进行数据交互,领导者3已存在,1、2、4成为跟随者
- 节点5启动,给自己投票,进行数据交互,领导者3已存在,1、2、4、5成为跟随者
第二种情况:领导者宕机
-
Zookeeper
旧的Leader
从宕机中恢复,并不能一定会成为Leader
,会重新触发投票 - 每一个已启动的节点都会通过广播机制来广播自己投票情况,这时
id
越大越容易当选
知识点
1.Zookeeper
的常用的端口:
- 2181:对
client
端提供服务 - 3888:选取
leader
使用 - 2888:集群内部使用
分布式锁
有的时候,我们需要保证一个方法在同 一时间内只能被同一个线程执行。在单机模式下,可以通过sychronized
、锁等方式来实现。
分布式锁的三个动作:
- 加锁
- 解锁
- 锁过期
实现分布式锁的解决方案
数据库锁
- 通过一个一张表的一条记录,来判断资源的占用情况
- 使用基于数据库的排它锁 (即
select * from tb_User for update
) - 使用乐观锁的方式,即
CAS
操作(或version
字段)
基于Redis分布式锁
Redis
分布式锁的三种行为:
- 加锁:使用
setnx
来抢夺锁,将锁的标识符设置为1,表示锁已被占用。 - 解锁:使用
setnx
来释放锁,将锁的标识符设置为0,表示锁已被释放。 - 锁过期:用
expire
给锁加一个过期时间防止锁忘记了释放,expire
时间过期将返回0。
Setnx
和expire
都是原子操作,实际应用中使用lua
脚本来确保操作的原子性。
基于Zookeeper的分布式锁(这种方式最可靠)
- 基于
zookeeper
临时有序节点可以实现的分布式锁。大致思想即为:每个客户端对某个方法加锁时,在zookeeper
上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 - 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。
Redis分布式锁和Zookeeper分布式锁的区别
-
redis
分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能; -
zk
分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。 -
redis
获取锁的那个客户端bug
了或者挂了,那么只能等待超时时间之后才能释放锁;而zk
的话,因为创建的是临时znode
,只要客户端挂了,znode
就没了,此时就自动释放锁。
分布式事务
分布式事务是确保数据最终一致性的一种方式。
事务的ACID
- 原子性:多条指令作为一个集体,要么都执行,要么都不执行
- 一致性:比如 a=100 b=100 a、b之间交易 总和一定是200
- 隔离性:一个事务不受其他事务的影响
- 持久性:事务一旦提交,它对数据库的改变时永久性的
分布式事务的解决方案
基于可靠消息的最终一致性方案
- 可独立部署、独立伸缩(扩展性)
- 兼容所有实现
JMS
标准的MQ
中间件 - 能降低业务系统与消息系统间的耦合性
- 可实现数据可靠的前提下确保一致性
业务场景:
- 那些不要求立即返回结果的业务,完成时间上的解耦
- 如对应支付系统会计异步记账业务、银行通知结果信息存储与驱动订单处理
实现方法:
结合ActiveMQ
、RabbitMQ
、RocketMQ
等消息组件实现分布式事务。
TCC事务补偿型方案
- 不与具体的服务框架耦合(在
RPC
框架中通用) - 位于业务服务层,而非资源层
- 可以灵活选择业务资源的锁定粒度
- 适用于强隔离性、严格一致性要求的业务场景
- 适用于执行时间较短的业务
业务场景:
- 一个业务逻辑涉及了多个业务组件
- 如:订单处理、资金账户处理、积分账户处理
实现方法:
-
TX-LCN
框架 - 阿里的
Seata
分布式事务框架
最大努力通知型方案
业务场景:
- 适用于跨平台业务
- 对于业务最终一致性的时间敏感度比较低的。
- 如:支付系统的商户通知业务
实现方法:
- 通过
ActiveMQ
、RabbitMQ
、RocketMQ
等消息组件的ack
机制(应答模式) - 如
ActiveMQ
的请求-应答模式,相当于通过消息队列,请求端注册了一个异步回调,在发送消息时指定回调消息的目的地和关联的id
,这样应答端在收到请求消息时,可以在处理后,将处理结果的应答消息发送到回调的目的地中。
分布式定时任务
Elastic-job实现分布式定时任务
ElasticJob是一种分布式调度解决方案,由两个单独的项目ElasticJob-Lite
和ElasticJob-Cloud
组成。
通过灵活的调度,资源管理和作业管理功能,它创建了适合Internet
场景的分布式调度解决方案,并通过开放式架构设计提供了多元化的作业生态系统。它为每个项目使用统一的作业API
。开发人员只需要一次编写代码,就可以随意部署。
ElasticJob-Lite
和ElasticJob-Cloud
存在一定的区别
-
cloud
基于mesos
运行,是mesos
的Framework
-
lite
自己可独立运行,轻量级去中心化
ElasticJob提供了三种作业类型:
- DataFlow类型:用于处理数据流,它又提供2种作业类型,分别是
ThroughputDataFlow
和SequenceDataFlow
。需要继承相应的抽象类。 - Script类型:用于处理脚本,可直接使用,无需编码。
- Simple类型(常用):用于作业简单的业务处理,未经任何封装的类型。需要继承
AbstractSimpleElasticJob
,该类只提供了一个方法用于覆盖,此方法将被定时执行。用于执行普通的定时任务,与Quartz
原生接口相似,只是增加了弹性扩缩容和分片等功能。
ElasticJob的原理
1.通过分片的方式拆分任务
- 任务的分布式执行,需要将一个任务拆分为多个独立的任务项,然后由分布式的服务器分别执行某一个或几个分片项。
例如:
- 有一个遍历数据库某张表的作业,现有2台服务器。为了快速的执行作业,那么每台服务器应执行作业的50%。为满足此需求,可将作业分成2片,每台服务器执行1片。作业遍历数据的逻辑应为:服务器A遍历ID以奇数结尾的数据;服务器B遍历ID以偶数结尾的数据。
- 如果分成10片,则作业遍历数据的逻辑应为:每片分到的分片项应为ID%10,而服务器A被分配到分片项0,1,2,3,4;服务器B被分配到分片项5,6,7,8,9,直接的结果就是服务器A遍历ID以0-4结尾的数据;服务器B遍历ID以5-9结尾的数据。
- 任务总片数:
shardingContext.getShardingTotalCount()
,当前分片项:shardingContext.getShardingItem()
2.分片项与业务处理解耦
-
Elastic-Job
并不直接提供数据处理的功能,框架只会将分片项分配至各个运行中的作业服务器,开发者需要自行处理分片项与真实数据的对应关系。
3.个性化参数的适用场景
- 个性化参数即
shardingItemParameter
,可以和分片项匹配对应关系,用于将分片项的数字转换为更加可读的业务代码。
例如:
- 按照地区水平拆分数据库,数据库A是北京的数据;数据库B是上海的数据;数据库C是广州的数据。如果仅按照分片项配置,开发者需要了解0表示北京;1表示上海;2表示广州。
- 合理使用个性化参数可以让代码更可读,如果配置为0=北京,1=上海,2=广州,那么代码中直接使用北京,上海,广州的枚举值即可完成分片项和业务逻辑的对应关系。
分布式索引
Elasticsearch是实时的分布式索引分析引擎,内部使用Lucene
做索引与搜索。
- 实时:指的是新增的数据会很快被检索到;
- 分布式:可以动态调整集群规模,弹性扩容。
- Lucene:是
Java
语言编写的全文搜索框架,用于处理纯文本的数据,但它只是一个库,提供建立索引、执行搜索等接口,但是不提供分布式服务。
ES中的基本概念
- cluster:代表一个集群,集群中有多个节点,其中有一个为主节点,这个节点是通过选举产生的,起到了去中心化。
- shards:代表索引分片,
es
可以把一个完整的索引分成多个分片,可以把一个索引拆分成多个,实现分布式搜索。 - replicas:代表索引副本,
es
可以设置多个索引的副本,既能提高系统的容错性,也能对搜索请求进行负载均衡。 - recovery:代表数据恢复或者叫数据重新分布,
es
在有节点加入或者退出时会根据机器的负载对索引进行重新分配,挂掉的节点重新启动时也会进行数据恢复。 - river:代表
es
的一个数据源,也是其他存储方式(如数据库)同步数据到es
的一个方法。(river
包括couchDB
、RabbiMQ
等) - gataway:代表
es
索引快照的存储方式,es
默认是先把索引放在内存中,当内存满了之后再持久化到本地硬盘。当es关闭重启时,es
会从硬盘中读取索引备份数据。es
支持多种 gateway
方式:本地文件系统(默认),分布式文件系统,Hadoop
的HDFS
和云存储等; - discovery.zen:代表
es
的自动发现节点机制,es
会通过广播寻找存在的节点,再通过多播协议来进行节点之间的通讯。 - Transport:代表
es
内部或者集群与客户端的交互方式,默认内部是使用tcp
协议进行交互,同时支持json
、thrift
等。
ES部分特性
- 扩展能力:通过分片的方式将数据分成若干小块分配到各个机器中,从而实现了系统的水平扩展能力。
- 读写并行:分片是底层的基本的读写单元,分片的目的是分割巨大的索引,让读写可以并行操作,由多台机器共同完成。
- 容错性:通过副本的形式,把数据复制成多个副本,放置到不同的机器中。
- 并发更新:
ES
将数据副本分为主从两个部分,即主分片和副分片。主数据作为权威数据,写过程先写主分区,成功后再写副分区,恢复阶段以主分区为主。 - 实时性:为尽可能让新的索引可以被索引,
ES
在系统将数据写入系统缓存后(还没有写入磁盘之前),该数据就对外可读。
ES的索引结构
ES
是面向文档的。各种文本内容以文档的形式存储的ES
中,文档可以是一封邮件、一条日志、或者一个网页的内容。
通常说ES
只支持JSON
格式,指的是ES
使用JSON
作为文档的序列化格式。
在存储结构上,由_index
、_ type
、和_id
三个参数来唯一标识一个文档:
- _index:指向一个或多个物理分片的逻辑命名空间
- _type:一个数据的整体模式是相似或相同的集合
- _id:文档标记符由系统自动生成或使用者提供
ES索引
一个ES
索引包含很多分片,一个分片是一个Lucene
的索引,它本身就是一个完整的搜索引擎,可以独立执行建立索引和搜索任务。Lucene
索引有由很多分段组成,每个分段都是一个倒排索引。
使用倒排索引一旦被写入文件后就具有不变性:对文件的访问不需要加锁,读取索引时可以被文件系统缓存等。
为了保持索引的一致性,我们在修改文档时,会把原先的索引标记成删除(但并没有做物理删除),使用新的索引。
在ES
中,每秒清空一次写缓存,将这些数据写入文件,这个过程称为refresh
,每次refresh
会创建一个新的Lucene
段。
但是过多的Lucene
段会影响其性能,所以ES
采用将较小的段合并为大的段的策略:在和并过程中,标记为删除的数据不会写入新分段,当合并过程结束,旧的分段数据被删除,标记删除的数据才从磁盘删除。
倒排索引
- 正排索引:可以理解为将一个文档进行
wordcount
统计后的结果 :hello
2次 world
1次 - 倒排索引: 倒排索引是实现“单词-文档矩阵”的一种具体存储形式,通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。
ES集群
ES
集群采用的是一种主从模式:
- 这种模式可以简化系统设计,
Master
作为权威节点,部分操作仅由Master
执行,并负责维护集群元数据。 -
Master
节点存在单节点故障需要灾备问题,并且集群规模会受限于Master
节点的管理能力。
ES集群中节点角色
主节点
- 负责集群侧面的相关操作,管理集群变更。
- 集群状态由主节点进行维护,如果主节点从数据节点接收更新,则将这些更新广播到集群的其他节点,让每个节点的集群状态保持最新。
数据节点
- 负责保存数据、执行数据相关操作:
CURD
、搜索、聚合等。 - 数据节点对
CPU
、内存、I/O
要求比较高 - 一般情况下,数据读写流程只和数据节点交互。不会和主节点打交道。
预处理节点
- 预处理操作允许在索引文档之前,即写入数据之前,通过事先定义好的一系列的
processors
(处理器)和pipeline
(管道),对数据进行某种转换、富化。 - 默认情况下,在所有的节点上启动了
ingest
。
协调节点
- 协调节点将请求转发给保存数据的数据节点
- 每个数据节点在本地执行请求,并将结果返回协调节点。协调节点收集数据后,将每个数据节点的结果合并为单个全局结果。
- 因为对结果收集和排序需要很多的
CPU
和内存资源,所以协调节点的存在,可以缓解其他节点的压力。
部落节点
- 部落节点可以在多个集群之间充当联合客户端
- 本质上是一个 智能负载均衡器,提供路由请求的功能
- 目前已经被协调节点所取代
集群健康状态
丛数据完整性的角度,集群健康状态分为三种:(针对单个索引也适用)
- Green:所有的主分片和副分片都正常运行。
- Yellow:所有的主分片都正常运行,但不是所有的副分片都正常运行。这意味着存在单节点故障风险。
- Red:有主分片没能正常运行。
集群扩容
当扩容集群、添加节点时,分片会均衡地分配到集群的各个节点,从而对索引和搜索过程进行负载均衡,这些都是系统自动完成的。
当ES
集群发生故障时,ES
会自动处理节点异常:
- 当主节点异常时,集群会重新选举主节点
- 当某个主分片异常时,会将副分片提升为主分片
集群进行扩展的过程:
1.当只有一个节点时:Node1
上有三个主分片,没有副分片
2.添加第二个节点后,副分片被分配到Node2
3.添加第三个节点后,索引的六个分片(三主三副)被平均分配到集群的三个节点。
这个过程会保证主分片和副分片不会分配到同一个节点,避免单个节点故障引起数据丢失。
ES集群的启动流程
ES
的在启动的过程中主要会经过:
1.electmaster(选举主节点)
ES
的选主算法是基于Bully
算法的改进,主要的思路是对节点ID
的排序,取ID
值最大的节点,作为Master
,每个节点都运行这个流程。
- 参赛人数需要过半
- 得票数需要过半
- 当存在节点离开时,要判断当前的节点数是否过半。如果没有过半,已选择的
Master
将放弃Master
地位,重新选取。
这里为了防止由于网络延迟等原因,出现“脑裂”现象
ID
分别为:1、2、3、4、5 节点
- 依次启动3会当选
- 当3发生宕机,5会当选
- 当3从宕机中恢复,5会继续保持
Master
(es
集群)
但是Zookeeper
集群下,3从宕机中恢复会获得Master
身份。
像比较而言zookeeper
选主机制更加优秀,支持如果Master
宕机后恢复,该节点会继续成为Master
.(不需要进行ID
的比较)
2.gateway(收集选举集群元信息)
Master
节点第一个任务是让各节点把各自存储的元信息发送过来,再比较版本号之后,将最新的元信息广播到所有的节点中。
集群元信息的选举包括:集群级和索引级。
- 集群元信息选举完毕后,
Master
发布首次集群状态,然后开始选举索引级元信息。 - 在
gateway
选举的过程中,不接受新节点的加入请求。
3.allocation(分配过程)
ES
中通过分配过程决定哪个分片位于哪个节点,重构内容路由表。
第一步:选主分片
ES
中所有的分配任务都是由Master
来完成的。
主分片选举过程中是通过集群级元信息中记录的“最新主分片的列表”(这里的新旧是通过UUID
来确定的)来确定主分片的。
第二步:选副分片
主分片选举完成后,从上一个过程(即选主分片)汇总的shard信息选择一个副本作为副分片。如果汇总信息不存在,则分配一个全新副本的操作依赖于延迟配置项:
在allocation
分配的过程中,运行新启动的节点加入集群。
4.index recovery(索引恢复)
主分片的recovery
不会等待其副分片分配成功才开始recovery
。它们是独立的流程,只是副分片的recovery
需要主分片恢复完毕才开始。
第一步:主分片recovery
由于每次写操作都会记录事务日志(translog
),事务日志中记录了具体操作以及相关数据。主分片通过将最后一次提交的事务日志重放,建立Lucene
索引,如此完成主分片的recovery
.
第二步:副分片recovery
副分片的恢复需要与主分片一致,同时,恢复期间允许新的索引操作。
通常会分成2个阶段来恢复:
- 在主分片的节点中,获取
translog
保留锁(这使得索引操作可以正常执行),然后调用Lunece
接口把shard
做快照(因为这时主分片已经完成索引恢复),然后将shard
数据复制到副本节点。(这时副分片就可以处理写请求了) - 对
translog
做快照处理,并发送大副本节点进行重放。(保证了主副分片数据的一致性)
ES启动时的外部检查
ES
中的“节点”在实现时被封装为Node
模块。在Node
类中调用其他内部组件,同时对外提供启动和关闭方法,对外部环境的检测就是在Node.start()
中进行的。
检测项包括:
- 堆大小检查:通常要将
JVM
堆大小(Xms
)与最大堆大小(Xmx
)的值设为一致。 - 文件描述符检查:调整系统的默认值 (如
/etc/security/limits.conf
设置最大线程数) - 内存锁定检查:
ES
允许进程使用物理内存,避免使用交换分区。(生产环境下建议关闭操作系统交换分区) - 最大线程数检查 :
ES
将请求分解为多个阶段执行,每个阶段使用不同的线程池来执行。因此ES
需要使用很多线程池。 - 最大虚拟内存检查:
Lucene
使用mmap
来映射部分索引到进程地址空间,所以要足够多的地址空间,即占用多的虚拟内存。 - 最大文件大小检查:段文件和事务日志存储在本地磁盘,它们可能会非常大,所以建议将系统的最大文件设置为无限。
- 虚拟内存区域最大数量检查:
ES
进程需要创建很多内存映射区,本项检查是要确保内核允许创建至少262144个内存映射区。 - JVM Client模式检查:
JVM
的运行模式:client JVM
模式与server JVM
模式。client JVM
调优了启动时间和内存消耗,server JVM
提供了更高的性能。默认使用server JVM.
- 串行收集检查:串行收集器会影响
ES
的性能,默认使用CMS
。 - 系统调用过滤器检查:根据不同的操作系统,
ES
安装各种不同的系统调用过滤器。这些过滤器可以阻止一些攻击行为。(同时要求使用普通用户来启动ES
,也是为安全性) - OnError与OnOutOfMemoryError:
JVM
遇到致命错误或内存溢出。