Juice-一種基于MesosFramework的任務云框架

作者:徐佳
文為原創文章,轉載請注明作者及出處

在介紹Juice之前,我想先聊一聊Mesos,Mesos被稱為2層調度框架,是因為Master通過內部的Allocator完成Master->Framework的第一層調度,再由Framework通過調度器完成對于資源->任務的分配,這個過程稱為第二層調度。

About MesosFramework

先來看一看Mesos&Framework的整體架構圖:


arch.png

Mesos的Framework分為2部分組成,分別為調度器和執行器。
調度器被稱為Scheduler,從Mesos1.0版本開始,官方提供了基于HTTP的RestAPI供外部調用并進行二次開發。
Scheduler用于處理Master端發起的回調事件(資源列表并加載任務、任務狀態通知等),進行相應處理。Agent接收到Master分配的任務時,會根據任務的container-type進行不同的處理,當處理默認container-type='Mesos'時,先檢查Framework所對應的Executor進程是否啟動,如果沒有啟動則會先啟動Executor進程,然后再提交任務到該Executor去執行,當運行一個container-type='Docker'的任務時,則啟動Docker Executor進行處理,程序的運行狀態完全取決于Docker內部的處理及返回值。

MesosFramework交互API

交互分為2部分API,分別為SchedulerAPI(http://mesos.apache.org/documentation/latest/scheduler-http-api/) 與ExecutorAPI(http://mesos.apache.org/documentation/latest/Executor-http-api/), 每個API都會以TYPE來區分,具體的處理流程如下:
1.Scheduler提交一個請求(type='SUBSCRIBE')到Master(http://master-ip:5050/api/v1/scheduler), 并需要設置'subscribe.framework_info.id',該ID由Scheduler生成,在一個Mesos集群中必須保證唯一,Mesos以此FrameworkID來區分各個Framework所提交的任務,發送完畢后,Scheduler端等待Master的'SUBSCRIBE'回調事件,Master的返回事件被定義在event對象中,event.type為'SUBSCRIBE'(注意:'SUBSCRIBE'請求發起后,Scheduler與Master端會保持會話連接(keep-alive),Master端主動發起的事件回調都會通過該連接通知到Scheduler)。(scheduler-http-api中接口'SUBSCRIBE')
2.Master主動發起'OFFERS'事件回調,通知Scheduler目前集群可分配使用資源,事件的event.type為'OFFERS'。(scheduler-http-api中接口'OFFERS')
3.Scheduler調用resourcesOffer為Offers安排Tasks。當完成任務分配后,主動發起'ACCEPT'事件請求到Master端告知Offers-Tasks列表。(scheduler-http-api中接口'ACCEPT')
4.Master接收到Scheduler的任務請求后,將任務發送到OfferId對應的Agent中去執行任務。
5.Agent接收到任務,檢查任務對應的Executor是否啟動,如啟動,則調用該Executor執行任務,如未啟動,則調用lauchExecutor()創建Executor對象并執行initialize()初始化Executor,Executor初始化過程中會調用RegisterExecutorMessage在Agent上注冊,之后便接受任務開始執行。(Executor-http-api中接口'LAUNCH')
6.Executor執行完畢或錯誤時通知Agent任務的task_status。(Executor-http-api中接口'UPDATE')
7.Agent再同步task_status給Master,Master則調用'UPDATE'事件回調,通知Scheduler更新任務狀態。(scheduler-http-api中接口'UPDATE')
8.Scheduler確認后發送'ACKNOWLEDGE'請求告知Master任務狀態已確認。(scheduler-http-api中接口'ACKNOWLEDGE')

任務狀態標示及Agent宕機處理###

對于一個任務的運行狀態,Mesos定義了13種TASK_STATUS來標示,常用的有以下幾種:

TASK_STAGING-任務準備狀態,該任務已有Master分配給Slave,但Slave還未運行時的狀態。
TASK_RUNNING-任務已在Agent上運行。
TASK_FINISHED-任務已運行完畢。
TASK_KILLED-任務被主動終止,調用scheduler-http-api中'KILL'接口。
TASK_FAILED-任務執行失敗。
TASK_LOST-任務丟失,通常發生在Slave宕機。

當Agent宕機導致TASK_LOST時,Mesos又是怎么來處理的呢?
在Master和Agent之間,一般都是由Master主動向每一個Agent發送Ping消息,如果在設定時間內(flag.slave_ping_timeout,默認15s)沒有收到Agent的回復,并且達到一定次數(flag.max_slave_ping_timeouts,默認次數為5),那么Master會操作以下幾個步驟:
1.將該Agent從Master中刪除,此時該Agent的資源將不會再分配給Scheduler。
2.遍歷該Agent上運行的所有任務,向對應的Framework發送任務的Task_Lost狀態更新,同時把這些任務從Master中刪除。
3.遍歷該Agent上的所有Executor,并刪除。
4.觸發Recind Offer,把這個Agent上已經分配給Scheduler的Offer撤銷。
5.把這個Agent從master的Replicated log中刪除(Mesos Master依賴Replicated log中的部分持久化集群配置信息進行failer over/recovery)。

使用Marathon可以方便的發布及部署應用###

目前有很多基于MesosFramework的開源框架,例如Marathon。我們在生產環境中已經使用了Marathon框架,一般用它來運行long-run service/application,依靠marathon來管理應用服務,它支持應用服務自動/手動起停、水平擴展、健康檢查等。我們依靠jenkins+docker+marathon完成服務的自動化發布及部署。

Why Juice

下面來講下我基于MesosFramework所開發的一套框架-Juice。(開源地址:https://github.com/HujiangTechnology/Juice.git)
在開發Juice之前,我公司所有的音視頻轉碼切片任務都是基于一個叫TaskCenter的隊列分配框架,該框架并不具備分布式調度的功能(資源分配),所以集群的資源利用率一直是個問題,所以,我們想開發一套基于以下三點的新框架來替代老的TaskCenter。

1.一個任務調度型的框架,需要對資源(硬件)盡可能的做到最大的利用率。
2.框架必須可運行各種類型的任務。
3.平臺必須是穩定的。

憑借對Marathon的使用經驗,以及對于Mesos相關文檔的查閱,我們決定基于MesosFramework來開發一套任務調度型的框架,Mesos與Framework的特性剛才已經說過了,而我們將所需要執行的任務封在Docker中去執行,那么對于框架本身來說他就不用關心任務的類型了,這樣業務的邊界和框架的邊界就變得很清晰,對于Framework來說,運行一個Docker任務也很方便,剛才說過Mesos內置了DockerExecutor可以完美的啟動Docker任務,這樣,我們的框架在Agent端所需要的開發就非常的少。
Juice框架在這樣的背景下開始了開發的歷程,我們對于它的定位是一套分布式任務云系統,這里為什么要稱為任務云系統呢?因為對于調用者來說,使用Juice,只要做2件事情:把要做的任務打成Docker鏡像并Push到docker倉庫中,然后向Juice提交一個Docker類型的任務。其它的,交給Juice去完成就可以了,調用者不用關心任務會在哪臺物理機上被執行,只需要關心任務本身的執行狀況。

Juice架構

除此,Juice有以下一些特點,Juice框架分為Juice-Rest(Juice交互API層,可以完成外界對于Juice Task的CRUD操作)和Juice-Service(Juice核心層,負責與MesosMaster之間的交互,資源分配、任務提交、任務狀態更新等),在一套基于Juice框架的應用系統中,通常部署1-N個Juice-Rest(取決于系統的TPS),以及N個Juice-Service(Juice-Service分主從模式,為1主多從,by zookeeper),對于同一個Mesos集群來說,可以部署1-N套Juice框架,以FrameworkID來區分,需要部署多套的話在Juice-Service的配置文件中設置mesos.framework.tag為不同的值即可。

Juice.png

Juice-Rest參數設置

Juice-Rest采用Spring-Boot編寫(Juice-API接口參見:https://github.com/HujiangTechnology/Juice/blob/master/doc/api_document.md), 處理外界發起的對任務CURD操作,當提交一個任務到Juice-Rest時,需要設置一些參數,比如:

example to run docker:
{
    "callbackUrl":"http://www.XXXXXXXX.com/v5/tasks/callback",
    "taskName":"demo-task",
    "env":{"name":"environment","value":"dev"},
    "args":["this is a test"],
        "container":{
            "docker":{
                "image":"dockerhub.XXXX.com/demo-slice"
        },
        "type":"DOCKER"
    }
}

其中Container中的type目前僅支持'Docker',我們沒有加入'Mesos'類型的Container模式是因為目前項目組內部的服務已經都基于Docker化,但是預留了'Mesos'類型,在未來可以支持'Mesos'類型的任務。
commands模式支持運行Linux命令行命令和Shell腳本,比如:

"commands":"/home/app/entrypoint.sh"

這里支持Commands模式的原因有2點
1.有時調用方可能只是想在某臺制定的Agent上運行一個腳本。
2.公司內部其他有些項目組還在使用Jar包啟動的模式,預留一個Shell腳本的入口可以對這些項目產生支持。
env設置示例,設置運行的任務環境為dev:

"env":{"name":"environment","value":"dev"}

args設置示例,設置文件路徑:

"args":["/tid/res/test.mp4"]

PS:使用Commands模式時不支持args選項。
此外,Juice-Rest支持用戶自定義資源大小(目前版本僅支持自定義CPU、內存),如需要指定資源,需在請求接口中配置resources對象,否則,將會使用默認的資源大小運行任務。Juice-Rest支持資源約束(constrains),即滿足在特定Host或Rack_id標簽的Agent上運行某任務,設置接口中constrains對象字段即可。

Juice所使用的中間件(MQ、DB等)

下面講一下Rest層的處理模型,當外界發起一個任務請求時,Juice-Rest接收到任務后,并不是直接提交到Juice-Service層,而是做了以下2件事情:
1.將任務放入MQ中。(目前Juice使用Redis-List來作為默認的Queue,采用LPUSH、RPOP的模式,先進先出,為什么選擇使用Redis中的List作為Queue而沒有選擇其他諸如rabbitmq、kafka這些呢,首先,Redis相對來說是一個比較輕量級的中間件,而且HA方案比較成熟,同時,在我看來,隊列中的最佳任務wait數量是應該<10000的,否則,任務的執行周期將會被拉得很長,以我公司的Juice系統來舉例,由于處理的都是耗時的音視頻轉碼切片任務,通常情況下10000個任務的排隊等候時間會在幾個小時以上,所以當任務數量很大時,考慮擴大集群的處理能力而不是把過多的任務積壓在隊列中,基于此,選擇Redis-List相對其他的傳統MQ來說沒有什么劣勢。考慮到一些特殊情況,Juice也允許用戶實現CacheUtils接口使用其他MQ替換Redis-List)。
2.紀錄Tasks信息到Juice-Tasks表中,相當于數據落地。后續版本會基于此實現任務重試機制(目前的1.1.0內部開發版本已實現),或者在failover切換后完成任務恢復,此功能在后續1.2.0版本中考慮加入。(目前數據庫使用MySql)。
當Juice-Rest接受并完成任務提交后會返回給調用方一個Long型18位數字(JuiceID,全局唯一)作為憑證號。當任務完成后,Juice-Rest會主動發起回調請求,通知調用方該任務的運行結果(以此JuiceID作為業務憑證),前提是調用方必須設置callbackUrl。同時,調用方可以使用該JuiceID對進行任務查詢、終止等操作。
另外,在Juice-Rest層單獨維護一個線程池來處理由Juice-service端返回的任務狀態信息Task_status。

Juice-Service內部處理流程

Juice-Service可以看作是一個MesosFramework,與Master之間通訊協議采用ProtoBuf,每一種事件請求都通過對應類型的Call產生,這里Juice-Service啟動時會發出Subscribe請求,由SubscribeCall()方法產生requestBody,采用OKHTTP發送,并維持與Master之間的長連接

private void connecting() throws Exception {
        InputStream stream = null;
        Response res = null;

        try {
            Protos.Call call = subscribeCall();
            res = Restty.create(getUrl())
                    .addAccept(protocol.mediaType())
                    .addMediaType(protocol.mediaType())
                    .addKeepAlive()
                    .requestBody(protocol.getSendBytes(call))
                    .post();

            streamId = res.header(STREAM_ID);
            stream = res.body().byteStream();
            log.info("send subscribe, frameworkId : " + frameworkId + " , url " + getUrl() + ", streamId : " + streamId);
            log.debug("subscribe call : " + call);
            if (null == stream) {
                log.warn("stream is null");
                throw new DriverException("stream is null");
            }
            while (true) {
                int size = SendUtils.readChunkSize(stream);
                byte[] event = SendUtils.readChunk(stream, size);

                onEvent(event);
            }
        } catch (Exception e) {
            log.error("service handle error, due to : " + e);
            throw e;
        } finally {
            if (null != stream) {
                stream.close();
            }
            if (null != res) {
                res.close();
            }
            streamId = null;
        }
}

之后便進入while循環,當Master端的通知事件發生時,調用onEvent()方法執行。
Mesos的回調事件中,需要特別處理的主要事件由以下幾種:
1.SUBSCRIBED:Juice框架在接收到此事件后將注冊到Master中的FrameworkID紀錄到數據庫juice_framework表中。
2.OFFERS:當Juice-Service接收到該類型事件時,便會進入資源/任務分配環節,分配任務資源并提交到MesosMaster。
3.UPDATE:當Agent處理完任務時,任務會由Executor->Agent->Master->Juice-Service來完成任務的狀態通知。Juice-Service會將結果塞入result-list中。
4.ERROR:框架產生問題,通常這樣的問題分兩種,一種是比較嚴重的,例如Juice-Service使用了一個已經被Master端移除的FrameworkID,則Master會返回"framework has been removed"的錯誤信息,Juice-Service此時會拋出UnrecoverException錯誤:

throw new UnrecoverException(message, true)

Juice-Service在處理UnrecoverException類的錯誤時會Reset服務,當第二個參數為True時,會重新生成一個新的FrameworkID。
而當其他類型的錯誤,比如Master和Juice-Service之間的長鏈接中斷,僅僅Reset服務。

下面我想詳細來說說第二步,我們先來看下'OFFERS'請求處理代碼段:

private void onEvent(byte[] bytes) {
    ....
    switch (event.getType()) {
            ...
            case OFFERS:
                try {
                    event.getOffers().getOffersList().stream()
                            .filter(of -> {
                                if (SchedulerService.filterAndAddAttrSys(of, attrMap)) {
                                    return true;
                                }
                                declines.add(of.getId());
                                return false;
                            })
                            .forEach(
                                    of -> {
                                        List<TaskInfo> tasks = newArrayList();
                                        String offerId = of.getId().getValue();
                                        try {
                                            SchedulerService.handleOffers(killMap, support, of, attrMap.get(offerId), declines, tasks);
                                        } catch (Exception e) {
                                            declines.add(of.getId());
                                            tasks.forEach(
                                                    t -> {
                                                        AuxiliaryService.getTaskErrors()
                                                                .push(new TaskResult(com.hujiang.juice.common.model.Task.splitTaskNameId(t.getTaskId().getValue())
                                                                        , ERROR, "task failed due to exception!"));
                                                    }
                                            );
                                            tasks.clear();
                                        }
                                        if (tasks.size() > 0) {
                                            AuxiliaryService.acceptOffer(protocol, streamId, of.getId(), frameworkId, tasks, getUrl());
                                        }
                                    }
                            );

                    if (declines.size() > 0) {
                        AuxiliaryService.declineOffer(protocol, streamId, frameworkId, SchedulerCalls.decline(frameworkId, declines), getUrl());
                    }
                    long end = System.currentTimeMillis();
                } finally {
                    declines.clear();
                    attrMap.clear();
                }
                break;
            ...     
    }   
}

該段代碼是分配Offer-tasks的核心代碼,來看幾個方法:
1.SchedulerService.filterAndAddAttrSys(),該方法作用是過濾不符合的OFFER,我們知道在Mesos的Agent中是可以通過配置Attr來使一些機器跑特殊的任務,而這里的過濾正是基于該特性,比如我們設置了該Juice-Service只使用包含以下Attr屬性的資源時(在配置文件application.properties中)

mesos.framework.attr=lms,qa,mid|big

經過了SchedulerService.filterAndAddAttrSys()方法的過濾,符合以上attr的資源會被選取執行任務。同時不符合的Offer會加入declines List,通過AuxiliaryServic.declineOffer()一次性發送給Master告知忽略。
Agent的attr設置通過/etc/mesos-slave/attributes來設置。這個文件通常為這樣的:

cat /etc/mesos-slave/attributes

bz:xx;
env:xx;
size:xx;
rack_id:xx;
dc:xx

2.SchedulerService.handleOffers(),該方法實現了原先MesosFramework中的resourceOffer的功能,對Offer進行Tasks分配,最后產生TaskInfo List,由AuxiliaryService.acceptOffer()發送給Master通知處理任務。
注意:Master在發送完Offer事件通知后會一直處于wait狀態,直到Framework端調用Accept call(AuxiliaryService.acceptOffer())或Decline call(AuxiliaryServic.declineOffer())來告知Master資源是否使用后才會通知下一個Framework去分配資源。(默認Master會一直等待,如果沒有通知,則Mesos集群中的資源利用率將可能達到100%,可以通過在Master端設置Timeout來避免這個問題。)
在Juice-Service內部,當SchedulerDriver與Master產生交互后,Juice-Service的處理邏輯由SchedulerService以及AuxiliaryService來實現。
SchedulerService處理Juice的主要邏輯,比如資源分配算法、任務優先級算法,所有Master回調事件處理方法都定義在SchedulerService中。
AuxiliaryService維護幾組線程池,完成各自任務,剛才看到的AuxiliaryService.acceptOffer()和AuxiliaryServic.declineOffer(),都是通過調用AuxiliaryServic中的send-pool去完成call的發送,另外還有一些管理類的任務(比如實時查詢任務狀態、終止正在運行的任務等等)通過auxiliary-pool去完成。所以,AuxiliaryServic的調用都是異步的。

flow.png

Juice中各種隊列的功能介紹

剛才介紹了Juice的任務在JuiceRest提交時是被放入了一個MQ中,這個MQ在Juice-Service中被稱為juice.task.queue。除此之外,還有另外幾個MQ,分別是juice.task.retry.queue、juice.task.result.queue、juice.management.queue。下面來分別說說這些Queue的用處。
juice.task.retry.queue:Juice-Service在取任務時是按照每一個Offer輪詢分配的,當一個Offer在分配資源時,假如從MQ中R-POP出來的任務不滿足該Offer時(比如need-resources大于該Offer的max offer value時,或者存在constrains,當前的offer和指定執行任務的offer不match時),這時,Juice-Service的做法是將當前任務放入juice.task.retry.queue中,等待下一次Offer分配時,優先從juice.task.retry.queue獲取任務并分配,這里涉及到Juice內部獲取任務Queue的優先級,我用了一個比較簡單的方式,即每次分配一個新的Offer資源時,先從juice.task.retry.queue中取出一定數目的任務(CACHE_TRIES = 5),當還有剩余資源時,則從juice.task.queue中取任務,直到撐滿這個Offer。另外,處于juice.task.retry.queue會有淘汰機制,目前的任務淘汰機制遵循2點,當先觸發以下某一項時,則該任務會認為失敗,任務的Task_status被設置為Task_Failed,放入juice.task.result.queue,任務的淘汰算法如下:

1.過期時間淘汰制,任務處于juice.task.result.queue的時長>TASK_RETRY_EXPIRE_TIME,則淘汰(DEFAULT_TASK_RETRY_EXPIRE_TIME = 86400秒)。
2.大于最大檢索次數,任務被取出檢索但沒有被執行達到最大檢索次數>MAX_RESERVED,則淘汰(DEFAULT_MAX_RESERVED = 1024)。

juice.task.result.queue:任務結果隊列,Juice-Service在得到一個任務的狀態后(不一定是最終狀態),將任務的TaskResult對象放入juice.task.result.queue,Juice-Rest端從該隊列取出TaskResult,如果已經是任務的最終狀態,比如Task_Finished或者Task_Failed,則通過外部在提交任務時所填寫的callbackUrl回調調用方告知任務狀態。
juice.management.queue:管理類隊列,支持放入Reconcile類或Kill類的任務,由AuxiliaryService發起任務的查詢同步或Kill一個正在執行的任務。

通過SDK提交一個任務###

目前開源的Juice版本,已經提供了完整的SDK來完成對于Juice-Rest之間的交互,以下是提交一個docker任務的示例:

總結及未來

目前Juice 1.1.0開源版本已經處于測試階段,新版本除修復一些Bug之外,還增加了2個新功能:

1.增加了任務插隊功能,可以通過在傳入參數中設置priority=1來提高一個任務的執行優先級,該任務會被置于處理隊列的最前端。
2.任務失敗自動重試功能,設置傳入參數retry=1,任務失敗會自動重試,最多重試3次。

面對復雜的業務需求,Juice目前的版本還有一些特性/功能不支持,對于此,最好的方式是請大家Fork這個項目的Git,或直接聯系本人,大家一起來把Juice做好。

 @Test
    public void submitsDocker() {
        Submits submitsDocker = Submits.create()
                .setDockerImage("dockerhub.XXXX.com/demo-slice")
                .setTaskName("demo-slice")
                .addArgs("/10002/res/L2.mp4")
                .addEnv("environment", "dev")
                .addResources(2.0, 2048.0);

        Long taskId = JuiceClient.create("http://your-juice-rest-host/v1/tasks", "your-system-id-in-string")
                .setOperations(submitsDocker)
                .handle();

        if(null != taskId) {
            System.out.println("submitsDocker, taskId --> " + taskId);
        }
    }

Q&A:

Q.juice與elastic job的差異
A.我本身對于elastic job并不算太熟悉,就隨便說幾點,如果有錯還請各位糾正:
首先juice與elastic-job-cloud都基于mesos,資源-任務分配這塊elastic-job用了Fenzo(netflix),而juice是自己開發的調度算法。
juice在作業調用時不需要作業注冊,只要上傳任務的鏡像(Docker)到倉庫及任務觸發。而elastic-job需要注冊作業。
juice在Rest-Api接口上近乎完全和marathon一致,方便一些使用慣marathon部署service的用戶。
juice目前版本并不支持作業分片。

Q.能詳細介紹下任務資源分配這一塊的算法嗎?
A.之前已經簡單介紹過了,通過接收'OFFERS'事件觸發相關任務-資源分配的代碼塊。
由于得到的Offer對象實際為一個列表,處理邏輯會循環為每一個Offer分配具體的任務,而每個Offer的任務列表總資源(CPU,Memory等)必需小于Offer resources * RESOURCES_USE_THRESHOLD(資源使用閥值,可通過配置文件resources.use.threshold設置,默認0.8),每分配完一個Offer的task_infos后,便生成Accept Call由發送線程池進行發送處理,整個過程都是異步非阻塞的。

Q.所有的任務都存檔在docker里面對于一些臨時的任務如何處理?
A.臨時的任務確實會產生一些垃圾的鏡像,需要定期對Docker倉庫進行清理,一般設置清理周期為1個月。

Q.任務系統是是否有幫助用戶完成docker封裝的操作?
A.目前沒有,所以使用者必需會一些Docker的基本操作,至少要會打鏡像,提交鏡像等。當然,像一些Docker的設置,比如掛載volume,網絡(bridge、host)等可以在提交任務時通過參數設置。

Q.Mesos和kubernetes的優劣勢是什么?
A.其實我主要使用Mesos,Mesos相對K8S應該是一套更重的系統,Mesos更像是個分布式操作系統,而K8S在容器編排方面更有優勢(Pod之類)。

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

推薦閱讀更多精彩內容

  • 摘要 為了滿足渲染、基因測序等計算密集型服務的需求,UCloud 已推出了“計算工廠”產品,讓用戶可以快速創建大量...
    UCloud云計算閱讀 763評論 0 1
  • 滬江任務調度系統的演化http://mp.weixin.qq.com/s?src=3&timestamp=1500...
    葡萄喃喃囈語閱讀 351評論 0 1
  • 21XX年,科學家們在人體大腦的研究中取得了極大的進步! 每個人都能開發出一種異能,這種玄之又玄的東西沒有人能掌控...
    癮輪子閱讀 390評論 0 1
  • 1.每個人心中都有一個風箏,不管那意味著什么,讓我們勇敢地去追。 2.可是人就是這樣,總會活在某個時限內,那里的世...
    菱然閱讀 548評論 0 0
  • 期待了很久的正面管教課程,終于發現四月發布了網絡教學的發現,索性第一時間報名參加。寶寶現在十個月大,出現的問題并不...
    M唯一_bcfd閱讀 770評論 3 3