架构演进
- 大型机(Mainframe)
- 原始分布式(Distributed)
- 大型单体(Monolithic)
- 面向服务(Service Oriented)
- 微服务(Microservice)
- 服务网格(Service Mesh)
- 无服务(Serviceless)
原始分布式
远程调用需要解决的问题
- 远程的服务在哪?
服务发现
- 有多少个?
负载均衡
- 网络出现分区、超时、错误怎么办?
熔断、隔离、降级
- 方法参数与放回结果如何表示
序列化协议
- 信息如何传输
传输协议
- 服务权限如何管理
认证、授权
- 如何保证通信安全
网络安全层
- 如果令调用不同机器服务返回相同结果
分布式数据一致性
单体系统
构建可靠系统的观念从 “追求尽量不出错” 到正视 “出错是必然” 的转变,才是微服务架构得以挑战并逐步取代单体架构的底气所在
面向服务架构(Service Oriented Architecture)
将一个大的单体系统拆分为若干更小的、不运行在同一进程的独立服务
微服务
微服务是一种通过多小型服务组合来构建单个应用的架构风格,这写服务围绕业务能力而非特定的技术标准来构建。
各个服务可以采用不同的编程语言、不同的数据存储技术,运行在不同的进程之中。服务采取轻量级的通信机制
和自动化的部署机制实现通信与运维。
- 围绕业务能力构建
有怎样结构、规模、能力的团队,就会产生对应的结构、规模、能力的产品
- 分散治理
开发团队有直接对服务运行质量负责的责任,也有不受外界干预地掌控服务各个方面的能力
- 通过服务来实现独立自治的组件
服务是进程外的组件
- 产品化思维
把产品视作一种持续改进、提升的过程。
- 数据去中心化
提倡数据应该按领域分散管理、更新、维护、存储。
- 强终端弱管道
反对SOAP和ESB通信机制,提倡 RESTful 风格的通信方式
- 容错性设计
不再追求服务永远稳定,而是接受服务总会出错的现实,要求在微服务设计中,能够有自动机制对其依赖的服务进行
快速故障检测,出错时隔离,恢复时重新联通 - 演进式设计
服务是可替代的,不可替代不可更改的服务,是一种系统设计上脆弱的表现
- 基础设施自动化
CI/CD 单靠人工是很难支持成百上千的服务的
服务网格
直到 Docker K8S 兴起和完善
车边代理模式(Sidecar Proxy),通常是指k8s的Pod
业务与技术完全分离,远程与本地完全透明
Istio
无服务
- 后端设施(Backend)
数据库、消息队列、日志、存储等
- 函数
业务逻辑代码,无服务中的函数运行在云端,函数即服务
目前还未得到发展
访问远程服务
将计算机程序的工作范围从单机扩展至网络,从本地延伸至远程,是构建分布式系统的首要基础
远程服务调用(Remote Procedure Call,RPC)
gRPC Thrift
最初目的就是为了让计算机能够与调用本地方法一样去调用远程方法
需要解决的三个问题
- 如何表示数据?
传递给方法的参数以及执行后的返回值
gRPC的Protocol Buffers
XML JSON
- 如何传递数据
指应用层协议,而不是说传输层(TCP UDP)
Wire Protocol
SOAP JSON-RPC
- 如何表示方法
WSDL JSON-WSP
上面的3个问题,在本地调用都有对应的解决方案,RPC的设计始于本地方法调用。
各种RPC
- 朝着面向对象发展
希望在分布式系统中也能进行跨进程的面向对象编程,代表为RMI CORBA DCOM
- 朝着性能发展
gRPC Thrift
序列表效率、信息密度是决定RPC性能的主要因素
- 朝着简化发展
JSON-RPC
牺牲了性能和效率,换来的是简单,通用
REST(Representational State Transfer)
REST 是对 HTT(Hypertext Transfer)的进一步抽象,两者的关系就像接口和实现类的关系一般
- 资源(Resource)
例如一本书,无论是点子还是纸质,无论是在哪里阅读
- 表征(Representation)
信息与用户交互的表现形式,书可以看也能听
- 状态(State)
通常是对服务端来说,用户当前处于什么状态
- 转移(Transfer)
从一个状态转移到另一个状态
- 统一接口(Uniform Interface)
通过某种方式让状态转移,如下一篇文章的链接
- 超文本驱动(Hypertext Driven)
其实就是展示给用户的网页
- 自描述消息(Self Descriptive Message)
明确告诉客户端要怎么处理这条消息,如 “Content-Type”
一套理想的REST风格的系统应该满足一下6大原则
- 客户端与服务端分离
- 无状态,REST希望服务端不用负责维护状态(这点比较难)
- 可缓存
- 分层系统
- 统一接口
- 按需代码
REST以资源为主体
不足之处
- 面向资源编程思想适合做CRUD,复杂的业务逻辑还是得面向过程、面向对象
- REST与HTTP完全绑定,不适合高效性传输的场景
- 不利于事务支持
- 没有传输可靠性支持
- 缺乏对资源的拆分、批量处理
事务处理
ACID
AID是手段,C是目的
- 原子性(Atomic)
同一项业务处理过程中,事务保证了对多个数据的修改,要么同时成功,要么同时被撤销
- 一致性(Consistency)
保证系统中所有的数据都是符合期望的,状态一致,要达成这个需要AID三个方面来共同保证
- 隔离性(Isolation)
在不同的业务处理过程中,事务保证了各业务正在读、写的数据相互独立,互不影响
- 持久性(Durability)
事务应当保证所有成功被提交的数据都能正确被持久化,不丢失数据
本地事物(Local Transaction)
仅操作单一事物资源的、不需要全局事物管理器进行协调的事物
实现原子性和持久性
最大的困难是 “写入磁盘” 这个操作并不是原子的,不仅有 “写入” “未写入” 状态,还有 “正在写” 的中间状态。
- 奔溃恢复(Crash Recovery)
由于写入中间状态与崩溃都是无法避免的,为了保证原子性和持久性,就只能在崩溃后采取恢复的补救措施,这种数据恢复操作被称为 “奔溃恢复”
- 提交日志(Commit Logging)
不能直接改变某行某列数据,而是必须将修改数据的操作所需的全部信息(什么数据、内存位置 磁盘、修改后的值等)
以日志的形式,顺序追加记录到磁盘,只有在日志记录全部安全写入磁盘,才会根据日志信息对真正的数据进行修改。修改完加上一条结束日志(End Record)记录
缺陷:所有对数据的真实修改都必须发生在事物提交以后,即日志写入Commit Record之后,在此之前,即使磁盘I/O有足够空闲,
即使某个事物修改的数据量非常大,占用了大量的内存缓冲区,都不允许在事物提交之前就修改磁盘上的数据,对性能影响比较大
改进方案:提前写入日志(Write Ahead Logging)
按照事物提交时点,将何时写入变动数据划分为Force和Steal两种
- Force
当事物提交后,要求变动的数据必须同时完成写入,如果不强制变动数据必须同时写入则称为No-Force,
现实中大部分数据库采用的都是No-Force策略,因为只要有了日志,变动的数据随时可以持久化,从优化磁盘I/O性能考虑,没有必要强制数据写入时立即进行
- Steal
在事物提交前,允许变动数据提前写入,不允许则称为No-Steal,有利于I/O和节省数据库缓冲区内存
Commit Record 允许No-Force,但是不允许Steal
Write Ahead Logging 允许No-Force,也允许Steal,它给出的解决方法是增加了另一种被称为Undo Log的日志类型,
当变动数据写入磁盘前,必须先记录Undo Log(回滚日志),注明修改了哪个位置、从什么值修改成什么值等,以便在事物回归或者崩溃恢复时,
根据Undo Log对提前写入的数据变动进行擦除,前面说的就叫Redo Log(重做日志)
Write Ahead Logging 奔溃恢复
1、分析阶段(Analysis)
找出所有没有End Record的事物,组成待恢复的事物集合,至少包括事物表(Transaction Table) 脏页表(Dirty Page Table)
2、重做阶段(Redo)
根据上面的待恢复事物集合来重演历史(Repeat History),具体操作是找出所有包含Commit Record的日志,将这些日志修改的数据写入磁盘,
写入完成后在日志中增加一条End Record,然后移出待恢复事物集合
3、回滚阶段(Undo)
剩下的都是需要回滚的事物,被称为Loser,根据Undo Log中的信息,将已经提前写入磁盘的信息重新改回去,达到回滚
实现隔离性
- 写锁(Write Lock)
也叫排它锁(eXclusive Lock,简称X-Lock)
如果数据有加写锁,就只有持有写锁的事物才能对数据进行写入操作,其他事物不能写入和读取
- 读锁(Read Lock)
也叫共享锁(Shared Lock,简称S-Lock)
多个事物可以对同一数据添加多个读锁,数据加上读锁后就不能再被加写锁,所以其他事物不能对数据进行写入,但仍然可以读取。
- 范围锁(Range Lock)
mysql的GAP,对于某个范围直接加排他锁,这个范围的数据不能被写入
全局事物
这里的场景是指单个服务使用多个数据源的场景,理论上全局事物没有单个服务的约束
XA 将事物提交拆分成两阶段(两段式提交 2PC)
准备阶段:又叫投票阶段,协调者询问事务所有参与者是否准备好提交,参与者如果已经准备好提交则回复 Prepared,否则回复 Non-Prepared
,对于数据库来说,准备操作是在重做日志中记录全部事物提交操作所要做的内容,与本地事物真正提交的区别只是暂时不写入最后一条 Commit Record,仍然持有锁
提交阶段:又叫执行阶段,收到全部回复 Prepared,则先在自己本地持久化事物状态为 Commit,然后向所有参与者发送 Commit 命令,让所有参与者立即执行提交操作;
否则,任意一个用户回复 Non-Prepared 或者超时未回复,协调者将自己完成事物状态改为 Abort 指令,让参与者执行回滚操作
前提条件:网络在提交阶段的短时间内是可靠的,即提交阶段不会丢失消息
缺点明显
单点问题:协调者在两段式提交中具有举足轻重的作用
性能问题:所有参与者相当于被绑定为一个统一调度整体,期间要经过两次远程调用,三次数据持久化,整个过程要持续到参与者最慢那一个处理操作结束为止
一致性风险:网络稳定性和宕机恢复能力假设不成立时