按照做者分的章节名称来区分重点,进行总结和提炼。java
一、提出重编程能力仍是重架构的问题程序员
问:作一个高质量的软件,应该把精力集中在提高其中每个人员、过程、产出物的能力和质量上,仍是该把更多精力放在总体流程和架构上?算法
答:这二者都重要。前者重术,后者重道;前者更多与编码能力相关,后者更多与软件架构相关;前者主要由开发者个体水平决定,后者主要由技术决策者水平决定。数据库
二、提出构建一个大规模但依然可靠的软件系统是不是可行的编程
经过冯诺依曼研发自复制自动机的例子举例咱们一直是在用不可靠部件构造可靠的系统。好比咱们开发的每一个环节都是有可能出错的,但最终设计出的软件必然是不可靠的,但事实并不是如此。用冯诺依曼的自动机这个例子来说就是说这些零部件可能会出错,某个具体的零部件可能会崩溃消亡,但在存续生命的微生态系统中必定会有其后代的出现,从新代替该零部件的做用,以维持系统的总体稳定。在这个微生态里,每个部件均可以看做一只不死鸟,它会老去而后又能涅槃重生。后端
三、强调架构演变最终都是为了使咱们的服务更好地死去和重生设计模式
软件架构风格演变顺序:大型机 -> 原始分布式 -> 大型单体 -> 面向服务 -> 微服务 -> 服务网格 -> 无服务浏览器
技术架构上呈现出从大到小的发展趋势。做者提出了:相比于易于伸缩拓展应对更高的性能等新架构的优势,架构演变最重要的驱动力始终都是为了方便某个服务可以顺利地“死去”与“重生”而设计的,个体服务的生死更迭,是关系到整个系统可否可靠续存的关键因素。缓存
UNIX 的设计原则提出了:保持接口与实现的简单性,比系统的任何其余属性,包括准确性、一致性和完整性,都来得更加剧要。安全
负责制定 UNIX 系统技术标准的开放软件基金会(也叫OSF) 邀请了各大计算机厂商一块儿参与共同制订了名为“分布式运算环境”(也叫DCE)的分布式技术体系。DCE 包含一套相对完整的分布式服务组件规范与参考实现。
OSF 严格遵循 UNIX 设计风格,有一个预设的重要原则是使分布式环境中的服务调用、资源访问、数据存储等操做尽量透明化、简单化,使开发人员没必要过于关注他们访问的方法或其余资源是位于本地仍是远程。这样的主旨很是符合一向的UNIX 设计哲学。可是实现的目标背后包含着当时根本不可能完美解决的技术困难。 由于DCE一旦要考虑性能上的差别就不太行了。为了让程序在运行效率上可被用户接受,开发者只能在方法自己运行时间很长,能够相对忽略远程调用成本时的状况下才能考虑分布式,若是方法自己运行时间不够长,就要人为用各类方式刻意地构造出这样的场景,譬如将几个本来毫无关系的方法打包到一个方法体内,一块进行远程调用。这种构造长耗时方法自己就与指望用分布式来突破硬件算力限制、提高性能的初衷相互矛盾。而且此时的开发人员实际上仍然必须每时每刻都意识到本身是在编写分布式程序,不可轻易踏过本地与远程的界限。这和简单透明的原则相违背。
经过这个原始分布式开发得出了一个教训:某个功能可以进行分布式,并不意味着它就应该进行分布式,强行追求透明的分布式操做,只会自寻苦果。
基于当时的状况摆在计算机科学面前有两条通往更大规模软件系统的道路,一条是尽快提高单机的处理能力,以免分布式带来的种种问题;另外一条路是找到更完美的解决如何构筑分布式系统的解决方案
单体架构中“单体”只是代表系统中主要的过程调用都是进程内调用,不会发生进程间通讯。
单体架构的系统又叫巨石系统。单体架构自己具备简单的特性,简单到在至关长的时间内,你们都已经习惯了软件架构就应该是单体这种样子,因此并无多少人将“单体”视做一种架构来看待。
和不少书中的内容不一样的是,单体其实并非一个“反派角色”,单体并无你们口中的那么不堪。实际上,它时运行效率最高的一种架构风格。基于软件的性能需求超过了单机,软件开发人员规模扩大这样的状况,才体现了单体系统的不足之处。
单体架构因为全部代码都运行在同一个进程空间以内,全部模块、方法的调用都无须考虑网络分区、对象复制这些麻烦的事和性能损失。一方面得到了进程内调用的简单、高效等好处的同时,另外一方面也意味着若是任何一部分代码出现了缺陷,过分消耗了进程空间内的资源,所形成的影响也是全局性的、难以隔离的。好比内存泄漏、线程爆炸、阻塞、死循环等问题,都会影响整个程序,而不只仅是影响某一个功能、模块自己的正常运做。
一样的,因为全部代码都共享着同一个进程空间,不能隔离,也就没法作到单独中止、更新、升级某一部分代码,因此从可维护性来讲,单体系统也是不占优点的。程序升级、修改缺陷每每须要制定专门的停机更新计划,作灰度发布、A/B 测试也相对更复杂。
除了以上问题是单体架构的缺陷外,做者提出,最重要的仍是:单体系统很难兼容“Phoenix”的特性
单体架构这种风格潜在的观念是但愿系统的每个部件,每一处代码都尽可能可靠,靠不出或少出缺陷来构建可靠系统。可是单体靠高质量来保证高可靠性的思路,在小规模软件上还能运做良好,但系统规模越大,交付一个可靠的单体系统就变得愈来愈具备挑战性。
为了容许程序出错,为了得到隔离、自治的能力,为了能够技术异构等目标,是继为了性能与算力以后,让程序再次选择分布式的理由。在单体架构后,有一段时间是在尝试将一个大的单体系统拆分为若干个更小的、不运行在同一个进程的独立服务,这些服务拆分方法后来致使了面向服务架构(Service-Oriented Architecture)的一段兴盛期,这就是SOA 时代。
SOA是一次具体地、系统性地成功解决分布式服务主要问题的架构模式。
三种有表明性的SOA
信息烟囱又叫信息孤岛。使用这种架构的系统也被称为孤岛式信息系统或者烟囱式信息系统。它指的是一种彻底不与其余相关信息系统进行互操做或者协调工做的设计模式。
这样的系统其实并无什么“架构设计”可言。这样彻底不进行交互的模式不符合真实业务状况。
微内核架构也被称为插件式架构。微内核将主数据,连同其余可能被各子系统使用到的公共服务、数据、资源集中到一块,成为一个被全部业务系统共同依赖的核心(Kernel,也称为 Core System),具体的业务系统以插件模块(Plug-in Modules)的形式存在,这样也可提供可扩展的、灵活的、自然隔离的功能特性。
这种模式适合桌面应用程序和Web 应用程序。对于平台型应用来讲,常常会加入新的功能,就很像时不时加一个新的插件模块进来因此微内核架构比较适合。微内核架构也能够嵌入到其余的架构模式之中,经过插件的方式来提供新功能的定制开发能。
微内核架构也有它的局限和使用前提,架构中这些插件能够访问内核中一些公共的资源,但不会直接交互。可是不管是企业信息系统仍是互联网应用必须既能拆分出独立的系统,也能让拆分后的子系统之间顺畅地互相调用通讯。
为了能让子系统互相通讯,事件驱动架构的方案是在子系统之间创建一套事件队列管道,来自系统外部的消息将以事件的形式发送至管道中,各个子系统从管道里获取可以处理的事件消息,能够本身发布一些新的事件到管道队列中去,如此,每个消息的处理者都是独立的,高度解耦的,但又能与其余处理者经过事件管道进行互动。
演化至事件驱动架构时远程服务调用迎来了 SOAP 协议的诞生
微服务是一种经过多个小型服务组合来构建单个应用的架构风格,这些服务围绕业务能力而非特定的技术标准来构建。各个服务能够采用不一样的编程语言,不一样的数据存储技术,运行在不一样的进程之中。服务采起轻量级的通讯机制和自动化的部署机制实现通讯与运维。
“微服务”这个技术名词是由 Peter Rodgers 博士在 2005 年度的云计算博览会提出的。“Micro-Web-Service”,指的是一种专一于单一职责的、语言无关的、细粒度 Web 服务。最初的微服务能够说是 SOA 发展时催生的产物。随着时间的推动,技术的发展,微服务已经再也不是维基百科定义的那样,“仅仅只是一种 SOA 的变种形式”了。
微服务真正的崛起是在 2014 年,Martin Fowler 与 James Lewis 合写的文章《Microservices: A Definition of This New Architectural Term》中 给出了现代微服务的概念: “微服务是一种经过多个小型服务组合来构建单个应用的架构风格,这些服务围绕业务能力而非特定的技术标准来构建。各个服务能够采用不一样的编程语言,不一样的数据存储技术,运行在不一样的进程之中。服务采起轻量级的通讯机制和自动化的部署机制实现通讯与运维。”
文中列举了微服务的九个核心的业务与技术特征:
《Microservices》文中除了定义微服务是什么,还专门申明了微服务不是什么——微服务不是 SOA 的变体或衍生品,应该明确地与 SOA 划清了界线,再也不贴上任何 SOA 的标签。
微服务追求的是更加自由的架构风格,摒弃了几乎全部 SOA 里能够抛弃的约束和规定,提倡以“实践标准”代替“规范标准”。没有了统一的规范和约束,服务的注册发现、跟踪治理、负载均衡、故障隔离、认证受权、伸缩扩展、传输通讯、事务处理,等等这些问题,在微服务中再也不会有统一的解决方案,即便只讨论 Java 范围内会使用到的微服务,光一个服务间远程调用问题,能够列入解决方案的候选清单的就有:RMI(Sun/Oracle)、Thrift(Facebook)、Dubbo(阿里巴巴)、gRPC(Google)、Motan2(新浪)、Finagle(Twitter)、brpc(百度)、Arvo(Hadoop)、JSON-RPC、REST,等等;光一个服务发现问题,能够选择的就有:Eureka(Netflix)、Consul(HashiCorp)、Nacos(阿里巴巴)、ZooKeeper(Apache)、Etcd(CoreOS)、CoreDNS(CNCF),等等。其余领域的状况也是与此相似,总之,彻底是八仙过海,各显神通的局面。
做为一个普通的服务开发者,“螺丝钉”式的程序员,微服务架构是友善的。但是,微服务对架构者是满满的恶意,由于对架构能力要求已提高到前所未有的程度
定义:从软件层面独力应对微服务架构问题,发展到软、硬一体,协力应对架构问题的时代,此即为“后微服务时代”。
当虚拟化的基础设施从单个服务的容器扩展至由多个容器构成的服务集群、通讯网络和存储设施时,软件与硬件的界限便已经模糊。一旦虚拟化的硬件可以跟上软件的灵活性,那些与业务无关的技术性问题便有可能从软件层面剥离,悄无声息地解决于硬件基础设施以内,让软件得以只专一业务,真正“围绕业务能力构建”团队与产品。
Kubernetes 成为容器战争胜利者标志着后微服务时代的开端,但 Kubernetes 仍然没有可以完美解决所有的分布式问题——“不完美”的意思是,仅从功能上看,单纯的 Kubernetes 反而不如以前的 Spring Cloud 方案。这是由于有一些问题处于应用系统与基础设施的边缘,使得彻底在基础设施层面中确实很难精细化地处理。
举个例子,微服务 A 调用了微服务 B 的两个服务,称为 B1和 B2,假设 B1表现正常但 B2出现了持续的 500 错,那在达到必定阈值以后就应该对 B2进行熔断,以免产生雪崩效应。若是仅在基础设施层面来处理,这会遇到一个两难问题,切断 A 到 B 的网络通路则会影响到 B1的正常调用,不切断的话则持续受 B2的错误影响。
以上问题在经过 Spring Cloud 这类应用代码实现的微服务是能够处理和解决的,只受限于开发人员的想象力与技术能力,但基础设施是针对整个容器来管理的,粒度相对粗旷,只能到容器层面,对单个远程服务就很难有效管控。相似的状况不只仅在断路器上出现,服务的监控、认证、受权、安全、负载均衡等都有可能面临细化管理的需求,譬如服务调用时的负载均衡,每每须要根据流量特征,调整负载均衡的层次、算法,等等,而 DNS 尽管能实现必定程度的负载均衡,但一般并不能知足这些额外的需求。
为了解决这一类问题,虚拟化的基础设施很快完成了第二次进化,引入了“服务网格”(Service Mesh)的“边车代理模式”(Sidecar Proxy),所谓的“边车”是由系统自动在服务容器(一般是指 Kubernetes 的 Pod)中注入一个通讯代理服务器,以相似网络安全里中间人攻击的方式进行流量劫持,在应用毫无感知的状况下,悄然接管应用全部对外通讯。这个代理除了实现正常的服务间通讯外(称为数据平面通讯),还接收来自控制器的指令(称为控制平面通讯),根据控制平面中的配置,对数据平面通讯的内容进行分析处理,以实现熔断、认证、度量、监控、负载均衡等各类附加功能。这样便实现了既不须要在应用层面加入额外的处理代码,也提供了几乎不亚于程序代码的精细管理能力。
很难从概念上断定清楚一个与应用系统运行于同一资源容器以内的代理服务到底应该算软件仍是算基础设施,但它对应用是透明的,不须要改动任何软件代码就能够实现服务治理,这便足够了。服务网格在 2018 年才火起来,今天它仍然是个新潮的概念,仍然未彻底成熟,甚至连 Kubernetes 也还算是个新生事物。但做者提出,将来 Kubernetes 将会成为服务器端标准的运行环境,如同如今 Linux 系统;服务网格将会成为微服务之间通讯交互的主流模式,把“选择什么通讯协议”、“怎样调度流量”、“如何认证受权”之类的技术问题隔离于程序代码以外,取代今天 Spring Cloud 全家桶中大部分组件的功能,微服务只须要考虑业务自己的逻辑,这才是最理想的解决方案。
若是说微服务架构是分布式系统这条路的极致,那无服务架构,也许就是“不分布式”的云端系统这条路的起点。
虽然发展到了微服务架构解决了单台机器的性能没法知足系统的运行须要的问题,可是得到更好性能的需求在架构设计中依然占很大的比重。对软件研发而言,不去作分布式无疑才是最简单的,若是单台服务器的性能能够是无限的,那架构演进必定不是像今天这个样子。
绝对意义上的无限性能必然是不存在的,但在云计算落地已有十年时间的今日,相对意义的无限性能已经成为了现实。2012 年,Iron.io 公司率先提出了“无服务”的概念,2014 年开始,亚马逊发布了名为 Lambda 的商业化无服务应用,并在后续的几年里逐步获得开发者承认,发展成目前世界上最大的无服务的运行平台;到了 2018 年,中国的阿里云、腾讯云等厂商也开始跟进,发布了旗下的无服务的产品,“无服务”已成了近期技术圈里的“新网红”之一。
无服务如今尚未一个特别权威的“官方”定义,但它的概念并无前面各类架构那么复杂,原本无服务也是以“简单”为主要卖点的,它只涉及两块内容:后端设施和函数。
无服务的愿景是让开发者只须要纯粹地关注业务,不须要考虑技术组件,后端的技术组件是现成的,能够直接取用,没有采购、版权和选型的烦恼;不须要考虑如何部署,部署过程彻底是托管到云端的,工做由云端自动完成;不须要考虑算力,有整个数据中心支撑,算力能够认为是无限的;也不须要操心运维,维护系统持续平稳运行是云计算服务商的责任而再也不是开发者的责任。
做者认为无服务很难成为一种普适性的架构模式,由于无服务不适配于全部的应用。对于那些信息管理系统、网络游戏等应用,全部具备业务逻辑复杂,依赖服务端状态,响应速度要求较高,须要长连接等这些特征的应用,至少目前是相对并不适合的。由于无服务天生“无限算力”的假设决定了它必需要按使用量计费以控制消耗算力的规模,因此函数不会一直以活动状态常驻服务器,请求到了才会开始运行,这致使了函数不便依赖服务端状态,也致使了函数会有冷启动时间,响应的性能不可能太好
做者认为软件开发的将来不会只存在某一种“最早进的”架构风格,多种具针对性的架构风格同时并存,是软件产业更有生命力的形态。笔者一样相信软件开发的将来,多种架构风格将会融合互补,“分布式”与“不分布式”的边界将逐渐模糊,两条路线在云端的数据中心中交汇。
RPC 出现的最初目的,就是为了让计算机可以跟调用本地方法同样去调用远程方法。
进程间通讯的方式有
管道相似于两个进程间的桥梁,可经过管道在进程间传递少许的字符流或字节流。普通管道只用于有亲缘关系进程(由一个进程启动的另一个进程)间的通讯,具名管道摆脱了普通管道没有名字的限制,除具备管道全部的功能外,它还容许无亲缘关系进程间的通讯。管道典型的应用就是命令行中的|操做符,好比:
ps -ef | grep java
复制代码
ps与grep都有独立的进程,以上命令就经过管道操做符|将ps命令的标准输出链接到grep命令的标准输入上。
信号用于通知目标进程有某种事件发生,除了用于进程间通讯外,进程还能够发送信号给进程自身。信号的典型应用是kill命令,好比:
kill -9 pid
复制代码
以上就是由 Shell 进程向指定 PID 的进程发送 SIGKILL 信号。
信号量用于两个进程之间同步协做手段,它至关于操做系统提供的一个特殊变量,程序能够在上面进行wait()和notify()操做。
以上三种方式只适合传递传递少许信息,消息队列用于进程间数据量较多的通讯。进程能够向队列添加消息,被赋予读权限的进程则能够从队列消费消息。消息队列克服了信号承载信息量少,管道只能用于无格式字节流以及缓冲区大小受限等缺点,但实时性相对受限。
容许多个进程访问同一块公共的内存空间,这是效率最高的进程间通讯形式。本来每一个进程的内存地址空间都是相互隔离的,但操做系统提供了让进程主动建立、映射、分离、控制某一块内存的程序接口。当一块内存被多进程共享时,各个进程每每会与其它通讯机制,譬如信号量结合使用,来达到进程间同步及互斥的协调操做。
以上两种方式只适合单机多进程间的通讯,套接字接口是更为普适的进程间通讯机制,可用于不一样机器之间的进程通讯。套接字(Socket)起初是由 UNIX 系统的 BSD 分支开发出来的,如今已经移植到全部主流的操做系统上。出于效率考虑,当仅限于本机进程间通讯时,套接字接口是被优化过的,不会通过网络协议栈,不须要打包拆包、计算校验和、维护序号和应答等操做,只是简单地将应用层数据从一个进程拷贝到另外一个进程,这种进程间通讯方式有个专名的名称:UNIX Domain Socket,又叫作 IPC Socket
由于Socket是网络栈的统一接口,因此基于套接字接口的通讯方式不只适用于本地相同机器的不一样进程间通讯,也能支持基于网络的跨机器的进程间通讯。好比 Linux 系统的图形化界面中,X Window 服务器和 GUI 程序之间的交互就是由这套机制来实现。因为 Socket 是各个操做系统都有提供的标准接口,因此能够把远程方法调用的通讯细节隐藏在操做系统底层,从应用层面上看来能够作到远程调用与本地的进程间通讯在编码上彻底一致。这种透明的调用形式却形成了程序员误觉得通讯是无成本的假象,于是被滥用以至于显著下降了分布式系统的性能。1987 年,在“透明的 RPC 调用”一度成为主流范式的时候,Andrew Tanenbaum 教授曾发表了论文对这种透明的 RPC 范式提出了一系列质问,论文的中心观点是,本地调用与远程调用当作同样处理,这是犯了方向性的错误,把系统间的调用作成透明,反而会增长程序员工做的复杂度。此后几年,关于 RPC 应该如何发展、如何实现的论文层出不穷。最终,到 1994 年至 1997 年间,一众大佬们共同总结了经过网络进行分布式运算的八宗罪
以上这八条反话被认为是程序员在网络编程中常常被忽略的八大问题,潜台词就是若是远程服务调用要弄透明化的话,就必须为这些罪过埋单,这算是给 RPC 是否能等同于 IPC 来实现暂时定下了一个具备公信力的结论。至此,RPC 应该是一种高层次的或者说语言层次的特征,而不是像 IPC 那样,是低层次的或者说系统层次的特征成为工业界、学术界的主流观点。
远程服务调用的定义:远程服务调用是指位于互不重合的内存地址空间中的两个程序,在语言层面上,以同步的方式使用带宽有限的信道来传输程序控制信息。
从20 世纪 80 年代中后期开始直至接下来几十年来全部流行过的 RPC 协议,都不外乎变着花样使用各类手段来解决如下三个基本问题:
这里数据包括了传递给方法的参数,以及方法执行后的返回值。
如何经过网络,在两个服务的 Endpoint 之间相互操做、交换数据。
“如何表示同一个方法”,“如何找到对应的方法”仍是得弄个跨语言的统一的标准才行。
以上 RPC 中的三个基本问题,所有均可以在本地方法调用过程当中找到相对应的操做。RPC 的想法始于本地方法调用,尽管早已再也不追求实现成与本地方法调用彻底一致,但其设计思路仍然带有本地方法调用的深入烙印。
一、面向透明的、简单的 RPC 协议,如 DCE/RPC、DCOM、Java RMI,要么依赖于操做系统,要么依赖于特定语言,总有一些先天约束;
二、面向通用的、普适的 RPC 协议;如 CORBA,就没法逃过使用复杂性的困扰,CORBA 烦琐的 OMG IDL、ORB 都是很好的佐证;
三、经过技术手段来屏蔽复杂性的 RPC 协议,如 Web Service,又难免受到性能问题的束缚。
对于RPC协议,简单、普适、高性能这三点,彷佛真的难以同时知足。
因为一直没有一个同时知足以上三点的完美 RPC 协议出现,因此RPC这个领域里逐渐进入了百家争鸣并一直延续至今。如今,任何一款具备生命力的 RPC 框架,都再也不去追求大而全的完美,而是有本身的针对性特色做为主要的发展方向,举例分析以下。
不知足于 RPC 将面向过程的编码方式带到分布式,但愿在分布式系统中也可以进行跨进程的面向对象编程,表明为 RMI、.NET Remoting,这个分支也有个别名叫作分布式对象。
表明为 gRPC 和 Thrift。决定 RPC 性能的主要就两个因素:序列化效率和信息密度。序列化输出结果的容量越小,速度越快,效率天然越高;信息密度则取决于协议中有效荷载所占总传输数据的比例大小,使用传输协议的层次越高,信息密度就越低。gRPC 和 Thrift 都有本身优秀的专有序列化器,而传输协议方面,gRPC 是基于 HTTP/2 的,支持多路复用和 Header 压缩,Thrift 则直接基于传输层的 TCP 协议来实现,省去了额外应用层协议的开销。
表明为 JSON-RPC,说要选功能最强、速度最快的 RPC 可能会颇有争议,但选功能弱的、速度慢的,JSON-RPC 确定会候选人中之一。牺牲了功能和效率,换来的是协议的简单轻便,接口与格式都更为通用,尤为适合用于 Web 浏览器这类通常不会有额外协议支持、额外客户端支持的应用场合。
经历了 多种RPC 框架百家争鸣,你们都认识到了不一样的 RPC 框架所提供的特性或多或少是有矛盾的,很难有某一种框架能十全十美。由于必须有取舍,因此致使不断有新的 RPC 轮子出现,决定了选择框架时在得到一些利益的同时,要付出另一些代价。
到了最近几年,RPC 框架不只仅负责调用远程服务,还管理远程服务,再也不追求独立地解决 RPC 的所有三个问题(表示数据、传递数据、表示方法),而是将一部分功能设计成扩展点,让用户本身去选择。框架聚焦于提供核心的、更高层次的能力,好比提供负载均衡、服务注册、可观察性等方面的支持。这一类框架的表明有 Facebook 的 Thrift 与阿里的 Dubbo。
不少人会拿 REST 与 RPC 互相比较,可是REST 和RPC本质上并非同一类型的东西,不管是在思想上、概念上,仍是使用范围上,与 RPC 都只能算有一些类似。REST 只能说是风格而不是规范、协议,REST 与 RPC 做为主流的两种远程调用方式,在使用上是确有重合的。
一套理想的、彻底知足 REST 风格的系统应该知足如下六大原则。
无状态是 REST 的一条核心原则。REST 但愿服务器不要去负责维护状态,每一次从客户端发送的请求中,应包括全部的必要的上下文信息,会话信息也由客户端负责保存维护,服务端依据客户端传递的状态来执行业务处理逻辑,驱动整个应用的状态变迁。
这里所指的并非表示层、服务层、持久层这种意义上的分层。而是指客户端通常不须要知道是否直接链接到了最终的服务器,抑或链接到路径上的中间服务器。中间服务器能够经过负载均衡和共享缓存的机制提升系统的可扩展性,这样也便于缓存、伸缩和安全策略的部署。
这是 REST 的另外一条核心原则,REST 但愿开发者面向资源编程,但愿软件系统设计的重点放在抽象系统该有哪些资源上,而不是抽象系统该有哪些行为(服务)上。
这是一条可选原则。它是指任何按照客户端的请求,将可执行的软件程序从服务器发送到客户端的技术,按需代码赋予了客户端无需事先知道全部来自服务端的信息应该如何处理、如何运行的宽容度。
《RESTful Web APIs》和《RESTful Web Services》的做者 Leonard Richardson 提出过一个衡量“服务有多么 REST”的 Richardson 成熟度模型。Richardson 将服务接口“REST 的程度”从低到高,分为 1 至 4 级:
事务处理存在的意义是为了保证系统中全部的数据都是符合指望的,且相互关联的数据之间不会产生矛盾,即数据状态的一致性(Consistency)。
事务的三个重点方面:
原子性(Atomic)
在同一项业务处理过程当中,事务保证了对多个数据的修改,要么同时成功,要么同时被撤销。
隔离性(Isolation)
在不一样的业务处理过程当中,事务保证了各自业务正在读、写的数据互相独立,不会彼此影响。
持久性(Durability)
事务应当保证全部成功被提交的数据修改都可以正确地被持久化,不丢失数据。
四种属性即事务的ACID特性
事务的概念最初起源于数据库系统但已经有所延伸,而再也不局限于数据库自己了。全部须要保证数据一致性的应用场景,都有可能会用到事务。
外部一致性问题一般很难再使用 A、I、D 来解决,由于这样须要付出很大乃至不切实际的代价;可是外部一致性又是分布式系统中必然会遇到且必需要解决的问题,为此将一致性从“是或否”的二元属性转变为能够按不一样强度分开讨论的多元属性,在确保代价可承受的前提下得到强度尽量高的一致性保障,也正因如此,事务处理才从一个具体操做上的“编程问题”上升成一个须要全局权衡的“架构问题”。
本地事务是最基础的一种事务解决方案,只适用于单个服务使用单个数据源的场景。从应用角度看,它是直接依赖于数据源自己提供的事务能力来工做的,在程序代码层面,最多只能对事务接口作一层标准化的包装(如 JDBC 接口),并不能深刻参与到事务的运做过程中,事务的开启、终止、提交、回滚、嵌套、设置隔离级别,乃至与应用代码贴近的事务传播方式,所有都要依赖底层数据源的支持才能工做。
举个例子,假设你的代码调用了 JDBC 中的Transaction::rollback()
方法,方法的成功执行也并不必定表明事务就已经被成功回滚,若是数据表采用的引擎是MyISAM,那rollback()
方法即是一项没有意义的空操做。所以,咱们要想深刻地讨论本地事务,便不得不越过应用代码的层次,去了解一些数据库自己的事务实现原理,弄明白传统数据库管理系统是如何经过 ACID 来实现事务的。
原子性和持久性在事务里是密切相关的两个属性,原子性保证了事务的多个操做要么都生效要么都不生效,不会存在中间状态;持久性保证了一旦事务生效,就不会再由于任何缘由而致使其修改的内容被撤销或丢失。实现原子性和持久性的最大困难是“写入磁盘”这个操做并非原子的,不只有“写入”与“未写入”状态,还客观地存在着“正在写”的中间状态。正由于写入中间状态与崩溃都不可能消除,因此若是不作额外保障措施的话,将内存中的数据写入磁盘,并不能保证原子性与持久性。
好比购买图书的场景,在用户帐户中减去货款、在商家帐户中增长货款、在商品仓库中标记一本书为配送状态。因为写入存在中间状态,因此可能发生如下情形。
因为写入中间状态与崩溃都是没法避免的,为了保证原子性和持久性,就只能在崩溃后采起恢复的补救措施,这种数据恢复操做被称为“崩溃恢复”。为了可以顺利地完成崩溃恢复,在磁盘中写入数据就不能像程序修改内存中变量值那样,直接改变某表某行某列的某个值,而是必须将修改数据这个操做所需的所有信息,包括修改什么数据、数据物理上位于哪一个内存页和磁盘块中、从什么值改为什么值,等等,以日志的形式——即仅进行顺序追加的文件写入的形式先记录到磁盘中。只有在日志记录所有都安全落盘,数据库在日志中看到表明事务成功提交的“提交记录”后,才会根据日志上的信息对真正的数据进行修改,修改完成后,再在日志中加入一条“结束记录”表示事务已完成持久化,这种事务实现方法被称为“Commit Logging”(提交日志)。
Commit Logging 保障数据持久性,日志一旦成功写入 Commit Record,那整个事务就是成功的,即便真正修改数据时崩溃了,重启后根据已经写入磁盘的日志信息恢复现场、继续修改数据便可,这保证了持久性;其次,若是日志没有成功写入 Commit Record 就发生崩溃,那整个事务就是失败的,系统重启后会看到一部分没有 Commit Record 的日志,那将这部分日志标记为回滚状态便可,整个事务就像彻底没好有发生过同样,这保证了原子性。
Commit Logging 存在一个大缺点,就是全部对数据的真实修改都必须发生在事务提交之后,不管有何种理由,都不容许在事务提交以前就修改磁盘上的数据,对提高数据库的性能十分不利。
为了解决这个问题,ARIES 提出了“Write-Ahead Logging”的日志改进方案,所谓“提早写入”(Write-Ahead),就是容许在事务提交以前,提早写入变更数据的意思。
Write-Ahead Logging 先将什么时候写入变更数据,按照事务提交时点为界,划分为 FORCE 和 STEAL 两类状况。
Commit Logging 容许 NO-FORCE,但不容许 STEAL。由于假如事务提交前就有部分变更数据写入磁盘,那一旦事务要回滚,或者发生了崩溃,这些提早写入的变更数据就都成了错误。
Write-Ahead Logging 容许 NO-FORCE,也容许 STEAL,它给出的解决办法是增长了另外一种被称为 Undo Log 的日志类型,当变更数据写入磁盘前,必须先记录 Undo Log,注明修改了哪一个位置的数据、从什么值改为什么值,等等。以便在事务回滚或者崩溃恢复时根据 Undo Log 对提早写入的数据变更进行擦除。Undo Log 如今通常被翻译为“回滚日志”,此前记录的用于崩溃恢复时重演数据变更的日志就相应被命名为 Redo Log,通常翻译为“重作日志”。因为 Undo Log 的加入,Write-Ahead Logging 在崩溃恢复时会执行如下三个阶段的操做。
分析阶段
该阶段从最后一次检查点开始扫描日志,找出全部没有 End Record 的事务,组成待恢复的事务集合,这个集合至少会包括 Transaction Table 和 Dirty Page Table 两个组成部分。
重作阶段
该阶段依据分析阶段中产生的待恢复的事务集合来重演历史,具体操做为:找出全部包含 Commit Record 的日志,将这些日志修改的数据写入磁盘,写入完成后在日志中增长一条 End Record,而后移除出待恢复事务集合。
回滚阶段
该阶段处理通过分析、重作阶段后剩余的恢复事务集合,此时剩下的都是须要回滚的事务,它们被称为 Loser,根据 Undo Log 中的信息,将已经提早写入磁盘的信息从新改写回去,以达到回滚这些 Loser 事务的目的。
重作阶段和回滚阶段的操做都应该设计为幂等的。
隔离性保证了每一个事务各自读、写的数据互相独立,不会彼此影响。若是没有并发,全部事务全都是串行的,那就不须要任何隔离,若是有并发,就须要加锁同步。
数据库均提供了如下三种锁
写锁
也叫做排他锁,若是数据有加写锁,就只有持有写锁的事务才能对数据进行写入操做,数据加持着写锁时,其余事务不能写入数据,也不能施加读锁。
读锁
也叫做共享锁,多个事务能够对同一个数据添加多个读锁,数据被加上读锁后就不能再被加上写锁,因此其余事务不能对该数据进行写入,但仍然能够读取。对于持有读锁的事务,若是该数据只有它本身一个事务加了读锁,容许直接将其升级为写锁,而后写入数据。
范围锁
对于某个范围直接加排他锁,在这个范围内的数据不能被写入。
事务的隔离级别
串行化访问提供了强度最高的隔离性。不考虑性能优化的话,对事务全部读、写的数据全都加上读锁、写锁和范围锁便可作到可串行化
可重复读
对事务所涉及的数据加读锁和写锁,且一直持有至事务结束,但再也不加范围锁。相比于可串行化可能出现幻读问题(指在事务执行过程当中,两个彻底相同的范围查询获得了不一样的结果集)
读已提交
对事务涉及的数据加的写锁会一直持续到事务结束,但加的读锁在查询操做完成后就立刻会释放。相比于可重复度多了不可重复读的问题(在事务执行过程当中,对同一行数据的两次查询获得了不一样的结果)
读未提交就是“彻底不隔离”,读、写锁都不加。读未提交
会有脏读问题,但不会有脏写问题
幻读、不可重复读、脏读等问题都是因为一个事务在读数据过程当中,受另一个写数据的事务影响而破坏了隔离性。针对这种“一个事务读+另外一个事务写”的隔离问题,有一种叫作多版本并发控制”(Multi-Version Concurrency Control,MVCC)的无锁优化方案被主流的数据库普遍采用。
MVCC 是一种读取优化策略,它的“无锁”是特指读取时不须要加锁。MVCC 的基本思路是对数据库的任何修改都不会直接覆盖以前的数据,而是产生一个新版副本与老版本共存,以此达到读取时能够彻底不加锁的目的。 “版本”是个关键词,能够理解为数据库中每一行记录都存在两个看不见的字段:CREATE_VERSION 和 DELETE_VERSION,这两个字段记录的值都是事务 ID,事务 ID 是一个全局严格递增的数值,而后根据如下规则写入数据。
此时,若有另一个事务要读取这些发生了变化的数据,将根据隔离级别来决定到底应该读取哪一个版本的数据。
可重复读
:老是读取 CREATE_VERSION 小于或等于当前事务 ID 的记录,在这个前提下,若是数据仍有多个版本,则取最新(事务 ID 最大)的。读已提交
:老是取最新的版本便可,即最近被 Commit 的那个版本的数据记录。另外两个隔离级别都没有必要用到 MVCC,由于读未提交
直接修改原始数据便可,其余事务查看数据的时候马上能够看到,根本无须版本字段。可串行化
原本的语义就是要阻塞其余事务的读取操做,而 MVCC 是作读取时无锁优化的。
MVCC 是只针对“读+写”场景的优化,若是是两个事务同时修改数据,即“写+写”的状况,那就没有多少优化的空间了,此时加锁几乎是惟一可行的解决方案,惟一须要讨论的就是加锁的策略采起乐观锁仍是悲观锁。相对地,乐观锁策略的思路被称为乐观并发控制,没有必要迷信什么乐观锁要比悲观锁更快的说法,这纯粹看竞争的剧烈程度,若是竞争剧烈的话,乐观锁反而更慢。
为了解决分布式事务的一致性问题,X/Open组织在1991年提出了一套叫XA的( eXtended Architecture 的缩写)处理事务架构,其核心内容是定义了全局的事务管理器和局部的资源管理器之间的通讯接口。
XA 接口是双向的,能在一个事务管理器和多个资源管理器之间造成通讯桥梁,经过协调多个数据源的一致动做,实现全局事务的统一提交或者统一回滚。基于 XA 模式在 Java 语言中的实现了全局事务处理的标准,这也就是咱们如今所熟知的 JTA。
JTA 最主要的两个接口是:
javax.transaction.TransactionManager
。这套接口是给 Java EE 服务器提供容器事务(由容器自动负责事务管理)使用的,还提供了另一套javax.transaction.UserTransaction
接口,用于经过程序代码手动开启、提交和回滚事务。javax.transaction.xa.XAResource
,任何资源(JDBC、JMS 等等)若是想要支持 JTA,只要实现 XAResource 接口中的方法便可。XA 将事务提交拆分红为两阶段过程:
准备阶段
又叫做投票阶段,在这个阶段,协调者询问事务的全部参与者是否准备好提交,参与者若是已经准备好提交则回复 Prepared,不然回复 Non-Prepared。准备操做是在重作日志中记录所有事务提交操做所要作的内容,它与本地事务中真正提交的区别只是暂不写入最后一条 Commit Record 而已,这意味着在作完数据持久化后仍继续持有锁,维持数据对其余非事务内观察者的隔离状态。
提交阶段
又叫做执行阶段,协调者若是在上一阶段收到全部事务参与者回复的 Prepared 消息,则先本身在本地持久化事务状态为 Commit,在此操做完成后向全部参与者发送 Commit 指令,全部参与者当即执行提交操做;不然,任意一个参与者回复了 Non-Prepared 消息,或任意一个参与者超时未回复,协调者将将本身的事务状态持久化为 Abort 以后,向全部参与者发送 Abort 指令,参与者当即执行回滚操做。对于数据库来讲,这个阶段的提交操做应是很轻量的,仅仅是持久化一条 Commit Record 而已,一般可以快速完成,只有收到 Abort 指令时,才须要根据回滚日志清理已提交的数据,这多是相对重负载操做。
以上这两个过程被称为“两段式提交”(2 Phase Commit,2PC)协议,它可以成功保证一致性还须要一些其余前提条件:
协调者、参与者都是能够由数据库本身来扮演的,不须要应用程序介入。协调者通常是在参与者之间选举产生的,而应用程序相对于数据库来讲只扮演客户端的角色。
两段式提交原理简单,但有几个很是显著的缺点:
单点问题
协调者等待参与者回复时能够有超时机制,容许参与者宕机,但参与者等待协调者指令时没法作超时处理。一旦协调者宕机全部参与者都会受到影响。 若是协调者一直没有恢复,没有正常发送 Commit 或者 Rollback 的指令,那全部参与者都必须一直等待。
性能问题
两段提交过程当中,全部参与者至关于被绑定成为一个统一调度的总体,期间要通过两次远程服务调用,三次数据持久化,整个过程将持续到参与者集群中最慢的那一个处理操做结束为止,这决定了两段式提交的性能一般都较差。
一致性风险
当网络不稳定或宕机没法恢复可能出现一致性问题。尽管提交阶段时间很短,但仍存在风险。若是协调者在发出准备指令后,根据收到各个参与者发回的信息肯定事务状态是能够提交的,协调者会先持久化事务状态,并提交本身的事务,若是这时候网络突然被断开,没法再经过网络向全部参与者发出 Commit 指令的话,就会致使部分数据(协调者的)已提交,但部分数据(参与者的)既未提交,也没有办法回滚,产生了数据不一致的问题。
为了缓解两段式提交的单点问题和准备阶段的性能问题,后续发展出了三段式提交(3 Phase Commit,3PC)协议。
三段式提交把本来的两段式提交的准备阶段再细分为两个阶段,分别称为 CanCommit、PreCommit,把提交阶段改称为 DoCommit 阶段。其中,新增的 CanCommit 是一个询问阶段,协调者让每一个参与的数据库根据自身状态,评估该事务是否有可能顺利完成。将准备阶段一分为二的理由是这个阶段是重负载的操做,一旦协调者发出开始准备的消息,每一个参与者都将立刻开始写重作日志,它们所涉及的数据资源即被锁住,若是此时某一个参与者宣告没法完成提交,至关于你们都白作了一轮无用功。因此,增长一轮询问阶段,若是都获得了正面的响应,那事务可以成功提交的把握就比较大了,这也意味着因某个参与者提交时发生崩溃而致使你们所有回滚的风险相对变小。所以,在事务须要回滚的场景中,三段式的性能一般是要比两段式好不少的,但在事务可以正常提交的场景中,二者的性能都依然不好,甚至三段式由于多了一次询问,还要稍微更差一些。
三段式提交对单点问题和回滚时的性能问题有所改善,可是它对一致性风险问题并未有任何改进,在这方面它面临的风险甚至反而是略有增长了的。好比,进入 PreCommit 阶段以后,协调者发出的指令不是 Ack 而是 Abort,而此时因网络问题,有部分参与者直至超时都未能收到协调者的 Abort 指令的话,这些参与者将会错误地提交事务,这就产生了不一样参与者之间数据不一致的问题。
共享事务是指多个服务共用同一个数据源。
数据源和数据库的区别:数据源是指提供数据的逻辑设备,没必要与物理设备一一对应。
分布式事务指多个服务同时访问多个数据源的事务处理机制
CAP 定理(Consistency、Availability、Partition Tolerance Theorem),也称为 Brewer 定理,为分布式计算领域所公认的著名定理。这个定理里描述了一个分布式的系统中,涉及共享数据问题时,如下三个特性最多只能同时知足其中两个:
表明数据在任什么时候刻、任何分布式节点中所看到的都是符合预期的。
表明系统不间断地提供服务的能力,理解可用性要先理解与其密切相关两个指标:可靠性(Reliability)和可维护性(Serviceability)。可靠性使用平均无端障时间(Mean Time Between Failure,MTBF)来度量;可维护性使用平都可修复时间(Mean Time To Repair,MTTR)来度量。可用性衡量系统能够正常使用的时间与总时间之比,其表征为:A=MTBF/(MTBF+MTTR),便可用性是由可靠性和可维护性计算得出的比例值,譬如 99.9999%可用,即表明平均年故障修复时间为 32 秒。
表明分布式环境中部分节点因网络缘由而彼此失联后,即与其余节点造成“网络分区”时,系统仍能正确地提供服务的能力。
eBay 的系统架构师提出了一种独立于 ACID 得到的强一致性以外的、使用 BASE 来达成一致性目的的途径。BASE 分别是基本可用性(Basically Available)、柔性事务(Soft State)和最终一致性(Eventually Consistent)的缩写。
TCC 是另外一种常见的分布式事务机制,它是“Try-Confirm-Cancel”三个单词的缩写
可靠消息队列虽能保证最终的结果是相对可靠的,过程也足够简单但整个过程彻底没有任何隔离性可言,有一些业务中隔离性是可有可无的,但有一些业务中缺少隔离性就会带来许多麻烦。
TCC实现上较为烦琐,是一种业务侵入式较强的事务方案,要求业务处理过程必须拆分为“预留业务资源”和“确认/释放消费资源”两个子过程。它分为如下三个阶段。
Try
尝试执行阶段,完成全部业务可执行性的检查,而且预留好所有需用到的业务资源。
Confirm
确认执行阶段,不进行任何业务检查,直接使用 Try 阶段准备的资源来完成业务处理。Confirm 阶段可能会重复执行,所以本阶段所执行的操做须要具有幂等性。
Cancel
取消执行阶段,释放 Try 阶段预留的业务资源。Cancel 阶段可能会重复执行,也须要知足幂等性。
TCC 相似 2PC 的准备阶段和提交阶段,但 TCC 是位于用户代码层面,而不是在基础设施层面。
TCC因为它的业务侵入性很强因此不能知足全部的场景,咱们在有的时候能够考虑采用SAGA事务,SAGA 在英文中是“长篇故事、长篇记叙、一长串事件”的意思。
现代的企业级或互联网系统,“分流”是必需要考虑的设计
HTTP 协议的无状态性决定了它必须依靠客户端缓存来解决网络传输效率上的缺陷。
一、强制缓存
HTTP 的强制缓存对一致性处理的策略是很直接的,强制缓存在浏览器的地址输入、页面连接跳转、新开窗口、前进和后退中都可生效,但在用户主动刷新页面时应当自动失效。HTTP 协议中设有Expires、Cache-Control两类 Header 实现强制缓存。
Expires 是 HTTP 协议最第一版本中提供的缓存机制,设计很是直观易懂,但缺点有受限于客户端的本地时间、没法处理涉及到用户身份的私有资源、没法描述“不缓存”的语义。
Cache-Control 是HTTP/1.1 协议中定义的强制缓存 Header,相比于Expires语义更加丰富。
二、协商缓存
强制缓存是基于时效性的,协商缓存是基于变化检测的缓存机制基于变化检测的缓存机制,在一致性上会有比强制缓存更好的表现,但须要一次变化检测的交互开销,性能上就会略差一些。
DNS 也许是全世界最大、使用最频繁的信息查询系统,若是没有适当的分流机制,DNS 将会成为整个网络的瓶颈。DNS 的做用是将便于人类理解的域名地址转换为便于计算机处理的 IP 地址。
最近几年出现了另外一种新的 DNS 工做模式:HTTPDNS(也称为 DNS over HTTPS,DoH)。它将本来的 DNS 解析服务开放为一个基于 HTTPS 协议的查询服务,替代基于 UDP 传输协议的 DNS 域名解析,经过程序代替操做系统直接从权威 DNS 或者可靠的 Local DNS 获取解析数据,从而绕过传统 Local DNS。好处是彻底免去了“中间商赚差价”的环节,再也不害怕底层的域名劫持,可以有效避免 Local DNS 不可靠致使的域名生效缓慢、来源 IP 不许确、产生的智能线路切换错误等问题。
传输链路涉及到链接数优化、传输压缩、快速UDP网络链接。
内容分发网络,英文名称Content Distribution Network,简称CDN,现在CDN的应用有
调度后方的多台机器,以统一的接口对外提供服务,承担此职责的技术组件被称为“负载均衡”。四层负载均衡的优点是性能高,七层负载均衡的优点是功能强。作多级混合负载均衡,一般应是低层的负载均衡在前,高层的负载均衡在后。“四层”、“七层”,指的是OSI 七层模型中第四层传输层和第七层应用层
\