当前位置: 首页 > news >正文

《凤凰架构》第二章——访问远程服务

前言

这章挺难的,感觉离我比较远,不太好懂,简单记录吧。

这章主要讲访问远程服务,主要对比了RPC和REST的区别,可以结合知乎上的文章《既然有 HTTP 请求,为什么还要用 RPC 调用?》
这篇文章进行理解。

而对于远程服务调用,它的内容不单单只有REST、RPC,还有像SOAP,WebSocket,GraphQL等等,之后我会写文章进行具体说明。

总结

1)看完这章,最大的一个问题,REST和RPC到底有什么区别?

首先REST是基于HTTP的,我们这个问题可以先转换成HTTP和RPC的区别?

HTTP和RPC同一级别,还是被RPC包含?答案是都可能。可以看下面这张图,如果你说的HTTP是HTTP通信协议,那么显然RPC是“包含”HTTP的,因为RPC指的是远程调用,是一个完整的远程调用方案,它包括了:接口规范+序列化反序列化规范+通信协议等。也就是只要是远程调用都可以叫RPC,RPC可以基于HTTP这种通信协议实现,比如gRPC,也可以基于其它的。

如果你说的HTTP是指基于HTTP的远程调用方案(包含了接口规范RESTful、序列化反序列化JSON等),那它是与RPC同级的。举个例子,Thrift(一种RPC架构)和基于HTTP的远程调用方案比较,在这种情况下两者相互比较就成了,Thrift通信协议和HTTP通信协议比较,Binaryprotocal序列化方案和JSON序列化方案比较等。

在说回REST和RPC,到底有什么区别?

通过上面的解释,REST和RPC可以说都是一种规范、方案或者风格,只不过REST是基于HTTP远程调用方案的,而RPC是比较自由的,定制化程度比较高的。这也解释了,为什么作者要将这两者并列在一起写。
在这里插入图片描述
2)文章内容总结
在解决远程服务调用时,第一个问题是进程间如何通信,方法有利用管道、信号、信号量、消息队列、共享内存,以及套接字借口,各有利弊、适用场景,其中最后一个适合同机器之间的进程通信。但是RPC(远程调用)远远比IPC(进程间通信)复杂很多,会有很多问题,像是网络并不可靠、存在延迟、安全问题等,不能简单的把远程通信类比为IPC。

在RPC中,会面临3个基本的问题,如何表示数据(序列化和反序列化)、如何传递数据(要考虑异常、超时、安全等)、如何表示方法。基于此发展的RPC面临新的问题,简单、普适、高性能这三点非常难以满足,比如功能如果多起来,协议就会变复杂,效率会受影响;要简单易用,那很多事情必须遵循约定而不是配置才行;要重视效率,那就需要采用二进制的序列化器和较底层的传输协议,支持的语言范围容易受限。所以在RPC的选择上,决定了获得一些利益的同时,要付出另外一些代价。最近几年,RPC框架朝着更高层次(不仅仅负责调用远程服务,还管理远程服务)与插件化方向发展的趋势,不再追求独立地解决RPC的全部三个问题,而是将一部分功能设计成“插件”,让用户自己去选择。

对于REST,REST与RPC在思想上差异的核心是抽象的目标不一样,即面向资源的编程思想与面向过程的编程思想。REST最主要的风格特点其实就是无状态,接口抽象。可以用RMM模型(分为3级)来衡量服务的设计多么REST。
在这里插入图片描述
它优点有,接口面向资源,具有层次结构,更易理解;完全基于HTTP,简化调用复杂度;标准化并且实现广泛,任何语言都实现都有HTTP接口,不同程序交互非常,如果是RPC,自己还得重新实现。

缺点也很多,像不适合应用于要求高性能传输的场景中,REST与HTTP完全绑定,在特定的场景中,无法使用适合的传输协议、序列化方式等;没有传输可靠性支持,无法知道对方是否收到信息,只能利用HTTP的幂等性进行重发;REST缺乏对资源进行“部分”和“批量”的处理能力,也就是面向资源并不非常的细粒度,要拿一个资源可能会多拿,无法批量是因为多次重复HTTP请求会报错,只能自己控制请求频率。

正文

1 远程服务调用

1.1 进程间通信

远程服务调用(Remote Procedure Call),也就是我们经常听说的RPC。RPC出现的最初目的,就是为了让计算机能够与调用本地方法一样去调用远程方法。

在同一进程内的通信,以Java为例,进程内的通信主要通过栈内存,通过压进去里面的地址或事其他来进行消息传递。如果是不同进程之间则无法利用这个。

解决进程之间如何交换数据的问题(起初我一直不知道进程间通信,到底是要传递什么,其实就是各种各样的数据),被称为“进程间通信”(Inter-Process Communication,IPC),有以下几种方式:

  • 管道(Pipe):可通过管道在进程间传递少量的信息。普通管道只用于有亲缘关系进程(由一个进程启动的另外一个进程)间的通信,具名管道摆脱了普通管道没有名字的限制,可以允许无亲缘关系进程间的通信。管道典型的应用就是命令行中的|操作符,譬如:ps -ef | grep java,ps与grep都有独立的进程,以上命令就通过管道操作符|将ps命令的标准输出连接到grep命令的标准输入上(ps -ef命令用于显示当前系统中所有进程的详细信息,包括进程 ID、用户、CPU 占用率等;而grep java则用于过滤出包含“java”关键字的进程 )。
  • 信号(Signal):信号用于通知目标进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程自身。信号的典型应用是kill命令,譬如:kill -9 pid(用于强制终止指定进程。kill命令用于发送信号给指定的进程或进程组;而-9则表示发送SIGKILL信号)。
  • 信号量(Semaphore):信号量用于两个进程之间同步协作手段,它相当于操作系统提供的一个特殊变量。
    // 伪代码示例
    // 定义信号量
    semaphore S = 1; // 初始值为1,表示只有一个进程可以访问共享资源// P1进程
    P(S); // P1等待信号量可用
    // 访问共享资源R1
    V(S); // P1释放信号量,让其他进程可以访问共享资源// P2进程
    P(S); // P2等待信号量可用
    // 访问共享资源R1
    V(S); // P2释放信号量,让其他进程可以访问共享资源
    
  • 消息队列(Message Memory):以上三种方式只适合传递传递少量信息,进程可以利用消息队列,向队列添加消息,被赋予读权限的进程则可以从队列消费消息。消息队列克服了信号承载信息量少,管道只能用于无格式字节流以及缓冲区大小受限等缺点,但实时性相对受限。
  • 共享内存(Shared Memory):允许多个进程访问同一块公共的内存空间,这是效率最高的进程间通信形式。当一块内存被多进程共享时,各个进程往往会与其它通信机制,譬如信号量结合使用,来达到进程间同步及互斥的协调操作。
  • 本地套接字借口(IPC Socket):消息队列和共享内存只适合单机多进程间的通信,套接字接口是更为普适的进程间通信机制,可用于不同机器之间的进程通信。出于效率考虑,当仅限于本机进程间通信时,套接字接口是被优化过的,不会经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等操作,只是简单地将应用层数据从一个进程拷贝到另一个进程。
1.2 通信的成本

之所以花费那么多篇幅来介绍IPC的手段,是因为最初计算机科学家们的想法,就是将RPC作为IPC的一种特例来看待的,这个观点在今天,仅分类上这么说也仍然合理,只是到具体操作手段上不会这么做了。

对于最后一个本地套接字借口这样做的好处是,由于Socket是网络栈的统一接口,它也理所当然地能支持基于网络的跨机器的进程间通信。此外,由于Socket是各个操作系统都有提供的标准接口,完全有可能把远程方法调用的通信细节隐藏在操作系统底层,从应用层面上看来可以做到远程调用与本地的进程间通信在编码上完全一致。这种透明的调用形式造成了程序员误以为通信是无成本的假象,因而被滥用以致于显著降低了分布式系统的性能。

本地调用与远程调用当做一样处理,这是犯了方向性的错误,把系统间的调用做成透明,反而会增加程序员工作的复杂度。比如两个进程通信,谁作为服务端,谁作为客户端?怎样进行异常处理?服务端出现多线程竞争怎么办?等等问题。如果想要远程调用透明化,需要为以下罪过买单(反话),1)网络是可靠的;2)延迟是不存在的;3)带宽是无限的;4)网络是安全的;5)拓扑结构是一成不变的;6)总会有一个管理员;7)不必考虑传输成本;8)网络都是同质化的。

所以,RPC应该是一种高层次的或者说语言层次的特征,而不是像IPC那样,是低层次的或者说系统层次的。

1.3 三个基本问题

虽然有很多RPC协议,但都不外乎变着花样使用各种手段来解决一下三个基本问题:

  • 如何表示数据
    这里数据包括了传递给方法的参数,以及方法执行后的返回值。远程方法调用可能面临交互双方各自使用不同程序语言的情况,即使只支持一种程序语言的RPC协议,在不同硬件指令集、不同操作系统下,同样的数据类型也完全可能有不一样表现细节,譬如数据宽度、字节序的差异等等,有效的做法是将交互双方所涉及的数据转换为某种事先约定好的中立数据流格式来进行传输。这个过程其实就是我们常听的序列化和反序列化。
  • 如何传递数据
    准确地说,是指如何通过网络,在两个服务的Endpoint之间相互操作、交换数据。这里“交换数据”通常指的是应用层协议,实际传输一般是基于标准的TCP、UDP等标准的传输层协议来完成的。两个服务交互不是只扔个序列化数据流来表示参数和结果就行的,许多在此之外信息,譬如异常、超时、安全、认证、授权、事务,等等,都可能产生双方需要交换信息的需求。
  • 如何表示方法
    这在本地方法调用中并不是太大的问题,编译器或者解释器会根据语言规范,将调用的方法签名转换为进程空间中子过程入口位置的指针。而对于不同的语言,每种语言的签名都可能不同,所以需要一个跨语言的统一的标准才行。这个标准做起来可以非常简单,譬如直接给程序的每个方法都规定一个唯一的、在任何机器上都绝不重复的编号,调用时压根不管它什么方法签名是如何定义的,直接传这个编号就能找到对应的方法。这是最简单的方式,还有些比较高级的方式。
1.4 统一的RPC

那些面向透明的、简单的RPC协议,要么依赖于操作系统,要么依赖于特定语言,总有一些先天约束;那些面向通用的、普适的RPC协议,无法逃过使用复杂性的困扰;而那些意图通过技术手段来屏蔽复杂性的RPC协议,又不免受到性能问题的束缚。简单、普适、高性能这三点,似乎真的难以同时满足。

1.5 分裂的RPC

由于一直没有一个同时满足以上三点的“完美 RPC 协议”出现,所以远程服务器调用这领域里,逐渐进入了群雄混战的时代,距离“统一”是越来越远,并一直延续至今。现在,已经相继出现过 RMI(Sun/Oracle)、Thrift(Facebook/Apache)、Dubbo(阿里巴巴/Apache)、gRPC(Google)、Motan1/2(新浪)、Finagle(Twitter)、brpc(百度/Apache)、.NET Remoting(微软)、Arvo(Hadoop)、JSON-RPC 2.0(JSON-RPC工作组)等等。这些RPC功能、特点不尽相同,有的是某种语言私有,有的能支持跨越多门语言,有的运行在应用层HTTP协议之上,有的能直接运行于传输层TCP/UDP协议之上,但肯定不存在哪一款是“最完美的RPC”。今时今日,任何一款具有生命力的RPC框架,都不再去追求大而全的“完美”,而是有自己的针对性特点作为主要的发展方向,朝着面向对象发展,朝着性能发展朝着简化发展等等。

经历了 RPC 框架的战国时代,开发者们终于认可了不同的 RPC 框架所提供的特性或多或少是有矛盾的,很难有某一种框架说“我全部都要”。要把面向对象那套全搬过来,就注定不会太简单;功能多起来,协议就要弄得复杂,效率一般就会受影响;要简单易用,那很多事情就必须遵循约定而不是配置才行;要重视效率,那就需要采用二进制的序列化器和较底层的传输协议,支持的语言范围容易受限。决定了选择框架时在获得一些利益的同时,要付出另外一些代价。

最近几年,RPC框架有明显的朝着更高层次(不仅仅负责调用远程服务,还管理远程服务)与插件化方向发展的趋势,不再追求独立地解决RPC的全部三个问题,而是将一部分功能设计成扩展点,让用户自己去选择。框架聚焦于提供核心的、更高层次的能力,譬如提供负载均衡、服务注册、可观察性等方面的支持。这一类框架的代表有Facebook的Thrift与阿里的Dubbo。比如Dubbo,它默认有自己的传输协议(Dubbo协议),同时也支持其他协议;默认采用Hessian 2作为序列化器,如果你有JSON的需求,可以替换为 Fastjson,如果你对性能有更高的追求,可以替换为Kryo (opens new window)等效率更好的序列化器。这种设计在一定程度上缓和了RPC框架必须取舍,难以完美的缺憾。

2 REST设计风格

REST与RPC在思想上差异的核心是抽象的目标不一样,即面向资源的编程思想与面向过程的编程思想两者之间的区别。

而概念上的不同是指REST并不是一种远程服务调用协议,它就不是一种协议。协议都带有一定的规范性和强制性,它只有一些指导原则,但并不受强制的约束,所以REST只能说是风格,并且能完全达到REST所有指导原则的系统也是不多见的。

至于使用范围,REST与RPC作为主流的两种远程调用方式,在使用上是确有重合的,但重合的区域有多大就见仁见智了。上一节提到了当前的RPC协议框架都各有侧重点,并且列举了RPC一些发展方向,如分布式对象、提升调用效率、简化调用复杂性等等。这里面分布式对象这一条线的应用与REST可以说是毫无关联;而能够重视远程服务调用效率的应用场景,就基本上已经排除了REST应用得最多的供浏览器端消费的远程服务,因为以浏览器作为前端,对于传输协议、序列化器这两点都不会有什么选择的权力,哪怕想要更高效率也有心无力;而在移动端、桌面端或者分布式服务端的节点之间通讯这一块,REST虽然照样有宽阔的用武之地,只要支持HTTP就可以用于任何语言之间的交互,不过通常都会以网络没有成为性能瓶颈为使用前提,在需要追求传输效率的场景里,REST提升传输效率的潜力有限;对追求简化调用的场景——前面提到的浏览器端就属于这一类的典型,众多 RPC 里也就JSON-RPC有机会与REST竞争,其他RPC协议与框架,但很少见有实际项目把它们真的用到浏览器上的。

2.1 理解 REST

REST的英文是REpresentational State Transfer,表征状态转移。

  • 资源(Resource)
    其实就是数据。
  • 表征(Representation)
    浏览器向服务端发出请求“我需要这个资源的HTML格式”,服务端向浏览器返回的这个HTML就被称之为“表征”。
  • 状态(State)
    比如说,当你读完了一篇文章,想看后面是什么内容时,你向服务器发出请求“给我下一篇文章”。但是“下一篇”是个相对概念,必须依赖“当前你正在阅读的文章是哪一篇”才能正确回应,这类在特定语境中才能产生的上下文信息即被称为“状态”。
  • 转移(Transfer)
    服务器通过某种方式,把“用户当前阅读的文章”转变成“下一篇文章”,这就被称为“表征状态转移”。
2.2 RESTful的系统

以下为满足REST风格的六大原则:

  1. 客户端与服务端分离
    将用户界面所关注的逻辑和数据存储所关注的逻辑分离开来,有助于提高用户界面的跨平台的可移植性。
  2. 无状态
    每一次从客户端发送的请求中,应包括所有的必要的上下文信息,会话信息由客户端负责保存维护,服务端依据客户端传递的状态来执行业务处理逻辑。客户端承担状态维护职责以后,会产生一些新的问题,譬如身份认证、授权等可信问题,它们都应有针对性的解决方案。
    但必须承认的现状是,目前大多数的系统都达不到这个要求,往往越复杂、越大型的系统越是如此。服务端无状态可以在分布式计算中获得非常高价值的好处,但大型系统的上下文状态数量完全可能膨胀到让客户端在每次请求时提供变得不切实际的程度,在服务端的内存、会话、数据库或者缓存等地方持有一定的状态成为一种主流的方案。
  3. 可缓存
    使用无状态的设计则可能会需要多次请求,或者在请求中带有额外冗余的信息。为了缓解这个矛盾,REST将部分服务端的应答缓存起来。运作良好的缓存机制可以减少客户端、服务器之间的交互,甚至有些场景中可以完全避免交互,这进一步提高了性能。
  4. 分层系统
    这里所指的并不是表示层、服务层、持久层这种意义上的分层。而是指客户端一般不需要知道是否直接连接到了最终的服务器,抑或连接到路径上的中间服务器。中间服务器可以通过负载均衡和共享缓存的机制提高系统的可扩展性,这样也便于缓存、伸缩和安全策略的部署。该原则的典型的应用是内容分发网络(Content Distribution Network,CDN)。比如你访问Github,你所发出的请求一般(假设你在中国国境内的话)并不是直接访问位于GitHub的源服务器,而是访问了位于国内的CDN服务器,但作为用户,你完全不需要感知到这一点。
  5. 统一接口
    REST希望开发者面向资源编程,希望软件系统设计的重点放在抽象系统该有哪些资源上,而不是抽象系统该有哪些行为上。
    面向资源编程的抽象程度通常更高。抽象程度高意味着坏处是往往距离人类的思维方式更远,而好处是往往通用程度会更好。
  6. 按需代码

REST的基本思想是面向资源来抽象问题,它与此前流行的编程思想——面向过程的编程在抽象主体上有本质的差别。在REST提出以前,人们设计分布式系统服务的唯一方案就只有RPC,RPC是将本地的方法调用思路迁移到远程方法调用上,开发者是围绕着“远程方法”去设计两个系统间交互的。这样做的坏处不仅是“如何在异构系统间表示一个方法”、“如何获得接口能够提供的方法清单”都成了需要专门协议去解决的问题(RPC 的三大基本问题),更在于服务的每个方法都是完全独立的,服务使用者必须逐个学习才能正确地使用它们。Google 在《Google API Design Guide 》中曾经写下这样一段话:“以前,人们面向方法去设计RPC API,譬如CORBA和DCOM,随着时间推移,接口与方法越来越多却又各不相同,开发人员必须了解每一个方法才能正确使用它们,这样既耗时又容易出错。”以下为REST的好处:

  • 降低的服务接口的学习成本。统一接口是REST的重要标志,将对资源的标准操作都映射到了标准的HTTP方法上去,这些方法对于每个资源的用法都是一致的,语义都是类似的,不需要刻意去学习。
  • 资源天然具有集合与层次结构。举个具体例子,一个商城用户中心的接口设计“GET /users/icyfenix/cart/2”,用户资源会拥有多个不同的下级的资源,譬如若干条短消息资源、一份用户资料资源,购物车中又会有自己的下级资源,譬如多本书籍资源。很容易在程序接口中构造出这些资源的集合关系与层次关系,而且是符合人们长期在单机或网络环境中管理数据的直觉的。
  • REST绑定于HTTP协议。这是缺点,也是优点。因为HTTP本来就是面向资源而设计的网络协议,带来的好处是对于二进制细节、编码形式、报文格式、连接方式等细节不需要考虑,REST将复用HTTP协议中已经定义的概念和相关基础支持来解决问题。而坏处自然是,当你想去考虑那些HTTP不提供的特性时,便会彻底地束手无策。
2.3 RMM成熟度

RMM 成熟度一个衡量“服务有多么REST”的模型。简单来说:

  • 第0级:完全不REST。
    类似RPC,也就是面向过程,比如要得到一个时间段内医生的空闲时间:

    POST /appointmentService?action=query HTTP/1.1
    {date: "2020-03-04", doctor: "mjones"}
    
  • 第1级:开始引入资源的概念。
    依然是得到一个时间段内医生的空闲时间和预约,这里的问题,一是只处理了查询和预约,如果我临时想换个时间,要调整预约,或者我的病忽然好了,想删除预约,这都需要提供新的服务接口。二是处理结果响应时,只能靠着结果中的code、message这些字段做分支判断,每一套服务都要设计可能发生错误的code,这很难考虑全面,而且也不利于对某些通用的错误做统一处理;三是并没有考虑认证授权等安全方面的内容,譬如要求只有登陆用户才允许查询医生档期时间。

    POST /doctors/mjones HTTP/1.1
    {date: "2020-03-04"}POST /schedules/1234 HTTP/1.1
    {name: icyfenix, age: 30, ……}
    
  • 第2级:引入统一接口,映射到 HTTP 协议的方法上。
    解决了第一级留下的三个问题,REST的做法是把不同业务需求抽象为对资源的增加、修改、删除等操作来解决第一个问题;使用HTTP协议的Status Code(这里应该是指403、200这些已经定义好的,而不像第1级的code是自己定义的),可以涵盖大多数资源操作可能出现的异常,而且Status Code可以自定义扩展,以此解决第二个问题;依靠HTTP Header中携带的额外认证、授权信息来解决第三个问题。

  • 第3级:超媒体控制在本文里面的说法是“超文本驱动”。
    第2级是目前绝大多数系统所到达的REST级别,但仍不是完美的,至少还存在一个问题:你是如何知道预约mjones医生的档期是需要访问/schedules/1234这个服务Endpoint的?“超文本驱动”,所希望的是除了第一个请求是有你在浏览器地址栏输入所驱动之外,其他的请求都应该能够自己描述清楚后续可能发生的状态转移,由超文本自身来驱动。所以,当你输入了查询的指令之后:

    GET /doctors/mjones/schedule?date=2020-03-04&status=open HTTP/1.1HTTP/1.1 200 OK{schedules:[{id: 1234, start:"14:00", end: "14:50", doctor: "mjones",links: [{rel: "comfirm schedule", href: "/schedules/1234"}]},{id: 5678, start:"16:00", end: "16:50", doctor: "mjones",links: [{rel: "comfirm schedule", href: "/schedules/5678"}]}],links: [{rel: "doctor info", href: "/doctors/mjones/info"}]
    }
    

    如果做到了第3级REST,那服务端的API和客户端也是完全解耦的,你要调整服务数量,或者同一个服务做API升级将会变得非常简单。

2.4 不足与争议
  1. 面向资源的编程思想只适合做CRUD,面向过程、面向对象编程才能处理真正复杂的业务逻辑
    并不是。HTTP 的四个最基础的命令POST、GET、PUT 和 DELETE很容易让人直接联想到CRUD操作,它们涵盖了信息在客户端与服务端之间如何流动的几种主要方式。而针对一些比较抽象的场景,如果真不好把HTTP方法映射为资源的所需操作,REST也并非刻板的教条,用户是可以使用自定义方法的,按Google推荐的REST API风格,自定义方法应该放在资源路径末尾,嵌入冒号加自定义动词的后缀。譬如,可以把删除操作映射到标准DELETE方法上,如果此外还要提供一个恢复删除的API,那它可能会被设计为:“POST /user/user_id/cart/book_id:undelete”。如果你不想使用自定义方法,那就设计一个回收站的资源,在那里保留着还能被恢复的商品,将恢复删除视为对该资源某个状态值的修改,映射到PUT或者PATCH方法上,这也是一种完全可行的设计。
    面向资源的编程思想与另外两种主流编程思想只是抽象问题时所处的立场不同,只有选择问题,没有高下之分:
    1)面向过程编程时,为什么要以算法和处理过程为中心,输入数据,输出结果?当然是为了符合计算机世界中主流的交互方式。
    2)面向对象编程时,为什么要将数据和行为统一起来、封装成对象?当然是为了符合现实世界的主流的交互方式。
    3)面向资源编程时,为什么要将资源作为抽象的主体,把行为看作是统一的接口?当然是为了符合网络世界的主流的交互方式。

  2. REST与HTTP完全绑定,不适合应用于要求高性能传输的场景中
    作者很大程度上赞同此观点,但并不认为这是REST的缺陷,HTTP并不是传输层协议,它是应用层协议,如果仅将HTTP当作传输的全部是不恰当的。对于需要直接控制传输,如二进制细节、编码形式、报文格式、连接方式等细节的场景中,REST确实不合适。

  3. REST不利于事务支持
    如果“事务”指的是数据库那种的狭义的刚性ACID事务,那除非完全不持有状态,否则分布式系统本身与此就是有矛盾的(CAP 不可兼得),这是分布式的问题而不是REST的问题。如果“事务”是指通过服务协议或架构,在分布式服务中,获得对多个数据同时提交的统一协调能力(2PC/3PC),这 REST 确实不支持。如果“事务”只是指希望保障数据的最终一致性,说明你已经放弃刚性事务了,这才是分布式系统中的正常交互方式,使用REST肯定不会有什么阻碍,谈不上“不利于”。当然,对此REST也并没有什么帮助,这完全取决于你系统的事务设计。

  4. REST没有传输可靠性支持
    是的,并没有。在HTTP中你发送出去一个请求,通常会收到一个与之相对的响应,譬如HTTP/1.1 200 OK 或者 HTTP/1.1 404 Not Found诸如此类的。但如果你没有收到任何响应,那就无法确定消息到底是没有发送出去,抑或是没有从服务端返回回来,这其中的关键差别是服务端到底是否被触发了某些处理?应对传输可靠性最简单粗暴的做法是把消息再重发一遍。这种简单处理能够成立的前提是服务应具有幂等性,即服务被重复执行多次的效果与执行一次是相等的。HTTP也协议要求GET、PUT和DELETE应具有幂等性。

  5. REST缺乏对资源进行“部分”和“批量”的处理能力
    这个观点作者是认同的,这很可能是未来面向资源的思想和API设计风格的发展方向。譬如你仅仅想获得某个用户的姓名,RPC风格中可以设计一个“getUsernameById”的服务,返回一个字符串,尽管这种服务的通用性实在称不上“设计”二字,但确实可以工作;而REST风格中你将向服务端请求整个用户对象,然后丢弃掉返回的结果中该用户除用户名外的其他属性,这便是一种“过度获取”;你准备把某个用户的名字增加一个“VIP”前缀,提交一个PUT请求修改这个用户的名称即可,而你要给1000个用户加VIP时,如果真的去调用1000次PUT,浏览器会回应你HTTP/1.1 429 Too Many Requests,此时,你就不得不先创建一个任务资源,把1000个用户的ID交给这个任务,控制请求的频率,避免因为请求过于频繁而被服务器拒绝。

目前,一种理论上较优秀的可以解决以上这几类问题的方案是GraphQL,这是由Facebook提出的一种面向资源API的数据查询语言,如同SQL一样,挂了个“查询语言”的名字,但其实CRUD都有涉猎。比起依赖HTTP无协议的REST,GraphQL可以说是另一种“有协议”的、更彻底地面向资源的服务方式。然而凡事都有两面,离开了HTTP,它又面临着几乎所有RPC框架所遇到的那个如何推广交互接口的问题。

相关文章:

《凤凰架构》第二章——访问远程服务

前言 这章挺难的,感觉离我比较远,不太好懂,简单记录吧。 这章主要讲访问远程服务,主要对比了RPC和REST的区别,可以结合知乎上的文章《既然有 HTTP 请求,为什么还要用 RPC 调用?》 这篇文章进行…...

【Diffusion】李宏毅2023机器学习Diffusion笔记

文章目录 1 想法概述2 实际过程阶段1 Add Noise阶段2 Denoise 3 数学原理4 为什么推理时要额外加入noise5 一些不知道对不对的Summary 1 想法概述 从一张充满噪声的图中不断denoise,最终得到一张clear的图片。为了确定当前图片中噪声占比的大小,同时输入…...

CloudEvents—云原生事件规范

我们的系统中或多或少都会用到如下两类业务技术: 异步任务,用于降低接口时延或削峰,提升用户体验,降低系统并发压力;通知类RPC,用于微服务间状态变更,用户行为的联动等场景; 以上两种…...

神经网络基础-神经网络补充概念-51-局部最优问题

概念 局部最优问题是在优化问题中常见的一个挑战,特别是在高维、非凸、非线性问题中。局部最优问题指的是算法在优化过程中陷入了一个局部最小值点,而不是全局最小值点。这会导致优化算法在某个局部区域停止,而无法找到更好的解。 解决方案…...

深度学习中,什么是batch-size?如何设置?

什么是batch-size? batch-size 是深度学习模型在训练过程中一次性输入给模型的样本数量。它在训练过程中具有重要的意义,影响着训练速度、内存使用以及模型的稳定性等方面。 以下是 batch-size 大小的一些影响和意义: 训练速度:较大的 bat…...

[保研/考研机试] KY26 10进制 VS 2进制 清华大学复试上机题 C++实现

题目链接: 10进制 VS 2进制http://www.nowcoder.com/share/jump/437195121691738172415 描述 对于一个十进制数A,将A转换为二进制数,然后按位逆序排列,再转换为十进制数B,我们称B为A的二进制逆序数。 例如对于十进制…...

JSP-学习笔记

文章目录 1.JSP介绍2 JSP快速入门3 JSP 脚本3.1 JSP脚本案例3.2 JSP缺点 4 EL表达式4.1 快速入门案例 5. JSTL标签6. MVC模式和三层架构6.1 MVC6.2 三层架构 7. 案例-基于MVC和三层架构实现商品表的增删改查 1.JSP介绍 概念 JSP(JavaServer Pages)是一种…...

Golang协程,通道详解

进程、线程以及并行、并发 关于进程和线程 进程(Process)就是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位,进程是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,每一…...

unity 之 Vector 数据类型

文章目录 Vector 1Vector 2Vector 3Vector 4 Vector 1 在Unity中,Vector1 并不是一个常见的向量类型。 如果您需要表示标量(单个值)或者只需要一维的数据,通常会直接使用浮点数(float)或整数(in…...

私密数据采集:隧道爬虫IP技术的保密性能力探究

作为一名专业的爬虫程序员,今天要和大家分享一个关键的技术,它能够为私密数据采集提供保密性能力——隧道爬虫IP技术。如果你在进行敏感数据采集任务时需要保护数据的私密性,那么这项技术将是你的守护神。 在进行私密数据采集任务时&#xff…...

使用git rebase 之后的如何恢复到原始状态

我们常常喜欢使用git rebase去切换分支提交代码,操作流程就是: 先切换分支:比如当前是master 我们修改了一堆代码产生一个commit id :5555555567777 那么我们常常比较懒就直接切换了:git checkout dev 然后呢?使用命令git rebase 5555555567777,想把这笔修改提交到d…...

matlab相机标定知识整理

matlab相机标定知识整理 单目相机标定 单目相机标定 内参矩阵:cameraParams.Intrinsics.K 或者 cameraParams.K旋转矩阵:cameraParams.RotationMatrices 有待确定 cameraParams.RotationVectors平移矩阵:cameraParams.TranslationVectors径向…...

win11安装ubuntu 子系统安装过程及注意事项

第一步 :安装系统必须组件 由于子系统是系统自带组件,需要安装软件支持 第二步:应用商店安装 ubuntu 编辑 编辑 这个时候打开会报错 第三步,运行linux子系统 选择Windows PowerShell 以管理员身份运行) 输入&#…...

torch.cat((A,B),dim=1)解析

官方说明torch.cat 引用自:Pytorch中的torch.cat()函数 torch.cat(tensors, dim0, *, outNone) → Tensor # 连接给定维数的给定序列的序列张量。所有张量要么具有相同的形状(除了连接维度),要么为空。示例 输入: import torch a torch.Tens…...

apache配置安全证书https踩坑记录

apache配置安全证书有如下几步 一、申请证书 这个网上有很多免费的,我用的是阿里云的服务器,在阿里云后台就可以申请免费证书。 二、上传证书 申请好证书后,根据服务器用的什么软件,是apache还是ngnix,下载相应的证书…...

SQL Server Express 自动备份方案

文章目录 SQL Server Express 自动备份方案前言方案原理SQL Server Express 自动备份1.创建存储过程2.设定计划任务3.结果检查sqlcmd 参数说明SQL Server Express 自动备份方案 前言 对于许多小型企业和个人开发者来说,SQL Server Express是一个经济实惠且强大的数据库解决方…...

Docker资源控制

目录 一、CPU 资源控制 1.设置CPU使用率上限 2.设置CPU资源占用比(设置多个容器时才有效) 3.设置容器绑定指定的CPU 二、对内存使用的限制 三、对磁盘IO配额控制(blkio)的限制 一、CPU 资源控制 cgroups,是一个非常强…...

微服务中间件-分布式缓存Redis

分布式缓存 a.Redis持久化1) RDB持久化1.a) RDB持久化-原理 2) AOF持久化3) 两者对比 b.Redis主从1) 搭建主从架构2) 数据同步原理(全量同步)3) 数据同步原理(增量同步) c.Redis哨兵1) 哨兵的作用2) 搭建Redis哨兵集群3) RedisTem…...

java面试强基(16)

目录 clone方法的保护机制 Java中由SubString方法是否会引起内存泄漏? Java中提供了哪两种用于多态的机制? 程序计数器(线程私有) 如何判断对象是否是垃圾? clone方法的保护机制 clone0方法的保护机制在Object中是被声明为 protected的。以User…...

Python可视化在量化交易中的应用(13)_Seaborn直方图

Seaborn中带核密度的直方图的绘制方法 seaborn中绘制直方图使用的是sns.histlot()函数: sns.histplot(data,x,y,hue,weights,stat‘count’,bins‘auto’,binwidth,binrange,discrete,cumulative,common_bins,common_norm,multiple‘layer’,element‘bars’,fill,…...

NOIP 2006 普及组 第二题 开心的金明

开心的金明 说明 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。 更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超…...

「UG/NX」Block UI 指定点SpecifyPoint

✨博客主页何曾参静谧的博客📌文章专栏「UG/NX」BlockUI集合📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C+&#...

Linux Shell如果ping失败就重启网卡(详解)

直接上脚本 -------------------------------------------------------------------------- #vi /tmp/ping_check.sh #!/bin/bash IP="1.1.1.1" PacketLoss=`ping -c 4 -w 4 1.1.1.1 | grep packet loss | awk -F packet loss {print $1} | awk {print $NF}|se…...

每天一道leetcode:剑指 Offer 13. 机器人的运动范围(中等广度优先遍历剪枝)

今日份题目: 地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0]的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之…...

TypeError: a bytes-like object is required, not ‘str‘

raceback (most recent call last): File "D:\pycharmcode\client.py", line 12, in <module> tcp_socket.send(send_data) TypeError: a bytes-like object is required, not str 使用socket进行ubuntu与windows通信时&#xff0c;发送数据时报了以上错…...

题解 | #1005.List Reshape# 2023杭电暑期多校9

1005.List Reshape 签到题 题目大意 按一定格式给定一个纯数字一维数组&#xff0c;按给定格式输出成二维数组。 解题思路 读入初始数组字符串&#xff0c;将每个数字分离&#xff0c;按要求输出即可 参考代码 参考代码为已AC代码主干&#xff0c;其中部分功能需读者自行…...

会声会影2023旗舰版电脑端视频剪辑软件

随着短视频、vlog等媒体形式的兴起&#xff0c;视频剪辑已经成为了热门技能。甚至有人说&#xff0c;不会修图可以&#xff0c;但不能不会剪视频。实际上&#xff0c;随着各种智能软件的发展&#xff0c;视频剪辑已经变得越来越简单。功能最全的2023新版&#xff0c;全新视差转…...

【linux基础(四)】对Linux权限的理解

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到开通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux权限 1. 前言2. shell命…...

maven项目指定数据源

springboot项目 直接在pom.xml文件中添加以下配置 <!--使用阿里云maven中央仓库--> <repositories><repository><id>aliyun-repos</id><url>http://maven.aliyun.com/nexus/content/groups/public/</url><snapshots><ena…...

web3:使用Docker-compose方式部署blockscout

最近做的项目,需要blockscout来部署一个区块链浏览器,至于blockscout是什么,咱们稍后出一篇文章专门介绍下,本次就先介绍一下如何使用Docker-compose方式部署blockscout,以及过程中遇到的种种坑 目录 先决条件我的环境准备工作Docker-compose1.安装方式一:下载 Docker Co…...

C++11实用技术(五)泛型编程加载dll接口函数

C11泛型编程简化加载dll代码 常见的加载dll方式&#xff1a; HMODULE m_hDataModule; m_hDataModule LoadLibrary("myDll.dll");typedef int (*PfunA)(int a, int b);//定义函数指针 PfunA fun (PfunA)(GetProcAddress(m_hDataModule , "funA"));//加载…...

使用wxPython和PyMuPDF提取PDF页面指定页数的内容的应用程序

在本篇博客中&#xff0c;我们将探讨如何使用wxPython和PyMuPDF库创建一个简单的Bokeh应用程序&#xff0c;用于选择PDF文件并提取指定页面的内容&#xff0c;并将提取的内容显示在文本框中。 C:\pythoncode\new\pdfgetcontent.py 准备工作 首先&#xff0c;确保你已经安装了…...

k8s的pv和pvc创建

//NFS使用PV和PVC 1、配置nfs存储 2、定义PV 实现 下图的pv和pvc测试 pv的定义 这里定义5个PV&#xff0c;并且定义挂载的路径以及访问模式&#xff0c;还有PV划分的大小 vim /pv.yamlapiVersion: v1 kind: PersistentVolume metadata:name: pv001 spec:capacity:storage: …...

记K8S集群工作节点,AnolisOS 8.6部署显卡驱动集成Containerd运行时

1、安装gcc #安装编译环境 yum -y install make gcc gcc-c2、下载显卡驱动 点击 直达连接 nvidia高级搜索下载历史版本驱动程序&#xff08;下载历史版本驱动&#xff09; https://www.nvidia.cn/Download/Find.aspx?langcn3、安装驱动 安装显卡驱动 ./NVIDIA-Linux-x86…...

JavaScript 性能优化

优化JavaScript代码的性能是开发过程中的一个关键任务&#xff0c;它可以显著提升网站或应用的用户体验。以下是一些优化技巧&#xff0c;涵盖了减少重绘、减少内存占用和合并网络请求等方面&#xff1a; 1. **减少重绘和重排&#xff1a;** - **使用 CSS3 动画&#xff1a…...

架构演进及常用架构

1架构演进及常用架构 1.1单体分层架构 1.2 多应用微服务架构 1.3 分布式集群部署 部署 CDN 节点&#xff1a; 用户访问量的增加意味着用户地域的分散请求&#xff0c;如果所有请求都直接发送中心服务器的话&#xff0c;距离越远&#xff0c;响应速度越差&#xff0c;这时就需…...

WinCC V7.5 中的C脚本对话框不可见,将编辑窗口移动到可见区域的具体方法

WinCC V7.5 中的C脚本对话框不可见&#xff0c;将编辑窗口移动到可见区域的具体方法 由于 Windows 系统更新或使用不同的显示器&#xff0c;在配置C动作时&#xff0c;有可能会出现C脚本编辑窗口被移动到不可见区域的现象。 由于该窗口无法被关闭&#xff0c;故无法进行进一步…...

【实战】十一、看板页面及任务组页面开发(二) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十四)

文章目录 一、项目起航&#xff1a;项目初始化与配置二、React 与 Hook 应用&#xff1a;实现项目列表三、TS 应用&#xff1a;JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook&…...

Vue2.7.14、vuecli@5.0.8 升级 vite@4.4.8

项目背景 Vue2.7.14、vuecli5.0.8、element-ui2.15.13、node14.18.3 vite安装 pnpm add vite4.4.8 -D 入口文件index.html 文件位置修改 将pulic里的index.html移到根目录下 根目录/public/index.html 到 根目录/index.html 文件内容修改 <link rel"icon"…...

LeetCode[面试题04.12]求和路径

难度&#xff1a;Medium 题目&#xff1a; 给定一棵二叉树&#xff0c;其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法&#xff0c;打印节点数值总和等于某个给定值的所有路径的数量。注意&#xff0c;路径不一定非得从二叉树的根节点或叶节点开始或结束&#x…...

骑行运动耳机哪款好?五年骑行爱好者给你分享分享

作为一名骑行达人&#xff0c;我尝试过多种骑行耳机&#xff0c;有入耳式、耳罩式、骨传导等等&#xff0c;但总有一款让我特别满意。直到我遇到了这几款耳机&#xff0c;它不仅音质出色&#xff0c;而且非常适合骑行&#xff0c;让我爱不释手。下面&#xff0c;我将分享一下这…...

SpringBoot3集成ElasticSearch

标签&#xff1a;ElasticSearch8.Kibana8&#xff1b; 一、简介 Elasticsearch是一个分布式、RESTful风格的搜索和数据分析引擎&#xff0c;适用于各种数据类型&#xff0c;数字、文本、地理位置、结构化数据、非结构化数据&#xff1b; 在实际的工作中&#xff0c;历经过Ela…...

详解23种设计模式优缺点以及解决方案

1. 单例模式&#xff08;Singleton Pattern&#xff09;&#xff1a; 优点&#xff1a;确保一个类只有一个实例&#xff0c;提供全局访问点&#xff0c;节省资源。缺点&#xff1a;可能引入全局状态&#xff0c;难以扩展和测试。解决方法&#xff1a;使用依赖注入来替代直接访…...

Oracle 数据库中删除表空间的详细步骤与示例

系列文章目录 文章目录 系列文章目录前言一、查看表空间二、数据迁移和备份三、下线表空间中的对象四、删除表空间五、删除完成后的操作总结前言 在 Oracle 数据库中,表空间是存储数据的逻辑容器。有时候,我们可能需要删除不再使用的表空间以释放空间或进行数据库重组。本文…...

<kernel>kernel 6.4 笔记

&#xff1c;kernel&#xff1e;kernel 6.4 笔记 1、kernel 与用户层通信过程 (1) kernel 通过uevent事件 通知 用户层&#xff1b; 第一步&#xff1a;准备同事事件的参数键值对存到环境变量中&#xff1b; 第二步 &#xff1a;准备环境变量数据 ACTION、DEVPATH、SUBSYSTEM…...

介绍一些编程语言— Perl 语言

介绍一些编程语言— Perl 语言 Perl 语言 简介 Perl 是一种动态解释型的脚本语言。 最初的设计者为拉里・沃尔&#xff0c;它于 1987 1987 1987 年 12 12 12 月 18 18 18 日发表。Perl 借取了 C、sed、awk、shell scripting 以及很多其他编程语言的特性。其中最重要的特性…...

原型与继承

原型与继承 在 JavaScript 中&#xff0c;对象有一个特殊的隐藏属性 [[Prototype]]&#xff08;如规范中所命名的&#xff09;&#xff0c;它要么为 null&#xff0c;要么就是对另一个对象的引用。该对象被称为“原型。 当我们从 object 中读取一个缺失的属性时&#xff0c;Jav…...

Flink流批一体计算(14):PyFlink Tabel API之SQL查询

举个例子 查询 source 表&#xff0c;同时执行计算 # 通过 Table API 创建一张表&#xff1a; source_table table_env.from_path("datagen") # 或者通过 SQL 查询语句创建一张表&#xff1a; source_table table_env.sql_query("SELECT * FROM datagen&quo…...

JRebel插件扩展-mac版

前言 上一篇分享了mac开发环境的搭建&#xff0c;但是欠了博友几个优化的债&#xff0c;今天先还一个&#xff0c;那就是idea里jRebel插件的扩展。 一、场景回眸 这个如果在win环境那扩展是分分钟&#xff0c;一个exe文件点点就行。现在在mac环境就没有这样的dmg可以执行的&…...

C语言中常见的一些语法概念和功能

常用代码&#xff1a; 程序入口&#xff1a;int main() 函数用于定义程序的入口点。 输出&#xff1a;使用 printf() 函数可以在控制台打印输出。 输入&#xff1a;使用 scanf() 函数可以接收用户的输入。 条件判断&#xff1a;使用 if-else 语句可以根据条件执行不同的代码…...