Sentinel源碼分析----Node分析

Sentinel中有很多類(lèi)型的Node荠卷,例如DefaultNode豪筝、StatisticNode愚墓、ClusterNode输虱、還有個(gè)EntranceNode總共四種類(lèi)型的Node些楣,第一次看的時(shí)候非常懵逼,Node是啥宪睹?四個(gè)Node有什么不同愁茁?

上篇文章中,我們看到StatisticSlot中使用了Node去統(tǒng)計(jì)了請(qǐng)求信息亭病,那么Node應(yīng)該就是做請(qǐng)求統(tǒng)計(jì)用的鹅很,看下Node接口里定義

public interface Node {
    long totalRequest();
    long totalSuccess();
    long blockRequest();
    long totalException();
    long passQps();
    long blockQps();
    long totalQps();
    long successQps();
    long maxSuccessQps();
    long exceptionQps();
    long avgRt();
    long minRt();
    int curThreadNum();
    long previousBlockQps();
    long previousPassQps();
    Map<Long, MetricNode> metrics();
    void addPassRequest(int count);
    void addRtAndSuccess(long rt, int success);
    void increaseBlockQps(int count);
    void increaseExceptionQps(int count);
    void increaseThreadNum();
    void decreaseThreadNum();
    void reset();
    void debug();
}

方法比較多,但是方法名字很清晰罪帖,那么可以得出結(jié)論:

  • 內(nèi)部實(shí)現(xiàn)可以不說(shuō)促煮,但是對(duì)外部來(lái)說(shuō)Node是做為一個(gè)請(qǐng)求數(shù)據(jù)統(tǒng)計(jì)和獲取的載體

另外再看下四個(gè)Node的關(guān)系:


image.png

那么這四個(gè)Node中邮屁,哪個(gè)去實(shí)現(xiàn)了Node接口的方法呢? 通過(guò)代碼看到是StatisticNode實(shí)現(xiàn)了Node接口的所有方法菠齿,也就是說(shuō)佑吝,另外三個(gè)Node有兩種可能的作用:

  1. DefaultNode、ClusterNode绳匀、EntranceNode繼承于StatisticNode芋忿,基于其提供的數(shù)據(jù)統(tǒng)計(jì)和獲取方法,實(shí)現(xiàn)自身一些特殊邏輯
  2. StatisticNode提供了默認(rèn)的方法疾棵,DefaultNode戈钢、ClusterNode、DntranceNode有自身的計(jì)算邏輯陋桂,需要重寫(xiě)這些方法

接下來(lái)通過(guò)具體代碼分析,來(lái)看下到底是哪種情況蝶溶。

EntranceNode

首先在上一篇文章中嗜历,我們從入口開(kāi)始分析了整個(gè)調(diào)用鏈路流程,最先遇到和Node相關(guān)的代碼的時(shí)候抖所,是在ContextUtil#trueEnter

    //#com.alibaba.csp.sentinel.Constants
    public final static DefaultNode ROOT = new EntranceNode(new StringResourceWrapper(ROOT_ID, EntryType.IN),
        Env.nodeBuilder.buildClusterNode());
    //ContextUtil#trueEnter
    protected static Context trueEnter(String name, String origin) {
        Context context = contextHolder.get();
        if (context == null) {
            Map<String, DefaultNode> localCacheNameMap = contextNameNodeMap;
            DefaultNode node = localCacheNameMap.get(name);
            if (node == null) {
                if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) {
                            //....
                } else {
                    node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null);
                    // Add entrance node.
                    Constants.ROOT.addChild(node);

                    Map<String, DefaultNode> newMap = new HashMap<String, DefaultNode>(
                    contextNameNodeMap.size() + 1);
                    newMap.putAll(contextNameNodeMap);
                    newMap.put(name, node);
                    contextNameNodeMap = newMap;
                }
            }
            //....
        }
        return context;
    }

這里可以看到梨州,一個(gè)ContextName會(huì)對(duì)應(yīng)一個(gè)EntranceNode,EntranceNode從字面意思來(lái)看田轧,叫做入口節(jié)點(diǎn)暴匠,也就是說(shuō),一個(gè)上下文開(kāi)始的時(shí)候傻粘,會(huì)創(chuàng)建一個(gè)EntranceNode與其對(duì)應(yīng)每窖,代表該上下文的入口。

另外看到EntranceNode會(huì)掛在ROOT節(jié)點(diǎn)下面弦悉,而ROOT又是一個(gè)EntranceNode節(jié)點(diǎn)窒典,而其是全局唯一的,他代表應(yīng)用的入口節(jié)點(diǎn)稽莉,如下


image.png

ROOT是一個(gè)EntranceNode類(lèi)型的節(jié)點(diǎn)瀑志,他可以?huà)熳庸?jié)點(diǎn),子節(jié)點(diǎn)為上下文節(jié)點(diǎn)污秆,那么上下文節(jié)點(diǎn)下面掛的是啥劈猪?別急,我們繼續(xù)分析

接下看下類(lèi)的定義

public class EntranceNode extends DefaultNode {

    public EntranceNode(ResourceWrapper id, ClusterNode clusterNode) {
        super(id, clusterNode);
    }

    @Override
    public long avgRt() {
        long total = 0;
        long totalQps = 0;
        for (Node node : getChildList()) {
            total += node.avgRt() * node.passQps();
            totalQps += node.passQps();
        }
        return total / (totalQps == 0 ? 1 : totalQps);
    }

    @Override
    public long blockQps() {
        long blockQps = 0;
        for (Node node : getChildList()) {
            blockQps += node.blockQps();
        }
        return blockQps;
    }

    @Override
    public long blockRequest() {
        long r = 0;
        for (Node node : getChildList()) {
            r += node.blockRequest();
        }
        return r;
    }

    @Override
    public int curThreadNum() {
        int r = 0;
        for (Node node : getChildList()) {
            r += node.curThreadNum();
        }
        return r;
    }

    @Override
    public long totalQps() {
        long r = 0;
        for (Node node : getChildList()) {
            r += node.totalQps();
        }
        return r;
    }

    @Override
    public long successQps() {
        long r = 0;
        for (Node node : getChildList()) {
            r += node.successQps();
        }
        return r;
    }

    @Override
    public long passQps() {
        long r = 0;
        for (Node node : getChildList()) {
            r += node.passQps();
        }
        return r;
    }

    @Override
    public long totalRequest() {
        long r = 0;
        for (Node node : getChildList()) {
            r += node.totalRequest();
        }
        return r;
    }

}

可以看到EntranceNode重寫(xiě)了獲取數(shù)據(jù)統(tǒng)計(jì)的方法良拼,獲取的時(shí)候?qū)⑺凶庸?jié)點(diǎn)的數(shù)據(jù)全累加后返回

DefaultNode

第二次遇到與Node相關(guān)的應(yīng)該是調(diào)用鏈中的NodeSelectorSlot战得,在看代碼之前,先看下類(lèi)上的注釋?zhuān)渲凶⑨尷锂?huà)兩個(gè)圖:

                   machine-root
                   /         \
                  /           \
          EntranceNode1   EntranceNode2
                /               \
              /                 \
      DefaultNode(nodeA)   DefaultNode(nodeA)
              |                    |
              +- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);

EntranceNode1和EntranceNode2表示兩個(gè)不同的上下文庸推,而上下文節(jié)點(diǎn)下分別掛了個(gè)DefaultNode

    public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args)
        throws Throwable {

        DefaultNode node = map.get(context.getName());
        if (node == null) {
            synchronized (this) {
                node = map.get(context.getName());
                if (node == null) {
                    node = Env.nodeBuilder.buildTreeNode(resourceWrapper, null);
                    HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size());
                    cacheMap.putAll(map);
                    cacheMap.put(context.getName(), node);
                    map = cacheMap;
                }
                ((DefaultNode)context.getLastNode()).addChild(node);
            }
        }

        context.setCurNode(node);
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }
    // com.alibaba.csp.sentinel.node.DefaultNodeBuilder#buildTreeNode
    public DefaultNode buildTreeNode(ResourceWrapper id, ClusterNode clusterNode) {
        return new DefaultNode(id, clusterNode);
    }

這里與EntranceNode的創(chuàng)建有點(diǎn)類(lèi)似贡避,都是以ContextName為key去保存的痛黎,也就是說(shuō)DefaultNode也是和上下文相關(guān)的節(jié)點(diǎn)。

當(dāng)創(chuàng)建成功后刮吧,會(huì)調(diào)用DefaultNode的addChild方法將創(chuàng)建的DefaultNode掛在某個(gè)節(jié)點(diǎn)下湖饱,我們看下getLastNode返回的是什么

    /**
     * Current processing entry.
     */
    private Entry curEntry;
    public Node getLastNode() {
        if (curEntry != null && curEntry.getLastNode() != null) {
            return curEntry.getLastNode();
        } else {
            return entranceNode;
        }
    }

curEntry是什么?在上篇文章分析到的entryWithPriority方法中杀捻,有如下代碼

        Entry e = new CtEntry(resourceWrapper, chain, context);// 1
        try {
            chain.entry(context, resourceWrapper, null, count, prioritized, args);
        } catch (BlockException e1) {
            e.exit(count, args);
            throw e1;
        } catch (Throwable e1) {
            // This should not happen, unless there are errors existing in Sentinel internal.
            RecordLog.info("Sentinel unexpected exception", e1);
        }

注意看標(biāo)記1這個(gè)位置井厌,這里創(chuàng)建了一個(gè)Entry,并將創(chuàng)建好的上下文對(duì)象作為參數(shù)傳進(jìn)去致讥,看下其構(gòu)造方法

    CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot<Object> chain, Context context) {
        super(resourceWrapper);
        this.chain = chain;
        this.context = context;
        setUpEntryFor(context);
    }

    private void setUpEntryFor(Context context) {
        if (context instanceof NullContext) {
            return;
        }
        this.parent = context.getCurEntry();//1
        if (parent != null) {//2
            ((CtEntry)parent).child = this;//3
        }
        context.setCurEntry(this);//4
    }
  • 1~3:獲取當(dāng)前上下文的curEntry仅仆,如果不為空,則表示當(dāng)前上下文中垢袱,在創(chuàng)建Entry之前就已經(jīng)有一個(gè)Entry被創(chuàng)建過(guò)了墓拜,那么需要設(shè)置父子關(guān)系
  • 4:這個(gè)地方就是我們要找的curEntry初始化的地方

setUpEntryFor方法可能有點(diǎn)難理解,什么情況下parent不為空请契,首先要知道的是只有我們調(diào)用SphU#entry方法的時(shí)候才會(huì)創(chuàng)建一個(gè)Entry咳榜,即Entry代表一個(gè)當(dāng)前調(diào)用的一個(gè)標(biāo)志,假設(shè)我們?cè)谝淮紊舷挛闹姓{(diào)用了多次爽锥,也是可行的涌韩,那么這時(shí)候會(huì)創(chuàng)建多個(gè)Entry,如以下代碼:

        ContextUtil.enter("contextName1");
        Entry entry1 = null;
        try {
            entry1 = SphU.entry("resourceName1");
            System.out.println("run method 1");
            Entry entry2 = null;
            try {
                entry2 = SphU.entry("resourceName2");
                System.out.println("run method 1");
            }finally {
                if (entry2 != null) {
                    entry2.exit();
                }
            }
        } finally {
            if (entry1 != null) {
                entry1.exit();
            }
        }

這種情況下氯夷,entry2的parent就是entry1臣樱,而entry1的parent為空

分析完curEntry的獲取后 ,再回到getLastNode方法腮考,當(dāng)curEntry不為空雇毫,還需要再判斷一下curEntry.getLastNode是否為空,看下其實(shí)現(xiàn)

    //com.alibaba.csp.sentinel.CtEntry#getLastNode
    public Node getLastNode() {
        return parent == null ? null : parent.getCurNode();
    }

如果當(dāng)前Entry有parent踩蔚,則返回其parent對(duì)應(yīng)的節(jié)點(diǎn)嘴拢,如果parent為空,則返回Context對(duì)應(yīng)的EntranceNode
那么在上面的栗子中寂纪,entry1進(jìn)入到NodeSelectorSlot#entry方法的時(shí)候席吴,由于parent為空,所以curEntry != null && curEntry.getLastNode() != null這行代碼為false捞蛋,Context#getLastNode方法返回EntranceNode孝冒,這樣說(shuō)可能不好理解,現(xiàn)在以上面的代碼為例拟杉,一步步分析Entry和Node所構(gòu)成的結(jié)構(gòu)

當(dāng)代碼剛進(jìn)入Entry的時(shí)候庄涡,此時(shí)的結(jié)構(gòu)如下:


image.png
  • EntranceNode1在ContextUtil#trueEnter中被創(chuàng)建且和上下文Context綁定
  • curEntry在entryWithPriority方法中初始化且和上下文Context綁定,curEntry也即使代碼中的entry1

這時(shí)候代碼執(zhí)行到((DefaultNode)context.getLastNode()).addChild搬设,由于curEntry即entry1沒(méi)有parent穴店,所以context.getLastNode()返回的是EntranceNode1撕捍,將創(chuàng)建的DefaultNode掛在其下面,此時(shí)結(jié)構(gòu)如下:

image.png

代碼繼續(xù)走到context.setCurNode(node);curEntry.setCurNode(node)泣洞,執(zhí)行完畢后忧风,此時(shí)結(jié)構(gòu)如下:

image.png

然后entry1繼續(xù)往后執(zhí)行,執(zhí)行完畢后球凰,entry2有執(zhí)行NodeSelectorSlot#entry方法狮腿,此時(shí)結(jié)構(gòu)如下:

image.png

  • curEntry的變化在entryWithPriority方法中處理

代碼繼續(xù)走到((DefaultNode)context.getLastNode()).addChild,這時(shí)候由于curEntry的parent不為空呕诉,那么就會(huì)去到DefaultNode1缘厢,將創(chuàng)建的DefaultNode掛在其下面,執(zhí)行完畢后甩挫,此時(shí)結(jié)構(gòu)如下:

image.png

代碼繼續(xù)走到context.setCurNode(node)贴硫,執(zhí)行完畢后,此時(shí)結(jié)構(gòu)如下:

image.png

好了伊者,到此為止英遭,了解整個(gè)節(jié)點(diǎn)鏈路的構(gòu)建過(guò)程,可以發(fā)現(xiàn)删壮,對(duì)于一個(gè)資源贪绘,在同個(gè)上下文中兑牡,多次調(diào)用entry央碟,會(huì)創(chuàng)建多個(gè)DefaultNode節(jié)點(diǎn),這些節(jié)點(diǎn)依次掛在上下文的入口節(jié)點(diǎn)EntranceNode下面均函,而每個(gè)節(jié)點(diǎn)會(huì)負(fù)責(zé)當(dāng)前上下文中調(diào)用entry后一個(gè)代碼塊的的請(qǐng)求數(shù)據(jù)的統(tǒng)計(jì)亿虽,記住,DefaultNode是與上下文相關(guān)的苞也,假設(shè)是不同上下文洛勉,那么會(huì)呈現(xiàn)之前發(fā)過(guò)的結(jié)構(gòu)

 *                  machine-root
 *                  /         \
 *                 /           \
 *         EntranceNode1   EntranceNode2
 *               /               \
 *              /                 \
 *      DefaultNode(nodeA)   DefaultNode(nodeA)
 *             |                    |
 *             +- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA)

最下面兩個(gè)DefaultNode是在不同上下文中調(diào)用entry所產(chǎn)生的結(jié)構(gòu)(每個(gè)上下文只調(diào)用一次entry)

ClusterNode

終于輪到最后一個(gè)Node了,在NodeSelectorSlot之后如迟,還有個(gè)ClusterBuilderSlot收毫,其中有ClusterNode的處理

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args)
        throws Throwable {
        if (clusterNode == null) {
            synchronized (lock) {
                if (clusterNode == null) {
                    // Create the cluster node.
                    clusterNode = Env.nodeBuilder.buildClusterNode();
                    HashMap<ResourceWrapper, ClusterNode> newMap = new HashMap<ResourceWrapper, ClusterNode>(Math.max(clusterNodeMap.size(), 16));
                    newMap.putAll(clusterNodeMap);
                    newMap.put(node.getId(), clusterNode);

                    clusterNodeMap = newMap;
                }
            }
        }
        node.setClusterNode(clusterNode);

        if (!"".equals(context.getOrigin())) {
            Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
            context.getCurEntry().setOriginNode(originNode);
        }

        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

可以看到這里也有個(gè)Map結(jié)構(gòu),但是殷勘,key是資源此再,而不是像以前那樣是上下文,所以這里就已經(jīng)清楚了玲销,ClusterNode和資源綁定输拇,即使是不同上下文,同一個(gè)資源贤斜,應(yīng)該都是只有一個(gè)ClusterNode策吠,由其進(jìn)行流量統(tǒng)計(jì)

另外逛裤,當(dāng)創(chuàng)建完成后還會(huì)調(diào)用node.setClusterNode(clusterNode);ClusterNode與DefaultNode進(jìn)行關(guān)聯(lián),即不同的DefaultNode都關(guān)聯(lián)了一個(gè)ClusterNode猴抹,這樣我們?cè)诓煌舷挛闹卸伎梢阅玫疆?dāng)前資源一個(gè)總的流量統(tǒng)計(jì)情況

接著再看下DefaultNode重寫(xiě)的方法带族,以其中兩個(gè)為例

    @Override
    public void increaseBlockQps(int count) {
        super.increaseBlockQps(count);
        this.clusterNode.increaseBlockQps(count);
    }

    @Override
    public void increaseExceptionQps(int count) {
        super.increaseExceptionQps(count);
        this.clusterNode.increaseExceptionQps(count);
    }

統(tǒng)計(jì)方法中,除了調(diào)用父類(lèi)(即StatisticNode)來(lái)統(tǒng)計(jì)本身的一個(gè)流量外洽糟,還會(huì)再調(diào)用ClusterNode的相應(yīng)方法統(tǒng)計(jì)整個(gè)資源的一個(gè)流量

OriginNode

OriginNode整個(gè)東西其實(shí)在代碼中沒(méi)有對(duì)應(yīng)的類(lèi)炉菲,只不過(guò)是概念上的,其本身還是StatisticNode坤溃,這個(gè)東西又是什么呢拍霜,在ClusterBuilderSlot中有以下代碼:

if (!"".equals(context.getOrigin())) {
            Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin());
            context.getCurEntry().setOriginNode(originNode);
        }

假設(shè)origin屬性不為空,從通過(guò)origin去獲取一個(gè)Node節(jié)點(diǎn)薪介,然后放到Context中祠饺,getOrCreateOriginNode方法內(nèi)部邏輯比較簡(jiǎn)單,就是通過(guò)origin去獲取一個(gè)StatisticNode汁政,他是與origin屬性綁定的道偷,那么origin是什么呢?

在使用ContextUtil創(chuàng)建上下文的時(shí)候记劈,其實(shí)是可以傳入origin參數(shù)的勺鸦,這個(gè)就是上面的origin,他代表的是請(qǐng)求來(lái)源目木,例如我有三個(gè)dubbo服務(wù)换途,分別是A和B、C刽射,調(diào)用關(guān)系為A->C军拟,B->C那么C在限流的時(shí)候,可以將A和B作為origin傳入誓禁,那么ClusterBuilderSlot就會(huì)為其創(chuàng)建對(duì)應(yīng)節(jié)點(diǎn)懈息,用來(lái)統(tǒng)計(jì)AB服務(wù)對(duì)B服務(wù)調(diào)用的一個(gè)總體情況

總結(jié)

  1. StatisticNode實(shí)現(xiàn)了Node接口,封裝了基礎(chǔ)的流量統(tǒng)計(jì)和獲取方法
  2. EntranceNode代表入口節(jié)點(diǎn)摹恰,每個(gè)上下文都會(huì)有一個(gè)入口節(jié)點(diǎn)辫继,用來(lái)統(tǒng)計(jì)當(dāng)前上下文的總體流量情況
  3. DefaultNode代表同個(gè)資源在不同上下文中各自的流量情況
  4. ClusterNode代表同個(gè)資源在不同上下文中總體的流量情況
  5. OriginNode是一個(gè)StatisticNode類(lèi)型的節(jié)點(diǎn),代表了同個(gè)資源請(qǐng)求來(lái)源的流量情況

為什么需要這樣設(shè)計(jì)俗慈?這和Sentinel后續(xù)的限流降級(jí)等規(guī)則的設(shè)計(jì)有關(guān)姑宽,后續(xù)會(huì)繼續(xù)分析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市姜盈,隨后出現(xiàn)的幾起案子低千,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件示血,死亡現(xiàn)場(chǎng)離奇詭異棋傍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)难审,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)瘫拣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人告喊,你說(shuō)我怎么就攤上這事麸拄。” “怎么了黔姜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵拢切,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我秆吵,道長(zhǎng)淮椰,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任纳寂,我火速辦了婚禮主穗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘毙芜。我一直安慰自己忽媒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布腋粥。 她就那樣靜靜地躺著晦雨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灯抛。 梳的紋絲不亂的頭發(fā)上金赦,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天音瓷,我揣著相機(jī)與錄音对嚼,去河邊找鬼。 笑死绳慎,一個(gè)胖子當(dāng)著我的面吹牛纵竖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播杏愤,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼靡砌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了珊楼?” 一聲冷哼從身側(cè)響起通殃,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后画舌,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體堕担,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年曲聂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霹购。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朋腋,死狀恐怖齐疙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情旭咽,我是刑警寧澤贞奋,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站穷绵,受9級(jí)特大地震影響忆矛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜请垛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一催训、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宗收,春花似錦漫拭、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至匈勋,卻和暖如春礼旅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洽洁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工痘系, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饿自。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓汰翠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親昭雌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子复唤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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