在前一篇文章的路由的初始化
章节,我们探究了Zuul的路由是如何在事件的驱动下刷新路由的。了解了路由的刷新就已经非常接近动态刷新的功能了。
Zuul探究(一)——使用
最近开始做一个动态网关平台,集动态路由、动态权限、限流配额等功能为一体,为整个公司所有项目的对外接口提供一个统一的调用管理。之前各个项目的对外接口都是各自管理的,于是每个接口的权限控制不统一,接口的说明也散落在各个项目的接口文档里,导致对接工作十分繁复。
我们选择的技术方案是Zuul。
Zuul探究将是一个系列。从这篇文章开始,我将开始探究Zuul的相关技术,包括:
- Zuul的使用
- Zuul的工作原理
- Zuul动态路由的实现
MyBatis探究(四)——MyBatis与SpringBoot的结合及代码探究
MyBatis提供了MyBatis-Spring-Boot-Starter项目用于MyBatis与SpringBoot的整合。充分利用了SpringBoot自动配置的优点,使用起来非常方便。我们来看一下简单的使用:
MyBatis探究(三)——MyBatis与Spring的结合及代码探究
MyBatis与Spring的结合依赖于MyBatis-Spring。MyBatis-Spring会帮助你将MyBatis代码无缝地整合到Spring中。使用这个类库中的类,Spring将会加载必要的MyBatis工厂类和session类。这个类库也提供一个简单的方式来注入MyBatis数据映射器和SqlSession到业务层的bean中。而且它也会处理事务,翻译MyBatis的异常到Spring的DataAccessException异常(数据异常)。
MyBatis探究(二)——源码分析
在上一篇文章的基础上,我们从代码级别来分析一下MyBatis的执行过程。
Mybatis的运行分为两大部分,第一部分是读取配置文件到Configuration对象,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程。
MyBatis探究(一)——使用
MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解来配置和映射原生信息,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。
MyBatis探究将是一个系列。从这篇文章开始,我将开始探究MyBatis的相关技术,包括:
- MyBatis的使用
- MyBatis的源码分析
- MyBatis与Spring的结合
- MyBatis与Spring boot的结合
ConcurrentHashMap分析
ConcurrentHashMap的实现在JDK1.7和JDK1.8中有着很大的区别。
在JDK1.7中,ConcurrentHashMap采用分段锁的机制,实现并发的更新操作,底层采用数组+链表的存储结构。
其包含两个核心静态内部类Segment和HashEntry。
- Segment继承ReentrantLock用来充当锁的角色,每个Segment对象守护每个散列映射表的若干个桶
- HashEntry用来封装映射表的键/值对
- 每个桶是由若干个HashEntry对象链接起来的链表
JDK1.8中,ConcurrentHashMap抛弃了Segment分段锁机制,取而代之的是采用Node + CAS + Synchronized来保证并发安全地进行。对于锁的粒度,调整为对每个数组元素加锁(Node)。
本文根据JDK1.8来研究ConcurrentHashMap的实现。
从Paxos到ZooKeeper读书笔记
第一章 分布式架构
ACID:
- 原子性:事务包含的各项操作在一次执行过程中,只允许出现以下两种状态之一:全部成功执行、全部不执行
- 一致性:事务执行的结果必须是使数据库从一个一致性状态转变到另一个一致性状态,当数据库只包含成功事务提交的结果时,就能说数据库处于一致性状态
- 隔离性:不同的事务并发操纵相同的数据时,每个事务都有各自完整的数据空间,即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能互相干扰
- 持久性:一个事务一旦提交,它对数据库中对应数据的状态变更就应该是永久性的。
CAP理论:
一个分布式系统不可能同时满足一致性(C: Consistency)、可用性(A: Availability)和分区容错性(P: Partition tolerance)这三个基本需求,最多只能同时满足其中的两项
- 一致性:在分布式环境中,一致性是指数据在多个副本之间是否能够保持一致的特性。
- 可用性:系统提供的服务必须一直处于可用的状态,对于用户的每个操作请求总是能够在有限的时间内返回结果。
- 分区容错性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障
放弃CAP定理 | 说明 |
---|---|
放弃P | 如果希望能够避免系统出现分区容错性问题,一种较为简单的做法是将所有的数据都放在一个分布式节点上 |
放弃A | 一旦系统遇到网络分区或其他故障时,那么受到影响的服务需要等待一定的时间,因此在等待期间系统无法对外提供正常的服务,即不可用 |
放弃C | 放弃一致性指的是放弃数据的强一致性,而保留数据的最终一致性。这样的系统无法保证数据保持实时的一致性,但是能够承诺的是,数据最终会达到一个一致的状态。 |
对于一个分布式系统而言,分区容错性是一个最基本的要求。因此系统架构设计师往往需要把精力花在如何根据业务特点在C(一致性)和A(可用性)之间寻求平衡。
BASE理论:
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写。BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
基本可用
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。以下两个就是“基本可用”的典型例子。
- 响应时间上的损失
- 功能上的损失
弱状态
弱状态也称为软状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
最终一致性
最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
最终一致性存在以下五类主要变种:
因果一致性(Causal consistency)
因果一致性是指,如果进程A在更新完某个数据项后通知了进行B,那么进程B之后对该数据项的访问都应该能够获取到进程A更新后的最新值,并且如果进程B要对该数据项进行更新操作的话,务必基于进行A更新后的最新值,即不能发生丢失更新情况。与此同时,与进行A无因果关系的进程C的数据访问则没有这样的限制。
读己之所写(Read your writes)
读已之所写是指,进程A更新一个数据项之后,它自己总是能够访问到更新过的最新值,而不会看到旧值。也就是说,对于单个数据获取者来说,其读取到的数据,一定不会比自己上次写入的值旧。因此,读己之所写也可以看作是一种特殊的因果一致性。
会话一致性(Session consistency)
会话一致性将对系统数据的访问过程框定在了一个会话当中:系统能保证在同一个有效的会话中实现“读己之所写”的一致性,也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。
单调读一致性(Monotonic read consistency)
单调读一致性是指如果一个进程从系统中读取一个数据项的某个值后,那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。
单调写一致性(Monotonic write consistency)
单调写一致性是指,一个系统需要能够保证来自同一个进程的写操作被顺序地执行。
第二章 一致性协议
2PC与3PC
当一个事务操作需要跨越多个分布式节点的时候,为了保持事务处理的ACID特性,就需要引入一个称谓“协调者(Coordinator)”的组件来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点则被称为“参与者(Participant)”。
2PC
阶段一:提交事务请求
事务询问
协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应
执行事务
各参与者节点执行事务操作,并将undo和redo信息记入事务日志中
各参与者向协调者反馈事务询问的响应
如果参与者成功执行了事务操作,那么就反馈给协调者Yes响应,表示事务可以执行;如果参与者没有成功执行事务,那么就反馈给协调者No响应,表示事务不可以执行
阶段二:执行事务提交
协调者根据各参与者的反馈情况来决定最终是否可以进行事务提交操作,正常情况下,包含以下两种可能:
执行事务提交
假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务提交。
- 发送提交请求:协调者向所有参与者节点发出Commit请求
- 事务提交:参与者接收到Commit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源
- 反馈事务提交结果:参与者在完成事务提交之后,向协调者发送Ack消息
- 完成事务:协调者接收到所有参与者反馈的Ack消息后,完成事务
中断事务
假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。
- 发送回滚请求:协调者向所有参与者节点发出Rollback请求
- 事务回滚:参与者接收到Rollback请求后,会利用其在阶段一中记录的Undo信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源
- 反馈事务回滚结果:参与者在完成事务回滚之后,向协调者发送Ack消息
- 中断事务:协调者接收到所有参与者反馈的Ack消息后,完成事务中断
优点:原理简单,实现方便
缺点:同步阻塞、单点问题、数据不一致、太过保守
3PC
三阶段提交,是2PC的改进版,其将二阶段提交协议的“提交事务请求”过程一分为二,形成了由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议。
阶段一:CanCommit
- 事务询问:协调者向所有的参与者发送一个包含事务内容的CanCommit请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
- 各参与者向协调者反馈事务询问的响应:参与者在接收到来自协调者的CanCommit请求后,正常情况下,如果其自身认为可以顺利执行事务,那么会反馈Yes响应,并进入预备状态,否则反馈No响应。
阶段二:PreCommit
协调者根据各参与者的反馈情况来决定是否可以进行事务的PreCommit操作,正常情况下,包含两种可能。
执行事务预提交
假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务预提交
- 发送预提交请求:协调者向所有参与者节点发出preCommit的请求,并进入Prepared阶段
- 事务预提交:参与者接收到preCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中
- 各参与者向协调者反馈事务执行的响应:如果参与者成功执行了事务操作,那么就会反馈给协调者Ack响应,同时等待最终的指令:提交(commit)或中止(abort)
中断事务
假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。
- 发送中断请求:协调者向所有参与者节点发出abort请求
- 中断事务:无论是收到来自协调者的abort请求,或者是在等待协调者请求过程中出现超时,参与者都会中断事务
阶段三:doCommit
执行提交
- 发送提交请求:进入这一阶段,假设协调者处于正常工作状态,并且它接收到了来自所有参与者的Ack响应,那么它将从“预提交”状态转换到“提交”状态,并向所有的参与者发送doCommit请求
- 事务提交:参与者接收到doCommit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源
- 反馈事务提交结果:参与者在完成事务提交之后,向协调者发送Ack消息
- 完成事务:协调者接收到所有参与者反馈的Ack消息后,完成事务
中断事务
假设协调者处于正常工作状态,并且有任意一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。
- 发送中断请求:协调者向所有的参与者节点发送abort请求
- 事务回滚:参与者接收到abort请求后,会利用其在阶段二中记录的undo信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源
- 反馈事务回滚结果:参与者在完成事务回滚之后,向协调者发送Ack消息
- 中断事务:协调者接收到所有参与者反馈的Ack消息后,中断事务
一旦进入阶段三,可能会存在以下两种故障:协调者出现问题、协调者和参与者之间的网络出现故障。无论出现哪种情况,最终都会导致参与者无法及时接收到来自协调者的doCommit或是abort请求,针对这样的异常情况,参与者都会在等待超时之后,继续进行事务提交。
优点:降低了参与者的阻塞范围,并且能够在出现单点故障后继续达成一致。
缺点:参与者接收到preCommit消息后,如果网络出现分区,此时协调者所在的节点和参与者无法进行正常的网络通信,在这种情况下,该参与者依然会进行事务的提交,这必然出现数据的不一致性。
第4章 ZooKeeper与Paxos
Zookeeper为分布式应用提供了高效且可靠的分布式协调服务,提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。在解决分布式数据一致性方面,Zookeeper并没有直接采用Paxos算法,而是采用了一种被称为ZAB的一致性协议。
Zookeeper是一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁、分布式队列等功能。ZooKeeper可以保证如下分布式一致性特性。
顺序一致性
从同一个客户端发起的事务请求,最终将会严格地按照其发起顺序被应用到Zookeeper中去
原子性
所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群所有机器都成功应用了某一个事务,要么都没有应用,一定不会出现集群中部分机器应用了该事务,而另外一部分没有应用的情况。
单一视图
无论客户端连接的是哪个ZooKeeper服务器,其看到的服务器数据模型都是一致的。
可靠性
一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下来,除非有另一个事务又対其进行了变更。
实时性
通常人们看到实时性的第一个反应是,一旦一个事务被成功应用,那么客户端能够立刻从服务端上读取到这个事务变更后的最新数据状态。这里需要注意的是,ZooKeeper仅仅保证在一定的时间段内,客户端最终一定能够从服务端上读取到最新的数据状态。
第5章 使用ZooKeeper
Java客户端API使用
创建会话
1 | ZooKeeper(String connectString, int sessionTimeout, Watcher watcher); |
创建节点
1 | String create(final String path, byte data[], List<ACL> acl, CreateMode createMode); |
删除节点
1 | public void delete(final String path, int version); |
读取数据
1 | List<String> getChildren(final String path, Watcher watcher); |
1 | byte[] getData(final String path, Watcher watcher, Stat stat); |
更新数据
1 | Stat setData(final String path, byte data[], int version); |
检测节点是否存在
1 | public Stat exists(final String path, Watcher watcher); |
权限控制
1 | addAuthInfo(String scheme, byte[] auth); |
ZkClient
创建会话
1 | public ZkClient(String serverstring) |
创建节点
1 | String create(final String path, Object data, final CreateMode mode) |
删除节点
1 | boolean delete(final String path) |
读取数据
1 | List<String> getChildren(String path) |
1 | <T extends Object> T readData(String path) |
更新数据
1 | void writeData(String path, Object data) |
检测节点是否存在
1 | boolean exists(final String path) |