Dubbo高性能網(wǎng)關--Flurry介紹

1.背景

什么是API網(wǎng)關,它的作用是什么盆偿,產(chǎn)生的背景是啥柒爸?

從架構的角度來看,API網(wǎng)關暴露http接口服務,其本身不涉及業(yè)務邏輯,只負責包括請求路由事扭、負載均衡捎稚、權限驗證、流量控制句旱、緩存等等功能阳藻。其定位類似于Nginx請求轉(zhuǎn)發(fā)、但功能要多于Nginx,背后連接了成百上千個后臺服務谈撒,這些服務協(xié)議可能是rest的,也可能是rpc協(xié)議等等匾南。

網(wǎng)關的定位決定了它生來就需要高性能啃匿、高效率的。網(wǎng)關對接著成百上千的服務接口,承受者高并發(fā)的業(yè)務需求蛆楞,因此我們對其性能要求嚴苛溯乒,其基本功能如下:

  • 協(xié)議轉(zhuǎn)換
    一般從前端請求來的都是HTTP接口,而在分布式構建的背景下,我們后臺服務基本上都是以RPC協(xié)議而開發(fā)的服務豹爹。這樣就需要網(wǎng)關作為中間層對請求和響應作轉(zhuǎn)換裆悄。
    將HTTP協(xié)議轉(zhuǎn)換為RPC,然后返回時將RPC協(xié)議轉(zhuǎn)換為HTTP協(xié)議

  • 請求路由
    網(wǎng)關背后可能連接著成本上千個服務,其需要根據(jù)前端請求url來將請求路由到后端節(jié)點中去臂聋,這其中需要做到負載均衡

  • 統(tǒng)一鑒權
    對于鑒權操作不涉及到業(yè)務邏輯光稼,那么可以在網(wǎng)關層進行處理,不用下層到業(yè)務邏輯孩等。

  • 統(tǒng)一監(jiān)控
    由于網(wǎng)關是外部服務的入口艾君,所以我們可以在這里監(jiān)控我們想要的數(shù)據(jù),比如入?yún)⒊鰠⒁薹剑溌窌r間冰垄。

  • 流量控制,熔斷降級
    對于流量控制权她,熔斷降級非業(yè)務邏輯可以統(tǒng)一放到網(wǎng)關層虹茶。
    有很多業(yè)務都會自己去實現(xiàn)一層網(wǎng)關層,用來接入自己的服務隅要,但是對于整個公司來說這還不夠蝴罪。

2.Flurry Dubbo API網(wǎng)關

Flurry是云集自研的一款輕量級、異步流式化拾徙、針對Dubbo的高性能API網(wǎng)關洲炊。與業(yè)界大多數(shù)網(wǎng)關不同的是,flurry自己實現(xiàn)了 http與dubbo協(xié)議互轉(zhuǎn)的流式化的dubbo-json協(xié)議,可高性能暂衡、低內(nèi)存要求的對http和dubbo協(xié)議進行轉(zhuǎn)換询微。除此之外,其基于 netty作為服務容器狂巢,提供服務元數(shù)據(jù)模型等等都是非常具有特點的撑毛。下面我們將詳細介紹 flurry的特性:

2.1 基于Netty容器

傳統(tǒng)網(wǎng)關大多采用tomcat作為容器,其請求于響應沒有做到異步,tomcat會有一個核心線程池來處理請求和響應,如果RT比較高的話,將會對性能有一定的影響。

Flurry 網(wǎng)關請求響應基于Netty線程模型,后者是實現(xiàn)了Reactive,反應式模式規(guī)范的,其設計就是來榨干CPU的唧领,可以大幅提升單機請求響應的處理能力藻雌。
最終,Flurry通過使用Netty線程模型和NIO通訊協(xié)議實現(xiàn)了HTTP請求和響應的異步化。

每一次http請求最終都會由Netty的一個Client Handler來處理,其最終以異步模式請求后臺服務斩个,并返回一個CompletableFuture,當有結果返回時才會將結果返回給前端胯杭。
見下面一段例子:

  • ServerProcessHandler
public class ServerProcessHandler extends SimpleChannelInboundHandler<RequestContext> {
    public void handlerPostAsync(RequestContext context, ChannelHandlerContext ctx) throws RpcException {
            CompletableFuture<String> jsonResponse = asyncSender.sendAsync(context, ctx);

            jsonResponse.whenComplete((result, t) -> {
                long et = System.currentTimeMillis();
                if (t != null) {
                    String errorMessage = HttpHandlerUtil.wrapCode(GateWayErrorCode.RemotingError);
                    doResponse(ctx, errorMessage, context.request());
                } else {
                    doResponse(ctx, HttpHandlerUtil.wrapSuccess(context.requestUrl(), result), context.request());
                }
            });
    }

    //省略部分代碼
    public static void sendHttpResponse(ChannelHandlerContext ctx, String content, FullHttpRequest request) {
        //將結果寫回Client,返回前端
        ctx.writeAndFlush(response);
    }
}

2.2 服務元數(shù)據(jù)模型

所謂的服務元數(shù)據(jù)信息,是指服務接口的方法列表受啥、每個方法的輸入輸出參數(shù)信息做个、每個參數(shù)的字段信息以及上述(方法、參數(shù)以及字段)的注釋,其非常類似于Java Class的反射信息滚局。

元數(shù)據(jù)的作用

有了服務元數(shù)據(jù),我們就可以不必需要服務的API包,并能夠清晰的知道整個服務API的定義居暖。
這在Dubbo服務Mock調(diào)用、服務測試藤肢、文檔站點太闺、流式調(diào)用等等場景下都可以發(fā)揮搶到的作用。

元數(shù)據(jù)與注冊中心數(shù)據(jù)比較

\ 服務元數(shù)據(jù) 注冊中心數(shù)據(jù)
變化頻率 基本不變,隨著服務迭代更改而更改 隨著服務節(jié)點上下線而隨時進行變化
職責 描述服務嘁圈,定義服務的基本屬性 存儲地址列表
使用場景 無API依賴包模式調(diào)用(直接json),文檔站點省骂,Mock測試等 服務治理和調(diào)用
交互模型 服務編譯期生成元信息,client通過接口調(diào)用模式獲取 發(fā)布訂閱模型,線上進行交互

元數(shù)據(jù)中心的價值

小孩子才分對錯丑孩,成年人只看利弊冀宴。額外引入一個元數(shù)據(jù)生成機制,必然帶來運維成本温学、理解成本略贮、遷移成本等問題,那么它具備怎樣的價值仗岖,來說服大家選擇它呢逃延?上面我們介紹元數(shù)據(jù)中心時已經(jīng)提到了服務測試、服務 MOCK 等場景轧拄,這一節(jié)我們重點探討一下元數(shù)據(jù)中心的價值和使用場景揽祥。

那么,Dubbo服務元數(shù)據(jù)能夠利用到哪些場景呢檩电?下面我們來詳細描述拄丰。

2.3 流式協(xié)議轉(zhuǎn)換

flurry網(wǎng)關對外提供的是HTTP Rest風格的接口府树,并輔以JSON數(shù)據(jù)的形式進行傳輸。因此flurry需要做到將外部的HTTP協(xié)議請求轉(zhuǎn)換為dubbo RPC協(xié)議,然后再將dubbo協(xié)議的返回結果轉(zhuǎn)換為HTTP協(xié)議返回給前端料按。

Dubbo原生泛化實現(xiàn)

泛化模式就是針對Dubbo Consumer端沒有服務接口API包的情況下,使用Map的形式將POJO的每一個屬性映射為Key,Value的模式來請求Dubbo服務端奄侠,在Dubbo服務端處理完成請求之后,再將結果POJO轉(zhuǎn)為Map的形式返回給消費端载矿。

使用泛化模式來實現(xiàn)對Http <-> Dubbo的轉(zhuǎn)換流程大致如下圖所示:
泛化調(diào)用

Http請求垄潮,數(shù)據(jù)通過JSON傳輸,其格式嚴格按照接口POJO屬性。返回結果再序列化為Json返回前端∶瓶現(xiàn)在大多數(shù)開源的網(wǎng)關,在dubbo協(xié)議適配上都是采用的泛化模式來做到協(xié)議轉(zhuǎn)換的,這其中就包括 Soul 等弯洗。

泛化模式的協(xié)議轉(zhuǎn)換數(shù)據(jù)流動流程:

JsonString -> JSONObject(Map) -> Binary

將JSON 字符串轉(zhuǎn)換為 JSON 對象模型(JSONObject),此處通過第三方JSON映射框架(如Google的Gson, 阿里的FastJSON等)來做,然后將Map通過Hessian2 協(xié)議序列化為Binaray。

泛化缺點
  • 泛化過程數(shù)據(jù)流會經(jīng)過了三次轉(zhuǎn)換, 會產(chǎn)生大量的臨時對象, 有很大的內(nèi)存要求逢勾。使用反射方式對于旨在榨干服務器性能以獲取高吞吐量的系統(tǒng)來說, 難以達到性能最佳牡整。

  • 同時服務端也會對泛化請求多一重 Map <-> POJO 的來回轉(zhuǎn)換的過程。整體上溺拱,與普通的Dubbo調(diào)用相比有10-20%的損耗果正。

自定義Dubbo-Json協(xié)議

我們的需求是要打造一款高性能、異步盟迟、流式的Dubbo API網(wǎng)關,當然要對不能容忍上述泛化帶來的序列化的痛點,針對http與dubbo協(xié)議轉(zhuǎn)換潦闲,我們的要求如下:

自定義的Dubbo-Json協(xié)議參考了dapeng-soa 的流式解析協(xié)議的思想,詳情請參考:dapeng-json

流式解析數(shù)據(jù)轉(zhuǎn)換圖.png

針對上述泛化模式轉(zhuǎn)換Dubbo協(xié)議的缺點,我們在flurry-core 中的 Dubbo-Json 序列化協(xié)議做到了這點攒菠,下面我們來講解它是如何高效率的完成JsonString到 dubbo hessian2 序列化buffer的轉(zhuǎn)換的。

  • 高性能
    此協(xié)議用來在網(wǎng)關中作為http和rpc協(xié)議的轉(zhuǎn)換,要求其能實現(xiàn)高效的序列化以及反序列化, 以支持網(wǎng)關海量請求的處理.

  • 低內(nèi)存使用

雖然大部分情況下的JSON請求歉闰、返回都是數(shù)據(jù)量較小的場景, 但作為平臺框架, 也需要應對更大的JSON請求和返回, 比如1M辖众、甚至10M. 在這些場景下, 如果需要占用大量的內(nèi)存, 那么勢必導致巨大的內(nèi)存需求, 同時引發(fā)頻繁的GC操作, 也會聯(lián)動影響到整個網(wǎng)關的性能.

Dubbo-Json參考了XML SAX API的設計思想, 創(chuàng)造性的引入了JSON Stream API, 采用流式的處理模式, 實現(xiàn)JSON 對 hessian2 的雙向轉(zhuǎn)換, 無論數(shù)據(jù)包有多大, 都可以在一定固定的內(nèi)存規(guī)模內(nèi)完成.

  • 容錯性好
    前面我們引入了服務元數(shù)據(jù)的概念,因此在此基礎上,通過元數(shù)據(jù)提供的接口文檔站點,自動生成請求數(shù)據(jù)模板,每一個屬性的類型精確定義和敬,譬如必須使用正確的數(shù)據(jù)類型, 有的字段是必填的等凹炸。
    那么在Json -> Hessian2Buffer的轉(zhuǎn)換過程中,如果出現(xiàn)與元數(shù)據(jù)定義不符合的情況昼弟,就回直接報錯啤它,定位方便。

流式協(xié)議解析過程

流式協(xié)議舱痘,顧名思義就是邊讀取邊解析,數(shù)據(jù)像水流一樣在管道中流動,邊流動邊解析变骡,最后,數(shù)據(jù)解析完成時芭逝,轉(zhuǎn)換成的hessian協(xié)議也已全部寫入到了buffer中塌碌。
這里處理的核心思想就是實現(xiàn)自己的Json to hessian2 buffer 的語法和此法解析器,并配合前文提及的元數(shù)據(jù)功能,對每一個讀取到的json片段通過元數(shù)據(jù)獲取到其類型,并使用 hessian2協(xié)議以具體的方式寫入到buffer中旬盯。

JSON結構簡述

首先我們來看看JSON的結構. 一個典型的JSON結構體如下

{
    "request": {
        "orderNo": "1023101",
        "productCount": 13,
        "totalAmount: 16.54
    }
}

其對應Java POJO 自然就是上述三個屬性,這里我們略過台妆。下面是POJO生成的元數(shù)據(jù)信息

 <struct namespace="org.apache.dubbo.order" name="OrderRequest">
    <fields>
        <field tag="0" name="orderNo" optional="false" privacy="false">
            <dataType>
                <kind>STRING</kind>
            </dataType>
        </field>
        <field tag="1" name="productCount" optional="false" privacy="false">
            <dataType>
                <kind>INTEGER</kind>
            </dataType>
        </field>
        <field tag="2" name="totalAmount" optional="false" privacy="false">
            <dataType>
                <kind>DOUBLE</kind>
            </dataType>
        </field>
    </fields>
</struct>

Json解析器

相比XML而言翎猛,JSON數(shù)據(jù)類型比較簡單, 由Object/Array/Value/String/Boolean/Number等元素組成, 每種元素都由特定的字符開和結束. 例如Object以'{'以及'}'這兩個字符標志開始以及結束, 而Array是'['以及']'. 簡單的結構使得JSON比較容易組裝以及解析。

JSON

如圖,我們可以清晰的了解JSON的結構,那么對上述JSON進行解析時,當每一次解析到一個基本類型時,先解析到key接剩,然后根據(jù)key到元數(shù)據(jù)信息中獲取到其value類型切厘,然后直接根據(jù)對應類型的hessian2序列化器將其序列化到byte buffer中。

當解析到引用類型,即 Struct類型時,我們將其壓入棧頂,就和java方法調(diào)用壓棧操作類似搂漠。
通過上面的步驟一步一步迂卢,每解析一步Json,就將其寫入到byte buffer中桐汤,最終完成整個流式的解析過程而克。

拿上面json為例:

{
    "request": {
        "orderNo": "1023101",
        "productCount": 13,
        "totalAmount: 16.54
    }
}
  • 解析器每一步會解析一個單元,首先解析到request,然后根據(jù)請求附帶的接口怔毛、版本等信息找到當前請求服務的元數(shù)據(jù)信息(元數(shù)據(jù)信息緩存在網(wǎng)關之中员萍,定期刷新)。

  • 開始解析屬性,解析到 orderNo key時拣度,通過元數(shù)據(jù)中得知其數(shù)據(jù)類型為String碎绎,然后再解析完value之后,通過調(diào)用hessian2的writeString API將其寫入到hessian2序列化的byte buffer 緩存中。

  • 依次解析后面的屬性抗果,當全部屬性解析完成之后,解析到最后一個 } 時,此時證明數(shù)據(jù)全部解析完畢,于是hessian buffer 通過flush 將數(shù)據(jù)全部寫入到bytebuffer中筋帖,請求直接發(fā)送出去。

  • Dubbo服務端再接收到這個序列化的buffer之后冤馏,會像其他普通dubbo consumer調(diào)用服務的模式一樣的去解析,然后反序列化為請求實體,進行業(yè)務邏輯處理日麸。

  • flurry網(wǎng)關再接受到返回的數(shù)據(jù)時,在沒有反序列化之前逮光,其是一個hessian2的二級制字節(jié)流,我們?nèi)匀煌ㄟ^dubbo-json的解析模式,直接將反序列化出來的屬性寫為Json String

總結:

上述整個請求和響應,網(wǎng)關處理如下:

  • request: Json -> hessian2二進制字節(jié)流
  • response:hessian2二進制字節(jié)流 -> json

請求和響應中沒有像泛化模式中的中間對象轉(zhuǎn)換代箭,直接一步到位,沒有多余的臨時對象占用內(nèi)存,沒有多余的數(shù)據(jù)轉(zhuǎn)換,整個過程像在管道中流式的進行涕刚。

2.4 flurry網(wǎng)關與tomcat接入層比較

傳統(tǒng)的dubbo服務接入層是采用tomcat作為容器來實現(xiàn)嗡综,每一個業(yè)務模塊對應一個tomcat應用,其本身需要以來dubbo各個服務的API接口包,tomcat中啟動幾十個傳統(tǒng)的dubbo consumer 服務杜漠,然后通過webmvc的模式提供http接口极景。

flurry-and-tomcat

如上圖所示,flurry dubbo網(wǎng)關不必依賴任何dubbo接口API包,而是直接通過獲取服務元數(shù)據(jù)碑幅、并通過dubbo-json流式協(xié)議來調(diào)用后端服務戴陡。其本身不會耦合業(yè)務邏輯。

2.5 性能測試

測試環(huán)境

硬件部署與參數(shù)調(diào)整

機型 角色 CPU 內(nèi)存 帶寬
CVM虛擬機 API網(wǎng)關 8核 16GB 10Mbp/s
CVM虛擬機 X2 Dubbo Provider 4核 8GB 10Mbp/s
CVM虛擬機 wrk壓測機 8核 16GB 10Mbp/s

測試目的

對基于Y-Hessian的 異步化沟涨、流式轉(zhuǎn)換的Yunji Dubbo API網(wǎng)關進行性能壓測恤批,了解它的處理能力極限是多少,這樣有便于我們推斷其上線后的處理能力,以及對照現(xiàn)有的Tomcat接入層模式的優(yōu)勢,能夠節(jié)約多少資源裹赴,做到心里有數(shù)喜庞。

測試腳本

性能測試場景

  • 網(wǎng)關入?yún)?.5k請求json诀浪,深度為3層嵌套結構嘴脾,服務端接收請求纲堵,返回0.5k返回包

  • 網(wǎng)關入?yún)?k請求json,深度為3層嵌套結構,服務端接收請求余佛,返回0.5k返回包

上述場景均使用wrk在壓測節(jié)點上進行5~10min鐘的壓測晰房,壓測參數(shù)基本為12線程256連接或者512連接求摇,以發(fā)揮最大的壓測性能。

測試結果

性能指標(量化)
場景名稱 wrk壓測參數(shù) Avg RT 實際QPS值
0.5k數(shù)據(jù) -t12 -c256 3.77ms 69625
0.5k數(shù)據(jù) -t12 -c 512 7.26ms 71019
2k數(shù)據(jù) -t12 -c256 5.48ms 46576
2k數(shù)據(jù) -t12 -c512 10.80ms 46976
運行狀況(非量化)
  • API網(wǎng)關(8c16g)運行良好殊者,壓測期間CPU占用為550%左右

  • 兩個 Dubbo Provider 服務運行良好与境,CPU占用為100%左右

  • 各個角色內(nèi)存運行穩(wěn)定,無OOM猖吴,無不合理的大內(nèi)存占用等摔刁。

3.總結

flurry集Dubbo網(wǎng)關、異步海蔽、流式共屈、高性能于一身,其目標就是替代一些以tomcat作為dubbo消費者的接入層,以更少的節(jié)點獲得更多的性能提升党窜,節(jié)約硬件資源和軟件資源拗引。

4.后續(xù)

后續(xù)在flurry的基礎上,將實現(xiàn)鑒權管理幌衣、流量控制寺擂、限流熔斷、監(jiān)控收集等等功能

5.參考項目

Flurry: 基于Dubbo服務的高性能泼掠、異步、流式網(wǎng)關
dubbo-json: 自定義的Dubbo協(xié)議垦细,支持流式序列化模式择镇,為flurry網(wǎng)關序列化/反序列化組件。
Yunji-doc-site: 與元數(shù)據(jù)集成相關的項目,以及文檔站點

dapeng-soa: Dapeng-soa 是一個輕量級括改、高性能的微服務框架腻豌,構建在Netty以及定制的精簡版Thrift之上。 同時嘱能,從Thrift IDL文件自動生成的服務元數(shù)據(jù)信息是本框架的一個重要特性吝梅,很多其它重要特性都依賴于服務元數(shù)據(jù)信息。 最后惹骂,作為一站式的微服務解決方案苏携,Dapeng-soa還提供了一系列的腳手架工具以支持用戶快速的搭建微服務系統(tǒng)
dapeng-json:dapeng-json協(xié)議介紹

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市对粪,隨后出現(xiàn)的幾起案子右冻,更是在濱河造成了極大的恐慌装蓬,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纱扭,死亡現(xiàn)場離奇詭異牍帚,居然都是意外死亡,警方通過查閱死者的電腦和手機乳蛾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門暗赶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肃叶,你說我怎么就攤上這事蹂随。” “怎么了被环?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵糙及,是天一觀的道長。 經(jīng)常有香客問我筛欢,道長浸锨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任版姑,我火速辦了婚禮柱搜,結果婚禮上,老公的妹妹穿的比我還像新娘剥险。我一直安慰自己聪蘸,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布表制。 她就那樣靜靜地躺著健爬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪么介。 梳的紋絲不亂的頭發(fā)上娜遵,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音壤短,去河邊找鬼设拟。 笑死,一個胖子當著我的面吹牛久脯,可吹牛的內(nèi)容都是我干的纳胧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼帘撰,長吁一口氣:“原來是場噩夢啊……” “哼跑慕!你這毒婦竟也來了?” 一聲冷哼從身側響起摧找,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤相赁,失蹤者是張志新(化名)和其女友劉穎相寇,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钮科,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡唤衫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绵脯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佳励。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蛆挫,靈堂內(nèi)的尸體忽然破棺而出赃承,到底是詐尸還是另有隱情,我是刑警寧澤悴侵,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布瞧剖,位于F島的核電站,受9級特大地震影響可免,放射性物質(zhì)發(fā)生泄漏抓于。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一浇借、第九天 我趴在偏房一處隱蔽的房頂上張望捉撮。 院中可真熱鬧,春花似錦妇垢、人聲如沸巾遭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灼舍。三九已至,卻和暖如春涨薪,著一層夾襖步出監(jiān)牢的瞬間片仿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工尤辱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厢岂。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓光督,卻偏偏與公主長得像,于是被迫代替她去往敵國和親塔粒。 傳聞我的和親對象是個殘疾皇子结借,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 一、簡歷準備 1卒茬、個人技能 (1)自定義控件船老、UI設計咖熟、常用動畫特效 自定義控件 ①為什么要自定義控件? Andr...
    lucas777閱讀 5,217評論 2 54
  • 續(xù)~ 《牧羊少年奇幻之旅》柳畔,簡直是一本奇書馍管,一本天命圣經(jīng)。 我的腦海里忽然把主人公圣地亞哥的尋寶歷程和十三月亮歷法...
    玉新行者閱讀 335評論 1 2
  • 打開微信朋友圈 一刷都刷不到底 好像自己有很多人可以傾訴 其實等你的只有那么幾個 微信上有100%的人 90...
    雨季jd閱讀 433評論 0 1
  • 操作方法,我們根據(jù)每一個圖案去操作。 01 有些時候俘陷,我們的工具并不在挑選工具上面罗捎,我可以利用快捷鍵兌換工具使用。...
    黃探長閱讀 369評論 0 0