Flink SQL 知其所以然(二)| 自定義 redis 數(shù)據(jù)維表(附源碼)

感謝您的關(guān)注 + 點(diǎn)贊 + 再看灭返,對(duì)博主的肯定庄蹋,會(huì)督促博主持續(xù)的輸出更多的優(yōu)質(zhì)實(shí)戰(zhàn)內(nèi)容=芗恕!埋涧!

1.序篇-本文結(jié)構(gòu)

  1. 背景篇-為啥需要 redis 維表
  2. 目標(biāo)篇-做 redis 維表的預(yù)期效果是什么
  3. 難點(diǎn)剖析篇-此框架建設(shè)的難點(diǎn)、目前有哪些實(shí)現(xiàn)
  4. 維表實(shí)現(xiàn)篇-維表實(shí)現(xiàn)的過(guò)程
  5. 總結(jié)與展望篇

本文主要介紹了 flink sql redis 維表的實(shí)現(xiàn)過(guò)程奇瘦。

如果想在本地測(cè)試下:

  1. 在公眾號(hào)后臺(tái)回復(fù)flink sql 知其所以然(二)| sql 自定義 redis 數(shù)據(jù)維表獲取源碼(源碼基于 1.13.1 實(shí)現(xiàn))
  2. 在你的本地安裝并打開 redis-server棘催,然后使用 redis-cli 執(zhí)行命令 set a "{\"score\":3,\"name\":\"namehhh\",\"name1\":\"namehhh112\"}"
  3. 執(zhí)行源碼包中的 flink.examples.sql._03.source_sink.RedisLookupTest 測(cè)試類,就可以在 console 中看到結(jié)果耳标。

如果想直接在集群環(huán)境使用:

  1. 命令行執(zhí)行 mvn package -DskipTests=true 打包
  2. 將生成的包 flink-examples-0.0.1-SNAPSHOT.jar 引入 flink lib 中即可醇坝,無(wú)需其它設(shè)置。

2.背景篇-為啥需要 redis 維表

2.1.啥是維表?事實(shí)表呼猪?

Dimension Table 概念多出現(xiàn)于數(shù)據(jù)倉(cāng)庫(kù)里面画畅,維表與事實(shí)表相互對(duì)應(yīng)。

給兩個(gè)場(chǎng)景來(lái)看看:

比如需要統(tǒng)計(jì)分性別的 DAU:

  1. 客戶端上報(bào)的日志中(事實(shí)表)只有設(shè)備 id宋距,只用這個(gè)事實(shí)表是沒(méi)法統(tǒng)計(jì)出分性別的 DAU 的轴踱。
  2. 這時(shí)候就需要一張帶有設(shè)備 id、性別映射的表(這就是維表)來(lái)提供性別數(shù)據(jù)谚赎。
  3. 然后使用事實(shí)表去 join 這張維表去獲取到每一個(gè)設(shè)備 id 對(duì)應(yīng)的性別淫僻,然后就可以統(tǒng)計(jì)出分性別的 DAU。相當(dāng)于一個(gè)擴(kuò)充維度的操作壶唤。

https://blog.csdn.net/weixin_47482194/article/details/105855116?spm=1001.2014.3001.5501

比如目前想要統(tǒng)計(jì)整體銷售額:

  1. 目前已有 “銷售統(tǒng)計(jì)表”雳灵,是一個(gè)事實(shí)表,其中沒(méi)有具體銷售品項(xiàng)的金額闸盔。
  2. “商品價(jià)格表” 可以用于提供具體銷售品項(xiàng)的金額悯辙,這就是銷售統(tǒng)計(jì)的一個(gè)維度表。

事實(shí)數(shù)據(jù)和維度數(shù)據(jù)的識(shí)別必須依據(jù)具體的主題問(wèn)題而定迎吵《阕“事實(shí)表” 用來(lái)存儲(chǔ)事實(shí)的度量及指向各個(gè)維的外鍵值。維表用來(lái)保存該維的元數(shù)據(jù)钓觉。

參考:https://blog.csdn.net/lindan1984/article/details/96566626

2.2.為啥需要 redis 維表茴肥?

目前在實(shí)時(shí)計(jì)算的場(chǎng)景中,熟悉 datastream 的同學(xué)大多數(shù)都使用過(guò) mysql\Hbase\redis 作為維表引擎存儲(chǔ)一些維度數(shù)據(jù)荡灾,然后在 datastream api 中調(diào)用 mysql\Hbase\redis 客戶端去獲取到維度數(shù)據(jù)進(jìn)行維度擴(kuò)充瓤狐。

而 redis 作為 flink 實(shí)時(shí)場(chǎng)景中最常用的高速維表引擎,官方是沒(méi)有提供 flink sql api 的 redis 維表 connector 的批幌。如下圖础锐,基于 1.13 版本。

https://ci.apache.org/projects/flink/flink-docs-release-1.13/docs/connectors/table/overview/

1.png

阿里云 flink 是提供了這個(gè)能力的荧缘。但是這個(gè)需要使用阿里云的產(chǎn)品才能使用皆警。有錢人可以直接上苏研。

https://www.alibabacloud.com/help/zh/faq-detail/122722.htm?spm=a2c63.q38357.a3.7.a1227a53TBMuSY

2.png

因此本文在介紹怎樣自定義一個(gè) sql 數(shù)據(jù)維表的同時(shí)昂利,實(shí)現(xiàn)一個(gè) sql redis 來(lái)給大家使用。

3.目標(biāo)篇-做 redis 維表預(yù)期效果是什么

redis 作為維表在 datastream 中的最常用的數(shù)據(jù)結(jié)構(gòu)就是 kv张弛、hmap 兩種绸罗。本文實(shí)現(xiàn)主要實(shí)現(xiàn) kv 結(jié)構(gòu)意推,map 結(jié)構(gòu)大家可以拿到源碼之后進(jìn)行自定義實(shí)現(xiàn)。也就多加幾行代碼就完事了珊蟀。

預(yù)期效果就如阿里云的 flink redis:

下面是我在本地跑的結(jié)果菊值,先看看 redis 中存儲(chǔ)的數(shù)據(jù),只有這一條數(shù)據(jù),是 json 字符串:

9.png

下面是預(yù)期 flink sql:

CREATE TABLE dimTable (
    name STRING,
    name1 STRING,
    score BIGINT  -- redis 中存儲(chǔ)數(shù)據(jù)的 schema
) WITH (
    'connector' = 'redis', -- 指定 connector 是 redis 類型的
    'hostname' = '127.0.0.1', -- redis server ip
    'port' = '6379', -- redis server 端口
    'format' = 'json' -- 指定 format 解析格式
    'lookup.cache.max-rows' = '500', -- guava local cache 最大條目
    'lookup.cache.ttl' = '3600', -- guava local cache ttl
    'lookup.max-retries' = '1' -- redis 命令執(zhí)行失敗后重復(fù)次數(shù)
)
SELECT o.f0, o.f1, c.name, c.name1, c.score
FROM leftTable AS o
-- 維表 join
LEFT JOIN dimTable FOR SYSTEM_TIME AS OF o.proctime AS c
ON o.f0 = c.name
10.png

結(jié)果如下腻窒,后面三列就對(duì)應(yīng)到 c.name, c.name1, c.score

+I[a, b, namehhh, namehhh112, 3]
+I[a, b, namehhh, namehhh112, 3]
+I[a, b, namehhh, namehhh112, 3]
+I[a, b, namehhh, namehhh112, 3]
+I[a, b, namehhh, namehhh112, 3]
+I[a, b, namehhh, namehhh112, 3]
+I[a, b, namehhh, namehhh112, 3]
+I[a, b, namehhh, namehhh112, 3]
+I[a, b, namehhh, namehhh112, 3]

4.難點(diǎn)剖析篇-目前有哪些實(shí)現(xiàn)

目前可以從網(wǎng)上搜到的實(shí)現(xiàn)昵宇、以及可以參考的實(shí)現(xiàn)有以下兩個(gè):

  1. https://github.com/jeff-zou/flink-connector-redis。 但是其沒(méi)有實(shí)現(xiàn) flink sql redis 維表儿子,只實(shí)現(xiàn)了 sink 表瓦哎,并且使用起來(lái)有比較多的限制,包括需要在建表時(shí)就指定 key-column典徊,value-column 等杭煎,其實(shí)博主覺(jué)得沒(méi)必要指定這些字段,這些都可以動(dòng)態(tài)調(diào)整卒落。其實(shí)現(xiàn)是對(duì) apache-bahir-flink https://github.com/apache/bahir-flink 的二次開發(fā)羡铲,但與 bahir 原生實(shí)現(xiàn)有割裂感,因?yàn)檫@個(gè)項(xiàng)目幾乎重新實(shí)現(xiàn)了一遍儡毕,接口也和 bahir 不同也切。
  2. 阿里云實(shí)現(xiàn) https://www.alibabacloud.com/help/zh/faq-detail/122722.htm?spm=a2c63.q38357.a3.7.a1227a53TBMuSY。 可以參考的只有用法和配置等腰湾。但是有些配置項(xiàng)也屬于阿里自定義的雷恃。

因此博主在實(shí)現(xiàn)時(shí),就定了一個(gè)基調(diào)费坊。

  1. 復(fù)用 connector:復(fù)用 bahir 提供的 redis connnector
  2. 復(fù)用 format:復(fù)用 flink 目前的 format 機(jī)制倒槐,目前這個(gè)上述兩個(gè)實(shí)現(xiàn)都沒(méi)有做到
  3. 簡(jiǎn)潔性:實(shí)現(xiàn) kv 結(jié)構(gòu)。hget 封裝一部分
  4. 維表 local cache:為避免高頻率訪問(wèn) redis附井,維表加了 local cache 作為緩存

5.維表實(shí)現(xiàn)篇-維表實(shí)現(xiàn)的過(guò)程

在實(shí)現(xiàn) redis 維表之前讨越,不得不談?wù)?flink 維表加載和使用機(jī)制。

5.1.flink 維表原理

其實(shí)上節(jié)已經(jīng)詳細(xì)描述了 flink sql 對(duì)于 source\sink 的加載機(jī)制永毅,維表屬于 source 的中的 lookup 表把跨,在具體 flink 程序運(yùn)行的過(guò)程之中可以簡(jiǎn)單的理解為一個(gè) map,在 map 中調(diào)用 redis-client 接口訪問(wèn) redis 進(jìn)行擴(kuò)充維度的過(guò)程沼死。

  1. 通過(guò) SPI 機(jī)制加載所有的 source\sink\format 工廠 Factory
  2. 過(guò)濾出 DynamicTableSourceFactory + connector 標(biāo)識(shí)的 source 工廠類
  3. 通過(guò) source 工廠類創(chuàng)建出對(duì)應(yīng)的 source
7.png
5.png

如圖 source 和 sink 是通過(guò) FactoryUtil.createTableSourceFactoryUtil.createTableSink 創(chuàng)建的

4.png

所有通過(guò) SPI 的 source\sink\formt 插件都繼承自 Factory着逐。

整體創(chuàng)建 source 方法的調(diào)用鏈如下圖。

6.png

5.2.flink 維表實(shí)現(xiàn)方案

先看下博主的最終實(shí)現(xiàn)意蛀。

總重要的三個(gè)實(shí)現(xiàn)類:

  1. RedisDynamicTableFactory
  2. RedisDynamicTableSource
  3. RedisRowDataLookupFunction
8.png

具體流程:

  1. 定義 SPI 的工廠類 RedisDynamicTableFactory implements DynamicTableSourceFactory耸别,并且在 resource\META-INF 下創(chuàng)建 SPI 的插件文件
  2. 實(shí)現(xiàn) factoryIdentifier 標(biāo)識(shí) redis
  3. 實(shí)現(xiàn) RedisDynamicTableFactory#createDynamicTableSource 來(lái)創(chuàng)建對(duì)應(yīng)的 source RedisDynamicTableSource
  4. 定義 RedisDynamicTableSource implements LookupTableSource
  5. 實(shí)現(xiàn) RedisDynamicTableFactory#getLookupRuntimeProvider 方法,創(chuàng)建具體的維表 UDF TableFunction<T>县钥,定義為 RedisRowDataLookupFunction
  6. 實(shí)現(xiàn) RedisRowDataLookupFunction 的 eval 方法太雨,這個(gè)方法就是用于訪問(wèn) redis 擴(kuò)充維度的。

介紹完流程魁蒜,進(jìn)入具體實(shí)現(xiàn)方案細(xì)節(jié):

RedisDynamicTableFactory 主要?jiǎng)?chuàng)建 source 的邏輯:

public class RedisDynamicTableFactory implements DynamicTableSourceFactory {
    ...

    @Override
    public String factoryIdentifier() {
        // 標(biāo)識(shí) redis
        return "redis";
    }

    @Override
    public DynamicTableSource createDynamicTableSource(Context context) {

        // either implement your custom validation logic here ...
        // or use the provided helper utility
        final FactoryUtil.TableFactoryHelper helper = FactoryUtil.createTableFactoryHelper(this, context);

        // discover a suitable decoding format
        // format 實(shí)現(xiàn)
        final DecodingFormat<DeserializationSchema<RowData>> decodingFormat = helper.discoverDecodingFormat(
                DeserializationFormatFactory.class,
                FactoryUtil.FORMAT);

        // validate all options
        // 所有 option 配置的校驗(yàn),比如 cache 類參數(shù)
        helper.validate();

        // get the validated options
        final ReadableConfig options = helper.getOptions();

        final RedisLookupOptions redisLookupOptions = RedisOptions.getRedisLookupOptions(options);

        TableSchema schema = context.getCatalogTable().getSchema();

        // 創(chuàng)建 RedisDynamicTableSource
        return new RedisDynamicTableSource(
                schema.toPhysicalRowDataType()
                , decodingFormat
                , redisLookupOptions);
    }
}

resources\META-INF 文件:

13.png

RedisDynamicTableSource 主要?jiǎng)?chuàng)建 table udf 的邏輯:

public class RedisDynamicTableSource implements LookupTableSource {
    ...

    @Override
    public LookupRuntimeProvider getLookupRuntimeProvider(LookupContext context) {

        // 初始化 redis 客戶端配置
        FlinkJedisConfigBase flinkJedisConfigBase = new FlinkJedisPoolConfig.Builder()
                .setHost(this.redisLookupOptions.getHostname())
                .setPort(this.redisLookupOptions.getPort())
                .build();

        // redis key,value 序列化器
        LookupRedisMapper lookupRedisMapper = new LookupRedisMapper(
                this.createDeserialization(context, this.decodingFormat, createValueFormatProjection(this.physicalDataType)));

        // 創(chuàng)建 table udf
        return TableFunctionProvider.of(new RedisRowDataLookupFunction(
                flinkJedisConfigBase
                , lookupRedisMapper
                , this.redisLookupOptions));
    }
}

RedisRowDataLookupFunction table udf 執(zhí)行維表關(guān)聯(lián)的主要流程:

public class RedisRowDataLookupFunction extends TableFunction<RowData> {
    ...

    /**
     * 具體 redis 執(zhí)行方法
     */
    public void eval(Object... objects) throws IOException {

        for (int retry = 0; retry <= maxRetryTimes; retry++) {
            try {
                // fetch result
                this.evaler.accept(objects);
                break;
            } catch (Exception e) {
                LOG.error(String.format("HBase lookup error, retry times = %d", retry), e);
                if (retry >= maxRetryTimes) {
                    throw new RuntimeException("Execution of Redis lookup failed.", e);
                }
                try {
                    Thread.sleep(1000 * retry);
                } catch (InterruptedException e1) {
                    throw new RuntimeException(e1);
                }
            }
        }
    }


    @Override
    public void open(FunctionContext context) {
        LOG.info("start open ...");

        // redis 命令執(zhí)行器兜看,初始化 redis 鏈接
        try {
            this.redisCommandsContainer =
                    RedisCommandsContainerBuilder
                            .build(this.flinkJedisConfigBase);
            this.redisCommandsContainer.open();
        } catch (Exception e) {
            LOG.error("Redis has not been properly initialized: ", e);
            throw new RuntimeException(e);
        }

        // 初始化 local cache
        this.cache = cacheMaxSize <= 0 || cacheExpireMs <= 0 ? null : CacheBuilder.newBuilder()
                .recordStats()
                .expireAfterWrite(cacheExpireMs, TimeUnit.MILLISECONDS)
                .maximumSize(cacheMaxSize)
                .build();

        if (cache != null) {
            context.getMetricGroup()
                    .gauge("lookupCacheHitRate", (Gauge<Double>) () -> cache.stats().hitRate());


            this.evaler = in -> {
                RowData cacheRowData = cache.getIfPresent(in);
                if (cacheRowData != null) {
                    collect(cacheRowData);
                } else {
                    // fetch result
                    byte[] key = lookupRedisMapper.serialize(in);

                    byte[] value = null;

                    switch (redisCommand) {
                        case GET:
                            value = this.redisCommandsContainer.get(key);
                            break;
                        case HGET:
                            value = this.redisCommandsContainer.hget(key, this.additionalKey.getBytes());
                            break;
                        default:
                            throw new IllegalArgumentException("Cannot process such data type: " + redisCommand);
                    }

                    RowData rowData = this.lookupRedisMapper.deserialize(value);

                    collect(rowData);

                    cache.put(key, rowData);
                }
            };

        }
        ...
    }
}

5.2.1.復(fù)用 bahir connector

如圖是 bahir redis connector 的實(shí)現(xiàn)锥咸。

11.png

博主在實(shí)現(xiàn)過(guò)程中將能復(fù)用的都盡力復(fù)用。如圖是最終實(shí)現(xiàn)目錄细移。

12.png

可以看到目錄結(jié)構(gòu)是與 bahir redis connector 一致的搏予。

其中 redis 客戶端及其配置 是直接復(fù)用了 bahir redis 的。由于 bahir redis 基本都是 sink 實(shí)現(xiàn)弧轧,某些實(shí)現(xiàn)沒(méi)法繼承復(fù)用雪侥,所以這里我單獨(dú)開辟了目錄,redis 命令執(zhí)行器redis 命令定義器精绎,但是也基本和 bahir 一致速缨。
如果你想要在生產(chǎn)環(huán)境中進(jìn)行使用,可以直接將兩部分代碼合并代乃,成本很低旬牲。

5.2.2.復(fù)用 format

博主直接復(fù)用了 flink 本身自帶的 format 機(jī)制來(lái)作為維表反序列化機(jī)制。參考 HBase connector 實(shí)現(xiàn)將 cache 命中率添加到 metric 中搁吓。

public class RedisDynamicTableFactory implements DynamicTableSourceFactory {
    ...
    @Override
    public DynamicTableSource createDynamicTableSource(Context context) {
        ...

        // discover a suitable decoding format
        // 復(fù)用 format 實(shí)現(xiàn)
        final DecodingFormat<DeserializationSchema<RowData>> decodingFormat = helper.discoverDecodingFormat(
                DeserializationFormatFactory.class,
                FactoryUtil.FORMAT);
        ...
    }
}

format 同樣也是 SPI 機(jī)制加載原茅。

源碼公眾號(hào)后臺(tái)回復(fù)flink sql 知其所以然(二)| sql 自定義 redis 數(shù)據(jù)維表獲取。

5.2.3.維表 local cache

local cache 在初始化時(shí)可以指定 cache 大小堕仔,緩存時(shí)長(zhǎng)等擂橘。

this.evaler = in -> {
    RowData cacheRowData = cache.getIfPresent(in);
    if (cacheRowData != null) {
        collect(cacheRowData);
    } else {
        // fetch result
        byte[] key = lookupRedisMapper.serialize(in);

        byte[] value = null;

        switch (redisCommand) {
            case GET:
                value = this.redisCommandsContainer.get(key);
                break;
            case HGET:
                value = this.redisCommandsContainer.hget(key, this.additionalKey.getBytes());
                break;
            default:
                throw new IllegalArgumentException("Cannot process such data type: " + redisCommand);
        }

        RowData rowData = this.lookupRedisMapper.deserialize(value);

        collect(rowData);

        cache.put(key, rowData);
    }
};

6.總結(jié)與展望篇

6.1.總結(jié)

本文主要是針對(duì) flink sql redis 維表進(jìn)行了擴(kuò)展以及實(shí)現(xiàn),并且復(fù)用 bahir redis connector 的配置摩骨,具有良好的擴(kuò)展性通贞。
如果你正好需要這么一個(gè) connector,直接公眾號(hào)后臺(tái)回復(fù)flink sql 知其所以然(二)| sql 自定義 redis 數(shù)據(jù)維表獲取源碼吧仿吞。

6.2.展望

當(dāng)然上述只是 redis 維表一個(gè)基礎(chǔ)的實(shí)現(xiàn)滑频,用于生產(chǎn)環(huán)境還有很多方面可以去擴(kuò)展的。

  1. jedis cluster 的擴(kuò)展:目前 bahir datastream 中已經(jīng)實(shí)現(xiàn)了唤冈,可以直接參考峡迷,擴(kuò)展起來(lái)非常簡(jiǎn)單
  2. aync lookup 維表的擴(kuò)展:目前 hbase lookup 表已經(jīng)實(shí)現(xiàn)了,可以直接參考實(shí)現(xiàn)
  3. 異常 AOP你虹,alert 等
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绘搞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子傅物,更是在濱河造成了極大的恐慌夯辖,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件董饰,死亡現(xiàn)場(chǎng)離奇詭異蒿褂,居然都是意外死亡圆米,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門啄栓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)娄帖,“玉大人,你說(shuō)我怎么就攤上這事昙楚〗伲” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵堪旧,是天一觀的道長(zhǎng)削葱。 經(jīng)常有香客問(wèn)我,道長(zhǎng)淳梦,這世上最難降的妖魔是什么析砸? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮谭跨,結(jié)果婚禮上干厚,老公的妹妹穿的比我還像新娘。我一直安慰自己螃宙,他們只是感情好蛮瞄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谆扎,像睡著了一般挂捅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上堂湖,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天闲先,我揣著相機(jī)與錄音,去河邊找鬼无蜂。 笑死伺糠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的斥季。 我是一名探鬼主播训桶,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼酣倾!你這毒婦竟也來(lái)了舵揭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤躁锡,失蹤者是張志新(化名)和其女友劉穎午绳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體映之,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拦焚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年蜡坊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耕漱。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡算色,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出螟够,到底是詐尸還是另有隱情,我是刑警寧澤峡钓,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布妓笙,位于F島的核電站,受9級(jí)特大地震影響能岩,放射性物質(zhì)發(fā)生泄漏寞宫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一拉鹃、第九天 我趴在偏房一處隱蔽的房頂上張望辈赋。 院中可真熱鬧,春花似錦膏燕、人聲如沸钥屈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)篷就。三九已至,卻和暖如春近忙,著一層夾襖步出監(jiān)牢的瞬間竭业,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工及舍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留未辆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓锯玛,卻偏偏與公主長(zhǎng)得像咐柜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子更振,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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