動(dòng)手打造Nginx多進(jìn)程架構(gòu)

最近對(duì)Nginx源碼比較感興趣鸣个,借助于強(qiáng)大的VS Code帅矗,我一步一步,似魔鬼的步伐科吭,開始了Nginx的探索之旅昏滴。關(guān)于 VS Code 如何調(diào)試 Nginx 可參考上篇文章《VS CODE 輕松調(diào)試 Nginx》

一. 引言

Nginx 其實(shí)無(wú)需做太多介紹砌溺,作為業(yè)界知名的高性能服務(wù)器影涉,被廣大互聯(lián)網(wǎng)公司應(yīng)用,阿里的 Tegine 就是基于 Nginx 開發(fā)的规伐。

Nginx 基本上都是用來(lái)做負(fù)載均衡蟹倾、反向代理和動(dòng)靜分離。目前大部分公司都采用 Nginx 作為負(fù)載均衡器猖闪。作為 LBS鲜棠,最基本的要求就是要支持高并發(fā),畢竟所有的請(qǐng)求都要經(jīng)過(guò)它來(lái)進(jìn)行轉(zhuǎn)發(fā)培慌。

那么為什么 Nginx 擁有如此強(qiáng)大的并發(fā)能力呢豁陆?這便是我感興趣的事情,也是這篇文章所要講的事情吵护。但是標(biāo)題是《動(dòng)手打造Nginx多進(jìn)程架構(gòu)》盒音,難道這篇文章卻只是簡(jiǎn)單的源碼分析?

這幾天研究 Nginx 過(guò)程中馅而,我常常陷于Nginx 復(fù)雜的源碼之中祥诽,不得其解,雖然也翻了一些資料和書籍瓮恭,但是總覺(jué)得沒(méi)有 get 到精髓雄坪,就是好像已經(jīng)理解了,但是對(duì)于具體流程和細(xì)節(jié)屯蹦,總是模模糊糊维哈。于是趁著周末绳姨,花了小半天,再次梳理了下Nginx 多進(jìn)程事件的源碼阔挠,仿照著寫了一個(gè)普通的 Server飘庄,雖然代碼和功能都非常簡(jiǎn)單,不過(guò)剛好適合于讀者了解Nginx谒亦,而不至于陷于叢林之中竭宰,不知方向。

二. 傳統(tǒng) Web Server 架構(gòu)

讓我們來(lái)思考下份招,如果讓你動(dòng)手打造一個(gè) web 服務(wù)器切揭,你會(huì)怎么做?

第一步锁摔,監(jiān)聽(tīng)端口

第二步廓旬,處理請(qǐng)求

監(jiān)聽(tīng)端口倒是很簡(jiǎn)單,處理請(qǐng)求該怎么做呢谐腰?不知道大家上大學(xué)剛開始學(xué)c語(yǔ)言的時(shí)候孕豹,老師有沒(méi)有布置過(guò)聊天室之類的作業(yè)?那時(shí)候我其實(shí)完全靠百度來(lái)完成的:開啟端口監(jiān)聽(tīng)十气,死循環(huán)接收請(qǐng)求励背,每接收一個(gè)請(qǐng)求就直接開個(gè)新線程去處理。

image

這樣做當(dāng)然可以砸西,也很簡(jiǎn)單叶眉,完全滿足了我當(dāng)時(shí)的作業(yè)要求,其實(shí)目前很多web服務(wù)器芹枷,諸如tomcat之類衅疙,也都是這樣做的,為每個(gè)請(qǐng)求單獨(dú)分配一個(gè)線程鸳慈。那么這樣做饱溢,有什么弊端呢?

最直接的弊端就是線程數(shù)量開的太多走芋,會(huì)導(dǎo)致 CPU 在不同線程之間不斷的進(jìn)行上下文切換绩郎。CPU 的每次任務(wù)切換,都需要為上一次任務(wù)保存一些上下文信息(如寄存器的值)翁逞,再裝載新任務(wù)的上下文信息嗽上,這些都是不小的開銷。

第二個(gè)弊端就是CPU利用率的下降熄攘,考慮當(dāng)前只有一個(gè)線程的情況,當(dāng)線程在等待網(wǎng)絡(luò) IO 的時(shí)候其實(shí)是處于阻塞狀態(tài)彼念,這個(gè)時(shí)候 CPU 便處于空閑狀態(tài)挪圾,這直接導(dǎo)致了 CPU 沒(méi)有被充分利用浅萧,簡(jiǎn)直是暴殄天物!

這種架構(gòu)哲思,使 Web 服務(wù)器從骨子里洼畅,就對(duì)高并發(fā)沒(méi)有很好的承載能力!

三. Nginx 多進(jìn)程架構(gòu)

Nginx 之所以可以支持高并發(fā)棚赔,正是因?yàn)樗饤壛藗鹘y(tǒng) Web 服務(wù)器的多線程架構(gòu)帝簇,并充分利用了 CPU。

Nginx采用的是 單Master靠益、多Worker 架構(gòu)丧肴,顧名思義,Master 是老板胧后,而 Worker 才是真正干活的工人階層芋浮。

我們先來(lái)看下 Nginx 接收請(qǐng)求的大概架構(gòu)。

image

乍一看壳快,好像和傳統(tǒng)的 Web Server 也沒(méi)啥區(qū)別啊纸巷,不過(guò)是右邊的 Thread 變成了 Worker 罷了扫腺。這其實(shí)正是 Nginx 的精妙之處缴川。

Master 進(jìn)程啟動(dòng)后,會(huì) fork 出 N 個(gè) Worker 進(jìn)程谴垫,N 是 可配置的竖伯,一般來(lái)說(shuō)存哲,可以設(shè)置為服務(wù)器核心數(shù),設(shè)置更大值也沒(méi)有太多意義黔夭,無(wú)非是會(huì)增加 CPU 進(jìn)程切換的開銷宏胯。

每個(gè)Worker 進(jìn)程都會(huì)監(jiān)聽(tīng)來(lái)自客戶端的請(qǐng)求,并進(jìn)行處理本姥,與傳統(tǒng) Web Server 不同的是肩袍,Worker 進(jìn)程不會(huì)對(duì)于每個(gè)請(qǐng)求都分配一個(gè)單獨(dú)線程去處理,而是充分利用了IO多路復(fù)用 的特性婚惫。

如果讀者之前沒(méi)有了解或者使用過(guò)IO多路復(fù)用氛赐,那確實(shí)該好好充充電了。Android 中的 Looper先舷、Java 著名的開源庫(kù) Netty艰管,都是基于多路復(fù)用,所謂多路復(fù)用蒋川,與同步阻塞IO最大的區(qū)別就是牲芋,一個(gè)進(jìn)程可以同時(shí)處理多個(gè)IO操作,當(dāng) 某個(gè)IO 操作 Ready 時(shí),操作系統(tǒng)會(huì)主動(dòng)通知進(jìn)程缸浦。

Nginx 正是使用了這樣的思想夕冲,雖然同時(shí)有很多請(qǐng)求需要處理,但是沒(méi)必要為每個(gè)請(qǐng)求都分配一個(gè)線程啊裂逐。哪個(gè)請(qǐng)求的網(wǎng)絡(luò) IO Ready 了歹鱼,我就去處理哪個(gè),這樣不就可以了嗎卜高?何必創(chuàng)建一個(gè)線程在那傻傻的等著弥姻。

舉個(gè)不恰當(dāng)?shù)睦樱?wù)器就好比是學(xué)校掺涛,客戶端好比是學(xué)生庭敦,學(xué)生有不會(huì)的問(wèn)題就會(huì)問(wèn)老師。

  • 對(duì)于傳統(tǒng)的 Web 服務(wù)器鸽照,每個(gè)學(xué)生螺捐,學(xué)校都會(huì)派一個(gè)老師去服務(wù),一個(gè)學(xué)邪牵可能有幾千個(gè)學(xué)生定血,那豈不是要雇幾千個(gè)老師,校領(lǐng)導(dǎo)怕是連工資都發(fā)不出來(lái)了吧诞外。仔細(xì)想想澜沟,每個(gè)學(xué)生不可能隨時(shí)都在提問(wèn)吧,總得休息下吧峡谊!那學(xué)生休息時(shí)茫虽,老師干嘛呢?白拿工資還不干活既们。
  • 對(duì)于Nginx濒析,它就不給老師閑的機(jī)會(huì)啦,學(xué)校有幾間辦公室啥纸,就雇幾個(gè)老師号杏,有學(xué)生提問(wèn)時(shí),就派一個(gè)老師解答斯棒,所以一個(gè)老師會(huì)負(fù)責(zé)很多學(xué)生盾致,哪個(gè)學(xué)生舉手了,他就去幫助哪個(gè)學(xué)生解決問(wèn)題荣暮。

這里有讀者怕是會(huì)疑惑庭惜,如果哪個(gè)學(xué)生一直霸占著老師不放怎么辦?這樣老師不就沒(méi)有機(jī)會(huì)去解答其他同學(xué)的問(wèn)題了嗎穗酥?如果作為一個(gè)負(fù)責(zé)業(yè)務(wù)處理的 Web 服務(wù)器护赊,Nginx這種架構(gòu)確實(shí)可能出現(xiàn)這樣的問(wèn)題惠遏,但是要記住,Nginx主要是用來(lái)做負(fù)載均衡的骏啰,他的主要任務(wù)是接收請(qǐng)求爽哎、轉(zhuǎn)發(fā)請(qǐng)求,所以它的業(yè)務(wù)處理其實(shí)就是將請(qǐng)求再轉(zhuǎn)發(fā)給其他的服務(wù)器器一,那么接收用IO多路復(fù)用,轉(zhuǎn)發(fā)也用 IO 多路復(fù)用不就行了厨内。

四. 源碼分析

基于最新 1.15.5 版本

4.1 整體運(yùn)行機(jī)制

一切都從 main()開始祈秕。

nginx 的 main()方法中有不少邏輯,不過(guò)對(duì)于今天我要講的事情來(lái)說(shuō)雏胃,最重要的就是兩件事:

  1. 創(chuàng)建套接字请毛,監(jiān)聽(tīng)端口;
  2. Fork 出 N 個(gè) Worker 進(jìn)程瞭亮。

監(jiān)聽(tīng)端口沒(méi)什么太多邏輯方仿,我們先來(lái)看看 Worker 進(jìn)程的誕生:

static void
ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type)
{
    ngx_int_t      i;
    ngx_channel_t  ch;

    ....
    for (i = 0; i < n; i++) {

        ngx_spawn_process(cycle, ngx_worker_process_cycle,
                      (void *) (intptr_t) i, "worker process", type);
        ......
    }
}

這里主要是根據(jù)配置的 Worker 數(shù)量,創(chuàng)建出對(duì)應(yīng)數(shù)量的 Worker 進(jìn)程统翩,創(chuàng)建 Woker 進(jìn)程調(diào)用的是 ngx_spawn_process()仙蚜,第二個(gè)參數(shù) ngx_worker_process_cycle 就是子進(jìn)程的新起點(diǎn)。

static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    ......

    for ( ;; ) {

        ......

        ngx_process_events_and_timers(cycle);

        ......
    }
}

上面的代碼省略了一些邏輯厂汗,只保留了最核心的部分委粉。ngx_worker_process_cycle ,正如其名娶桦,在其內(nèi)部開啟了一個(gè)死循環(huán)贾节,不斷調(diào)用 ngx_process_events_and_timers()。

void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    ......

    if (ngx_use_accept_mutex) {
        if (ngx_accept_disabled > 0) {
            ngx_accept_disabled--;

        } else {
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return;
            }

            ......
        }
    }

    ......

    (void) ngx_process_events(cycle, timer, flags);

    ......
}

這里最后調(diào)用了ngx_process_events()來(lái)接收并處理事件衷畦。

ngx_process_events()在不同平臺(tái)指向不同的 IO 處理模塊栗涂,比如Linux上為epoll,而在Mac OS上指向的其實(shí)是kqueue模塊中的ngx_kqueue_process_events()祈争。

static ngx_int_t
ngx_kqueue_process_events(ngx_cycle_t *cycle, ngx_msec_t timer,
    ngx_uint_t flags)
{
    int               events, n;
    ngx_int_t         i, instance;
    ngx_uint_t        level;
    ngx_err_t         err;
    ngx_event_t      *ev;
    ngx_queue_t      *queue;
    struct timespec   ts, *tp;

    n = (int) nchanges;
    nchanges = 0;

    ......

    events = kevent(ngx_kqueue, change_list, n, event_list, (int) nevents, tp);

    ......

    for (i = 0; i < events; i++) {

        ......

        ev = (ngx_event_t *) event_list[i].udata;

        switch (event_list[i].filter) {

        case EVFILT_READ:
        case EVFILT_WRITE:

            ......

            break;

        case EVFILT_VNODE:
            ev->kq_vnode = 1;

            break;

        case EVFILT_AIO:
            ev->complete = 1;
            ev->ready = 1;

            break;
        ......

        }
        ......

        ev->handler(ev);
    }

    return NGX_OK;
}

上面其實(shí)就是一個(gè)比較基本的 kqueue 使用方式了斤程。說(shuō)到這里,我們就不得不說(shuō)下 kqueue 的使用方式了铛嘱。

kqueue 主要依托于兩個(gè) API:

// 創(chuàng)建一個(gè)內(nèi)核消息隊(duì)列暖释,返回隊(duì)列描述符
int  kqueue(void); 

// 用途:注冊(cè)\反注冊(cè) 監(jiān)聽(tīng)事件,等待事件通知
// kq墨吓,上面創(chuàng)建的消息隊(duì)列描述符
// changelist球匕,需要注冊(cè)的事件
// changelist,changelist數(shù)組大小
// eventlist帖烘,內(nèi)核會(huì)把返回的事件放在該數(shù)組中
// nevents亮曹,eventlist數(shù)組大小
// timeout,等待內(nèi)核返回事件的超時(shí)事件,NULL 即為無(wú)限等待
int  kevent(int kq, 
           const struct kevent *changelist, int nchanges,
           struct kevent *eventlist, int nevents,
           const struct timespec *timeout);

我們回過(guò)頭再來(lái)看看上面 ngx_kqueue_process_events()中代碼照卦,其實(shí)也就是在調(diào)用kevent()等待內(nèi)核返回消息式矫,收到消息后再進(jìn)行處理。這里消息處理主要是進(jìn)行ACCEPT役耕、READ采转、WRITE等。

所以從整體來(lái)看瞬痘,Nginx事件模塊的運(yùn)行就是 Worker 進(jìn)程在死循環(huán)中故慈,不斷等待內(nèi)核消息隊(duì)列返回事件消息,并加以處理的一個(gè)過(guò)程框全。

4.2 驚群?jiǎn)栴}

到這里我們一直在討論一個(gè)單獨(dú)的 Worker 進(jìn)程運(yùn)行機(jī)制察绷,那么每個(gè) Worker 進(jìn)程之間有沒(méi)有什么交互呢?

回到上面的 ngx_process_events_and_timers()中津辩,在每次調(diào)用 ngx_process_events()等待消息之前拆撼,Worker 進(jìn)程都會(huì)進(jìn)行一個(gè) ngx_trylock_accept_mutex()操作,這其實(shí)就是多個(gè) Worker 進(jìn)程之間在爭(zhēng)奪監(jiān)聽(tīng)資格的過(guò)程喘沿,是 Nginx 為了解決驚群?jiǎn)栴}而設(shè)計(jì)出的方案闸度。

所謂驚群,其實(shí)就是如果有多個(gè)Worker進(jìn)程同時(shí)在監(jiān)聽(tīng)內(nèi)核消息事件摹恨,當(dāng)有請(qǐng)求到來(lái)時(shí)筋岛,每個(gè)Worker進(jìn)程都會(huì)被喚醒,去accept同一個(gè)請(qǐng)求晒哄,但是只能有一個(gè)進(jìn)程會(huì)accept成功睁宰,其他進(jìn)程會(huì)accept失敗,被白白的喚醒了寝凌,就像你再睡覺(jué)時(shí)被突然叫醒柒傻,卻發(fā)現(xiàn)壓根沒(méi)你啥事,你說(shuō)氣不氣人较木。

為了解決這個(gè)問(wèn)題红符,Nginx 讓每個(gè)Worker 進(jìn)程在監(jiān)聽(tīng)內(nèi)核消息事件前去競(jìng)爭(zhēng)一把鎖,只有成功獲得鎖的進(jìn)程才能去監(jiān)聽(tīng)內(nèi)核事件伐债,其他進(jìn)程就乖乖的睡眠在鎖的等待隊(duì)列上预侯。當(dāng)獲得鎖的進(jìn)程處理完accept事件,就會(huì)回來(lái)釋放掉這把鎖峰锁,這時(shí)所有進(jìn)程又會(huì)同時(shí)去競(jìng)爭(zhēng)鎖了萎馅。

為了不讓每次都是同一個(gè)進(jìn)程搶到鎖,Nginx 設(shè)計(jì)了一個(gè)小算法虹蒋,用一個(gè)因子ngx_accept_disabled 去 平均每個(gè)進(jìn)程獲得鎖的概率糜芳,感興趣的同學(xué)可以自己看下這塊源碼飒货。

五. 動(dòng)手打造 Nginx 多進(jìn)程架構(gòu)

終于到DIY的環(huán)節(jié)了,這里我基于 MacOS 平臺(tái)來(lái)開發(fā)峭竣,IO多路復(fù)用也是選用上面所講的 kqueue塘辅。

5.1 創(chuàng)建進(jìn)程鎖,用于搶到監(jiān)聽(tīng)事件資格

mm = (mt*)mmap(NULL,sizeof(*mm),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
memset(mm,0x00,sizeof(*mm));

pthread_mutexattr_init(&mm->mutexattr);
pthread_mutexattr_setpshared(&mm->mutexattr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&mm->mutex,&mm->mutexattr);

5.2 創(chuàng)建套接字皆撩,監(jiān)聽(tīng)端口

// 創(chuàng)建套接字
int serverSock =socket(AF_INET, SOCK_STREAM, 0);
if (serverSock == -1)
{
    
    printf("socket failed\n");
    exit(0);
}

//綁定ip和端口
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(9999);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(::bind(serverSock, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1)
{
    printf("bind failed\n");
    exit(0);
}

//啟動(dòng)監(jiān)聽(tīng)
if(listen(serverSock, 20) == -1)
{
    printf("listen failed\n");
    exit(0);
}

5.3 創(chuàng)建多個(gè) Worker 進(jìn)程

// fork 出 3 個(gè) Worker 進(jìn)程
int result;
for(int i = 1; i< 3; i++){
    result = fork();
    if(result == 0){
        startWorker(i,serverSock);
        printf("start worker %d\n",i);
        break;
    }
}

5.4 啟動(dòng)Worker 進(jìn)程扣墩,監(jiān)聽(tīng) IO 事件

void startWorker(int workerId,int serverSock)
{ 
    // 創(chuàng)建內(nèi)核事件隊(duì)列
    int kqueuefd=kqueue();
    struct kevent change_list[1];  //想要監(jiān)控的事件的數(shù)組
    struct kevent event_list[1];  //用來(lái)接受事件的數(shù)組

    //初始化所需注冊(cè)事件
    EV_SET(&change_list[0], serverSock, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
    
    // 循環(huán)接受事件
    while (true) {
        // 競(jìng)爭(zhēng)鎖,獲取監(jiān)聽(tīng)資格
        pthread_mutex_lock(&mm->mutex);
        printf("Worker %d get the lock\n",workerId);
        // 注冊(cè)事件扛吞,等待通知
        int nevents = kevent(kqueuefd, change_list, 1, event_list, 1, NULL);
        // 釋放鎖
        pthread_mutex_unlock(&mm->mutex);
        //遍歷返回的所有就緒事件
        for(int i = 0; i< nevents;i++){
            struct kevent event =event_list[i];
            if(event.ident == serverSock){
                // ACCEPT 事件
                handleNewConnection(kqueuefd,serverSock);
            }else if(event.filter == EVFILT_READ){
                //讀取客戶端傳來(lái)的數(shù)據(jù)
                char * msg = handleReadFromClient(workerId,event);
                handleWriteToClient(workerId,event,msg);
            }
        }
    }
}

5.5 開啟多個(gè) Client 進(jìn)程測(cè)試

運(yùn)行結(jié)果:

image

哈哈沮榜,基本實(shí)現(xiàn)了我的要求。

Demo 源碼見(jiàn):https://github.com/HalfStackDeveloper/LearnNginx

六. 總結(jié)

Nginx 之所以有強(qiáng)大的高并發(fā)能力喻粹,得益于它與眾不同的架構(gòu)設(shè)計(jì),無(wú)論是多進(jìn)程還是 IO 多路復(fù)用草巡,都是 Nginx 不可或缺的一部分守呜。研究 Nginx 源碼十分有趣,但是看源碼和動(dòng)手寫又是兩回事山憨,看源碼只能大概了解脈絡(luò)查乒,只有自己操刀,才能真正理解和運(yùn)用郁竟!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玛迄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子棚亩,更是在濱河造成了極大的恐慌蓖议,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讥蟆,死亡現(xiàn)場(chǎng)離奇詭異勒虾,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)瘸彤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門修然,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人质况,你說(shuō)我怎么就攤上這事愕宋。” “怎么了结榄?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵中贝,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我潭陪,道長(zhǎng)雄妥,這世上最難降的妖魔是什么最蕾? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮老厌,結(jié)果婚禮上瘟则,老公的妹妹穿的比我還像新娘。我一直安慰自己枝秤,他們只是感情好醋拧,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淀弹,像睡著了一般丹壕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上薇溃,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天菌赖,我揣著相機(jī)與錄音,去河邊找鬼沐序。 笑死琉用,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的策幼。 我是一名探鬼主播邑时,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼特姐!你這毒婦竟也來(lái)了晶丘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤唐含,失蹤者是張志新(化名)和其女友劉穎浅浮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捷枯,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脑题,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铜靶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叔遂。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖争剿,靈堂內(nèi)的尸體忽然破棺而出已艰,到底是詐尸還是另有隱情,我是刑警寧澤蚕苇,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布哩掺,位于F島的核電站,受9級(jí)特大地震影響涩笤,放射性物質(zhì)發(fā)生泄漏嚼吞。R本人自食惡果不足惜盒件,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舱禽。 院中可真熱鬧炒刁,春花似錦、人聲如沸誊稚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)里伯。三九已至城瞎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疾瓮,已是汗流浹背脖镀。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狼电,地道東北人认然。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像漫萄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盈匾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

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