Apache Thrift系列詳解(二) - 網(wǎng)絡(luò)服務(wù)模型

前言

Thrift提供的網(wǎng)絡(luò)服務(wù)模型單線程多線程事件驅(qū)動,從另一個角度劃分為:阻塞服務(wù)模型非阻塞服務(wù)模型

  • 阻塞服務(wù)模型:TSimpleServerTThreadPoolServer

  • 非阻塞服務(wù)模型:TNonblockingServerTHsHaServerTThreadedSelectorServer

TServer類的層次關(guān)系:

正文

TServer

TServer定義了靜態(tài)內(nèi)部類ArgsArgs繼承自抽象類AbstractServerArgsAbstractServerArgs采用了建造者模式,向TServer提供各種工廠:

工廠屬性 工廠類型 作用
ProcessorFactory TProcessorFactory 處理層工廠類,用于具體的TProcessor對象的創(chuàng)建
InputTransportFactory TTransportFactory 傳輸層輸入工廠類,用于具體的TTransport對象的創(chuàng)建
OutputTransportFactory TTransportFactory 傳輸層輸出工廠類,用于具體的TTransport對象的創(chuàng)建
InputProtocolFactory TProtocolFactory 協(xié)議層輸入工廠類,用于具體的TProtocol對象的創(chuàng)建
OutputProtocolFactory TProtocolFactory 協(xié)議層輸出工廠類,用于具體的TProtocol對象的創(chuàng)建

下面是TServer的部分核心代碼:

public abstract class TServer {
    public static class Args extends org.apache.thrift.server.TServer.AbstractServerArgs<org.apache.thrift.server.TServer.Args> {
        public Args(TServerTransport transport) {
            super(transport);
        }
    }

    public static abstract class AbstractServerArgs<T extends org.apache.thrift.server.TServer.AbstractServerArgs<T>> {
        final TServerTransport serverTransport;
        TProcessorFactory processorFactory;
        TTransportFactory inputTransportFactory = new TTransportFactory();
        TTransportFactory outputTransportFactory = new TTransportFactory();
        TProtocolFactory inputProtocolFactory = new TBinaryProtocol.Factory();
        TProtocolFactory outputProtocolFactory = new TBinaryProtocol.Factory();

        public AbstractServerArgs(TServerTransport transport) {
            serverTransport = transport;
        }
    }

    protected TProcessorFactory processorFactory_;
    protected TServerTransport serverTransport_;
    protected TTransportFactory inputTransportFactory_;
    protected TTransportFactory outputTransportFactory_;
    protected TProtocolFactory inputProtocolFactory_;
    protected TProtocolFactory outputProtocolFactory_;
    private boolean isServing;

    protected TServer(org.apache.thrift.server.TServer.AbstractServerArgs args) {
        processorFactory_ = args.processorFactory;
        serverTransport_ = args.serverTransport;
        inputTransportFactory_ = args.inputTransportFactory;
        outputTransportFactory_ = args.outputTransportFactory;
        inputProtocolFactory_ = args.inputProtocolFactory;
        outputProtocolFactory_ = args.outputProtocolFactory;
    }

    public abstract void serve();
    public void stop() {}

    public boolean isServing() {
        return isServing;
    }

    protected void setServing(boolean serving) {
        isServing = serving;
    }
}

TServer的三個方法:serve()stop()isServing()serve()用于啟動服務(wù),stop()用于關(guān)閉服務(wù),isServing()用于檢測服務(wù)的起停狀態(tài)。

TServer不同實現(xiàn)類的啟動方式不一樣,因此serve()定義為抽象方法。不是所有的服務(wù)都需要優(yōu)雅的退出, 因此stop()方法沒有被定義為抽象。


TSimpleServer

TSimpleServer工作模式采用最簡單的阻塞IO,實現(xiàn)方法簡潔明了,便于理解,但是一次只能接收和處理一個socket連接,效率比較低。它主要用于演示Thrift的工作過程,在實際開發(fā)過程中很少用到它。

(一) 工作流程

image

(二) 使用入門

服務(wù)端:

    ServerSocket serverSocket = new ServerSocket(ServerConfig.SERVER_PORT);
    TServerSocket serverTransport = new TServerSocket(serverSocket);
    HelloWorldService.Processor processor =
            new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());
    TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();

    TSimpleServer.Args tArgs = new TSimpleServer.Args(serverTransport);
    tArgs.processor(processor);
    tArgs.protocolFactory(protocolFactory);
    // 簡單的單線程服務(wù)模型 一般用于測試
    TServer tServer = new TSimpleServer(tArgs);
    System.out.println("Running Simple Server");
    tServer.serve();

客戶端:

    TTransport transport = new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT);
    TProtocol protocol = new TBinaryProtocol(transport);
    HelloWorldService.Client client = new HelloWorldService.Client(protocol);
    transport.open();

    String result = client.say("Leo");
    System.out.println("Result =: " + result);
    transport.close();

(三) 源碼分析

查看上述流程的源代碼,即TSimpleServer.java中的serve()方法如下:

image

serve()方法的操作:

  1. 設(shè)置TServerSocketlisten()方法啟動連接監(jiān)聽
  2. 阻塞的方式接受客戶端地連接請求,每進入一個連接即為其創(chuàng)建一個通道TTransport對象。
  3. 為客戶端創(chuàng)建處理器對象輸入傳輸通道對象輸出傳輸通道對象輸入?yún)f(xié)議對象輸出協(xié)議對象
  4. 通過TServerEventHandler對象處理具體的業(yè)務(wù)請求。

ThreadPoolServer

TThreadPoolServer模式采用阻塞socket方式工作,主線程負責阻塞式監(jiān)聽是否有新socket到來,具體的業(yè)務(wù)處理交由一個線程池來處理。

(一) 工作流程

image

(二) 使用入門

服務(wù)端:

    ServerSocket serverSocket = new ServerSocket(ServerConfig.SERVER_PORT);
    TServerSocket serverTransport = new TServerSocket(serverSocket);
    HelloWorldService.Processor<HelloWorldService.Iface> processor =
            new HelloWorldService.Processor<>(new HelloWorldServiceImpl());

    TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();
    TThreadPoolServer.Args ttpsArgs = new TThreadPoolServer.Args(serverTransport);
    ttpsArgs.processor(processor);
    ttpsArgs.protocolFactory(protocolFactory);

    // 線程池服務(wù)模型 使用標準的阻塞式IO 預先創(chuàng)建一組線程處理請求
    TServer ttpsServer = new TThreadPoolServer(ttpsArgs);
    System.out.println("Running ThreadPool Server");
    ttpsServer.serve();

客戶端:

    TTransport transport = new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT);
    TProtocol protocol = new TBinaryProtocol(transport);
    HelloWorldService.Client client = new HelloWorldService.Client(protocol);

    transport.open();
    String result = client.say("ThreadPoolClient");
    System.out.println("Result =: " + result);
    transport.close();

(三) 源碼分析

ThreadPoolServer解決了TSimpleServer不支持并發(fā)多連接的問題,引入了線程池。實現(xiàn)的模型是One Thread Per Connection。查看上述流程的源代碼,先查看線程池的代碼片段:

image

TThreadPoolServer.java中的serve()方法如下:

image

serve()方法的操作:

  1. 設(shè)置TServerSocketlisten()方法啟動連接監(jiān)聽
  2. 阻塞的方式接受客戶端連接請求,每進入一個連接,將通道對象封裝成一個WorkerProcess對象(WorkerProcess實現(xiàn)了Runnabel接口),并提交到線程池
  3. WorkerProcessrun()方法負責業(yè)務(wù)處理,為客戶端創(chuàng)建了處理器對象輸入傳輸通道對象輸出傳輸通道對象輸入?yún)f(xié)議對象輸出協(xié)議對象
  4. 通過TServerEventHandler對象處理具體的業(yè)務(wù)請求。

WorkerProcessrun()方法:

image

(四) 優(yōu)缺點

TThreadPoolServer模式的優(yōu)點

拆分了監(jiān)聽線程(Accept Thread)和處理客戶端連接工作線程(Worker Thread),數(shù)據(jù)讀取業(yè)務(wù)處理都交給線程池處理。因此在并發(fā)量較大時新連接也能夠被及時接受。

線程池模式比較適合服務(wù)器端能預知最多有多少個客戶端并發(fā)的情況,這時每個請求都能被業(yè)務(wù)線程池及時處理,性能也非常高。

TThreadPoolServer模式的缺點

線程池模式的處理能力受限于線程池的工作能力,當并發(fā)請求數(shù)大于線程池中的線程數(shù)時,新請求也只能排隊等待


TNonblockingServer

TNonblockingServer模式也是單線程工作,但是采用NIO的模式,借助Channel/Selector機制, 采用IO事件模型來處理。

所有的socket都被注冊到selector中,在一個線程中通過seletor循環(huán)監(jiān)控所有的socket

每次selector循環(huán)結(jié)束時,處理所有的處于就緒狀態(tài)socket,對于有數(shù)據(jù)到來的socket進行數(shù)據(jù)讀取操作,對于有數(shù)據(jù)發(fā)送的socket則進行數(shù)據(jù)發(fā)送操作,對于監(jiān)聽socket則產(chǎn)生一個新業(yè)務(wù)socket并將其注冊selector上。

注意:TNonblockingServer要求底層的傳輸通道必須使用TFramedTransport。

(一) 工作流程

image

(二) 使用入門

服務(wù)端:

    TProcessor tprocessor = new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());
    TNonblockingServerSocket tnbSocketTransport = new TNonblockingServerSocket(ServerConfig.SERVER_PORT);

    TNonblockingServer.Args tnbArgs = new TNonblockingServer.Args(tnbSocketTransport);
    tnbArgs.processor(tprocessor);
    tnbArgs.transportFactory(new TFramedTransport.Factory());
    tnbArgs.protocolFactory(new TCompactProtocol.Factory());

    // 使用非阻塞式IO服務(wù)端和客戶端需要指定TFramedTransport數(shù)據(jù)傳輸?shù)姆绞?    TServer server = new TNonblockingServer(tnbArgs);
    System.out.println("Running Non-blocking Server");
    server.serve();

客戶端:

    TTransport transport = new TFramedTransport(new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT));
    // 協(xié)議要和服務(wù)端一致
    TProtocol protocol = new TCompactProtocol(transport);
    HelloWorldService.Client client = new HelloWorldService.Client(protocol);
    transport.open();

    String result = client.say("NonBlockingClient");
    System.out.println("Result =: " + result);
    transport.close();

(三) 源碼分析

TNonblockingServer繼承于AbstractNonblockingServer,這里我們更關(guān)心基于NIOselector部分的關(guān)鍵代碼。

image

(四) 優(yōu)缺點

TNonblockingServer模式優(yōu)點

相比于TSimpleServer效率提升主要體現(xiàn)在IO多路復用上TNonblockingServer采用非阻塞IO,對accept/read/writeIO事件進行監(jiān)控處理,同時監(jiān)控多個socket的狀態(tài)變化。

TNonblockingServer模式缺點

TNonblockingServer模式在業(yè)務(wù)處理上還是采用單線程順序來完成。在業(yè)務(wù)處理比較復雜耗時的時候,例如某些接口函數(shù)需要讀取數(shù)據(jù)庫執(zhí)行時間較長,會導致整個服務(wù)阻塞住,此時該模式效率也不高,因為多個調(diào)用請求任務(wù)依然是順序一個接一個執(zhí)行。

THsHaServer

鑒于TNonblockingServer的缺點,THsHaServer繼承于TNonblockingServer,引入了線程池提高了任務(wù)處理的并發(fā)能力THsHaServer半同步半異步(Half-Sync/Half-Async)的處理模式,Half-Aysnc用于IO事件處理(Accept/Read/Write),Half-Sync用于業(yè)務(wù)handlerrpc同步處理上。

注意:THsHaServer和TNonblockingServer一樣,要求底層的傳輸通道必須使用TFramedTransport。

(一) 工作流程

image

(二) 使用入門

服務(wù)端:

    TNonblockingServerSocket tnbSocketTransport = new TNonblockingServerSocket(ServerConfig.SERVER_PORT);
    TProcessor tprocessor = new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());
    // 半同步半異步
    THsHaServer.Args thhsArgs = new THsHaServer.Args(tnbSocketTransport);
    thhsArgs.processor(tprocessor);
    thhsArgs.transportFactory(new TFramedTransport.Factory());
    thhsArgs.protocolFactory(new TBinaryProtocol.Factory());

    TServer server = new THsHaServer(thhsArgs);
    System.out.println("Running HsHa Server");
    server.serve();

客戶端:

    TTransport transport = new TFramedTransport(new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT));
    // 協(xié)議要和服務(wù)端一致
    TProtocol protocol = new TBinaryProtocol(transport);
    HelloWorldService.Client client = new HelloWorldService.Client(protocol);
    transport.open();

    String result = client.say("HsHaClient");
    System.out.println("Result =: " + result);
    transport.close();

(三) 源碼分析

THsHaServer繼承于TNonblockingServer,新增了線程池并發(fā)處理工作任務(wù)的功能,查看線程池的相關(guān)代碼:

image

任務(wù)線程池的創(chuàng)建過程:

image

下文的TThreadedSelectorServer囊括了THsHaServer的大部分特性,源碼分析可參考TThreadedSelectorServer。

(四) 優(yōu)缺點

THsHaServer的優(yōu)點

THsHaServerTNonblockingServer模式相比,THsHaServer在完成數(shù)據(jù)讀取之后,將業(yè)務(wù)處理過程交由一個線程池來完成,主線程直接返回進行下一次循環(huán)操作,效率大大提升。

THsHaServer的缺點

主線程仍然需要完成所有socket監(jiān)聽接收數(shù)據(jù)讀取數(shù)據(jù)寫入操作。當并發(fā)請求數(shù)較大時,且發(fā)送數(shù)據(jù)量較多時,監(jiān)聽socket新連接請求不能被及時接受。


TThreadedSelectorServer

TThreadedSelectorServer是對THsHaServer的一種擴充,它將selector中的讀寫IO事件(read/write)從主線程中分離出來。同時引入worker工作線程池,它也是種Half-Sync/Half-Async的服務(wù)模型。

TThreadedSelectorServer模式是目前Thrift提供的最高級的線程服務(wù)模型,它內(nèi)部有如果幾個部分構(gòu)成:

  1. 一個AcceptThread線程對象,專門用于處理監(jiān)聽socket上的新連接。
  2. 若干個SelectorThread對象專門用于處理業(yè)務(wù)socket網(wǎng)絡(luò)I/O讀寫操作,所有網(wǎng)絡(luò)數(shù)據(jù)的讀寫均是有這些線程來完成。
  3. 一個負載均衡器SelectorThreadLoadBalancer對象,主要用于AcceptThread線程接收到一個新socket連接請求時,決定將這個新連接請求分配給哪個SelectorThread線程
  4. 一個ExecutorService類型的工作線程池,在SelectorThread線程中,監(jiān)聽到有業(yè)務(wù)socket中有調(diào)用請求過來,則將請求數(shù)據(jù)讀取之后,交給ExecutorService線程池中的線程完成此次調(diào)用的具體執(zhí)行。主要用于處理每個rpc請求的handler回調(diào)處理(這部分是同步的)。

(一) 工作流程

image

(二) 使用入門

服務(wù)端:

    TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(ServerConfig.SERVER_PORT);
    TProcessor processor = new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());
    // 多線程半同步半異步
    TThreadedSelectorServer.Args ttssArgs = new TThreadedSelectorServer.Args(serverSocket);
    ttssArgs.processor(processor);
    ttssArgs.protocolFactory(new TBinaryProtocol.Factory());
    // 使用非阻塞式IO時 服務(wù)端和客戶端都需要指定數(shù)據(jù)傳輸方式為TFramedTransport
    ttssArgs.transportFactory(new TFramedTransport.Factory());

    // 多線程半同步半異步的服務(wù)模型
    TThreadedSelectorServer server = new TThreadedSelectorServer(ttssArgs);
    System.out.println("Running ThreadedSelector Server");
    server.serve();

客戶端:

for (int i = 0; i < 10; i++) {
    new Thread("Thread " + i) {
        @Override
        public void run() {
            // 設(shè)置傳輸通道 對于非阻塞服務(wù) 需要使用TFramedTransport(用于將數(shù)據(jù)分塊發(fā)送)
            for (int j = 0; j < 10; j++) {
                TTransport transport = null;
                try {
                    transport = new TFramedTransport(new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT));
                    TProtocol protocol = new TBinaryProtocol(transport);
                    HelloWorldService.Client client = new HelloWorldService.Client(protocol);
                    transport.open();
                    String result = client.say("ThreadedSelector Client");
                    System.out.println("Result =: " + result);
                    transport.close();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 關(guān)閉傳輸通道
                    transport.close();
                }
            }
        }
    }.start();
}

(三) 核心代碼

以上工作流程的三個組件AcceptThreadSelectorThreadExecutorService在源碼中的定義如下:

TThreadedSelectorServer模式中有一個專門的線程AcceptThread用于處理新連接請求,因此能夠及時響應大量并發(fā)連接請求;另外它將網(wǎng)絡(luò)I/O操作分散到多個SelectorThread線程中來完成,因此能夠快速對網(wǎng)絡(luò)I/O進行讀寫操作,能夠很好地應對網(wǎng)絡(luò)I/O較多的情況。

image

TThreadedSelectorServer默認參數(shù)定義如下:

image
  • 負責網(wǎng)絡(luò)IO讀寫的selector默認線程數(shù)(selectorThreads):2
  • 負責業(yè)務(wù)處理的默認工作線程數(shù)(workerThreads):5
  • 工作線程池單個線程的任務(wù)隊列大小(acceptQueueSizePerThread):4

創(chuàng)建、初始化并啟動AcceptThreadSelectorThreads,同時啟動selector線程的負載均衡器(selectorThreads)。

image

AcceptThread源碼

AcceptThread繼承于Thread,可以看出包含三個重要的屬性:非阻塞式傳輸通道(TNonblockingServerTransport)、NIO選擇器(acceptSelector)和選擇器線程負載均衡器(threadChooser)。

image

查看AcceptThreadrun()方法,可以看出accept線程一旦啟動,就會不停地調(diào)用select()方法:

image

查看select()方法,acceptSelector選擇器等待IO事件的到來,拿到SelectionKey即檢查是不是accept事件。如果是,通過handleAccept()方法接收一個新來的連接;否則,如果是IO讀寫事件AcceptThread不作任何處理,交由SelectorThread完成。

image

handleAccept()方法中,先通過doAccept()去拿連接通道,然后Selector線程負載均衡器選擇一個Selector線程,完成接下來的IO讀寫事件

[圖片上傳失敗...(image-314d7a-1536936397051)]

接下來繼續(xù)查看doAddAccept()方法的實現(xiàn),毫無懸念,它進一步調(diào)用了SelectorThreadaddAcceptedConnection()方法,把非阻塞傳輸通道對象傳遞給選擇器線程做進一步的IO讀寫操作

image

SelectorThreadLoadBalancer源碼

SelectorThreadLoadBalancer如何創(chuàng)建?

image

SelectorThreadLoadBalancer是一個基于輪詢算法Selector線程選擇器,通過線程迭代器為新進來的連接順序分配SelectorThread

image

SelectorThread源碼

SelectorThreadAcceptThread一樣,是TThreadedSelectorServer的一個成員內(nèi)部類,每個SelectorThread線程對象內(nèi)部都有一個阻塞式的隊列,用于存放該線程被接收連接通道

[站外圖片上傳中...(image-ce3409-1536936397051)]

阻塞隊列的大小可由構(gòu)造函數(shù)指定:

image

上面看到,在AcceptThreaddoAddAccept()方法中調(diào)用了SelectorThreadaddAcceptedConnection()方法。

這個方法做了兩件事:

  1. 將被此SelectorThread線程接收的連接通道放入阻塞隊列中。
  2. 通過wakeup()方法喚醒SelectorThread中的NIO選擇器selector
image

既然SelectorThread也是繼承于Thread,查看其run()方法的實現(xiàn):

image

SelectorThread方法的select()監(jiān)聽IO事件,僅僅用于處理數(shù)據(jù)讀取數(shù)據(jù)寫入。如果連接有數(shù)據(jù)可讀,讀取并以frame的方式緩存;如果需要向連接中寫入數(shù)據(jù),緩存并發(fā)送客戶端的數(shù)據(jù)。且在數(shù)據(jù)讀寫處理完成后,需要向NIOselector清空注銷自身的SelectionKey

image
  • 數(shù)據(jù)寫操作完成以后,整個rpc調(diào)用過程也就結(jié)束了,handleWrite()方法如下:
image
  • 數(shù)據(jù)讀操作完成以后,Thrift會利用已讀數(shù)據(jù)執(zhí)行目標方法handleRead()方法如下:
image

handleRead方法在執(zhí)行read()方法,將數(shù)據(jù)讀取完成后,會調(diào)用requestInvoke()方法調(diào)用目標方法完成具體業(yè)務(wù)處理。requestInvoke()方法將請求數(shù)據(jù)封裝為一個Runnable對象,提交到工作任務(wù)線程池(ExecutorService)進行處理。

image

select()方法完成后,線程繼續(xù)運行processAcceptedConnections()方法處理下一個連接IO事件。

image

這里比較核心的幾個操作:

  1. 嘗試從SelectorThread阻塞隊列acceptedQueue中獲取一個連接的傳輸通道。如果獲取成功,調(diào)用registerAccepted()方法;否則,進入下一次循環(huán)。
  2. registerAccepted()方法將傳輸通道底層的連接注冊到NIO選擇器selector上面,獲取到一個SelectionKey
  3. 創(chuàng)建一個FrameBuffer對象,并綁定到獲取的SelectionKey上面,用于數(shù)據(jù)傳輸時的中間讀寫緩存

總結(jié)

本文對Thrift的各種線程服務(wù)模型進行了介紹,包括2種阻塞式服務(wù)模型TSimpleServerTThreadPoolServer,3種非阻塞式服務(wù)模型TNonblockingServerTHsHaServerTThreadedSelectorServer。對各種服務(wù)模型的具體用法工作流程原理和源碼實現(xiàn)進行了一定程度的分析。

鑒于篇幅較長,請各位看官請慢慢批閱!


歡迎關(guān)注技術(shù)公眾號: 零壹技術(shù)棧

零壹技術(shù)棧

本帳號將持續(xù)分享后端技術(shù)干貨,包括虛擬機基礎(chǔ),多線程編程,高性能框架,異步、緩存和消息中間件,分布式和微服務(wù),架構(gòu)學習和進階等學習資料和文章。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,120評論 1 32
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,472評論 8 265
  • Java繼承關(guān)系初始化順序 父類的靜態(tài)變量-->父類的靜態(tài)代碼塊-->子類的靜態(tài)變量-->子類的靜態(tài)代碼快-->父...
    第六象限閱讀 2,167評論 0 9
  • 參加了四次線下拆書,對于拆書已經(jīng)有了一些認知,聽水菱說她與拆書幫結(jié)緣也是因為這本書!我想既然想成為拆書家,怎么著也...
    驊姑娘閱讀 381評論 0 0
  • 文/孤鳥差魚 心里的谷堆 長滿了倉鼠 沒人來問 還剩多少 吃跑的人 總在肆無忌憚的揮霍 不懂得沒有的難過 是有如針...
    孤鳥差魚閱讀 214評論 3 3