Dubbo服務(wù)暴露原理

1.概述

RPC作為分布式系統(tǒng)中不可或缺的中間件,在業(yè)界已經(jīng)具有相當(dāng)成熟的技術(shù)實現(xiàn)攒钳,其中Dubbo應(yīng)用得特別廣泛,本文將對Dubbo服務(wù)暴露的流程進行介紹。在正式進入Dubbo原理探究之前中狂,需要先弄清楚RPC的基本模型:

RPC原理.png

consumer代表服務(wù)調(diào)用方,provider代表服務(wù)提供方扑毡,registry代表注冊中心吃型。當(dāng)服務(wù)提供方啟動時會將自己的信息(服務(wù)ip,port等)記錄在注冊中心僚楞,這樣在調(diào)用方調(diào)用的時候,會先從注冊中心獲取到提供方的基本信息枉层,然后發(fā)送網(wǎng)絡(luò)請求給provider完成調(diào)用泉褐;同時consumer在啟動的時候,會向注冊中心訂閱消息鸟蜡,這樣就能在provider發(fā)生變更的時候獲取到最新的信息膜赃,保證請求路由到正確的provider

2.Dubbo服務(wù)暴露概覽

Dubbo服務(wù)暴露就是指providerregistry注冊的過程揉忘,以zookeeper作為注冊中心跳座,netty作為通訊框架,本文基于2.6.x版本代碼分析泣矛。使用Dubbo官方提供的demo運行疲眷,dubbo-demo-provider配置文件如下:

    <!-- provider's application name, used for tracing dependency relationship -->
    <dubbo:application name="demo-provider"/>

    <!-- use multicast registry center to export service -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <!-- use dubbo protocol to export service on port 20880 -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- service implementation, as same as regular local bean -->
    <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>

    <!-- declare the service interface to be exported -->
    <dubbo:service proxy="jdk" interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>

服務(wù)暴露的整個過程大致可以分為四個階段:

  • 創(chuàng)建invoker對象并對其加工
  • 開啟netty服務(wù),用于后續(xù)接收消費方發(fā)起的網(wǎng)絡(luò)請求
  • 獲取zookeeper連接并將服務(wù)注冊成為zookeeper上的節(jié)點(服務(wù)引用的時候通過獲取節(jié)點則能夠找到provider然后建立網(wǎng)絡(luò)連接)
  • 返回Exporter對象并放入ServiceConfig中的exporters變量中存儲
    dubbo服務(wù)啟動時序圖.png

3.創(chuàng)建invoker對象并對其加工

ServiceConfig類中會先調(diào)用exportLocal完成本地暴露您朽,然后調(diào)用proxyFactory.getInvoker獲取invoker對象狂丝,先關(guān)注下該類中的靜態(tài)變量proxyFactory,它會調(diào)用ExtensionLoader中的getAdaptiveExtension方法完成初始化,該類是Dubbo框架自身實現(xiàn)的一種SPI機制几颜,能夠根據(jù)配置文件靈活選擇接口的具體實現(xiàn)倍试。

package com.alibaba.dubbo.config;


/**
 * ServiceConfig
 *
 * @export
 */
public class ServiceConfig<T> extends AbstractServiceConfig {

    private static final long serialVersionUID = 3033787999037024738L;

    /**根據(jù)dubbo spi機制加載**/
    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    /**根據(jù)dubbo spi機制加載**/
    private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

    private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();

    private Class<?> interfaceClass;

    // don't export when none is configured
    if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

        // export to local if the config is not remote (export to remote only when config is remote)
        if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
            /**本地暴露**/
            exportLocal(url);
        }
        // export to remote if the config is not local (export to local only when config is local)
        if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
            if (registryURLs != null && !registryURLs.isEmpty()) {
                for (URL registryURL : registryURLs) {
                    /**獲取invoker對象**/
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this)
                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            }
        }
    }
}

proxyFactory在初始化的時候會被賦值為ProxyFactory$Adaptive,該類是通過字節(jié)碼增強實現(xiàn)的

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class ProxyFactory$Adaptive implements com.alibaba.dubbo.rpc.ProxyFactory {
    public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg2;
        /**獲取url中的proxy參數(shù)蛋哭,默認為javassist**/
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        /**根據(jù)proxy參數(shù)獲取對應(yīng)的ProxyFactory接口的實現(xiàn)**/
        com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getInvoker(arg0, arg1, arg2);
    }
}

Dubbo針對每一個需要根據(jù)配置決定具體實現(xiàn)的接口都會在運行時生成以$Adaptive后綴結(jié)尾的類县习,目的是能夠根據(jù)配置文件,在運行時靈活地選擇接口的具體實現(xiàn)谆趾,例如:String extName = url.getParameter("proxy", "javassist")這行代碼躁愿,如果url中的proxy屬性為空即沒有在配置文件中指定,則默認使用javassist作為extName的值棺妓,加載出JavassistProxyFactory類作為ProxyFactory接口的缺省實現(xiàn)攘已。因為這里配置<dubbo:service proxy="jdk" interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>,所以會加載JdkProxyFactory怜跑。但通過debug發(fā)現(xiàn)样勃,com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName)返回的是StubProxyFactoryWrapper,原因是ProxyFactory接口默認有一個包裝類實現(xiàn)性芬,無論是JavassistProxyFactory還是JdkProxyFactory都是在StubProxyFactoryWrapper中通過構(gòu)造函數(shù)完成proxyFactory屬性的設(shè)置峡眶,當(dāng)ProxyFactory$Adaptive中執(zhí)行extension.getInvoker(arg0, arg1, arg2)的時候,先會調(diào)用到StubProxyFactoryWrappergetInvoker方法植锉,該方法的實現(xiàn)為proxyFactory.getInvoker(proxy, type, url)辫樱,所以最后會調(diào)用到JdkProxyFactorygetInvoker方法。對于這種有包裝類實現(xiàn)的接口俊庇,$Adaptive會優(yōu)先加載出包裝類狮暑,而根據(jù)配置所對應(yīng)的具體實現(xiàn)則是通過構(gòu)造函數(shù)的形式作為包裝類的屬性被注入,在調(diào)用的時候先調(diào)用包裝類從而間接調(diào)用到配置所對應(yīng)的實現(xiàn)辉饱,這里使用了裝飾器模式搬男,可以在調(diào)用之間增加額外的處理。整個流程可以歸納如下:

  • ProxyFactory$Adaptive 獲取extension
  • 初始化 StubProxyFactoryWrapper彭沼,構(gòu)造函數(shù)設(shè)置proxyFactory屬性為JdkProxyFactory
public class StubProxyFactoryWrapper implements ProxyFactory {

    private final ProxyFactory proxyFactory;

   /**構(gòu)造函數(shù)中設(shè)置proxyFactory值為配置所對應(yīng)的接口實現(xiàn)**/
    public StubProxyFactoryWrapper(ProxyFactory proxyFactory) {
        this.proxyFactory = proxyFactory;
    }

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException {
        return proxyFactory.getInvoker(proxy, type, url);
    }

}
  • ProxyFactory$Adaptive調(diào)用extension.getInvoker等同于StubProxyFactoryWrapper調(diào)用getInvoker

對應(yīng)時序圖如下所示:


getInvoker.png

返回invoker對象后缔逛,進入到protocol.export方法,protocol初始化為Protocol$Adaptive姓惑,Protocol接口有兩個包裝類ProtocolListenerWrapperProtocolFilterWrapper褐奴,顧名思義監(jiān)聽器與過濾器,該過程會先調(diào)用ProtocolListenerWrapper于毙,該類的protocol屬性在初始化的時候會被設(shè)置為ProtocolFilterWrapper敦冬,在ServiceConfig中調(diào)用protocol.export時會直接進入到ProtocolListenerWrapper中的protocol.export(invoker)方法,接著進入ProtocolFilterWrapper中的protocol.export(invoker)方法唯沮,ProtocolFilterWrapper初始化時會設(shè)置protocol屬性為RegistryProtocol匪补,因此該過程最終會調(diào)用到RegistryProtocolexport方法


/**
 * ListenerProtocol
 */
public class ProtocolListenerWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolListenerWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }

    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            /**ServiceConfig調(diào)用protocol.export直接進入到該方法**/
            return protocol.export(invoker);
        }
        /**RegistryProtocol調(diào)用protocol.export時候進入**/
        return new ListenerExporterWrapper<T>(protocol.export(invoker),
                Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(ExporterListener.class)
                        .getActivateExtension(invoker.getUrl(), Constants.EXPORTER_LISTENER_KEY)));
    }

}

根據(jù)ProtocolFilterWrapper類的命名伞辛,可以看出它擁有與Spring Filter類似的功能,本質(zhì)上是一個過濾器

  /**
 * ListenerProtocol
 */
public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }


    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
           /**ServiceConfig調(diào)用protocol.export直接進入到該方法**/
            return protocol.export(invoker);
        }
       /**RegistryProtocol調(diào)用protocol.export時候進入**/
        return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
    }

}

因為設(shè)置<dubbo:protocol name="dubbo" port="20880"/>夯缺,RegistryProtocol中的protocol會被設(shè)置為DubboRegistry蚤氏,當(dāng)調(diào)用protocol.export(invokerDelegete)的時候,會再次進入到ProtocolListenerWrapper包裝類中踊兜,這次會執(zhí)行new ListenerExporterWrapper<T>


/**
 * ListenerExporter
 */
public class ListenerExporterWrapper<T> implements Exporter<T> {

    private static final Logger logger = LoggerFactory.getLogger(ListenerExporterWrapper.class);

    private final Exporter<T> exporter;

    private final List<ExporterListener> listeners;

    public ListenerExporterWrapper(Exporter<T> exporter, List<ExporterListener> listeners) {
        if (exporter == null) {
            throw new IllegalArgumentException("exporter == null");
        }
        this.exporter = exporter;
        this.listeners = listeners;
        if (listeners != null && !listeners.isEmpty()) {
            RuntimeException exception = null;
            for (ExporterListener listener : listeners) {
                if (listener != null) {
                    try {
                        /**調(diào)用listener的exported方法竿滨,作為hook函數(shù)**/
                        listener.exported(this);
                    } catch (RuntimeException t) {
                        logger.error(t.getMessage(), t);
                        exception = t;
                    }
                }
            }
            if (exception != null) {
                throw exception;
            }
        }
    }

    @Override
    public Invoker<T> getInvoker() {
        return exporter.getInvoker();
    }

    @Override
    public void unexport() {
        try {
            exporter.unexport();
        } finally {
            if (listeners != null && !listeners.isEmpty()) {
                RuntimeException exception = null;
                for (ExporterListener listener : listeners) {
                    if (listener != null) {
                        try {
                            listener.unexported(this);
                        } catch (RuntimeException t) {
                            logger.error(t.getMessage(), t);
                            exception = t;
                        }
                    }
                }
                if (exception != null) {
                    throw exception;
                }
            }
        }
    }

}

重點關(guān)注ListenerExporterWrapper的構(gòu)造函數(shù),該函數(shù)主要完成兩件事捏境,一是賦值exporter變量于游,將外部傳入的Exporter對象保存,二是遍歷外部傳入的ExporterListener列表并調(diào)用其exported方法垫言。此處為擴展點贰剥,Dubbo對于ExporterListener接口只給出了ExporterListenerAdapter這一實現(xiàn)且exported方法實現(xiàn)為空,開發(fā)者可以自己實現(xiàn)該方法以達到擴展的目的筷频,當(dāng)然也可以自己實現(xiàn)ExporterListener接口蚌成,依據(jù)Dubbo SPI機制加載執(zhí)行自定義的業(yè)務(wù)邏輯。除構(gòu)造函數(shù)以外凛捏,unexport也擁有類似的功能担忧,當(dāng)調(diào)用unexport的時候會遍歷之前保存的listeners并調(diào)用其unexport方法。
繼續(xù)回到ProtocolFilterWrapper坯癣,當(dāng)調(diào)用return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER))時瓶盛,會觸發(fā)buildInvokerChain

private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
    Invoker<T> last = invoker;
    List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
    if (!filters.isEmpty()) {
        for (int i = filters.size() - 1; i >= 0; i--) {
            final Filter filter = filters.get(i);
            final Invoker<T> next = last;
            last = new Invoker<T>() {

                @Override
                public Class<T> getInterface() {
                    return invoker.getInterface();
                }

                @Override
                public URL getUrl() {
                    return invoker.getUrl();
                }

                @Override
                public boolean isAvailable() {
                    return invoker.isAvailable();
                }

                @Override
                public Result invoke(Invocation invocation) throws RpcException {
                    /**包裝invoker對象**/
                    return filter.invoke(next, invocation);
                }

                @Override
                public void destroy() {
                    invoker.destroy();
                }

                @Override
                public String toString() {
                    return invoker.toString();
                }
            };
        }
    }
    return last;
}

遍歷通過SPI加載出來的Filter來包裝invoker對象,當(dāng)invoker調(diào)用invoke方法時示罗,就會觸發(fā)Filter中的實現(xiàn)邏輯惩猫,此處使用責(zé)任鏈設(shè)計模式,當(dāng)然開發(fā)者也可以實現(xiàn)自己的Filter來對此處邏輯進行擴展蚜点。整個流程時序圖如下所示:

ServiceConfig -_ RegistryProtocol -_ DubboProtocol.png

4.開啟netty服務(wù)

RegistryProtocol中調(diào)用exporter = new ExporterChangeableWrapper<T>((Exporter<T>) protocol.export(invokerDelegete), originInvoker)進入到DubboProtocol類中的export方法帆锋,

/**
 * dubbo protocol support.
 */
public class DubboProtocol extends AbstractProtocol {

    public static final String NAME = "dubbo";

    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();

        // export service.
        /**獲取key,com.alibaba.dubbo.demo.DemoService:20880**/
        String key = serviceKey(url);
        /**創(chuàng)建exporter對象**/
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        /**保存exporter對象**/
        exporterMap.put(key, exporter);

        //export an stub service for dispatching event
        Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice) {
            String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
                if (logger.isWarnEnabled()) {
                    logger.warn(new IllegalStateException("consumer [" + url.getParameter(Constants.INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }
            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }

        /**創(chuàng)建netty server**/
        openServer(url);
        /**初始化序列化器**/
        optimizeSerialization(url);
        return exporter;
    }

    private void openServer(URL url) {
        // find server.
        /**ip+port**/
        String key = url.getAddress();
        //client can export a service which's only for server to invoke
        boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
        if (isServer) {
            /**從緩存中獲取server對象**/
            ExchangeServer server = serverMap.get(key);
            if (server == null) {
                /**緩存中沒有則創(chuàng)建server**/
                serverMap.put(key, createServer(url));
            } else {
                // server supports reset, use together with override
                server.reset(url);
            }
        }
    }

    private ExchangeServer createServer(URL url) {
        // send readonly event when server closes, it's enabled by default
        url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
        // enable heartbeat by default
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
        String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);

        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);
       /**設(shè)置傳輸協(xié)議為dubbo**/
        url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
        ExchangeServer server;
        try {
            /**netty Server初始化**/
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }
        str = url.getParameter(Constants.CLIENT_KEY);
        if (str != null && str.length() > 0) {
            Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }
        return server;
    }
}

首先會創(chuàng)建DubboExporter對象禽额,并把該對象保存到exporterMap中,其中類:端口會被作為key皮官,例如:com.alibaba.dubbo.demo.DemoService:20880脯倒,openServer中是獲取netty Server的具體邏輯,serverMap中會存放創(chuàng)建好的server捺氢,key是ip:port藻丢,如果在serverMap中沒有存儲server,則調(diào)用createServer方法創(chuàng)建摄乒。

5.獲取zookeeper連接并將服務(wù)注冊成為zookeeper上的節(jié)點

完成Netty Server啟動后悠反,通過RegistryProtocol中的getRegistry方法創(chuàng)建ZookeeperRegistry對象残黑,該對象的構(gòu)造函數(shù)會進行zookeeper的連接。

private Registry getRegistry(final Invoker<?> originInvoker) {
    URL registryUrl = getRegistryUrl(originInvoker);
    return registryFactory.getRegistry(registryUrl);
}

通過返回的ZookeeperRegistry對象斋否,調(diào)用subscribe方法便將服務(wù)注冊成為了zookeeper的節(jié)點梨水,/dubbo/com.alibaba.dubbo.demo.DemoService/providers,值為dubbo://ip:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bean.name=com.alibaba.dubbo.demo.DemoService&dubbo=2.0.2&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=1644&proxy=jdk&side=provider&timestamp=1603026176171茵臭,里面包含服務(wù)的ip疫诽、端口、接口名旦委、接口中包含的方法奇徒、代理模式等等。

6.總結(jié)

整個服務(wù)暴露的過程就是服務(wù)向注冊中心注冊的過程缨硝,除了基本的實現(xiàn)以外摩钙,Dubbo在該過程中還提供了ListenerFilter這兩個擴展點方便開發(fā)者進行定制化的實現(xiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末查辩,一起剝皮案震驚了整個濱河市胖笛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宜肉,老刑警劉巖匀钧,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谬返,居然都是意外死亡之斯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門遣铝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佑刷,“玉大人,你說我怎么就攤上這事酿炸√毙酰” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵填硕,是天一觀的道長麦萤。 經(jīng)常有香客問我,道長扁眯,這世上最難降的妖魔是什么壮莹? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮姻檀,結(jié)果婚禮上命满,老公的妹妹穿的比我還像新娘。我一直安慰自己绣版,他們只是感情好胶台,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布歼疮。 她就那樣靜靜地躺著,像睡著了一般诈唬。 火紅的嫁衣襯著肌膚如雪韩脏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天讯榕,我揣著相機與錄音骤素,去河邊找鬼。 笑死愚屁,一個胖子當(dāng)著我的面吹牛济竹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播霎槐,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼送浊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丘跌?” 一聲冷哼從身側(cè)響起袭景,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闭树,沒想到半個月后耸棒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡报辱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年与殃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碍现。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡幅疼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昼接,到底是詐尸還是另有隱情爽篷,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布慢睡,位于F島的核電站逐工,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏漂辐。R本人自食惡果不足惜泪喊,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望者吁。 院中可真熱鬧,春花似錦饲帅、人聲如沸复凳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽育八。三九已至对途,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間髓棋,已是汗流浹背实檀。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留按声,地道東北人膳犹。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像签则,于是被迫代替她去往敵國和親须床。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345