一杯清茶

          統(tǒng)計(jì)

          留言簿

          Oracle SQL/PLSQL

          PowerDesigner教程系列

          Struts2.0

          web開發(fā)

          三人行

          從事RCP開發(fā)的同行

          工作流和權(quán)限設(shè)置

          閱讀排行榜

          評論排行榜

          微服務(wù)實(shí)戰(zhàn)(三):深入微服務(wù)架構(gòu)的進(jìn)程間通信

          【編者的話】這是采用微服務(wù)架構(gòu)創(chuàng)建自己應(yīng)用系列第三篇文章。第一篇介紹了微服務(wù)架構(gòu)模式,和單體式模式進(jìn)行了比較,并且討論了使用微服務(wù)架構(gòu)的優(yōu)缺點(diǎn)。第二篇描述了采用微服務(wù)架構(gòu)應(yīng)用客戶端之間如何采用API Gateway方式進(jìn)行通信。在這篇文章中,我們將討論系統(tǒng)服務(wù)之間如何通信。

          簡介

          在單體式應(yīng)用中,各個(gè)模塊之間的調(diào)用是通過編程語言級別的方法或者函數(shù)來實(shí)現(xiàn)的。但是一個(gè)基于微服務(wù)的分布式應(yīng)用是運(yùn)行在多臺機(jī)器上的。一般來說,每個(gè)服務(wù)實(shí)例都是一個(gè)進(jìn)程。因此,如下圖所示,服務(wù)之間的交互必須通過進(jìn)程間通信(IPC)來實(shí)現(xiàn)。

          Richardson-microservices-part3-monolith-vs-microservices-1024x518.png


          后面我們將會詳細(xì)介紹IPC技術(shù),現(xiàn)在我們先來看下設(shè)計(jì)相關(guān)的問題。

          交互模式

          當(dāng)為某一個(gè)服務(wù)選擇IPC時(shí),首先需要考慮服務(wù)之間如何交互。客戶端和服務(wù)器之間有很多的交互模式,我們可以從兩個(gè)維度進(jìn)行歸類。第一個(gè)維度是一對一還是一對多:

          • 一對一:每個(gè)客戶端請求有一個(gè)服務(wù)實(shí)例來響應(yīng)。
          • 一對多:每個(gè)客戶端請求有多個(gè)服務(wù)實(shí)例來響應(yīng)

          第二個(gè)維度是這些交互式同步還是異步:

          • 同步模式:客戶端請求需要服務(wù)端即時(shí)響應(yīng),甚至可能由于等待而阻塞。
          • 異步模式:客戶端請求不會阻塞進(jìn)程,服務(wù)端的響應(yīng)可以是非即時(shí)的。

          下表顯示了不同交互模式:

          74.pic_.jpg


          一對一的交互模式有以下幾種方式:

          • 請求/響應(yīng):一個(gè)客戶端向服務(wù)器端發(fā)起請求,等待響應(yīng)??蛻舳似谕隧憫?yīng)即時(shí)到達(dá)。在一個(gè)基于線程的應(yīng)用中,等待過程可能造成線程阻塞。
          • 通知(也就是常說的單向請求):一個(gè)客戶端請求發(fā)送到服務(wù)端,但是并不期望服務(wù)端響應(yīng)。
          • 請求/異步響應(yīng):客戶端發(fā)送請求到服務(wù)端,服務(wù)端異步響應(yīng)請求。客戶端不會阻塞,而且被設(shè)計(jì)成默認(rèn)響應(yīng)不會立刻到達(dá)。

          一對多的交互模式有以下幾種方式:

          • 發(fā)布/ 訂閱模式:客戶端發(fā)布通知消息,被零個(gè)或者多個(gè)感興趣的服務(wù)消費(fèi)。

          • 發(fā)布/異步響應(yīng)模式:客戶端發(fā)布請求消息,然后等待從感興趣服務(wù)發(fā)回的響應(yīng)。

          每個(gè)服務(wù)都是以上這些模式的組合,對某些服務(wù),一個(gè)IPC機(jī)制就足夠了;而對另外一些服務(wù)則需要多種IPC機(jī)制組合。下圖展示了在一個(gè)打車服務(wù)請求中服務(wù)之間是如何通信的。

          Richardson-microservices-part3-taxi-service-1024x609.png


          上圖中的服務(wù)通信使用了通知、請求/響應(yīng)、發(fā)布/訂閱等方式。例如,乘客通過移動(dòng)端給『行程管理服務(wù)』發(fā)送通知,希望申請一次出租服務(wù)?!盒谐坦芾矸?wù)』發(fā)送請求/響應(yīng)消息給『乘客服務(wù)』以確認(rèn)乘客賬號是有效的。緊接著創(chuàng)建此次行程,并用發(fā)布/訂閱交互模式通知其他服務(wù),包括定位可用司機(jī)的調(diào)度服務(wù)。

          現(xiàn)在我們了解了交互模式,接下來我們一起來看看如何定義API。

          定義API

          API是服務(wù)端和客戶端之間的契約。不管選擇了什么樣的IPC機(jī)制,重要的是使用某種交互式定義語言(IDL)來精確定義一個(gè)服務(wù)的API。甚至有一些關(guān)于使用API first的方法(API-first approach)來定義服務(wù)的很好的理由。在開發(fā)之前,你需要先定義服務(wù)的接口,并與客戶端開發(fā)者詳細(xì)討論確認(rèn)。這樣的討論和設(shè)計(jì)會大幅度提到API的可用度以及滿意度。

          在本文后半部分你將會看到,API定義實(shí)質(zhì)上依賴于選擇哪種IPC。如果使用消息機(jī)制,API則由消息頻道(channel)和消息類型構(gòu)成;如果選擇使用HTTP機(jī)制,API則由URL和請求、響應(yīng)格式構(gòu)成。后面將會詳細(xì)描述IDL。

          API的演化

          服務(wù)端API會不斷變化。在一個(gè)單體式應(yīng)用中經(jīng)常會直接修改API,然后更新給所有的調(diào)用者。而在基于微服務(wù)架構(gòu)應(yīng)用中,這很困難,即使只有一個(gè)服務(wù)使用這個(gè)API,不可能強(qiáng)迫用戶跟服務(wù)端保持同步更新。另外,開發(fā)者可能會嘗試性的部署新版本的服務(wù),這個(gè)時(shí)候,新舊服務(wù)就會同事運(yùn)行。你需要知道如何處理這些問題。

          你如何處理API變化,這依賴于這些變化有多大。某些改變是微小的,并且可以和之前版本兼容。比如,你可能只是為某個(gè)請求和響應(yīng)添加了一個(gè)屬性。設(shè)計(jì)客戶端和服務(wù)端時(shí)候應(yīng)該遵循健壯性原理,這很重要。客戶端使用舊版API應(yīng)該也能和新版本一起工作。服務(wù)端仍然提供默認(rèn)響應(yīng)值,客戶端忽略此版本不需要的響應(yīng)。使用IPC機(jī)制和消息格式對于API演化很有幫助。

          但是有時(shí)候,API需要進(jìn)行大規(guī)模的改動(dòng),并且可能與之前版本不兼容。因?yàn)槟悴豢赡軓?qiáng)制讓所有的客戶端立即升級,所以支持老版本客戶端的服務(wù)還需要再運(yùn)行一段時(shí)間。如果你正在使用基于基于HTTP機(jī)制的IPC,例如REST,一種解決方案是把版本號嵌入到URL中。每個(gè)服務(wù)都可能同時(shí)處理多個(gè)版本的API?;蛘?,你可以部署多個(gè)實(shí)例,每個(gè)實(shí)例負(fù)責(zé)處理一個(gè)版本的請求。

          處理部分失敗

          在上一篇關(guān)于API gateway的文章中,我們了解到分布式系統(tǒng)中部分失敗是普遍存在的問題。因?yàn)榭蛻舳撕头?wù)端是都是獨(dú)立的進(jìn)程,一個(gè)服務(wù)端有可能因?yàn)楣收匣蛘呔S護(hù)而停止服務(wù),或者此服務(wù)因?yàn)檫^載停止或者反應(yīng)很慢。

          考慮這篇文章中描述的部分失敗的場景。假設(shè)推薦服務(wù)無法響應(yīng)請求,那客戶端就會由于等待響應(yīng)而阻塞,這不僅會給客戶帶來很差的體驗(yàn),而且在很多應(yīng)用中還會占用很多資源,比如線程,以至于到最后由于等待響應(yīng)被阻塞的客戶端越來越多,線程資源被耗費(fèi)完了。如下圖所示:

          Richardson-microservices-part3-threads-blocked-1024x383.png


          為了預(yù)防這種問題,設(shè)計(jì)服務(wù)時(shí)候必須要考慮部分失敗的問題。

          Netfilix提供了一個(gè)比較好的解決方案,具體的應(yīng)對措施包括:

          • 網(wǎng)絡(luò)超時(shí):當(dāng)?shù)却憫?yīng)時(shí),不要無限期的阻塞,而是采用超時(shí)策略。使用超時(shí)策略可以確保資源不會無限期的占用。
          • 限制請求的次數(shù):可以為客戶端對某特定服務(wù)的請求設(shè)置一個(gè)訪問上限。如果請求已達(dá)上限,就要立刻終止請求服務(wù)。
          • 斷路器模式(Circuit Breaker Pattern):記錄成功和失敗請求的數(shù)量。如果失效率超過一個(gè)閾值,觸發(fā)斷路器使得后續(xù)的請求立刻失敗。如果大量的請求失敗,就可能是這個(gè)服務(wù)不可用,再發(fā)請求也無意義。在一個(gè)失效期后,客戶端可以再試,如果成功,關(guān)閉此斷路器。
          • 提供回滾:當(dāng)一個(gè)請求失敗后可以進(jìn)行回滾邏輯。例如,返回緩存數(shù)據(jù)或者一個(gè)系統(tǒng)默認(rèn)值。

          Netflix Hystrix是一個(gè)實(shí)現(xiàn)相關(guān)模式的開源庫。如果使用JVM,推薦考慮使用Hystrix。而如果使用非JVM環(huán)境,你可以使用類似功能的庫。

          IPC技術(shù)

          現(xiàn)在有很多不同的IPC技術(shù)。服務(wù)之間的通信可以使用同步的請求/響應(yīng)模式,比如基于HTTP的REST或者Thrift。另外,也可以選擇異步的、基于消息的通信模式,比如AMQP或者STOMP。除以之外,還有其它的消息格式供選擇,比如JSON和XML,它們都是可讀的,基于文本的消息格式。當(dāng)然,也還有二進(jìn)制格式(效率更高)的,比如Avro和Protocol Buffer。接下來我們將會討論異步的IPC模式和同步的IPC模式,首先來看異步的。
          異步的,基于消息通信
          當(dāng)使用基于異步交換消息的進(jìn)程通信方式時(shí),一個(gè)客戶端通過向服務(wù)端發(fā)送消息提交請求。如果服務(wù)端需要回復(fù),則會發(fā)送另外一個(gè)獨(dú)立的消息給客戶端。因?yàn)橥ㄐ攀钱惒降模蛻舳瞬粫驗(yàn)榈却枞?,相反,客戶端理所?dāng)然的認(rèn)為響應(yīng)不會立刻接收到。

          一個(gè)消息由頭部(元數(shù)據(jù)例如發(fā)送方)和消息體構(gòu)成。消息通過channel發(fā)送,任何數(shù)量的生產(chǎn)者都可以發(fā)送消息到channel,同樣的,任何數(shù)量的消費(fèi)者都可以從渠道中接受數(shù)據(jù)。有兩類channel,點(diǎn)對點(diǎn)發(fā)布/訂閱。點(diǎn)對點(diǎn)channel會把消息準(zhǔn)確的發(fā)送到某個(gè)從channel讀取消息的消費(fèi)者,服務(wù)端使用點(diǎn)對點(diǎn)來實(shí)現(xiàn)之前提到的一對一交互模式;而發(fā)布/訂閱則把消息投送到所有從channel讀取數(shù)據(jù)的消費(fèi)者,服務(wù)端使用發(fā)布/訂閱channel來實(shí)現(xiàn)上面提到的一對多交互模式。

          下圖展示了打車軟件如何使用發(fā)布/訂閱:

          Richardson-microservices-part3-pub-sub-channels-1024x639.png


          行程管理服務(wù)在發(fā)布-訂閱channel內(nèi)創(chuàng)建一個(gè)行程消息,并通知調(diào)度服務(wù)有一個(gè)新的行程請求,調(diào)度服務(wù)發(fā)現(xiàn)一個(gè)可用的司機(jī)然后向發(fā)布-訂閱channel寫入司機(jī)建議消息(Driver Proposed message)來通知其他服務(wù)。

          有很多消息系統(tǒng)可以選擇,最好選擇一種支持多編程語言的。一些消息系統(tǒng)支持標(biāo)準(zhǔn)協(xié)議,例如AMQP和STOMP。其他消息系統(tǒng)則使用獨(dú)有的協(xié)議,有大量開源消息系統(tǒng)可選,比如RabbitMQApache Kafka、Apache ActiveMQNSQ。它們都支持某種形式的消息和channel,并且都是可靠的、高性能和可擴(kuò)展的;然而,它們的消息模型完全不同。

          使用消息機(jī)制有很多優(yōu)點(diǎn):

          • 解耦客戶端和服務(wù)端:客戶端只需要將消息發(fā)送到正確的channel??蛻舳送耆恍枰私饩唧w的服務(wù)實(shí)例,更不需要一個(gè)發(fā)現(xiàn)機(jī)制來確定服務(wù)實(shí)例的位置。

          • Message Buffering:在一個(gè)同步請求/響應(yīng)協(xié)議中,例如HTTP,所有的客戶端和服務(wù)端必須在交互期間保持可用。而在消息模式中,消息broker將所有寫入channel的消息按照隊(duì)列方式管理,直到被消費(fèi)者處理。也就是說,在線商店可以接受客戶訂單,即使下單系統(tǒng)很慢或者不可用,只要保持下單消息進(jìn)入隊(duì)列就好了。

          • 彈性客戶端-服務(wù)端交互:消息機(jī)制支持以上說的所有交互模式。

          • 直接進(jìn)程間通信:基于RPC機(jī)制,試圖喚醒遠(yuǎn)程服務(wù)看起來跟喚醒本地服務(wù)一樣。然而,因?yàn)槲锢矶珊筒糠质】赡苄裕麄儗?shí)際上非常不同。消息使得這些不同非常明確,開發(fā)者不會出現(xiàn)問題。

          然而,消息機(jī)制也有自己的缺點(diǎn):

          • 額外的操作復(fù)雜性:消息系統(tǒng)需要單獨(dú)安裝、配置和部署。消息broker(代理)必須高可用,否則系統(tǒng)可靠性將會受到影響。

          • 實(shí)現(xiàn)基于請求/響應(yīng)交互模式的復(fù)雜性:請求/響應(yīng)交互模式需要完成額外的工作。每個(gè)請求消息必須包含一個(gè)回復(fù)渠道ID和相關(guān)ID。服務(wù)端發(fā)送一個(gè)包含相關(guān)ID的響應(yīng)消息到channel中,使用相關(guān)ID來將響應(yīng)對應(yīng)到發(fā)出請求的客戶端。也許這個(gè)時(shí)候,使用一個(gè)直接支持請求/響應(yīng)的IPC機(jī)制會更容易些。

          現(xiàn)在我們已經(jīng)了解了基于消息的IPC,接下來我們來看看基于請求/響應(yīng)模式的IPC。

          同步的,基于請求/響應(yīng)的IPC
          當(dāng)使用一個(gè)同步的,基于請求/響應(yīng)的IPC機(jī)制,客戶端向服務(wù)端發(fā)送一個(gè)請求,服務(wù)端處理請求,返回響應(yīng)。一些客戶端會由于等待服務(wù)端響應(yīng)而被阻塞,而另外一些客戶端也可能使用異步的、基于事件驅(qū)動(dòng)的客戶端代碼(Future或者Rx Observable的封裝)。然而,不像使用消息機(jī)制,客戶端需要響應(yīng)及時(shí)返回。這個(gè)模式中有很多可選的協(xié)議,但最常見的兩個(gè)協(xié)議是REST和Thrift。首先我們來看下REST。

          REST

          現(xiàn)在很流行使用RESTful風(fēng)格的API。REST是基于HTTP協(xié)議的。另外,一個(gè)需要理解的比較重要的概念是,REST是一個(gè)資源,一般代表一個(gè)業(yè)務(wù)對象,比如一個(gè)客戶或者一個(gè)產(chǎn)品,或者一組商業(yè)對象。REST使用HTTP語法協(xié)議來修改資源,一般通過URL來實(shí)現(xiàn)。舉個(gè)例子,GET請求返回一個(gè)資源的簡單信息,響應(yīng)格式通常是XML或者JSON對象格式。POST請求會創(chuàng)建一個(gè)新資源,PUT請求更新一個(gè)資源。這里引用下REST之父Roy Fielding說的:

          當(dāng)需要一個(gè)整體的、重視模塊交互可擴(kuò)展性、接口概括性、組件部署獨(dú)立性和減小延遲、提供安全性和封裝性的系統(tǒng)時(shí),REST可以提供這樣一組滿足需求的架構(gòu)。
          下圖展示了打車軟件是如何使用REST的。

          Richardson-microservices-part3-rest-1024x397.png


          乘客通過移動(dòng)端向行程管理服務(wù)的/trips資源提交了一個(gè)POST請求。行程管理服務(wù)收到請求之后,會發(fā)送一個(gè)GET請求到乘客管理服務(wù)以獲取乘客信息。當(dāng)確認(rèn)乘客信息之后,緊接著會創(chuàng)建一個(gè)行程,并向移動(dòng)端返回201(譯者注:狀態(tài)碼)響應(yīng)。

          很多開發(fā)者都表示他們基于HTTP的API是RESTful的。但是,如同F(xiàn)ielding在他的博客中所說,這些API可能并不都是RESTful的。Leonard Richardson為REST定義了一個(gè)成熟度模型,具體包含以下4個(gè)層次(摘自IBM):
          • 第一個(gè)層次(Level 0)的 Web 服務(wù)只是使用 HTTP 作為傳輸方式,實(shí)際上只是遠(yuǎn)程方法調(diào)用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬于此類。
          • 第二個(gè)層次(Level 1)的 Web 服務(wù)引入了資源的概念。每個(gè)資源有對應(yīng)的標(biāo)識符和表達(dá)。
          • 第三個(gè)層次(Level 2)的 Web 服務(wù)使用不同的 HTTP 方法來進(jìn)行不同的操作,并且使用 HTTP 狀態(tài)碼來表示不同的結(jié)果。如 HTTP GET 方法來獲取資源,HTTP DELETE 方法來刪除資源。
          • 第四個(gè)層次(Level 3)的 Web 服務(wù)使用 HATEOAS。在資源的表達(dá)中包含了鏈接信息??蛻舳丝梢愿鶕?jù)鏈接來發(fā)現(xiàn)可以執(zhí)行的動(dòng)作。

          使用基于HTTP的協(xié)議有如下好處:

          • HTTP非常簡單并且大家都很熟悉。
          • 可以使用瀏覽器擴(kuò)展(比如Postman)或者curl之類的命令行來測試API。
          • 內(nèi)置支持請求/響應(yīng)模式的通信。
          • HTTP對防火墻友好的。
          • 不需要中間代理,簡化了系統(tǒng)架構(gòu)。

          不足之處包括:

          • 只支持請求/響應(yīng)模式交互??梢允褂肏TTP通知,但是服務(wù)端必須一直發(fā)送HTTP響應(yīng)才行。
          • 因?yàn)榭蛻舳撕头?wù)端直接通信(沒有代理或者buffer機(jī)制),在交互期間必須都在線。
          • 客戶端必須知道每個(gè)服務(wù)實(shí)例的URL。如之前那篇關(guān)于API Gateway的文章所述,這也是個(gè)煩人的問題。客戶端必須使用服務(wù)實(shí)例發(fā)現(xiàn)機(jī)制。

          開發(fā)者社區(qū)最近重新發(fā)現(xiàn)了RESTful API接口定義語言的價(jià)值。于是就有了一些RESTful風(fēng)格的服務(wù)框架,包括RAMLSwagger。一些IDL,例如Swagger允許定義請求和響應(yīng)消息的格式。其它的,例如RAML,需要使用另外的標(biāo)識,例如JSON Schema。對于描述API,IDL一般都有工具來定義客戶端和服務(wù)端骨架接口。

          Thrift

          Apache Thrift是一個(gè)很有趣的REST的替代品。它是Facebook實(shí)現(xiàn)的一種高效的、支持多種編程語言的遠(yuǎn)程服務(wù)調(diào)用的框架。Thrift提供了一個(gè)C風(fēng)格的IDL定義API。使用Thrift編譯器可以生成客戶端和服務(wù)器端代碼框架。編譯器可以生成多種語言的代碼,包括C++、Java、Python、PHP、Ruby, Erlang和Node.js。

          Thrift接口包括一個(gè)或者多個(gè)服務(wù)。服務(wù)定義類似于一個(gè)JAVA接口,是一組方法。Thrift方法可以返回響應(yīng),也可以被定義為單向的。返回值的方法其實(shí)就是請求/響應(yīng)類型交互模式的實(shí)現(xiàn)??蛻舳说却憫?yīng),并可能拋出異常。單向方法對應(yīng)于通知類型的交互模式,服務(wù)端并不返回響應(yīng)。

          Thrift支持多種消息格式:JSON、二進(jìn)制和壓縮二進(jìn)制。二進(jìn)制比JSON更高效,因?yàn)槎M(jìn)制解碼更快。同樣原因,壓縮二進(jìn)制格式可以提供更高級別的壓縮效率。JSON,是易讀的。Thrift也可以在裸TCP和HTTP中間選擇,裸TCP看起來比HTTP更加有效。然而,HTTP對防火墻,瀏覽器和人來說更加友好。
          消息格式
          了解完HTTP和Thrift后,我們來看下消息格式方面的問題。如果使用消息系統(tǒng)或者REST,就可以選擇消息格式。其它的IPC機(jī)制,例如Thrift可能只支持部分消息格式,也許只有一種。無論哪種方式,我們必須使用一個(gè)跨語言的消息格式,這非常重要。因?yàn)橹覆欢奶炷銜褂闷渌Z言。

          有兩類消息格式:文本和二進(jìn)制。文本格式的例子包括JSON和XML。這種格式的優(yōu)點(diǎn)在于不僅可讀,而且是自描述的。在JSON中,一個(gè)對象就是一組鍵值對。類似的,在XML中,屬性是由名字和值構(gòu)成。消費(fèi)者可以從中選擇感興趣的元素而忽略其它部分。同時(shí),小幅度的格式修改可以很容器向后兼容。

          XML文檔結(jié)構(gòu)是由XML schema定義的。隨著時(shí)間發(fā)展,開發(fā)者社區(qū)意識到JSON也需要一個(gè)類似的機(jī)制。一個(gè)選擇是使用JSON Schema,要么是獨(dú)立的,要么是例如Swagger的IDL。

          基于文本的消息格式最大的缺點(diǎn)是消息會變得冗長,特別是XML。因?yàn)橄⑹亲悦枋龅?,所以每個(gè)消息都包含屬性和值。另外一個(gè)缺點(diǎn)是解析文本的負(fù)擔(dān)過大。所以,你可能需要考慮使用二進(jìn)制格式。

          二進(jìn)制的格式也有很多。如果使用的是Thrift RPC,那可以使用二進(jìn)制Thrift。如果選擇消息格式,常用的還包括Protocol BuffersApache Avro。它們都提供典型的IDL來定義消息架構(gòu)。一個(gè)不同點(diǎn)在于Protocol Buffers使用的是加標(biāo)記(tag)的字段,而Avro消費(fèi)者需要知道模式(schema)來解析消息。因此,使用前者,API更容易演進(jìn)。這篇博客很好的比較了Thrift、Protocol Buffers、Avro三者的區(qū)別。

          總結(jié)

          微服務(wù)必須使用進(jìn)程間通信機(jī)制來交互。當(dāng)設(shè)計(jì)服務(wù)的通信模式時(shí),你需要考慮幾個(gè)問題:服務(wù)如何交互,每個(gè)服務(wù)如何標(biāo)識API,如何升級API,以及如何處理部分失敗。微服務(wù)架構(gòu)有兩類IPC機(jī)制可選,異步消息機(jī)制和同步請求/響應(yīng)機(jī)制。在下一篇文章中,我們將會討論微服務(wù)架構(gòu)中的服務(wù)發(fā)現(xiàn)問題。

          原文鏈接:Building Microservices: Inter-Process Communication in a Microservices Architecture(翻譯:楊峰 校對:李穎杰)

          posted on 2016-03-17 18:05 一杯清茶 閱讀(137) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 名山县| 基隆市| 周宁县| 辽阳县| 盐边县| 吴川市| 巴楚县| 恩平市| 普洱| 衡水市| 赤峰市| 抚顺县| 光泽县| 灵山县| 普安县| 汉中市| 京山县| 济宁市| 临城县| 财经| 西昌市| 咸丰县| 东阳市| 新乐市| 昌黎县| 晴隆县| 磐安县| 宜宾县| 乐平市| 夏河县| 九江县| 奎屯市| 海伦市| 郑州市| 陕西省| 南陵县| 芦山县| 凭祥市| 贵阳市| 子长县| 六枝特区|