[thrift] thrift基本原理及使用

<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&lt;PersonServiceImpl&gt; processor = <span class="hljs-keyword">new</span> PersonService.Processor&lt;&gt;(<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>

相關文章
相關標籤/搜索