什么需要远程调用
为了提升饭店的服务能力,饭店从一开始只有一个负责所有事情的厨师发展成有厨师、切菜师、备菜师等多个角色。
在饭店只有一个厨师的时候,厨师想要做出一道美味的番茄炒蛋的时候,他需要自己洗番茄、切番茄、打鸡蛋、炒菜。整个过程不需要其他人参与自己就完全可以完成了。这就是古老的集中式应用中,一台单体计算机就可以搞定所有事情了。
制作番茄炒蛋{ 厨师->洗菜->切菜->炒菜 }
随着饭店发展,需要明确分工,让专业的人负责专业的事儿。所以,整个做菜过程中不再只有厨师参与了。需要有多个角色,备菜师傅负责准备番茄和鸡蛋、切菜师傅负责切菜、厨师只要负责炒菜就行了。
但是,随着分工明确,制作番茄炒蛋的过程不再是只有一个人参与的过程了。这个过程中需要多方协作。厨师准备炒菜之前,需要先通知备菜师傅和切菜师傅,前序工作准备好之后才能进行炒菜。
制作番茄炒蛋{ 备菜师->洗菜 切菜师->切菜 厨师->炒菜 }
这种情况下,厨师就要依赖很多外人来参与这个炒菜工作。而他在通知备菜师帮他洗菜,通知切菜师傅帮他切菜的时候,这个过程就是远程过程调用。
大多数情况下,一般是服务员直接到厨房下单,然后后厨有一个人员分别把菜单分发给备菜师、切菜师和厨师。
这个过程就和计算机系统很像了。如今的大型网站都是分布式部署的。拿一个下单流程来说,可能涉及到物流、支付、库存、红包等多个系统后,而多个系统又是分别部署在不同的机器上的,分别由不同的团队负责。而要想实现下单流程,就需要用到远程调用。
下单{ 库存->减少库存 支付->扣款 红包->红包抵用 物流->生成物流信息 }
到底什么是远程过程调用
RPC 是指计算机 A 上的进程,调用另外一台计算机 B 上的进程,其中 A 上的调用进程被挂起,而 B 上的被调用进程开始执行,当值返回给 A 时,A 进程继续执行。调用方可以通过使用参数将信息传送给被调用方,而后可以通过传回的结果得到信息。而这一过程,对于开发人员来说是透明的。
就像后厨的例子一样,服务员把菜单传给后厨,厨师告诉备菜师和洗菜师开始工作,然后他等待他们完成工作。备菜师和洗菜师工作完之后,厨师开始炒菜。这个过程对于服务员来说其实是透明的,他不需要关心到底后厨是怎么做菜的。
< class="pgc-img">>?由于各服务部署在不同机器上,要想在服务间进行远程调用免不了网络通信过程,服务消费方每调用一个服务都要写一坨网络通信相关的代码,不仅复杂而且极易出错。
如果有一种方式能让我们像调用本地服务一样调用远程服务,而让调用者对网络通信这些细节透明,那么将大大提高生产力,比如服务消费方在执行orderService.buy("HHKB键盘")时,实质上调用的是远端的服务。这种方式其实就是RPC。而提供了这种功能的工具我们称之为RPC框架。
在RPC框架中主要有三个角色:Provider、Consumer和Registry。如下图所示:
< class="pgc-img">>?Server: 暴露服务的服务提供方。 Client: 调用远程服务的服务消费方。 Registry: 服务注册与发现的注册中心。
服务提供方和服务消费方都比较好理解,就是后厨的洗菜师和厨师啦。厨师就是服务消费方,洗菜师就是服务提供方。厨师依赖洗菜师提供的服务。
服务注册中心又是个什么东西呢?
其实这个也比较好理解。对于那种很大的饭店来说,厨师可能有很多(集群部署),洗菜师也有很多(集群部署)。而厨师想要洗菜师帮忙洗菜的时候,他不会直接找某个洗菜师,而是通知一个中间人,这个人可能是洗菜师团队的领导,也可能就是一个专门协调后厨的人员。他知道整个厨房有多少洗菜师,也知道哪个洗菜师今天来上班了(需要先进行服务注册)。而且,他还可以根据各个洗菜师的忙碌情况动态分配任务(负载均衡)。
这个中间人就是服务注册中心。
< class="pgc-img">>?服务提供者启动后主动向注册中心注册机器ip、port以及提供的服务列表; 服务消费者启动时向注册中心获取服务提供方地址列表,可实现软负载均衡和Failover;
实现RPC需要用到的技术
一个成熟的RPC框架需要考虑的问题有很多,这里只介绍实现一个远程调用需要用到的基本技术,感兴趣的朋友可以找一些开源的RPC框架代码来看下。
动态代理
生成 client stub和server stub需要用到Java 动态代理技术,我们可以使用JDK原生的动态代理机制,可以使用一些开源字节码工具框架 如:CgLib、Javassist等。
序列化
为了能在网络上传输和接收 Java对象,我们需要对它进行序列化和反序列化操作。
可以使用Java原生的序列化机制,但是效率非常低,推荐使用一些开源的、成熟的序列化技术,例如:protobuf、Thrift、hessian、Kryo、Msgpack
NIO
当前很多RPC框架都直接基于netty这一IO通信框架,比如阿里巴巴的HSF、dubbo,Hadoop Avro,推荐使用Netty 作为底层通信框架。
服务注册中心
可选技术: Redis、Zookeeper、Consul、Etcd
参考资料 :https://www.jianshu.com/p/dbfac2b876b1
开源RPC框架
Dubbo
Dubbo 是阿里巴巴公司开源的一个Java高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。目前已经进入Apache孵化器。
Motan
Motan是新浪微博开源的一个Java RPC框架。2016年5月开源。Motan 在微博平台中已经广泛应用,每天为数百个服务完成近千亿次的调用。
gRPC
gRPC是Google开发的高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。本身它不是分布式的,所以要实现上面的框架的功能需要进一步的开发。
thrift
thrift是Apache的一个跨语言的高性能的服务框架,也得到了广泛的应用。
>器之心分析师网络
分析师:王子嘉
编辑: H4O
提到生成模型,每个人首先要考虑的问题应该都是这两个——生成什么,如何生成。本文介绍的三篇论文就包含了三种生成模型(GNN、RL、VAE,即怎么生成),同时也介绍了这些生成模型各自当前的应用场景(场景图生成、序列生成、任务型对话生成,即生成什么)。
机器学习模型的一种分类方式就是将模型分为分类模型和生成模型,GAN 的出现使得生成模型一度大火,GAN 也开始被应用于各个领域,但是生成模型并不只是 GAN。除了 GAN 相关的模型,其他的生成模型也是很多人在研究的方向,AAAI 中收录的 1500 多篇文章中就有 100 多篇直接以「生成」命名的文章。本文对其中一些重要论文进行了解读。
论文列表:
- Probabilistic Graph Neural Network(PGNN):Deep Generative Probabilistic Graph Neural Networks for Scene Graph Generation
- Reinforcement Learning(RL): Sequence Generation with Optimal-Transport-Enhanced Reinforcement Learning
- Action Learning: MALA: Cross-Domain Dialogue Generation with Action Learning
1. Probabilistic Graph Neural Network(PGNN)
< class="pgc-img">>论文链接:?https://grlearning.github.io/papers/135.pdf?
1.1 任务描述
视觉理解是计算机视觉中一个很重要的任务,而过去的几年中,很多专家将这个问题归结成图像分类、对象检测等任务。然而,理解一个场景并不仅仅是识别场景中的物体,物体之间的相互关系也是很重要的信息。为了表征这种语义,最近一些论文引入了场景图(scene graph)的概念,其中的节点表示对象,边则表示对象之间的关系。这个概念其实源于游戏,具体的介绍可以自己查一下 Wiki(wiki 对应链接:?https://en.wikipedia.org/wiki/Scene_graph?,很不幸这个介绍的中文版是空的),本身说的是游戏开发者们在画面编程的时候需要关注的东西,在做 Computer Graph(如 WebGL, OpenGL 等)时,这种关系就是程序员需要编程的东西之一,比如一个很简单的只有两个球的场景,哪个球在另一个球的左边,再或者一个游戏中如果定义一个骑士和一匹马之间有逻辑关系,那么这个骑士就可以被认为是与马一体的,这些东西都是需要在程序中进行编程的。本文的聚焦点就是这种场景图的生成,在本文中(如图 1 所示),这种场景图就也需要包含各个对象之间的关系,比如图 1 左边图中的橘子和苹果都在碗中,右边图中的小女孩是坐在马上的。
< class="pgc-img">>场景图(Scene Graph)示例(图源自论文)
1.2 算法概述
为了生成场景图,过去的方法先是学习了这些边和节点的表达(嵌入),如 [1] 中通过在 Fixed Complete Graph 中间传递信息以生成场景图,[2] 则把各个对象看做一个序列,利用双向 LSTM 获取各个对象之间的信息。然后再用这些嵌入来获取各个节点和边的类型。但是这些方法并不能利用图像中丰富的结构信息,这些信息在输入时就包含在了这些图像输入中。本文作者基于 [3] 中提出的 Graph Network,进而提出了适合表征 Scene Graph 的 Probabilistic Graph Network(PGN)。在 PGN 中,边和节点都被表示成了用于 CNN 的特征向量,边和节点的种类则被表示成概率质量函数(probability mass function,PMF)。
如图 2 所示,本文作者在建立 PGN 的过程中也使用了 Reinforcement Learning,因为整个 PGN 添加节点的过程是序列化的,而对于这种过程,Markov 是一个很好的决策工具。具体来说,作者首先利用 Deep Q-Learning 框架学习最优排序,从而依次在当前的 partial PGN 上增加新节点,这里 RL 的动作就是选择一个新节点,RL 的奖励 reward 则根据输入图像的 ground-truth 场景图进行定义。添加节点后,通过消息(上下文关系信息、对象共现信息和图片解释中的先验知识)传递来更新当前 PGN 的特征向量。然后使用更新的特性对 PMFs 进行微调。
< class="pgc-img">>PGN 建立过程(图源自论文)
1.3 算法细节
如前文所述,Scene Graph 是由 PGN 生成的。因此本节首先介绍了什么是 PGN,然后对其生成方法 Deep Generative Probabilistic Graph Neural Networks (DG-PGNN) 进行详细介绍。
Probabilistic Graph Networks:PGN 是基于一个图(文中为场景图)G=(V, E) 建成的。其中 V 为节点的集合,E 为边的集合。假设边的种类有 K 种,那么就有 K 个 E_k(k ∈ {1, . . . , K}) 矩阵,每个矩阵中包含了某条边属于某个类别的可能性,如 E_k(u, v) 就表示从节点 u 到节点 v 的边可以表示为 k 的可能性。因此,对于某一条边 (u, v),其概率质量函数(probability mass function,PMF)e_{u, v}=[E_1(u, v), . . . , E_K(u, v)]^T。正常来讲,PMF 的和应该是为 1,但是本文为了让边的可能性更多(不属于任何一类,也就是不存在;或是有很多种可能),取消了这一限制。同样的,每个节点也有自己的 PMF 以表征其类别,即 n_v ∈ R^M,其中 v 表示某个节点,M 表示节点种类数。同时,每条边和节点都由向量表示,节点向量在目标检测时获得(文中使用的 faster_rcnn_nas),边的向量则由边连接的两个节点合并得到。
Q-Learning:在整个流程开始之前,要先建立一个 Complete PGN(如图 2 所示),这个 Complete PGN 包含了所有的节点和所有的边,可以为 Q-Learning 过程提供原始的特征向量和 PMF(用于更新),具体的建立方法在论文的附录中,有兴趣可以自己研究一下。总之,假设当前状态(某个 Partial PGN)的状态为 s,那么 Q-Learning 的输入是这样的:
这里的 g 是整个图像的特征向量(Complete PGN 建立过程中 152 层 ResNet 的最后一层输出),置信度矩阵 p=[p(1), ..., p(N)],p(j) 代表第 j 个框的置信度(目标检测过程中),d 表示了当前节点的状态,如 d(v)=1 则表示节点 v 已经被选择过,0 则表示其还没被选择过。o^n 和 o^e 都是目前 PGN 的特征向量(初始化为 0 向量),h 和 n 则分别为边向量和节点向量。这里的\Phi 就记录了选择的过程,因此可以发现一些共现现象。如当现在的图中有了「街道」这个词,那么下一步添加的节点就很可能是跟「车」相关的东西。每一步的动作则是在没有被选择过的节点中选择一个添加到图中,如果图已经建立完毕了,可以选择停止。如上文所述,这里的 reward 根据输入图像的 ground-truth 场景图定义。因此,假设一个动作-状态对为 (φ, ? s, ? a, ? r, ? φ?0 , s? 0 ),则其目标函数为:
其中 r 为当前 reward,A 表示可以选择的动作,\Phi(? s + v)表示加入节点 v 后的\Phi。因此,参数的更新过程为:
< class="pgc-img">>更新:在 Q-Learning 选择了新的节点加入 PGN 后,首先这个节点与所有其他节点的边向量、PMF 以及其自己的节点向量都是从原始的 Complete PGN 中获取,然后通过后续的方法(见原论文附录)对 PMF、边向量、节点向量进行更新。
1.4 实验结果
本文实验基于 Visual Genome (VG) 数据集,使用 Top-K recall(R@K)作为评价标准,与当前的 SOTA 方法进行了比较,同时也对其本身模型进行了 ablation study,记录了在不使用 RL(DG-PGNN^o)、不使用图片注释(DG-PGNN^-)、使用 VGG(完整模型使用的 ResNet)提取 feature(DG-PGNN^+)下的实验结果。实验结果如表 1 所示。由表中可见,论文中完整 DG-PGNN 的结果几乎在各个任务中的表现都是最好的。
< class="pgc-img">>表 1:实验结果对比(表源自原论文)
1.5 小结
本篇论文使用了 PGN 来生成 scene graph,其重点主要有两个,一个是利用了 PGN 生成 scene graph,第二个则是利用 Deep Generative Probabilistic Graph Neural Networks (DG-PGNN) 学习 PGN 的过程。除此之外,这种 DG-PGNN 还可以用在其他需要学习结构的任务中(如知识图谱的建立等)。
2. Reinforcement Learning
论文链接:
?https://pdfs.semanticscholar.org/826d/b2e5f340a90fc9672279f9e921b596aba4b7.pdf?
第一篇文章在 PGN 的学习过程中利用了 RL,但是如果在语言生成(序列生成)任务中使用 RL,就会基于最大似然来确定奖励函数,这时就会出现高差异梯度(high-variance gradients)、 奖励信息不明(uninformative rewards)和训练困难(brittle training)等问题,本文的提出就是为了解决了这一问题。
2.1 任务描述
序列生成是 NLP 研究中极其重要的组成部分,序列生成任务包含了很多应用——机器翻译(Sutskever, Vinyals, and Le, 2014; Cho et al., 2014; Bahdanau, Cho, and Bengio, 2015)、文本摘要(Rush, Chopra, and Weston, 2015; Chopra, Auli, and Rush, 2016)、图像注释(Vinyals et al., 2015; Xu et al., 2015)以及风格迁移(Shen et al., 2017; Prabhumoye et al., 2018)等。
2.2 算法概述
在序列生成模型中,常见的方法之一就是使用 Maximum Likelihood Estimation(MLE)。这里的 MLE 主要是基于自回归的形式,即最大化已知背景知识(对前面文本进行的编码)后当前单词的条件概率,这种做法导致了暴露偏差(exposure bias)问题,即在训练阶段模型对生成序列的暴露不足,从而导致测试时长序列的语义一致性快速降低。因此,很多为了解决这个问题的正则化方法相继被提出来,RL 就是其中一种,而决定 RL 能否成功的最重要一环就是是否有合适的奖励函数,这个奖励函数不管是动态的还是静态的都有其对应的问题。
为了解决这个问题,本文对 RL 和 OT(Optimal Transport,最优运输)学习的不同状态进行分析,从而发现了一种融合了 RL 和 OT 正则化的退火调度学习策略——最优运输 RL (OTRL),让 OT 损失自适应地调节 RL 探索时的策略空间,从而让训练过程更加稳定。
2.3 算法细节
如前文所述,本文的算法结合了 RL 和 OT 的优势,从而提出了 OTRL(Optimal-Transport-Enhanced Reinforcement Learning),因此本节先介绍 RL 如何生成序列,再介绍 OT 如何实现语义匹配,最后介绍其融合结果。
RL 如何进行序列生成
前文提到的自回归式的序列生成模型(如 LSTM,Transformer 等)就是这个任务中的 agent,可以采取的动作则是生成的单词,策略即是模型的参数,在整个序列生成结束后,系统会给 agent 一个奖励 r(),这个奖励函数就是讲生成的序列和目标序列(ground-truth)进行对比(使用 BLEU(Papineni et al. (2002))等方法),此时 RL 算法的损失函数即为:
< class="pgc-img">>这里的 y 是从生成的序列中采样的,y^*则是 ground-truth,\theta 是指一个特定的策略(一组参数)。为了获取上述损失函数(不可微时)的梯度,文中利用下式对其梯度进行估计:
< class="pgc-img">>为了降低梯度的差异,文中还在上式中加入了基线方程 b(),此时梯度变成了:
< class="pgc-img">>OT 如何进行语义匹配
简而言之,OT 就是在最优运输计划下计算了两个分布的移动距离,这里的最优运输计划是可以学习的。本文中,OT 就是要利用嵌入来计算两个生成序列的距离,从而利用 OT 来扩展 RL 的探索空间。具体来说,作者使用 Gumbel-softmax(为了使模型可微)来将模型的输出变成对应的序列嵌入,然后就能得到最终的 OT Loss(一个序列):
< class="pgc-img">>这里的 n 是指生成序列中单词的数量,m 指 ground-truth 序列中单词的数量,C_{ij} 指用来计算生成序列中第 i 个单词和 ground-truth 中第 j 个单词的嵌入相似度的 Cosine Similarity,T_{ij} 是转移矩阵,d_i 的定义如下所示:
< class="pgc-img">>上述 Loss 可以用 IPOT(Xie et al. (2018),)算法求出最优解,算法流程如下图所示,具体细节可以去看一下 Xie 提出这个算法的论文。
< class="pgc-img">>图 3:IPOT 算法流程
Optimal-Transport-Enhanced RL(OTRL)
上文提到的两种方法都有各自的缺点,只使用 RL 的方法虽然可以获取长序列的信息,但是会有很大的梯度差异;而只使用 OT 的方法虽然解决了梯度问题,目前又只是适配与 1-gram,这样很多信息都会流失,而如果简单的将其扩展到 k-gram,那么复杂度会变得极其巨大。因此作者将两者合并,通过在 RL 中加入 OT 来获得稳定、全面的模型。此时模型的损失变成了:
这里的 L_{MLE} 就是最基本的 MLE 的损失函数,三个\lambda 则是超参数。作者首先只使用最严格的损失函数 L_{MLE} 来训练模型,从而获得一个较好的起始点,然后开始加入 L_{OT} 使得模型有更大的搜索空间,最后再加入 L_{RL} 对模型进行 fine tune。
2.4 实验结果
作者在三个任务上测试了其提出的模型——机器翻译(数据集为 IWSLT 2014)、文本摘要(数据集为 English gigawords 和 CNN/Dailymail)和图像注释(数据集为 COCO),具体实验结果如下面几张表所示:
< class="pgc-img">>< class="pgc-img">>由上面这些表格可见,作者提出的方法在这些任务上的表现基本都比其他算法好
2.5 小结
本文主要是结合了两种不同的方法的优点,从而互补了对方的缺点,将一些简单的算法应用到深度学习算法中去,也可以获得一些很好的效果。
3. Cross-Domain Dialogue Generation
< class="pgc-img">>论文链接:?https://arxiv.org/pdf/1912.08442.pdf?
前面两篇论文说的都是使用 RL(或部分利用 RL)进行目标生成的,这篇文章的算法对于只熟悉 GAN 的研究者来说可能就会感觉更亲切一些,因为终于回归到典型的生成模型——VAE 了。
3.1 任务描述
任务导向的对话系统的主要工作就是在多轮对话中提取出用户的需求,并完成其需求。过去的方法将整个任务看做一个整体来解决,使用生成模型(Encoder-Decoder)直接将对话映射到对应的回答上,但实际上,这个任务是由两部分组成的——对话规划(Dialogue planning)和外部实现(surface realization)。对话规划是指找到完成用户的需求的动作(如找到用户喜欢的食谱或是向用户推荐餐厅),而外部实现则是指将这些动作变成对话内容,这两个任务是会互相影响的,在优化动作选择时会影响到生成的对话的质量,所以直接将这两步合并成一步是不可行的。
为了解决这个问题,有些论文提出了为每个回答匹配一个动作表征,这样这个任务就被分成了两步,从而减轻了上述影响。但是目前这些论文主要使用的方式是使用 VAE(Variational AutoEncoder)来获取低维度的隐变量,从而获取这种隐动作表征(Latent action),但是如图 4 所示,(a) 和 (c) 对应的需求是不同的,但是却获得了完全一样的 Latent action。这是因为这种方法只能获取这些语料的相似性,并不能深入理解这些语料的含义,因此本文作者希望获取语义隐动作表征(Semantic latent action),这种表征的主要依据是相同的需求对应的对话走向也应该是相似的,因此也可以获得相似的表征。
< class="pgc-img">>图 4:VAE 与 Semantic Latent Action 对比(图源自原论文)
3.2 算法概述
为了完成上述任务,本文提出了一种三段式方法。首先,为了将潜在意图编码为语义隐动作,作者将一个损失定义为 VAE 重建的对话是否会像输入话语一样引起相似的状态转换。为了更有效地区分话语之间的潜在意图,作者还引入了一种比较两个系统话语之间结果状态转换相似性的正则化。
学习语义潜动作需要对对话状态进行标注。在许多领域,根本不存在这样的注释,因为它们需要大量的人工工作,并且获取它们的成本很高。作者希望从从状态注释丰富的领域域(源域)迁移到到那些没有状态注释的目标域。本文以渐进的方式实现知识转移,从源域和目标域同时存在的行为开始,如酒店域和景点域都有对价格的询问,文中将此类操作称为共享操作,而仅在目标域中存在的操作则称为特定于域的操作(domain-specific actions)。作者观察到,具有共享动作的系统话语虽然属于不同的域,但会导致相似的状态转移,因此本文通过将从上述共享动作中收集到的动作话语对对齐,训练了一个网络,通过仅将系统话语的文本作为输入来预测结果对话状态转换的相似性。然后利用相似性预测作为监督,更好地学习具有领域特异性动作的所有话语的语义潜动作。
因此本文主要提出了三步分别是对应有标注数据、无标注数据但可以迁移、专有领域三种情况下的模型训练方法。
3.3 算法细节
a. VQ-VAE:
本文的模型是基于 VQ-VAE 提出的,隐动作表征就是希望将每句话 x 表征成 z_d(x),其中 d 表示第 d 个对话。这个表征可以是 one-hot、multi-way categorical 的形式,也可以是连续的。本文中采用了 one-hot 的形式。这种表征是通过 VQ-VAE 获得的,其基本结构可见图 5,由一个编码器 p_E、一个解码器 p_G 和一个嵌入查询表 e 组成。首先编码器 p_E 输入了一句话 x,从而编码成 z_e(x),然后通过下式获取 z_q(x)(找到嵌入表中跟 z_e(x) 最相近的嵌入),最后通过解码器将 z_q(x) 重建成原来的输入。而这个 z_q(x) 就是我们需要的隐动作表征。
这个整体是一起训练的,其损失函数为:
b. Stage I - Semantic Latent Action Learning:
这一步主要是训练了一个对话状态跟踪模型以确定两句话是否会导致相同的对话走向。因此首先要获得对话的状态,一个有状态标注的对话 d_i 就可以表示为:
这里的 c_t 是第 t 轮之前的聊天记录,b_t 则记录了第 t 轮对话的状态,N_b 表示 state 对的数量,x_t 表示第 t 轮系统回答。本文训练了一个模型来获取当前的状态:
此时损失函数即为(这里的 h() 为打分函数,这个打分可以由很多种方式或者直接用一个 encoder-decoder 实现):
获取了对话的状态后,作者就利用了两种方式来衡量提取的语义隐动作表征,一种是系统的回答与 VAE 重建的对话作比较(point-wise,对应的损失为 L_{PT}),另一种是系统与系统之间的比较(pair-wise,对应的损失为 L_{PR})。整个第一步的流程如图 5 所示,p_B 就是前面说的状态跟踪模型,p_B^{inv} 则是作者为了获取更完整地信息而训练的反向跟踪模型,对应的损失函数只要反过来即可,对两种衡量方式具体计算细节感兴趣的可以参考原论文。
< class="pgc-img">>图 5:Stage I 框架图(图源自原论文)
因此在第一阶段(Stage I)中的总损失为:
< class="pgc-img">>\alpha 和\beta 都是超参数,L_{a-e} 就是一开始定义的 VAE 的损失。
c. Stage-II: Action Alignment across Domains
这一步主要是为了在那些没有状态标注的领域里实现上述算法,因此本文提出了一种迁移的方法(如图 6 所示),其中的模型 P_B、P_G、P_B^{inv}、P_E 使用的都是第一步训练的模型,一开始只使用需求相同的对话进行训练,对模型进行更新。
< class="pgc-img">>图 6:Stage II 框架图(图源自原论文)
在上一步中,作者定义 pair-wise 衡量方式为:
< class="pgc-img">>在这一步中源域(X^S)和目标域(X^T)的状态相似度衡量方式变成了(这里的 s_{fwd} 和 s_{inv} 用的都是一开始在有标注的领域训练的模型):
而且为了进行源与目标的对齐,这一步的 VAE 的损失也变成了:
最终这一步的总损失为:
d. Stage-III: Domain-specific Actions Learning
第二阶段解决了那些没有标注但是却可以迁移的领域的语义潜动作表征的学习,但是对于那些专有领域的动作,第二阶段的模型还是不能解决其问题,因此第三阶段的模型(如图 7 所示)被提出来了。
< class="pgc-img">>图 7:Stage III 框架图(图源自原论文)
这一步中,由于无法再进行迁移,因此作者没有再使用前面的状态追踪模型,而是从新训练了一个 Similarity Prediction Network(SPN)来判断两句话是否会导致相同的走向,并使用了新的损失函数(原理差别不大,详见原论文)。
3.4 实验结果
本文选取了两种实验来体现其算法的先进性——多领域联合训练与跨领域生成,并与 (1) KVRN (Eric and Manning 2017); (2) Mem2seq (Madotto, Wu, and Fung 2018); (3) Sequicity (Lei et al. 2018); and two baselines that adopt conditioned generation: (4) LIDM (Wen et al. 2017a); (5) LaRL (Zhao, Xie, and Eskenazi 2019) 等基线进行了比较,同时也对自己的三个阶段进行了实验(MALA-(S1/S2/S3)),实验结果如下表所示:
< class="pgc-img">>从表中可以看出,完整模型(MALA-S3)的效果在两个实验中表现都是最好的。
3.5 小结
本文主要贡献是通过将问题分解的更加精细,更加全面的模型(三段式模型)来对问题进行解决,这应该给各位在研究复杂问题领域的研究者们一种启示——对问题进行更深层次的剖析,然后分成小问题一步一步进行解决,说不定可以得到更好的结果。
总结
本文的三篇文章主要介绍了三种不同的生成模型,同时也代表了当下很常见的集中创新方式。同时,这些文章中,有些核心概念(如场景图)是来自于非常规机器学习领域的,这也再一次证明了万物皆可机器学习的理念。毕竟机器学习是想模仿人类的学习能力,而现在各个领域的知识则是人类学习能力的具象化,多了解一门其他知识,也就多了一个创新的可能。
参考文献(除四篇主论文)
[1] Danfei Xu, Yuke Zhu, Christopher B Choy, and Li Fei-Fei. Scene graph generation by iterative message passing. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, volume 2, 2017.
[2] Rowan Zellers, Mark Yatskar, Sam Thomson, and Yejin Choi. Neural motifs: Scene graph parsing with global context. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, pages 5831–5840, 2018.
[3] Peter W Battaglia, Jessica B Hamrick, Victor Bapst, Alvaro Sanchez-Gonzalez, Vinicius Zambaldi, Mateusz Malinowski, Andrea Tacchetti, David Raposo, Adam Santoro, Ryan Faulkner, et al. Relational inductive biases, deep learning, and graph networks. arXiv preprint arXiv:1806.01261, 2018.
分析师介绍:本文作者为王子嘉,目前在帝国理工学院人工智能硕士在读。主要研究方向为 NLP 的推荐等,喜欢前沿技术,热爱稀奇古怪的想法,是立志做一个不走寻常路的研究者的男人!
关于机器之心全球分析师网络 Synced Global Analyst Network
机器之心全球分析师网络是由机器之心发起的全球性人工智能专业知识共享网络。在过去的四年里,已有数百名来自全球各地的 AI 领域专业学生学者、工程专家、业务专家,利用自己的学业工作之余的闲暇时间,通过线上分享、专栏解读、知识库构建、报告发布、评测及项目咨询等形式与全球 AI 社区共享自己的研究思路。
<>51CTO.com原创稿件】RPC(Remote Procedure Call):远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。
RPC 是一种技术思想而非一种规范或协议,常见 RPC 技术和框架有:
- 应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
- 远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
- 通信框架:MINA 和 Netty。
目前流行的开源 RPC 框架还是比较多的,有阿里巴巴的 Dubbo、Facebook 的 Thrift、Google 的 gRPC、Twitter 的 Finagle 等。
下面重点介绍三种:
- gRPC:是 Google 公布的开源软件,基于最新的 HTTP 2.0 协议,并支持常见的众多编程语言。RPC 框架是基于 HTTP 协议实现的,底层使用到了 Netty 框架的支持。
- Thrift:是 Facebook 的开源 RPC 框架,主要是一个跨语言的服务开发框架。
用户只要在其之上进行二次开发就行,应用对于底层的 RPC 通讯等都是透明的。不过这个对于用户来说需要学习特定领域语言这个特性,还是有一定成本的。
- Dubbo:是阿里集团开源的一个极为出名的 RPC 框架,在很多互联网公司和企业应用中广泛使用。协议和序列化框架都可以插拔是极其鲜明的特色。
完整的 RPC 框架
在一个典型 RPC 的使用场景中,包含了服务发现、负载、容错、网络传输、序列化等组件,其中“RPC 协议”就指明了程序如何进行网络传输和序列化。
图 1:完整 RPC 架构图
如下是 Dubbo 的设计架构图,分层清晰,功能复杂:
图 2:Dubbo 架构图
< class="pgc-img">>RPC 核心功能
RPC 的核心功能是指实现一个 RPC 最重要的功能模块,就是上图中的”RPC 协议”部分:
图 3:RPC 核心功能
一个 RPC 的核心功能主要有 5 个部分组成,分别是:客户端、客户端 Stub、网络传输模块、服务端 Stub、服务端等。
图 4:RPC 核心功能图
下面分别介绍核心 RPC 框架的重要组成:
- 客户端(Client):服务调用方。
- 客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
- 服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
- 服务端(Server):服务的真正提供者。
- Network Service:底层传输,可以是 TCP 或 HTTP。
Python 自带 RPC Demo
Server.py:
from SimpleXMLRPCServer import SimpleXMLRPCServer def fun_add(a,b): totle=a + b return totle if __name__=='__main__': s=SimpleXMLRPCServer(('0.0.0.0', 8080)) #开启xmlrpcserver s.register_function(fun_add) #注册函数fun_add print "server is online..." s.serve_forever() #开启循环等待
Client.py:
from xmlrpclib import ServerProxy #导入xmlrpclib的包 s=ServerProxy("http://172.171.5.205:8080") #定义xmlrpc客户端 print s.fun_add(2,3)
开启服务端:
开启客户端:
Wireshark 抓包分析过程
客户端去往服务端:
- 客户端 IP:172.171.4.176
- 服务端 IP:172.171.5.95
通信使用 HTTP 协议,XML 文件传输格式。传输的字段包括:方法名 methodName,两个参数 2,3。
图 5:Request 抓包
服务端返回结果,字段返回值 Value,结果是 5:
图 6:Response 抓包
在这两次网络传输中使用了 HTTP 协议,建立 HTTP 协议之间有 TCP 三次握手,断开 HTTP 协议时有 TCP 四次挥手。
图 7:基于 HTTP 协议的 RPC 连接过程
详细调用过程
Python 自带 RPC 的 Demo 小程序的实现过程,流程和分工角色可以用下图来表示:
图 8:RPC 调用详细流程图
一次 RPC 调用流程如下:
- 服务消费者(Client 客户端)通过本地调用的方式调用服务。
- 客户端存根(Client Stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体。
- 客户端存根(Client Stub)找到远程的服务地址,并且将消息通过网络发送给服务端。
- 服务端存根(Server Stub)收到消息后进行解码(反序列化操作)。
- 服务端存根(Server Stub)根据解码结果调用本地的服务进行相关处理
- 服务端(Server)本地服务业务处理。
- 处理结果返回给服务端存根(Server Stub)。
- 服务端存根(Server Stub)序列化结果。
- 服务端存根(Server Stub)将结果通过网络发送至消费方。
- 客户端存根(Client Stub)接收到消息,并进行解码(反序列化)。
- 服务消费方得到最终结果。
RPC 核心之功能实现
RPC 的核心功能主要由 5 个模块组成,如果想要自己实现一个 RPC,最简单的方式要实现三个技术点,分别是:
- 服务寻址
- 数据流的序列化和反序列化
- 网络传输
服务寻址
服务寻址可以使用 Call ID 映射。在本地调用中,函数体是直接通过函数指针来指定的,但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。
所以在 RPC 中,所有的函数都必须有自己的一个 ID。这个 ID 在所有进程中都是唯一确定的。
客户端在做远程过程调用时,必须附上这个 ID。然后我们还需要在客户端和服务端分别维护一个函数和Call ID的对应表。
当客户端需要进行远程调用时,它就查一下这个表,找出相应的 Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
实现方式:服务注册中心。
要调用服务,首先你需要一个服务注册中心去查询对方服务都有哪些实例。Dubbo 的服务注册中心是可以配置的,官方推荐使用 Zookeeper。
实现案例:RMI(Remote Method Invocation,远程方法调用)也就是 RPC 本身的实现方式。
图 9:RMI 架构图
Registry(服务发现):借助 JNDI 发布并调用了 RMI 服务。实际上,JNDI 就是一个注册表,服务端将服务对象放入到注册表中,客户端从注册表中获取服务对象。
RMI 服务在服务端实现之后需要注册到 RMI Server 上,然后客户端从指定的 RMI 地址上 Lookup 服务,调用该服务对应的方法即可完成远程方法调用。
Registry 是个很重要的功能,当服务端开发完服务之后,要对外暴露,如果没有服务注册,则客户端是无从调用的,即使服务端的服务就在那里。
序列化和反序列化
客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。
但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。
这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。
只有二进制数据才能在网络中传输,序列化和反序列化的定义是:
- 将对象转换成二进制流的过程叫做序列化
- 将二进制流转换成对象的过程叫做反序列化
这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
网络传输
网络传输:远程调用往往用在网络上,客户端和服务端是通过网络连接的。
所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把 Call ID 和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。
只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。
尽管大部分 RPC 框架都使用 TCP 协议,但其实 UDP 也可以,而 gRPC 干脆就用了 HTTP2。
TCP 的连接是最常见的,简要分析基于 TCP 的连接:通常 TCP 连接可以是按需连接(需要调用的时候就先建立连接,调用结束后就立马断掉),也可以是长连接(客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制定期检测建立的连接是否存活有效),多个远程过程调用共享同一个连接。
所以,要实现一个 RPC 框架,只需要把以下三点实现了就基本完成了:
- Call ID 映射:可以直接使用函数字符串,也可以使用整数 ID。映射表一般就是一个哈希表。
- 序列化反序列化:可以自己写,也可以使用 Protobuf 或者 FlatBuffers 之类的。
- 网络传输库:可以自己写 Socket,或者用 Asio,ZeroMQ,Netty 之类。
RPC 核心之网络传输协议
在第三节中说明了要实现一个 RPC,需要选择网络传输的方式。
图 10:网络传输
在 RPC 中可选的网络传输方式有多种,可以选择 TCP 协议、UDP 协议、HTTP 协议。
每一种协议对整体的性能和效率都有不同的影响,如何选择一个正确的网络传输协议呢?首先要搞明白各种传输协议在 RPC 中的工作方式。
基于 TCP 协议的 RPC 调用
由服务的调用方与服务的提供方建立 Socket 连接,并由服务的调用方通过 Socket 将需要调用的接口名称、方法名称和参数序列化后传递给服务的提供方,服务的提供方反序列化后再利用反射调用相关的方法。
最后将结果返回给服务的调用方,整个基于 TCP 协议的 RPC 调用大致如此。
但是在实例应用中则会进行一系列的封装,如 RMI 便是在 TCP 协议上传递可序列化的 Java 对象。
基于 HTTP 协议的 RPC 调用
该方法更像是访问网页一样,只是它的返回结果更加单一简单。
其大致流程为:由服务的调用者向服务的提供者发送请求,这种请求的方式可能是 GET、POST、PUT、DELETE 等中的一种,服务的提供者可能会根据不同的请求方式做出不同的处理,或者某个方法只允许某种请求方式。
而调用的具体方法则是根据 URL 进行方法调用,而方法所需要的参数可能是对服务调用方传输过去的 XML 数据或者 JSON 数据解析后的结果,最后返回 JOSN 或者 XML 的数据结果。
由于目前有很多开源的 Web 服务器,如 Tomcat,所以其实现起来更加容易,就像做 Web 项目一样。
两种方式对比
基于 TCP 的协议实现的 RPC 调用,由于 TCP 协议处于协议栈的下层,能够更加灵活地对协议字段进行定制,减少网络开销,提高性能,实现更大的吞吐量和并发数。
但是需要更多关注底层复杂的细节,实现的代价更高。同时对不同平台,如安卓,iOS 等,需要重新开发出不同的工具包来进行请求发送和相应解析,工作量大,难以快速响应和满足用户需求。
基于 HTTP 协议实现的 RPC 则可以使用 JSON 和 XML 格式的请求或响应数据。
而 JSON 和 XML 作为通用的格式标准(使用 HTTP 协议也需要序列化和反序列化,不过这不是该协议下关心的内容,成熟的 Web 程序已经做好了序列化内容),开源的解析工具已经相当成熟,在其上进行二次开发会非常便捷和简单。
但是由于 HTTP 协议是上层协议,发送包含同等内容的信息,使用 HTTP 协议传输所占用的字节数会比使用 TCP 协议传输所占用的字节数更高。
因此在同等网络下,通过 HTTP 协议传输相同内容,效率会比基于 TCP 协议的数据效率要低,信息传输所占用的时间也会更长,当然压缩数据,能够缩小这一差距。
使用 RabbitMQ 的 RPC 架构
在 OpenStack 中服务与服务之间使用 RESTful API 调用,而在服务内部则使用 RPC 调用各个功能模块。
正是由于使用了 RPC 来解耦服务内部功能模块,使得 OpenStack 的服务拥有扩展性强,耦合性低等优点。
OpenStack 的 RPC 架构中,加入了消息队列 RabbitMQ,这样做的目的是为了保证 RPC 在消息传递过程中的安全性和稳定性。
下面分析 OpenStack 中使用 RabbitMQ 如何实现 RPC 的调用。
RabbitMQ 简介
以下摘录自知乎:
对于初学者,举一个饭店的例子来解释这三个分别是什么吧。不是百分百恰当,但是应该足以解释这三者的区别。
RPC:假设你是一个饭店里的服务员,顾客向你点菜,但是你不会做菜,所以你采集了顾客要点什么之后告诉后厨去做顾客点的菜,这叫 RPC(remote procedure call),因为厨房的厨师相对于服务员而言是另外一个人(在计算机的世界里就是 Remote 的机器上的一个进程)。厨师做好了的菜就是RPC的返回值。
任务队列和消息队列:本质都是队列,所以就只举一个任务队列的例子。假设这个饭店在高峰期顾客很多,而厨师只有很少的几个,所以服务员们不得不把单子按下单顺序放在厨房的桌子上,供厨师们一个一个做,这一堆单子就是任务队列,厨师们每做完一个菜,就从桌子上的订单里再取出一个单子继续做菜。
角色分担如下图:
图 11:RabbitMQ 在 RPC 中角色
使用 RabbitMQ 的好处:
- 同步变异步:可以使用线程池将同步变成异步,但是缺点是要自己实现线程池,并且强耦合。使用消息队列可以轻松将同步请求变成异步请求。
- 低内聚高耦合:解耦,减少强依赖。
- 流量削峰:通过消息队列设置请求最大值,超过阀值的抛弃或者转到错误界面。
- 网络通信性能提高:TCP 的创建和销毁开销大,创建 3 次握手,销毁 4 次分手,高峰时成千上万条的链接会造成资源的巨大浪费,而且操作系统每秒处理 TCP 的数量也是有数量限制的,必定造成性能瓶颈。
RabbitMQ 采用信道通信,不采用 TCP 直接通信。一条线程一条信道,多条线程多条信道,公用一个 TCP 连接。
一条 TCP 连接可以容纳无限条信道(硬盘容量足够的话),不会造成性能瓶颈。
RabbitMQ 的三种类型的交换器
RabbitMQ 使用 Exchange(交换机)和 Queue(队列)来实现消息队列。
在 RabbitMQ 中一共有三种交换机类型,每一种交换机类型都有很鲜明的特征。
基于这三种交换机类型,OpenStack 完成两种 RPC 的调用方式。首先简单介绍三种交换机。
图 12:RabbitMQ 架构图
①广播式交换器类型(Fanout)
该类交换器不分析所接收到消息中的 Routing Key,默认将消息转发到所有与该交换器绑定的队列中去。
图 13:广播式交换机
②直接式交换器类型(Direct)
该类交换器需要精确匹配 Routing Key 与 Binding Key,如消息的 Routing Key=Cloud,那么该条消息只能被转发至 Binding Key=Cloud 的消息队列中去。
图 14:直接式交换机
③主题式交换器(Topic Exchange)
该类交换器通过消息的 Routing Key 与 Binding Key 的模式匹配,将消息转发至所有符合绑定规则的队列中。
Binding Key 支持通配符,其中“*”匹配一个词组,“#”匹配多个词组(包括零个)。
图 15:主题式交换机
注:以上四张图片来自博客园,如有侵权,请联系作者:https://www.cnblogs.com/dwlsxj/p/RabbitMQ.html。
当生产者发送消息 Routing Key=F.C.E 的时候,这时候只满足 Queue1,所以会被路由到 Queue 中。
如果 Routing Key=A.C.E 这时候会被同时路由到 Queue1 和 Queue2 中,如果 Routing Key=A.F.B 时,这里只会发送一条消息到 Queue2 中。
Nova 基于 RabbitMQ 实现两种 RPC 调用:
- RPC.CALL(调用)
- RPC.CAST(通知)
其中 RPC.CALL 基于请求与响应方式,RPC.CAST 只是提供单向请求,两种 RPC 调用方式在 Nova 中均有典型的应用场景。
RPC.CALL
RPC.CALL 是一种双向通信流程,即 RabbitMQ 接收消息生产者生成的系统请求消息,消息消费者经过处理之后将系统相应结果反馈给调用程序。
图 16:RPC.CALL 原理图
一个用户通过 Dashboard 创建一个虚拟机,界面经过消息封装后发送给 NOVA-API。
NOVA-API 作为消息生产者,将该消息以 RPC.CALL 方式通过 Topic 交换器转发至消息队列。
此时,Nova-Compute 作为消息消费者,接收该信息并通过底层虚拟化软件执行相应虚拟机的启动进程。
待用户虚拟机成功启动之后,Nova-Compute 作为消息生产者通过 Direct 交换器和响应的消息队列将虚拟机启动成功响应消息反馈给 Nova-API。
此时 Nova-API 作为消息消费者接收该消息并通知用户虚拟机启动成功。
RPC.CALL 工作原理如下图:
图 17:RPC.CALL 具体实现图
工作流程:
- 客户端创建 Message 时指定 reply_to 队列名、correlation_id 标记调用者。
- 通过队列,服务端收到消息。调用函数处理,然后返回。
- 返回的队列是 reply_to 指定的队列,并携带 correlation_id。
- 返回消息到达客户端,客户端根据 correlation_id 判断是哪一个函数的调用返回。
如果有多个线程同时进行远程方法调用,这时建立在 Client Server 之间的 Socket 连接上会有很多双方发送的消息传递,前后顺序也可能是随机的。
Server 处理完结果后,将结果消息发送给 Client,Client 收到很多消息,怎么知道哪个消息结果是原先哪个线程调用的?
Client 线程每次通过 Socket 调用一次远程接口前,生成一个唯一的 ID,即 Request ID(Request ID必需保证在一个 Socket 连接里面是唯一的),一般常常使用 AtomicLong 从 0 开始累计数字生成唯一 ID。
RPC.CAST
RPC.CAST 的远程调用流程与 RPC.CALL 类似,只是缺少了系统消息响应流程。
一个 Topic 消息生产者发送系统请求消息到 Topic 交换器,Topic 交换器根据消息的 Routing Key 将消息转发至共享消息队列。
与共享消息队列相连的所有 Topic 消费者接收该系统请求消息,并把它传递给响应的服务端进行处理。
其调用流程如图所示:
图 18:RPC.CAST 原理图
连接设计
RabbitMQ 实现的 RPC 对网络的一般设计思路:消费者是长连接,发送者是短连接。但可以自由控制长连接和短连接。
一般消费者是长连接,随时准备接收处理消息;而且涉及到 RabbitMQ Queues、Exchange 的 auto-deleted 等没特殊需求没必要做短连接。发送者可以使用短连接,不会长期占住端口号,节省端口资源。
Nova 中 RPC 代码设计:
简单对比 RPC 和 Restful API
RESTful API 架构
REST 最大的几个特点为:资源、统一接口、URI 和无状态。
①资源
所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,就是一个具体的实在。
②统一接口
RESTful 架构风格规定,数据的元操作,即 CRUD(Create,Read,Update 和 Delete,即数据的增删查改)操作,分别对应于 HTTP 方法:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源,这样就统一了数据操作的接口,仅通过 HTTP 方法,就可以完成对数据的所有增删查改工作。
③URL
可以用一个 URI(统一资源定位符)指向资源,即每个 URI 都对应一个特定的资源。
要获取这个资源,访问它的 URI 就可以,因此 URI 就成了每一个资源的地址或识别符。
④无状态
所谓无状态的,即所有的资源,都可以通过 URI 定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而改变。有状态和无状态的区别,举个简单的例子说明一下。
如查询员工的工资,如果查询工资是需要登录系统,进入查询工资的页面,执行相关操作后,获取工资的多少,则这种情况是有状态的。
因为查询工资的每一步操作都依赖于前一步操作,只要前置操作不成功,后续操作就无法执行。
如果输入一个 URI 即可得到指定员工的工资,则这种情况是无状态的,因为获取工资不依赖于其他资源或状态。
且这种情况下,员工工资是一个资源,由一个 URI 与之对应,可以通过 HTTP 中的 GET 方法得到资源,这是典型的 RESTful 风格。
RPC 和 Restful API 对比
面对对象不同:
- RPC 更侧重于动作。
- REST 的主体是资源。
RESTful 是面向资源的设计架构,但在系统中有很多对象不能抽象成资源,比如登录,修改密码等而 RPC 可以通过动作去操作资源。所以在操作的全面性上 RPC 大于 RESTful。
传输效率:
- RPC 效率更高。RPC,使用自定义的 TCP 协议,可以让请求报文体积更小,或者使用 HTTP2 协议,也可以很好的减少报文的体积,提高传输效率。
复杂度:
- RPC 实现复杂,流程繁琐。
- REST 调用及测试都很方便。
RPC 实现(参见第一节)需要实现编码,序列化,网络传输等。而 RESTful 不要关注这些,RESTful 实现更简单。
灵活性:
- HTTP 相对更规范,更标准,更通用,无论哪种语言都支持 HTTP 协议。
- RPC 可以实现跨语言调用,但整体灵活性不如 RESTful。
总结
RPC 主要用于公司内部的服务调用,性能消耗低,传输效率高,实现复杂。
HTTP 主要用于对外的异构环境,浏览器接口调用,App 接口调用,第三方接口调用等。
RPC 使用场景(大型的网站,内部子系统较多、接口非常多的情况下适合使用 RPC):
- 长链接。不必每次通信都要像 HTTP 一样去 3 次握手,减少了网络开销。
- 注册发布机制。RPC 框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作。
- 安全性,没有暴露资源操作。
- 微服务支持。就是最近流行的服务化架构、服务化治理,RPC 框架是一个强力的支撑。
【51CTO原创稿件,合作站点转载请注明原文作者和出处为51CTO.com】
>