<div id="content_views" class="markdown_views"> <!-- flowchart 箭头图标 勿删 --> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path> </svg> <p>参考文章<a href="https://blog.csdn.net/lihao21/article/details/54909236" rel="nofollow" target="_blank">RPC 基本原理与 Apach Thrift 初体验 <br> </a></p>java
<h2 id="rpc基本原理"><a name="t0"></a>RPC基本原理</h2>python
<p>RPC(Remote Procedure Call),远程过程调用,大部分的RPC框架都遵循以下三个开发步骤:</p>web
<pre class="prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)">1. 定义一个接口说明文件:描述了对象(结构体)、对象成员、接口方法等一系列信息; 2. 经过RPC框架所提供的编译器,将接口说明文件编译成具体的语言文件; 3. 在客户端和服务器端分别引入RPC编译器所生成的文件,便可像调用本地方法同样调用服务端代码; <div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>apache
<p>RPC通讯过程以下图所示 <br> <img src="https://img-blog.csdn.net/20170207141803075?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGloYW8yMQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="RPC通讯过程" title=""> <br> 通讯过程包括如下几个步骤:</p>编程
<pre class="prettyprint"><code class="has-numbering" onclick="mdcp.signin(event)">一、客户过程以正常方式调用客户桩(client stub,一段代码); 二、客户桩生成一个消息,而后调用本地操做系统; 三、客户端操做系统将消息发送给远程操做系统; 四、远程操做系统将消息交给服务器桩(server stub,一段代码); 五、服务器桩将参数提取出来,而后调用服务器过程; 六、服务器执行要求的操做,操做完成后将结果返回给服务器桩; 七、服务器桩将结果打包成一个消息,而后调用本地操做系统; 八、服务器操做系统将含有结果的消息发送回客户端操做系统; 九、客户端操做系统将消息交给客户桩; 十、客户桩将结果从从消息中提取出来,返回给调用它的客户过程; <div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>json
<p>全部这些步骤的效果是,将客户过程对客户桩发出的本地调用转换成对服务器过程的本地调用,而客户端和服务器都不会意识到有中间步骤的存在。</p>服务器
<p>这个时候,你可能会想,既然是调用另外一台机器的服务,使用 RESTful API 也能够实现啊,为何要选择 RPC 呢?咱们能够从两个方面对比:</p>markdown
<ul> <li>资源粒度。RPC 就像本地方法调用,RESTful API 每一次添加接口均可能须要额外地组织开放接口的数据,这至关于在应用视图中再写了一次方法调用,并且它还须要维护开发接口的资源粒度、权限等;</li> <li>流量消耗。RESTful API 在应用层使用 HTTP 协议,哪怕使用轻型、高效、传输效率高的 JSON 也会消耗较大的流量,而 RPC 传输既可使用 TCP 也可使用 UDP,并且协议通常使用二制度编码,大大下降了数据的大小,减小流量消耗。</li> </ul>网络
<p>对接异构第三方服务时,一般使用 HTPP/RESTful 等公有协议,对于内部的服务调用,应用选择性能更高的二进制私有协议。</p>多线程
<h2 id="thrift架构"><a name="t1"></a>Thrift架构</h2>
<p>thrift主要用于各个服务之间的RPC通讯,支持跨语言。thrift是一个典型的CS结构,客户端和服务端可使用不一样的语言开发,thrift经过IDL(Interface Description Language)来关联客户端和服务端。thrift的总体架构图以下图所示 <br> </p><center><img src="https://img-blog.csdn.net/2018081919583516?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3prcF9qYXZh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="thrift架构" title=""></center> <br> 图中Your Code是用户实现的业务逻辑,接下来的<code>FooService.Client</code>和<code>Foo.write()/read()</code>是thrift根据IDL生成的客户端和服务端的代码,对应于RPC中Client stub和Server stub。TProtocol 用来对数据进行序列化与反序列化,具体方法包括二进制,JSON 或者 Apache Thrift 定义的格式。TTransport 提供数据传输功能,使用 Apache Thrift 能够方便地定义一个服务并选择不一样的传输协议。 <br> 以下图所示为thrift的网络栈结构 <br> <center><img src="https://img-blog.csdn.net/20180820225105991?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3prcF9qYXZh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="thrift网络栈结构" title=""></center><p></p>
<p>thirft使用socket进行数据传输,数据以特定的格式发送,接收方进行解析。咱们定义好thrift的IDL文件后,就可使用thrift的编译器来生成双方语言的接口、model,在生成的model以及接口代码中会有解码编码的代码。</p>
<h3 id="ttransport层"><a name="t2"></a>TTransport层</h3>
<p>表明thrift的数据传输方式,thrift定义了以下几种经常使用数据传输方式</p>
<ul> <li><code>TSocket</code>: 阻塞式socket;</li> <li><code>TFramedTransport</code>: 以frame为单位进行传输,非阻塞式服务中使用;</li> <li><code>TFileTransport</code>: 以文件形式进行传输;</li> </ul>
<h3 id="tprotocol层"><a name="t3"></a>TProtocol层</h3>
<p>表明thrift客户端和服务端之间传输数据的协议,通俗来说就是客户端和服务端之间传输数据的格式(例如json等),thrift定义了以下几种常见的格式</p>
<ul> <li><code>TBinaryProtocol</code>: 二进制格式;</li> <li><code>TCompactProtocol</code>: 压缩格式;</li> <li><code>TJSONProtocol</code>: JSON格式;</li> <li><code>TSimpleJSONProtocol</code>: 提供只写的JSON协议;</li> </ul>
<h3 id="thrift支持的server模型"><a name="t4"></a>thrift支持的Server模型</h3>
<p>thrift主要支持如下几种服务模型</p>
<ul> <li><code>TSimpleServer</code>: 简单的单线程服务模型,经常使用于测试;</li> <li><code>TThreadPoolServer</code>: 多线程服务模型,使用标准的阻塞式IO;</li> <li><code>TNonBlockingServer</code>: 多线程服务模型,使用非阻塞式IO(须要使用<code>TFramedTransport</code>数据传输方式);</li> <li><code>THsHaServer</code>: <code>THsHa</code>引入了线程池去处理,其模型读写任务放到线程池去处理,<code>Half-sync/Half-async</code>处理模式,<code>Half-async</code>是在处理IO事件上(accept/read/write io),<code>Half-sync</code>用于handler对rpc的同步处理;</li> </ul>
<h2 id="thrift-idl文件"><a name="t5"></a>thrift IDL文件</h2>
<p>thrift IDL不支持无符号的数据类型,由于不少编程语言中不存在无符号类型,thrift支持一下几种基本的数据类型</p>
<ul> <li><code>byte</code>: 有符号字节</li> <li><code>i16</code>: 16位有符号整数</li> <li><code>i32</code>: 32位有符号整数</li> <li><code>i64</code>: 63位有符号整数</li> <li><code>double</code>: 64位浮点数</li> <li><code>string</code>: 字符串</li> </ul>
<p>此外thrift还支持如下容器类型:</p>
<ul> <li><code>list</code>: 一系列由T类型的数据组成的有序列表,元素能够重复;</li> <li><code>set</code>: 一系列由T类型的数据组成的无序集合,元素不可重复;</li> <li><code>map</code>: 一个字典结构,Key为K类型,Value为V类型,至关于java中的HashMap;</li> </ul>
<p>thrift容器中元素的类型能够是除了<code>service</code>以外的任何类型,包括<code>exception</code></p>
<p>thirft支持struct类型,目的就是讲一些数据聚合在一块儿,方便传输管理,struct定义形式以下:</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)">struct People { <span class="hljs-number">1</span>:string name; <span class="hljs-number">2</span>:i32 age; <span class="hljs-number">3</span>:string gender; }<div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p>thrift支持枚举类型,定义形式以下:</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)"><span class="hljs-keyword">enum</span> Gender { MALE, FEMALE }<div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p>thrift支持自定义异常类型exception,异常定义形式以下:</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)">exception RequestException { <span class="hljs-number">1</span>:i32 code; <span class="hljs-number">2</span>:string reason; }<div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p>thrift定义服务至关于Java中建立接口同样,建立的service通过代码生thrift代码生成工具编译后就会生成客户端和服务端的框架代码,service的定义形式以下:</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)">service HelloWorldService { <span class="hljs-comment">// service中能够定义若干个服务,至关于Java Interface中定义的方法</span> string doAction(<span class="hljs-number">1</span>:string name, <span class="hljs-number">2</span>:i32 age); }<div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p>thrift支持给类型定义别名,以下所示:</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)">typedef i32 <span class="hljs-keyword">int</span> typedef i64 <span class="hljs-keyword">long</span><div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p>thrift也支持常量的定义,使用<code>const</code>关键字:</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)"><span class="hljs-keyword">const</span> i32 MAX_RETRIES_TIME = <span class="hljs-number">10</span>; <span class="hljs-keyword">const</span> string MY_WEBSITE = <span class="hljs-string">"http://facebook.com"</span>;<div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p>thrift支持命名空间,命名空间至关于Java中的package,主要用于组织代码,thrift使用关键字<code>namespace</code>定义命名空间,格式是<code>namespace 语言名 路径</code>,以下示例所示:</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)">namespace java com.test.thrift.demo<div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p>thrift也支持文件包含,至关于CPP中的<code>include</code>,Java中的<code>import</code>,使用关键字<code>include</code>:</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)">include <span class="hljs-string">"global.thrift"</span><div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p><code>#</code>、<code>//</code>、<code>/**/</code>均可以做为thrift文件中的注释。</p>
<p>thrift提供两个关键字<code>required</code>和<code>optional</code>,分别用于表示对应的字段是必填的仍是可选的(推荐尽可能使用<code>optional</code>),以下所示:</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)">struct People { <span class="hljs-number">1</span>:required string name; <span class="hljs-number">2</span>:optional i32 age; }<div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<h2 id="thrift应用示例"><a name="t6"></a>thrift应用示例</h2>
<p>本示例中咱们使用java编写thrift的服务端程序,使用python编写thrift的客户端程序。</p>
<p>首先定义thrift IDL文件</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)"><span class="hljs-comment">// data.thrift</span> namespace java thrift.generated namespace py py.thrift.generated typedef i16 <span class="hljs-keyword">short</span> typedef i32 <span class="hljs-keyword">int</span> typedef i64 <span class="hljs-keyword">long</span> typedef bool <span class="hljs-keyword">boolean</span> typedef string String <span class="hljs-comment">// struct关键字用于定义结构体,至关于面向对象编程语言中的类</span> struct Person { <span class="hljs-comment">// 至关于定义类中的成员,并生成相应的get和set方法,optional表示username这个成员能够没有</span> <span class="hljs-number">1</span>: optional String username, <span class="hljs-number">2</span>: optional <span class="hljs-keyword">int</span> age, <span class="hljs-number">3</span>: optional <span class="hljs-keyword">boolean</span> married } <span class="hljs-comment">// 定义一个异常类型,用于接口中可能抛出的异常</span> exception DataException { <span class="hljs-number">1</span>: optional String message, <span class="hljs-number">2</span>: optional String callStack, <span class="hljs-number">3</span>: optional String date } <span class="hljs-comment">// 定义服务接口</span> service PersonService { Person getPersonByUsername(<span class="hljs-number">1</span>: required String username) <span class="hljs-keyword">throws</span> (<span class="hljs-number">1</span>: DataException data), <span class="hljs-keyword">void</span> savePerson(<span class="hljs-number">1</span>: required Person person) }<div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p>执行<code>thrift --gen java src/thrift/data.thrift</code>生成对应的<code>java</code>代码,并引入到Java工程当中,代码结构以下图所示 <br> </p><center><img src="https://img-blog.csdn.net/20180821001427657?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3prcF9qYXZh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="thrift示例代码结构" title=""></center><p></p>
<p>编写Java服务端代码,<code>data.thrift</code>的service中定义了两个服务,咱们须要定义相应服务的实现类(至关于handler),以下所示:</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)"><span class="hljs-keyword">import</span> thrift.generated.DataException; <span class="hljs-keyword">import</span> thrift.generated.Person; <span class="hljs-keyword">import</span> thrift.generated.PersonService; <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersonServiceImpl</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">PersonService</span>.<span class="hljs-title">Iface</span> {</span> <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> Person <span class="hljs-title">getPersonByUsername</span>(String username) <span class="hljs-keyword">throws</span> DataException { System.out.println(<span class="hljs-string">"Got Client Param: "</span> + username); <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Person().setUsername(username).setAge(<span class="hljs-number">20</span>).setMarried(<span class="hljs-keyword">false</span>); } <span class="hljs-annotation">@Override</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">savePerson</span>(Person person) <span class="hljs-keyword">throws</span> DataException { System.out.println(<span class="hljs-string">"Got Client Param:"</span>); System.out.println(person.username); System.out.println(person.age); System.out.println(person.married); } }<div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p>同时咱们须要借助thrift为咱们提供的类库实现一个服务器来监听rpc请求,代码以下所示:</p>
<pre class="prettyprint" name="code"><code class="language-java hljs has-numbering" onclick="mdcp.signin(event)"><span class="hljs-keyword">import</span> org.apache.thrift.TProcessorFactory; <span class="hljs-keyword">import</span> org.apache.thrift.protocol.TCompactProtocol; <span class="hljs-keyword">import</span> org.apache.thrift.server.THsHaServer; <span class="hljs-keyword">import</span> org.apache.thrift.server.TServer; <span class="hljs-keyword">import</span> org.apache.thrift.transport.TFramedTransport; <span class="hljs-keyword">import</span> org.apache.thrift.transport.TNonblockingServerSocket; <span class="hljs-keyword">import</span> thrift.generated.PersonService; <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ThriftServer</span> {</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(String[] args) <span class="hljs-keyword">throws</span> Exception { <span class="hljs-comment">// 定义服务器使用的socket类型</span> TNonblockingServerSocket tNonblockingServerSocket = <span class="hljs-keyword">new</span> TNonblockingServerSocket(<span class="hljs-number">8899</span>); <span class="hljs-comment">// 建立服务器参数</span> THsHaServer.Args arg = <span class="hljs-keyword">new</span> THsHaServer.Args(tNonblockingServerSocket).minWorkerThreads(<span class="hljs-number">2</span>).maxWorkerThreads(<span class="hljs-number">4</span>); <span class="hljs-comment">// 请求处理器</span> PersonService.Processor<PersonServiceImpl> processor = <span class="hljs-keyword">new</span> PersonService.Processor<>(<span class="hljs-keyword">new</span> PersonServiceImpl()); <span class="hljs-comment">// 配置传输数据的格式</span> arg.protocolFactory(<span class="hljs-keyword">new</span> TCompactProtocol.Factory()); <span class="hljs-comment">// 配置数据传输的方式</span> arg.transportFactory(<span class="hljs-keyword">new</span> TFramedTransport.Factory()); <span class="hljs-comment">// 配置处理器用来处理rpc请求</span> arg.processorFactory(<span class="hljs-keyword">new</span> TProcessorFactory(processor)); <span class="hljs-comment">// 本示例中使用半同步半异步方式的服务器模型</span> TServer server = <span class="hljs-keyword">new</span> THsHaServer(arg); System.out.println(<span class="hljs-string">"Thrift Server Started!"</span>); <span class="hljs-comment">// 启动服务</span> server.serve(); } }<div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p>编写python客户端,执行<code>thrift --gen py src/thrift/data.thrift</code>生成对应的<code>python</code>代码,并引入到python工程当中,代码结构以下图所示 <br> </p><center><img src="https://img-blog.csdn.net/2018082100160781?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3prcF9qYXZh/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" alt="thrift python代码结构" title=""></center> <br> python客户端代码以下所示:<p></p>
<pre class="prettyprint" name="code"><code class="language-python hljs has-numbering" onclick="mdcp.signin(event)"><span class="hljs-comment"># -*- coding:utf-8 -*-</span> __author__ = <span class="hljs-string">'kpzhang'</span> <span class="hljs-keyword">from</span> py.thrift.generated <span class="hljs-keyword">import</span> PersonService <span class="hljs-keyword">from</span> py.thrift.generated <span class="hljs-keyword">import</span> ttypes <span class="hljs-keyword">from</span> thrift <span class="hljs-keyword">import</span> Thrift <span class="hljs-keyword">from</span> thrift.transport <span class="hljs-keyword">import</span> TSocket <span class="hljs-keyword">from</span> thrift.transport <span class="hljs-keyword">import</span> TTransport <span class="hljs-keyword">from</span> thrift.protocol <span class="hljs-keyword">import</span> TCompactProtocol <span class="hljs-keyword">import</span> sys reload(sys) sys.setdefaultencoding(<span class="hljs-string">'utf8'</span>) <span class="hljs-keyword">try</span>: tSocket = TSocket.TSocket(<span class="hljs-string">"localhost"</span>, <span class="hljs-number">8899</span>) tSocket.setTimeout(<span class="hljs-number">900</span>) transport = TTransport.TFramedTransport(tSocket) protocol = TCompactProtocol.TCompactProtocol(transport) client = PersonService.Client(protocol) transport.open() person = client.getPersonByUsername(<span class="hljs-string">"张三"</span>) <span class="hljs-keyword">print</span> person.username <span class="hljs-keyword">print</span> person.age <span class="hljs-keyword">print</span> person.married <span class="hljs-keyword">print</span> <span class="hljs-string">'---------------------'</span> newPerson = ttypes.Person(); newPerson.username = <span class="hljs-string">"李四"</span> newPerson.age = <span class="hljs-number">30</span> newPerson.married = <span class="hljs-keyword">True</span> client.savePerson(newPerson) transport.close() <span class="hljs-keyword">except</span> Thrift.TException, tx: <span class="hljs-keyword">print</span> <span class="hljs-string">'%s'</span> % tx.message<div class="hljs-button signin" data-title="登陆后复制"></div></code></pre>
<p>客户端能够向调用本地的方法同样调用服务端的方法。</p> </div>