场景是这样的:
以开发一个医生预约系统为例,让病人可以通过这个系统,知道自己想要预约的医生在指定日期是否有空闲时间,以方便就诊
医院开放一个/appointmentService的web api。传入日期、医生姓名作为参数,就可以得到该时间段内,该医生的空闲时间
1 | //请求 |
这是基于RPC风格的服务设计,很好的解决了这个问题
围绕资源而非过程来设计服务,Endpoint应该是一个名词而不是动词,此外每次请求都应该包含资源ID,所有操作均通过资源ID来进行。上述的场景中,<font color="red">应该把什么看成是一种资源呢?医生?不是吧,应该是把医生的档期视作为一种资源</font>。具体过程,可以观察一下请求和响应的URI
1 | //请求医生档期 |
这些个交互看起来解决了一些问题,而且抽象程度也第0级高了不少。但是还有一些问题需要解决
通过统一接口来解决第1成熟度的三个问题,统一接口是什么呢?就是HTTP协议的标准方法。这些方法都是经过精心设计的,几乎可以涵盖资源可能会遇到的所有操作场景。具体要如何理解呢?1.针对预约变更的问题,把不同的业务需求抽象为对资源的增加、修改、删除等操作来解决2.针对响应码的问题,可以直接使用HTTP协议的Status Code,这就可以涵盖大多数据资源操作可能会出现的异常问题3.安全性问题嘛,可以依靠着Http Header中携带的额外认证、授权信息来解决
反应到代码请求中是这样的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22//请求
GET /doctors/mjones/schedule?date=2020-03-04&status=open HTTP/1.1
//响应
HTTP/1.1 200 OK
[
{id: 1234, start:"14:00", end: "14:50", doctor: "mjones"},
{id: 5678, start:"16:00", end: "16:50", doctor: "mjones"}
]
//预约
POST /schedules/1234 HTTP/1.1
{name: xx, age: 30, ……}
//预约成功
HTTP/1.1 201 Created
Successful confirmation of appointment
大多数系统都能够达到第2级成熟度,但是这种还是有点问题,就是前后不连贯,也就是我知道医生的空闲时间以后,怎么知道预约的地址呢?当然这是浏览器的角度,咱们大多都是依靠在程序硬编码来实现的。这是我们习以为常的操作,但REST的目的并不是这样的,它想的是:<strong>除了第一请求是由你在浏览器地址栏中输入驱动的以外,其它所有后续请求都能够自己描述清楚后续可能发生的状态转移,由超文本自身来驱动。</strong>唉多美好的愿望呀它的表现形式是这样的:
1 | //请求 |
这样的话,服务端的API和客户端的请求就能够做到完全解耦了,这个时候你再想调整什么东西是不是就很easy了
其实,面向资源的编程思想与面向过程、面向对象编程思想,只是抽象问题时所处的立场不同,只有选择问题,没有高下之分:
REST的确依赖着HTTP协议的标准方法,状态码和协议头等,但我们要明确一个前提,HTTP协议是应用层协议,而不是传输层协议,如果在应用层协议追求传输效率,有点买椟还珠的意思。
对事务的理解不一一致的话,在这块歧义也会比较大。
刚性ACID事务,
分布式事务。
最终一致性事务
REST的可靠性需要很多其他策略的配合,比如重试机制、幂等机制
这点还是挺有道理的,这可能也是未来面向资源思想和API设计风格发展的方向。
暂时的处理办法是:
行文思路:
另一种主流的远程服务访问风格:RESTful服务
其实两者另不是同一类型的东西,充其量只能算是有一些相似。在应用中会有一些重合的地方,两者差异的核心是抽象的目标不一样,也就是面向资源与面向过程的编程思想的区别。
RESTful并不是一种远程调用协议。说到协议啊,一般是都有一定规范性和强制性的。但RESTful却什么都没有,所以只能说RESTful只是一种风格。而这种风格一般还挺难达到的
在使用范围上RPC和RESTful是两种主流的远程调用方式,在使用上确实有重合。
RESTful讨论的是架构风格与网络的软件架构设计,RPC只是一种远程服务调用的方式。这逼格明显不一样好不啦。
Representational State Transfer ,表征状态转移。
ps:哇塞,太抽象了。什么表征,什么的状态,从哪转移到哪儿。这算是什么狗屁定义呀
等会再来管个定义吧。先来看看几个概念
这几个概念还是挺好理解的,别放松,接着再来三个概念
这一个特征可以参考,前后端分离架构来理解
REST不希望服务端维护状态,每次请求包含必要的上下文信息,会话由客户端保存,服务端根据客户状传递的状态信息进行处理,并驱动整个应用状态变迁
由于无状态,所以每次请求都得带上大量的冗余信息,为了解决这个问题,提出的这么一个特征,也是一个用于提升响应性能的手段
客户端一般不需要知道是否直接连接到了最终的服务器,或者是连接到路径上的中间服务器
REST希望开发者可以面向资源来编程,希望软件系统的设计重点放在对抽象系统该有哪些资源上,而不是哪些行为上。一般来说面向资源的抽象程度通常比较高。
ps:我觉得这块一可以结合,软件的复杂度,以及领域驱动设计来理解。这样大家就不再纠结为什么抽象资源比面向过程更有优势这一点了。架构设计的本质,是降低软件复杂度再来的影响,那咋降低呢?把变化控制来一个小的范围内,这是一个思路。
那怎么才能在架构设计中合理恰当地利用统一接口呢?RESTful的提出者,Fielding 给出了三个建议
CPU Load
内存屏障
网络的4/7层模型
非链接、非可靠传输、效率非常高
QUIC已被标准化为HTTP3协议,QUIC基于UDP协议,但QUIC提供了类似TCP的可靠性保证和流量控制,有效的避免了HTTP2协议的前序包阻塞的问题
能实现零RTT建连,提供FEC前向纠错能力。这是什么鬼一点都看不懂呀
TCP是传输层协议,对应OSI网络模型的第四层传输层,特点如下:
特定场景下,Nagel和ACK延迟机制配合使用可能会出现delay40ms超时后才回复ACK包的问题
TCP是基于链接的,所以在传输数据前需要先建立链接,TCP在传输上双工传输,不区分Client端与Server端,我们把主动发起建连请求的一端称作Client端,把被动态建立链接的一端称作Server端
SYN洪水攻击发生的原因,就是Server端收到Client端的SYN请求后,发送了ACK和SYN,但是Client端不进行回复,导致Server端大量的链接处在SYN_RCVD状态,进而影响其他正常请求的建连。可以通过设计tcp_synack_retries=0 加快半链接的回收速度,或者调大tcp_max_syn_backlog来应对少量的SYN洪水攻击 。
TCP 链接的关闭通信双方都可以先发起,我们暂且把先发起的一方看作 Client。
Q:为什么需要等待2倍最大报文段生存时间之后再关闭链接?
从这个交互流程可以看出,无论是建连还是断链,都是需要在两个方向上进行,只不过建连时,Server 端的 SYN 和 ACK 合并为一次发送,而断链时,两个方向上数据发送停止的时间可能不同,所以不能合并发送 FIN 和 ACK。这就是建连三次握手而断链需要四次的原因。
实际应用中有可能遇到大量 Socket 处在 TIME_WAIT 或者 CLOSE_WAIT 状态的问题。一般开启 tcp_tw_reuse 和 tcp_tw_recycle 能够加快 TIME-WAIT 的 Sockets 回收;而大量 CLOSE_WAIT 可能是被动关闭的一方存在代码 bug,没有正确关闭链接导致的。
]]>这一篇先来个图图:
今天就看图说话啦。
大家先想一下,世面上有很多的RPC框架,各种讲解RPC框架的教程,大家也都是有一个学一个。今天学学dubbo,明天学学gRPC的,那这些RPC都有什么共性吗?为什么会有这么多RPC呢?为什么没有一个通用的RPC框架呢?这些问题是不是大家也都想过,不过存在即有理,接下来我们就从RPC最核心的内容来看看,这些问题有没有一个答案。
看到这问题是不是有点懵圏了。RPC要解决什么问题?不就是解决远程服务通讯的问题吗?不是的这个问题的意思就是要想从0开发一个RPC,都需要解决什么问题。
首先就是解决数据表示的问题,什么意思呢?各个语言各种操作系统都有不小的差异那怎么才能保证数据在源和在目标表示 的意义是一样的呢。这说的也就是数据如何表示 的意思了。再具体一点就是参数、返回值之类的这些玩艺儿。
在明确了问题之后,是不是得想办法解决了,有什么好的解决思路呢?单纯依靠某一方肯定是不行的,至少不公平也会产生不少的纠纷。那好吧,映射到现实社会中,自然而然就是找一个大家都看好的第三方喽。不是玩笑,真的是这么个思路。
因为RPC发展现在已经有很多成熟的方案的了。像gRPC的ProtocolBuffer ,web service的XML Serialization,以及很多轻量级RPC的Json Serialization都可以拿得出手的
既然数据怎么表示这事儿咱们想明白了,那数据的传输过程又该是怎么样的呢?肯定不会是只搞一个序列化的数据流扔给底层传输协议就完事儿了,这里面需要考虑的东西太多了,像超时了怎么办,发生了异常怎么处理,安全认证怎么搞等等方面都是需要考虑的因素,不过既然能把数据如何表示这件事交给第三方,那第三方肯定也是提供了大量的解决办法的,提一个大家都比较熟悉的SOAP,它就能把这件事处理的明明白白的。
这这这,又得解释一下了。先来对比一下本地方法的调用,本地调用某个方法,编译器会直接帮助我们把要调用的方法转换成内存中方法的入口地址,发生调用的时候你直接按顺序执行就ok了。那一换成远程调用,这点优势突然间就没了。那该怎么破呢?要不给每个方法编个号?也是一种办法哈。但更加靠谱的方法还是得搞一套与程序语言无关的接口描述语言(IDL),像WSDL,JSON-WSP都是基于这个参考这个方法来处理的。
怎么样,怎么样有没有发现一个问题,我好喜欢webservice那套产品呀,有了它岂不是也能一统江湖?用它来搞序列化、数据传输、接口描述,简直不能太美了。但是回过头来再瞅瞅WebService现在那副尿样子,真的是想完美想疯了,啥都很标准,啥都很规范。一点都不接地气,真正用过的人,想一想,它简单吗?它通用吗?它的性能好吗?其实通用方面我觉得做的还可以,如果大家都使用的话。可是它压根儿就没有把性能这事儿重视过哪怕一次。那你说说对得起现在这4G的网络,对得起互联网大厂飞速发展吗?更别提5G了。
搞不成啊,搞不成。
不过说实话,要说把简单、通用、性能都完美的结合在一起真的不是那么容易的。
既然Web service没戏了,大家就都取多长吧。这个时候各种RPC框架就开始hi了。什么RMI、Thrift、Dubbo、gRPC等等,发展的猛的很。发展归发展,虽然很多都是在重复造轮子,但大部分都还是很有方向的,有注重性能的、有注重简单的,还有注重通用的,但是大家都不会把追求完美做为首要目标了。
典型的发展方向,有这么几个:
总结一下吧,RPC要解决的三个问题是所有RPC框架都需要面临的问题,简单、通用、性能三个方面,不同的要求也有各自成熟的产品,你可以结合自己的需求选择最合适的那个,总有一款适合你。
]]> 在这篇文章中有几个提法比较有意思:比如RPC作为分布式前置的基础条件,再比如RPC应该是一种高层次的,或者说语言层次的特征,而不是像IPC那样,是低层次的,或者说系统层次的特征;还有RPC以模拟进程间方法调用为起点,许多思想和概念都借鉴的是IPC,都能给人耳目一新的感觉。
好了下面咱们开始正式的内容:
1 | 什么架构师呢? |
关于RPC三个小问题:
1. RPC本身可以解决什么问题呢 2. 这些问题又是怎么被解决的呢? 3. 为什么要这样解决呀
RPC为什么这么火热的原因:
本地方法调用几个概念
1 | // 调用者(Caller) : main() |
做一个假设:如果在调用println()的时候,发现它并不在当前内存地址空间中,又会出现什么问题呢?
如何解决两个进程间通讯的问题问题:
1. 管道(Pipe)或具名管道(Named Pipe): 管道其实类似于两个进程间的桥梁,用于进程间传递少量的字符流或字节流。2. 信号(Signal): 信号是用来通知目标进程有某种事件发生的。除了用于进程间通信外,信号还可以被进程发送给进程自身。信号的典型应用是kill命令,比如“kill -9 pid”,意思就是由Shell进程向指定PID的进程发送SIGKILL信号。 3. 信号量(Semaphore):信号量是用于两个进程之间同步协作的手段,相当于操作系统提供的一个特殊变量。我们可以在信号量上,进行wait()和notify()操作。 4. 消息队列(Message Queue):进程可以向队列中添加消息,而被赋予读权限的进程则可以从队列中消费消息。消息队列就克服了信号承载信息量少、管道只能用于无格式字节流,以及缓冲区大小受限等缺点 ,但实时性相对受限。 5. 共享内存(Shared Memory): 允许多个进程可以访问同一块内存空间,这是效率最高的进程间通讯形式。进程的内存地址空间是独立隔离的,但操作系统提供了让进程主动创建、映射、分离、控制某一块内存的接口。由于内存是多进程共享的,所以往往会与其它通信机制,如信号量等结合使用,来达到进程间的同步及互斥。 6. <font color="red">本地套接字接口(IPC Socket): 消息队列和共享内存这两种方式,只适合单机多进程间的通讯。而套接字接口,是更为普适的进程间通信机制,可用于不同机器之间的进程通信。</font>
RPC可以作为IPC的一种特例来看待。
IPC Socket是操作系统提供的标准接口,所以它完全有可能把远程方法调用的通讯细节,隐藏在操作系统底层,从应用层面上来看,可以做到远程调用与本地方法调用几乎完全一致。
还记得远程服务调用最初的目的吗?与调用本地方法一致。
分布式运算的八宗罪:
传奇的施乐Palo Alto研究中心,发布了基于Cedar语言的RPC框架Lupine,并实现了世界上第一个基于RPC的商业应用Courier。并提出了RPC的概念,也就我们今天看到:
1 | RPC是一种语言级别的通讯协议,它允许运行于一台计算机上的程序以某种管道作为通讯媒介(即某种传输协议的网络),去调用另外一个地址空间(通常为网络上的另外一台计算机) |
1 | “远程方法不应该无视通讯成本”这个观点,从性能的角度来看是有益的,但从简单的角度看则是有害的。在现代的软件系统开发中,你用过什么RPC框架吗?它们有没有把“像本地方法一样调用远程方法”作为卖点? |
云计算的成功其实已经实现了相对意义上无限性能。
对基于云计算的软件系统来说,无论用户有多少、逻辑如何复杂,AWS、阿里云等云服务提供商都能在算力上满足系统对性能的需求,只要你能为这种无限的性能支付得起对应的代价。这样”无服务“概念也就产生了。
虽然无服务架构的远期前景也许很美好,但无服务中短期内的发展,并没有那么乐观。为什么这么说呢? 与单体架构、微服务架构不同,无服务架构天生的一些特点,比如冷启动、 无状态、运行时间有限制等等,决定了它不是一种具有普适性的架构模式。所以除非是有重大变革,否则它也很难具备普适性。
无服务天生“无限算力”的假设,就决定了它必须要按使用量(函数运算的时间和内存)来计费,以控制消耗算力的规模,所以函数不会一直以活动状态常驻服务器,只有请求到了才会开始运行。这导致了函数不便于依赖服务端状态,也导致了函数会有冷启动时间,响应的性能不可能会太好。
前面第一节讲到,在首次对分布式的探索失败之后,大型软件的发展出现了两个方向,一个是以分布式为基础的探索,另一个以不分布式为目的的探索。如果说服务网格是在分布式道路上的探索的最新方向,那无服务架构就是在不分布式这条道路上的努力 。但这两条路线的边界也是越来越模糊,最终将会在云端的数据中心处交汇。
需要注意的是:无服务和微服务、云原生并没有继承替代的关系,因此也不能有无服务比微服务更加先进的想法
是否了解、接触过无服务架构?无服务目前在中国处于起步的发展阶段,阿里云、腾讯云的无服务计算框架,都给了普通用户相当大的免费额度,你愿意去试一下吗?
]]>从上面的几个问题,我们就可以发现这些问题已经大多都有了专职的基础设施来帮助解决了,那为什么微服务还必须在应用层面上去解决这问题呢?
原因就在于:因为硬件构成的基础设施,跟不上软件构成应用服务的灵活性。
既然软件可以通过命令就可以拆分出不同的服务,那么硬件不可以吗?这就到云原生的时代,微服务时代所取得的成就,本身就离不开以Docker为代表的早期容器化技术的巨大贡献。
近些年蓬勃发展的Kubernetes,可以说是开启了下一个软件架构发展的新纪元。对比下Spring Cloud中提供的应用层面的解决方案,Kubernetes也从基础设施层面给出它的解决方案,而且还是一条全新的、前途更加广阔的解题思路。虽然这一切看起来都很美好,但是从功能灵活性的特点上来,Kubernetes还不如Spring Cloud方案。因为从基础设施层面上很精细化解决一些边缘化的问题(比如做服务熔断)。因为基础设施针对的是整个容器做整体的管理,它的粒度相对来说比较粗犷。
所以微服务的基础设施再次进化,就引出了叫做服务网格的”边车代理模式“。
微服务基础设施会由系统自动地在服务的资源容器(指Kubernetes的Pod)中注入一个通讯代理服务器(相当于那个挎斗),用类似网络安全里中间人攻击的方式进行流量劫持,在应用毫无感知的情况下,悄悄接管掉应用的所有对外通讯。 这个代理除了会实现正常的服务调用以外(称为数据平面通讯),同时还接受来自控制器的指令(称为控制平面通讯),根据控制平面中的配置,分析数据平面通讯的内容,以实现熔断、认证、度量、监控、负载均衡等各种附加功能。 这样,就实现了既不需要在应用层面附带额外的代码,也提供了几乎不亚于应用代码的精细管理能力的目的。
代表性技术:Istio、Envoy
新技术发展时间比较短,还没有完全成熟起来。但未来可期
分布式架构发展到服务网格后,真的是到达“最好的时代”了吗?软件架构的发展不太可能真的就此止步,你认为今天的云原生还有哪些主要矛盾,下一次软件架构的进化将会主要解决什么问题?
]]>微服务是一种通过多个小型服务的组合,来构建的单个应用的架构风格,这些服务会围绕业务能力而非特定的技术标准来构建。各个服务可以采用不同的编程语言、不同的存储技术、运行在不同的进程之中。服务会采用轻量级的通讯机制和自动化的部署机制来实现通讯与运维。
有怎样的结构、规模、能力的团队,就会产生出对应结构、规模、能力的产品。
微服务开发团队有着直接对服务运行质量负责的责任,也应该有不受外界干预,掌控服务各方面能力的权力,能选择与其他服务异构的技术来实现自己的服务。
通过远程服务而非类库来提供功能,从而得到组件的隔离与自治能力。
避免把软件开发看作是要去完成某种功能,而是要把它当作是一种持续改进、提升的过程
数据应该按领域来分散管理、更新、维护和存储。有时候一致性问题也可能是必须要付出的代价
如果服务需要上面的某一种功能或能力,那就应该在服务自己的Endpoint上解决,而不是在通讯管道上一揽子处理。RESTful风格的通讯方式,在微服务中就是比较适合的。
承认服务会出错,接收服务总会出错的现实。有了这个认识的前提,在设计微服务的时候就要求有自动的机制能对其依赖的服务进行快速的故障检测,持续出错的时候可以进行自动的隔离,在服务恢复好之后重新联通。
可靠的系统由不会出错的服务来组成,这就是微服务最大的价值所在
承认服务会被淘汰。一个良好的设计,应该是能够报废的,而不是指望着它长久。
服务一多,靠人工来运维这根本就是不可能的事情。所以会要依赖大量的基础设施来自动化完成
注意:以上9个是一个合理的微服务系统展示出来的内、外在表现,它能够指导你该如何应用微服务架构,而不能作为一种强加于系统中的束缚来看待。
这么自由的微服务理念咋不上天呢?以前提到的那个分布式问题就不存在了吗?还是得一个个的解决。于是各种技术框架纷纷出现。比如像Eureka、Consul、Nacos、Zookeeper、Etcd用来解决服务发现的技术、像Dubbo、Thrift、gRPC用来解决服务通讯的技术,真的是层出不穷。更甚至还有springcloud之类的全家桶,真的是给开发人员带来巨大的便利。
便对架构的要求也越来越高了。架构者如何在各种决策之间权衡能力也变得至关重要起来!
那有没有一种即可以得到微服务自由的权力、还能专注于自己的业务,同时又不用费力去解决分布式带来的问题的解决方案呢?
你所负责的产品是不是基于微服务的?如果是,它符合微服务的9个特征吗?如果不是,你的产品适合微服务架构吗?你所在的企业、团队适合引入微服务吗?
]]>所以本节就系统的讨论一下SOA的设计思想与原则,找找他为什么没有成功的原因?
前提:假设完全不会跟其他相关的信息系统之间进行协作
互不交互的系统,各自使用独立的数据库、服务器,即可以完成拆分。
问题就在于:不交互的系统(组织)真的存在吗?
把一些公共的主数据:人员、组织 、权限等公用的服务、数据、资源,都集中到一块儿,成为被所有业务系统共同依赖的核心系统。
这种模式很适合桌面应用程序的开发,如果想实现一个能够支持二次开发的软件系统,微内核架构也是一种良好的架构模式。
问题在于:各业务系统不直接交互,(比如:支付系统和用户系统是独立的,但彼此会有业务的调用),这时需要找到一个办法,即能拆分出独立的系统,也能让拆分后的子系统之间可以顺畅的互相调用
为了能让子系统之间相互通讯,事件驱动架构就产生了
它是这样的一张种模式:在子系统之间建立一套事件队列管道,来自系统外部的消息将以事件的形式发送管道中,各子系统可以从管道中获取自己感兴趣、可以处理的事件消息,也可以为事件新增或修改其中的附加信息,甚至还可以自己发布一些新的事件到管道队列中去。
同时SOAP协议的诞生,事件驱动架构+SOAP协议==催生出=>面向服务架构
SOA本身还是属于一种抽象概念,而不是特指某一种具体的技术,但它比单体架构和烟囱式架构、微内核架构、事件驱动架构,都要更具可操作性,细节也充实了很多。所以,我们已经不能简单地把SOA看作是一种架构风格了,而是可以称之为一套软件架构的基础平台。
SOA最根本的目标,就是希望能够总结出一套自上而下的软件研发方法论,让企业只需要跟着它的思路,就能够一揽子解决掉软件开发过程中的全套问题。比如,如何挖掘需求、如何将需求分解为业务能力、如何编排已有服务、如何开发测试部署新的功能,等等
过于严格的规范定义,给架构带来了过度的复杂性,这也是Web Service衰落最本质的原因。
你是否使用过SOA的方法论来开发软件系统呢?无论有还是没有,作为一个软件开发者,你是否愿意软件开发向着工业化方向发展,让软件类似工业产品制造那样,可以在规范、理论、工具、技术的支持下,以流水线的方式生产出来?
思考:还真的是使用过某国产的ESB开发一个项目,但是受限于项目的规模只是做了课题性质的研究。虽然配套设施都很齐全,但是用起却不并不那么的流畅,再加上当时思路受制于服务编排的困扰。好不容易把思路给理顺了,同时又被微服务给冲击了。如果软件开发朝着工业化的方向发展,听起来像是很美妙的事情,那样的话,软件的质量应该会有很大的提高。但是自己会不会被淘汰,软件的定制化(灵活性)怎么体现,软件开发的工作会不会朝着工具化的思路去发展,到处去写补丁。还有一个问题,工业化产出的东西都是一样的,就算再扩展一点可以满足可以提供各种参数来配置。那这个基础工具该有多复杂呀。
]]>1 | 错误的认知: |
也被称为巨石系统,把单体看作成是一种架构,确切的说是在微服务流行之后,才被追加的。
对于一个小型的系统来说,使用单体架构,易于开发,易于测试,易于部署,而且是进程内方法调用。
所以要想讨论单体系统的缺陷,必须要有一个前提:软件的性能需求超过了单机 所能处理的最大能力
题外话:当前市面上所有主流的IDE,也都是对单体架构支持最友好。
在拆分 方面,单体系统真正的缺陷,实际上并不在于如何 拆分 ,而是在于拆分 之后它会存在隔离与自治能力上的欠缺。
以微服务去代替单体系统的原因:
* 无法做故障隔离* 无法做到动态可维护* 无法做到技术异构* 不兼容phoenix特性(不死-->能自治-->可靠)
1 | UNIX设计哲学: |
在这个阶段提出的分布式架构的目标:使用多个独立的分布式服务来共同构建出一个更大型的系统。而这个目标,从历史的局限性就决定了他不可能 是一蹴而就的解决分布式系统中的难题。其中面临的最大问题就是:由于调用本地方法与调用远程方法的差别,如何像调用本地方法一样去调用远程方法
那“远程”二字代表了什么呢?
针对大部分问题,DCE从0开始从无到有回答了其中大部分的问题。
在这个阶段采用分布式架构,为了解决上述各种问题,所付出的代价远远超过了所取得的收益。那接下来该如何发展呢?
1 | 某个功能能够进行分布式,并不意味着它就应该进行分布式,强行追求透明的分布式操作只会自食其果 |
行文思路:
1 | 今天以微服务为代表的分布式系统,是如何看待简单的? |
思考:
微服务所谓的简单,不管是从开发工具、开发形式亦或是运维上来看,都是在趋于看上去的简单。把复杂 留给 自己,简单留给别人。易开发、易部署、易扩展、易运维等等的说法。都在宣誓着把简单作为主要的原则 ,但背后所面临着诸如本节课所有的讨论的问题,都是需要一一关注,一一解决的。所以我觉得这里的简单并不是真正的简单。
]]>渐渐地认识到了自身很多不的足,做什么事情都没有长性。去年做了一件关于坚持的事情,参加了百日跑的活动。连续两届200天,每天跑步5公里。成绩什么的就都不重要,体重减少了多少也不重要。重要的是懂得了一些道理,养成了一些习惯。以前的自己想要的东西太多了,以至于什么事情都没有做太好。所以这次以自身的品性出发,慢慢地来。一点点来个,一个个来。坚持在缓慢中见识到真正的自己,我希望以后能够多多来看看自己内心深处的东西。
真正想要得到的是什么,多思考,多总结,多输出。
]]>保证数据的正确性,不同的数据之间不会产生矛盾。也就是保证数据状态的一致性。
数据库事务状态的一致性1
2
3
4
5
6
7
8
9
### 实现数据库事务状态一致性目标的前提
* 原子性:在同一项业务处理中,事务保证对多个数据的修改要么都成功,要么都被撤销
* 隔离性:在不同的业务处理过程中,事务保证各自读、写的数据互相独立,不会彼此影响
* 持久性:事务应当保证所有被成功提交的数据修改,都能正确的持久化到数据库当中去,不会丢失数据。
```原子性、隔离性、持久性是手段,一致性是目标
单个服务使用单个数据源,也就是本地事务场景,也可以叫作局部事务
指仅操作特定单一事务资源的、不需要“全局事务管理器”参与协调的事务。
本地事务,依赖于数据源本身的事务能力来工作,我们常见的在程序代码中的事务也最多就是对事务接口进行的一层标准化包装。事务的开启、终止、提交、回滚、嵌套、设置隔离级别乃至与应用代码贴近的传播方式都需要依赖底层数据库的支持。
Algorithms for Recovery and Isolation Exploiting Semantics ,基于语义的恢复与隔离算法
当前主流关系性数据在事务实现上都受到该理论的影响。
实现原子性和持久性最大的困难是什么?
写入磁盘这个操作不会是原子性,不仅存在写入、未写入,还存在“正在写”的中间状态。
这可咋办呀,怎么记录这个中间状态呢?日志?
把日志顺序追加写入文件方式,记录到磁盘中。见到代表事务提交成功的Commit Record之后 ,数据库才根据日志上的信息对真实数据进行修改,修改完之后,在日志中再加入一条End Record,表示数据库已完成持久化。这种事务实现方法就是Commit Logging
Commit Logging,有几个切入的判断点:
Commit Logging的缺陷:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
为了解决Commit Logging的缺陷,基于ARIES理论的"Write Ahead Logging"的日志改进方案就出现了
##### Write Ahead Logging
就是允许事务在提交之前,提前写入变动数据的意思。
* FORCE:当事务提交后,要求变动数据必须同时完成写入则称为FORCE,如果不强制变动数据必须同时完成写入则称为NO-FORCE。现实中绝大多数数据库采用的都是NO-FORCE策略,只要有了日志,变动数据随时可以持久化,从优化磁盘I/O性能考虑,没有必要强制数据写入立即进行。
* STEAL:在事务提交前,允许变动数据提前写入则称为STEAL,不允许则称为NO-STEAL。从优化磁盘I/O性能考虑,允许数据提前写入,有利于利用空闲I/O资源,也有利于节省数据库缓存区的内存。
Commit Logging允许NO-FORCE,但不允许STEAL。
Write-Ahead Logging允许NO-FORCE,也允许STEAL。(性能最高,复杂性最高)
实现方式:增加UnDo log日志,在变动数据写入磁盘之前,必须先记录UnDo log。这个log中明确记录修改了哪个位置的数据,从什么值改成了什么值。在需要回滚的时候,再根据这个日志来进行处理。
##### 使用UnDo log,Write Ahead Logging在崩溃恢复时的三个步骤
* 分析阶段:把没有End Record的事务组成一个待恢复集合
* 重做阶段:从上述的集合中找到包含Commit Record的日志,把它们持久化到磁盘当中
* 回滚阶段:经过上述两个步骤之后,把这个待恢复的集中的剩余事务全部圆润
#### 如何实现隔离性
隔离性保证了每个事务在各自读、写的数据都互相独立,彼此不受影响
##### 数据库提供的三种锁
* 写锁(排他锁)
只有持有写锁的事务才能对数据进行写入操作,数据被加上写锁时,其他事务不能写入数据,也不能加读锁。
写锁禁止其他事务施加读锁,而不是禁止事务读取数据
* 读锁(共享锁)
多个事务可以对一行数据添加多个读锁,数据被加上读锁之后就不能再加写锁了,所有数据都不能对该数据进行写入,但仍然可以读取。
* 范围锁
对于某个范围直接加排他锁,在这个范围内的数据不能被读取,也不能被写入
```sql
select * from books where price<100 for update;##范围锁
对事务所有读、写的数据全部加上读锁、写锁和范围锁
对事务所涉及到的数据加读锁、写锁,但不加范围锁
可重复读比可串行化弱化的地方在于幻读问题,它是指在事务执行的过程中,两个完全相同的范围查询得到了不同的结果集
特例:MySQL/InnoDB默认隔离级别是可重复读,但是在只读事务中避免了幻读问题。但是在读写事务中幻读问题依然存在
对事务所涉及到的数据加的写锁,会一直持续到事务结束。而加的读锁在查询完成之后就会马上释放。
读已提交比可重复读弱化的地方在于不可重复读问题,它是指在事务执行过程中,对同一行数据的两次查询得到了不同的结果。
在这个隔离级别下,两次重复执行的查询结果不一样的原因:读是在查询完成就直接释放了。并没有贯穿整个事务的生命周期,也没有办法禁止读取过的数据发生修改。
对事务涉及到数据只加写锁,一直持续到事务结束,但完全不加读锁。
读未提交比读已提交弱化的地方在于脏读问题,它是指在事务执行的过程中,一个事务读取到了另一个事务未提交的数据。
针对一个事务读、另一个事务写的场景而提出的无锁优化方案,是一种读取优化策略。注意只是针对读+写的事务场景(写+写,就令当别论了)
基本思路:对数据的任何修改都不会直接覆盖之前的数据,而是产生一个新版副本与老版本同时存在。借用这种手段来达到读取时不加锁的目的。
从全局的角度来看,可以理解为给每一行数据都增加两个默认为空的字段:CREATE_VERSION和DELETE_VERSION。当有数据发生变化时,通过控制这两个字段来进行处理:
数据是记录好了,怎么用呢?
会根据隔离级别来决定到底应该读取哪个版本的数据:
分布式事务:多个服务同时访问多个数据源的事务处理机制
主流RDBMS集群通常是采用放弃分区容错性的工作模式,例如RAC集群采用共享磁盘的方式来避免网络分区的出现。每个节点都有SGA,UNDO LOG 和REDO LOG,但各节点共享同一份数据文件和控制文件
先假设一旦出现网络分区,节点之间信息的同步可以无限的延长。从而采用2PC/3PC手段来,同时获取分区容错性和一致性。
目前大多数分布式系统设计的主流选择,大多数的NoSQL库和支持分布式的缓存都是AP系统。
1 |
|
最终一致性的实现方式:可靠消息队列、TCC、SAGA
通过持续重试的机制,来保证可靠性。最大努力交付
具体实现如下:
系统把最有可能出错的业务,以本地事务的方式完成后,通过不断重试的方式,来促使同个事务的其他关联业务来完成。
缺点:没有任务隔离性,在电商场景中可能导致的问题就是超售
TCC天生适合用于需要强隔离性的分布式事务中。他需要先把资源给冻住,然后执行后续的操作。
一般通过事务中间件(阿里seata)来完成
缺点:
如果出现不合作(不可控)的第三方,往往在第一步Try就无法施行了。
引入事务补偿的机制来代替回滚的操作。
实现原理:将大事务拆解为N个小事务,并且为每个事务设计一个对应的补偿机制
]]>全局事务和共享事务,是分布式事务的一种中间形式,起到的是承上启下的作用
1 | 回忆 |
全局事务是一种适用于单个服务使用多个数据源场景的事务解决方案。
XA协议:定义了全局事务管理器和局部的资源管理器之间的通讯接口。常见的现象有:XADataSource/XAResource
Java对于XA的实现:JTA,定义了事务管理器接口、满足XA规范的资源定义接口。
两段式提交、三段式提交也是基于XA协议的实现
1 | 两阶段提交保证成功的前提: |
两段式提交存在的问题
解决两段式提供存在的三个问题中的前两个,也就是单点问题和性能问题,但并没有解决一致性风险。
如何解决的呢
多个服务共同一个数据源,共享数据库连接,通过数据库中间件的形式,通过消息将所有对数据库的改动传送到消息队列服务器,然后通过消息的消费者来统一处理,实现由本地事务保障的持久化操作
]]>