Kylin官方案例详细剖析及剪枝优化-OLAP商业环境实战

版权声明:本套技术专栏是作者(秦凯新)平时工作的总结和升华,通过从真实商业环境抽取案例进行总结和分享,并给出商业应用的调优建议和集群环境容量规划等内容,请持续关注本套博客。版权声明:禁止转载,欢迎学习。

Kylin官方案例是一个非常经典的案例,包含的技术细节通过深挖能呈现出更具价值的信息,如:数据仓库理论,星型模型,雪花模型,角色扮演维度,维度剪枝等。相信您会通过我的深入剖析,有一种恍然大悟的感觉。官方案例是一个订单案例,其中包含了订单事实表,订单日期表,商品分类维度,账号维度(购买者和销售者)以及区域维度(购买者和销售者)。

一 Kylin官方案例表关系及字段详解

1.1 Kylin官方案例表简要说明

  • KYLIN_SALES是事实表,保存了销售订单的明细信息。各列分别保存着卖家、商品分类、订单金额、商品数量等信息,每一行对应着一笔交易订单。
  • KYLIN_CATEGORY_GROUPINGS是维表,保存了商品分类的详细介绍,例如商品分类名称等。
  • KYLIN_CAL_DT也是维表,保存了时间的扩展信息。如单个日期所、月始、周始、年份、月份等。
  • KYLIN_ACCOUNT也是维表,作为角色扮演维度,包含购买者和开发者账号。
  • KYLIN_COUNTRY包含区域维度,作为角色扮演维度,包含购买者和开发者所在区域信息。

1.2 KYLIN_SALES 事实表字段详解:

image

1.3 KYLIN_CATEGORY_GROUPINGS 字段说明 :

1.3.1 KYLIN_CATEGORY_GROUPINGS主要字段:

  • LEAF_CATEG_ID:主键
  • SITE_ID:外键


    image

1.3.2 KYLIN_CATEGORY_GROUPINGS 对外连接关系:

image

1.3.3 KYLIN_CATEGORY_GROUPINGS 主要字段解释:

image

1.4 KYLIN_COUNTRY 主要字段详解:

image

1.5 KYLIN_ACCOUNT 主要字段详解:

image

1.6 雪花模型关系总览:

image

二 建立模型(Model)

2.1 Kylin 官方模型 model关系图如下:

image

2.2 第一步:雪花和星型模型,建立inner join关系,重要的是确定字段关联,对于角色维度(KYLIN_ACCOUNT和KYLIN_COUNTRY),需要别名处理。

对 “Add Lookup Table” 页面的几点说明:

  1. 数据关系不仅仅是事实表与维度表之间(星型模型),维度表和维度表之间(雪花模型)也可以建立联系;
  2. 表与表之间的连接添加有三种:“Left Join”、“Inner Join”、“Right Join”;
  3. Skip snapshot for this lookup table 选项指的是是否跳过生成 snapshotTable,由于某些 Lookup 表特别大(大于 300M),如果某一个维度的基数比较大 ,可能会导致内存出现 OOM,所以在创建 snapshotTable 的时候会限制原始表的大小不能超过配置的一个上限值(kylin.snapshot.max-mb,默认值300);
  4. 跳过构建 snapshot 的 lookup 表将不能搜索,同时不支持设置为衍生维度(Derived);
  5. 大部分情况下都是使用 “Left Join”,其他两种 Join 方式不是很常用。

2.2.1 每一个 Snapshot 是和一个 Hive 维度表对应的,生成的过程是:

  • 从原始的hive维度表中顺序得读取每一行每一列的值;

  • 使用 TrieDictionary 方式对这些所有的值进行编码(一个值对应一个 Id);

  • 再次读取原始表中每一行的值,将每一列的值使用编码之后的 Id 进行替换,得到了一个只有 Id 的新表;

  • 同时保存这个新表和 Dictionary 对象(Id 和值的映射关系)就能够保存整个维度表;

  • Kylin 将这个数据存储到元数据库中。

      该处Snapshot Table 总结的很好,所以标明引用地址:https://juejin.im/post/5bcf370d6fb9a05cff3255dd
    

2.2.2 雪花模型关系图

image

2.3 第二步:资格维度选择:

在 Dimensions 页面选择可能参与计算的维度,这里被选择的只是在 Cube 构建的时候拥有被选择资格的维度,并不是最后参与 Cube 构建的维度,推荐将维度表中的字段都选择上。
如下展示了Dimensions的选择:

  • 对于KYLIN_SALES,其中SLR_SEGMENT_CD,PRICE,ITEM_COUNT没有选择,所以没有资格参与cubeId构建
image
  • 对于KYLIN_CAL_DT,其中仅选择了部分参与的维度,其他没有资格参与cubeId构建
image
  • 对于KYLIN_CATEGORY_GROUPING,其中仅选择了部分参与的维度,其他没有资格参与cubeId构建
image
  • 对于BUYER_ACCOUNT 与 SELLER_ACCOUNT,全部有资格参与cubeId构建
image
  • 对于BUYER_COUNTRY,只有COUNTRY , NAME 有资格参与cubeId构建


    image
  • 对于SELLER_COUNTRY,只有COUNTRY , NAME 有资格参与cubeId构建

同理如上

2.3.1 资格维度选择结果总览:

image

2.4 第三步: Measures 度量指标选择:

在 Measures 页面选择可能用于计算的度量。一般而言,销售额、流量、温湿度等会作为度量。

image

2.5 第四步:Settings设置

在 Settings 页面可以设置分区以及过滤条件,其中分区是为了系统可以进行增量构建而设计的,目前 Kylin 支持基于日期的分区,在 “Partition Date Column” 后面选择事实表或者维度表中的日期字段,然后选择日期格式即可;过滤条件设置后,Kylin 在构建的时候会选择符合过滤条件的数据进行构建。
需要注意的几点:

  1. 时间分区列可以支持日期或更细粒度的时间分区;
  2. 时间分区列支持的数据类型有 time/date/datetime/integer等;
  3. 过滤条件不需要写 WHERE;
  4. 过滤条件不能包含日期维度。
image

三 构建cube模型

3.1 维度选择

在选择维度时,每一个维度列可以作为普通维度(Normal),也可以作为衍生维度(Derived)。相对于普通维度来说,衍生维度并不参与维度的 Cuboid,衍生维度对应的外键(FK)参与维度 Cuboid,从而降低 Cuboid 数。在查询时,对衍生维度的查询会首先转换为对外键所在维度的查询,因此会牺牲少量性能(大部分情况下可以接受)。

3.1.1 维度剪枝优化

如何进行维度优化,首先请确认你设置的cube维度都是你查询时会使用到的。

目前Kylin可以使用的维度优化手段有以下几种:

  • 聚集组
  • 衍生纬度
  • 强制维度
  • 层次维度
  • 联合维度
  • Extended Column

在一个多维数据集合中,维度的个数决定着维度之间可能的组合数,而每一个维度中成员集合的大小决定着每一个可能的组合的个数,例如有三个普通的维度A、B、C,他们的不同成员数分别为10/100/1000,那么一个维度的组合有2的3次方个,分别是{空、A、B、C、AB、BC、AC、ABC},每一个成员我们称为cuboid(维度的组合),而这些集合的成员组合个数分别为1、10、100、1000、10*100、100 1000、101000和10 *100 *1000。我们称每一个dimension中不同成员个数为cardinatily,我们要尽量避免存储cardinatily比较高的维度的组合。

在上面的例子中我们可以不缓存BC和C这两个cuboid,可以通过计算的方式通过ABC中成员的值计算出BC或者C中某个成员组合的值,这相当于是时间和空间的一个权衡吧。在kylin中存在的四种维度是为了减少cuboid的个数,而不是每一个维度是否缓存的,当前kylin是对所有的cuboid中的所有组合都进行计算和存储的,对于普通的dimension,从上面的例子中可以看出N个维度的cuboid个数为2的N次方,而kylin中设置了一些维度可以减少cuboid个数,当然,这需要使用者对自己需要的维度十分了解,知道自己可能根据什么进行group by。

3.1.2 Mandatory维度

这种维度意味着每次查询的group by中都会携带的,将某一个dimension设置为mandatory可以将cuboid的个数减少一半,如下图:


image

这是因为我们确定每一次group by都会携带A,那么就可以省去所有不包含A这个维度的cuboid了。

3.1.3 hierarchy维度

这种维度是最常见的,尤其是在mondrian中,我们对于多维数据的操作经常会有上卷下钻之类的操作,这也就需要要求维度之间有层级关系,例如国家、省、城市,年、季度、月等。有层级关系的维度也可以大大减少cuboid的个数。如下图:

image

这里仅仅局限于A/B/C是一个层级,例如A是年份,B是季度、C是月份,那么查询的时候可能的组合只有年、xx年的季度、xx年xx季度的xx月,这就意味着我们不能再单独的对季度和月份进行聚合了,例如我们查询的时候不能使用group by month,而必须使用group by year,quart,month。如果需要单独的对month进行聚合,那么还需要再使用month列定义一个单独的普通维度。

3.1.4 derived维度

这类维度的意思是可推导的维度,需要该维度对应的一个或者多个列可以和维度表的主键是一对一的,这种维度可以大大减少cuboid个数,如下图:

image

例如timeid是时间这个维度表的主键,也就是事实表的外键,时间只精确到天,那么year、month、day三列可以唯一对应着一个time_id,而time_id是事实表的外键,那么我们可以指定year、month、day为一个derived维度,实际存储的时候可以只根据timeid的取值决定维度的组合,但这就要求我们在查询的时候使用的group by必须指定derived维度集合中的所有列。
3.联合维度(Joint)
每一个联合维度包括两个或者更多的维度,联合维度内的维度,要么不出现,要么必须一起出现。不同的联合之间不应当有共同的维度

3.1.5 联合维度

联合维度:将几个维度视为一个维度。
适用场景:

  • 1 可以将确定在查询时一定会同时使用的几个维度设为一个联合维度。

  • 2 可以将基数很小的几个维度设为一个联合维度。

  • 3 可以将查询时很少使用的几个维度设为一个联合维度。

优化效果:将N个维度设置为联合维度,则这N个维度组合成的cuboid个数会从2的N次方减少到1。

应用实例:

假设创建一个交易数据的Cube,它具有很多普通的维度,像是交易日期 cal_dt,交易的城市 city,顾客性别 sex_id 和支付类型 pay_type 等。分析师常用的分析方法为通过按照交易时间、交易地点和顾客性别来聚合,获取不同城市男女顾客间不同的消费偏好,例如同时聚合交易日期 cal_dt、交易的城市 city 和顾客性别 sex_id来分组。
聚合组:[cal_dt, city, sex_id,pay_type]
联合维度: [cal_dt, city, sex_id]

Case 1:
SELECT cal_dt, city, sex_id, count(*) FROM table GROUP BY cal_dt, city, sex_id 
则它将从Cuboid [cal_dt, city, sex_id]中获取数据
Case2:

如果有一条不常用的查询:

 SELECT cal_dt, city, count(*) FROM table GROUP BY cal_dt, city
 则没有现成的完全匹配的 Cuboid,Apache Kylin 会通过在线计算的方式,从现有的 Cuboid 中计算出最终结果。

3.1.6 粒度优化

粒度优化对应的是提高Cube的并发度,其设置是在自定义属性中的
一共有三个属性可以提高并发度。
1.kylin.hbase.region.cut(共使用几个分区)
2.kylin.hbase.region.count.min(最少使用几个分区)
3.kylin.hbase.region.count.max(最多使用几个分区)

根据相对应的情况调高最少使用分区,降低最大使用分区,能够有效增加系统的并行度。

3.1.7 RowKey优化

Rowkeys: 是由维度编码值组成。”Dictionary” (字典)是默认的编码方式; 字典只能处理中低基数(少于一千万)的维度;如果维度基数很高(如大于1千万), 选择 “false” 然后为维度输入合适的长度,通常是那列的最大长度值; 如果超过最大值,会被截断。请注意,如果没有字典编码,cube 的大小可能会非常大。
你可以拖拽维度列去调整其在 rowkey 中位置; 位于rowkey前面的列,将可以用来大幅缩小查询的范围。通常建议将 mandantory 维度放在开头, 然后是在过滤 ( where 条件)中起到很大作用的维度;如果多个列都会被用于过滤,将高基数的维度(如 user_id)放在低基数的维度(如 age)的前面。

Kylin 以 Key-Value 的方式将 Cube 存储到 HBase 中,HBase 的 key,也就是 Rowkey,是由各维度的值拼接而成的;为了更高效地存储这些值,Kylin 会对它们进行编码和压缩;每个维度均可以选择合适的编码(Encoding)方式,默认采用的是字典(Dictionary)编码技术;字段支持的基本编码类型如下:

  • dict:适用于大部分字段,默认推荐使用,但在超高基情况下,可能引起内存不足的问题;对于使用该种编码的维度,每个Segment在构建的时候都会为这个维度所有可能的值创建一个字典,然后使用字典中每个值的编号来编码。Dict的优势是产生的编码非常紧凑,尤其在维度值的基数较小且长度较大的情况下,特别节约空间。由于产生的字典是在查询时加载入构建引擎和查询引擎的,所以在维度的基数大、长度也大的情况下,容易造成构建引擎或查询引擎的内存溢出。

  • boolean:适用于字段值为true, false, TRUE, FALSE, True, False, t, f, T, F, yes, no, YES, NO, Yes, No, y, n, Y, N, 1, 0;

  • integer:适用于字段值为整数字符,支持的整数区间为[ -2^(8N-1), 2^(8N-1)];Integer编码需要提供一个额外的参数“Length”来代表需要多少个字节。Length的长度为1~8。如果用来编码int32类型的整数,可以将Length设为4;如果用来编码int64类型的整数,可以将Length设为8。在更多情况下,如果知道一个整数类型维度的可能值都很小,那么就能使用Length为2甚至是1的int编码来存储,这将能够有效避免存储空间的浪费。

  • date:适用于字段值为日期字符,支持的格式包括yyyyMMdd、yyyy-MM-dd、yyyy-MM-dd HH:mm:ss、yyyy-MM-dd HH:mm:ss.SSS,其中如果包含时间戳部分会被截断;将日期类型的数据使用三个字节进行编码,其支持从0000-01-01到9999-01-01中的每一个日期。

  • time:适用于字段值为时间戳字符,支持范围为[ 1970-01-01 00:00:00, 2038/01/19 03:14:07],毫秒部分会被忽略,time编码适用于 time, datetime, timestamp 等类型;Time编码仅仅支持到秒。但是Time编码的优势是每个维度仅仅使用4个字节,这相比普通的长整数编码节约了一半。如果能够接受秒级的时间精度,请选择Time编码来代表时间的维度。

  • fix_length:适用于超高基场景,将选取字段的前 N 个字节作为编码值,当 N 小于字段长度,会造成字段截断,当 N 较大时,造成 RowKey 过长,查询性能下降,只适用于 varchar 或 nvarchar 类型;编码需要提供一个额外的参数“Length”来代表需要多少个字节。该编码可以看作Dict编码的一种补充。对于基数大、长度也大的维度来说,使用Dict可能不能正常工作,于是可以采用一段固定长度的字节来存储代表维度值的字节数组,该数组为字符串形式的维度值的UTF-8字节。如果维度值的长度大于预设的Length,那么超出的部分将会被截断。

  • fixed_length_hex:适用于字段值为十六进制字符,比如 1A2BFF 或者 FF00FF,每两个字符需要一个字节,只适用于 varchar 或 nvarchar 类型。

  • 和Hbase 的RowKey优化类似,在查询的过程中,被用作过滤条件的维度可能放在其他维度的前面,经常出现的维度应该放在前面,基数比较大的维度应该放在前面

      (参考链接:https://juejin.im/post/5bd5c59851882565e031f4be)
    

3.2:官方案例维度选择

3.2.1 KYLIN_SALES维度选择遇到的问题

发现连接键没有勾选,如:LEAF_CATEG_ID 和LSTG_SITE_ID是外键,PART_DT 的是衍生外键也没有选择,因此,意味着在CUBE构建时,只用关心计算维度,连接键虽然不选择,衍生维度还是生效的:

image
3.2.2 KYLIN_CAL_DT 维度选择,发现ART_DT 的是衍生主键没有勾选:
image
3.2.3 KYLIN_CATEGORY_GROUPINGS维度选择遇到的问题

维度选择,发现连接键LEAF_CATEG_ID和SITE_ID没有选择,那么USER_DEFINED_FIELD1和USER_DEFINED_FIELD3作为衍生列,那么衍生列有什么用呢??看我和我朋友的聊天记录:

image
image
3.2.3 BUYER_ACCOUNT 维度选择遇到的问题

发现连接键ACCOUNT_ID没有选择,另外无关列ACCOUNT_SELLER_LEVEL和 ACCOUNT_CONTACT没有被选择,注意ACCOUNT_COUNTRY改名为BUYER_COUNTRY,如下所示:

image
3.2.4 SELLER_ACCOUNT 维度选择遇到的问题

发现连接键ACCOUNT_ID没有选择,另外无关列ACCOUNT_BUYER_LEVEL和 ACCOUNT_CONTACT没有被选择,注意ACCOUNT_COUNTRY改名为SELLER_COUNTRY,如下所示:

image
3.2.5 BUYER_COUNTRY 和 SELLER_COUNTRY 维度选择遇到的问题

发现连接键COUNTRY没有选择,另外 NAME改名为BUYER_COUNTRY_NAME和SELLER_COUNTRY_NAME。


image

3.3 官方案例度量选择

主要的聚合方式有:COUNT、SUM、MIN、MAX、PERCENTILE,下面将详细介绍其他几种聚合方式:

3.3.1 Count Distinct 理论知识:

Apache Kylin提供了两种Count Distinct计算方式,一种是近似的,一种是精确的,精确的Count Distinct指标在Build时候
会消耗更多的资源(内存和存储),Build的过程也比较慢。

近似Count Distinct
Apache Kylin使用HyperLogLog算法实现了近似Count Distinct,提供了错误率从9.75%到1.22%几种精度供选择;
算法计算后的Count Distinct指标,理论上,结果最大只有64KB,最低的错误率是1.22%;
这种实现方式用在需要快速计算、节省存储空间,并且能接受错误率的Count Distinct指标计算。

精准Count Distinct
Kylin中实现了基于bitmap的精确Count Distinct计算方式。当数据类型为tiny int(byte)、small int(short)以及int,
会直接将数据值映射到bitmap中;当数据类型为long,string或者其他,则需要将数据值以字符串形式编码成dict(字典),再将字典ID映射到bitmap; 指标计算后的结果,并不是计数后的值,而是包含了序列化值的bitmap.这样,才能确保在任意维度上的Count Distinct结果是正确的。 这种实现方式提供了精确的无错误的Count Distinct结果,但是需要更多的存储资源,如果数据中的不重
复值超过百万,结果所占的存储应该会达到几百MB。

image

3.3.2 EXTEND_COLUMN 理论知识:

Extended Column
在OLAP分析场景中,经常存在对某个id进行过滤,但查询结果要展示为name的情况,比如user_id和user_name。这类问题通常有三种解决方式:

  • a. 将ID和Name都设置为维度,查询语句类似select name, count(*) from table where id = 1 group by id,name。这种方式的问题是会导致维度增多,导致预计算结果膨胀;
  • b. 将id和name都设置为维度,并且将两者设置为联合。这种方式的好处是保持维度组合数不会增加,但限制了维度的其它优化,比如ID不能再被设置为强制维度或者层次维度;
  • c. 将ID设置为维度,Name设置为特殊的Measure,类型为Extended Column。这种方式既能保证过滤id且查询name的需求,同时也不影响id维度的进一步优化。
image

3.4 Count Distinct和TopN 官方案例讲解:

3.4.1 Count Distinct案例,主要用于对订单去重,错误率要求小于一定百分比

image

3.4.2 TopN 案例,主要用于按照seller_id 进行分组,然后对price进行聚合操作,实现每一笔订单的销售总额

image

3.5 cube中Messures总览

image

4 官方案例Refresh Setting 设置

  • Auto Merge Thresholds :自动合并阈值,按天增加的segement,每7天合并一次;7天的segment每28天合并一次
  • Retention Threshold:默认为0,保留历史所有的segment。
  • Volatile Range: 默认为 0,‘Auto Merge’ 会自动合并所有可能的 cube segments;设置具体的数值后,‘Auto Merge’ 将不会合并最近 Volatile Range 天的 cube segments;假设 Volatile Range 设置为 7,则最近 7 天内生成的 cube segments 不会被自动合并;
  • Partition Start Date:分区开始时间
image

4.1 官方案例Refresh Setting 强制维度设置

和朋友进行反复确认的 强制维度,这里官方案例真的出现了,在进行查询的时候,PART_DT不出现也是可以的,为什么呢?请参考下面的解释

Mandatory 维度指的是那些总是会出现 在Where 条件或 Group By 语句里的维度。当然必须存在不一定是显式出现在查询语句中,例如查询日期是必要字段,月份、季度、年属于它的衍生字段,那么查询的时候出现月份、季度、年这些衍生字段等效于出现查询日期这个必要字段。
image

4.2 官方案例Refresh Setting层次维度的设置

可以看到官方案例按照层次顺序,进行cubeId的构建,根据绑定关系,进行了剪枝处理


image

4.3 官方案例Refresh Setting联合维度的设置

根据绑定关系,进行了剪枝处理

image

4.4 Rokeys 的设置

各维度在 Rowkeys 中的顺序,对于查询的性能会产生较明显的影响;在这里用户可以根据查询的模式和习惯,通过拖曳的方式调整各个维度在Rowkeys上的顺序。推荐的顺序为:Mandatory 维度、where 过滤条件中出现频率较多的维度、高基数维度、低基数维度。这样做的好处是,充分利用过滤条件来缩小在 HBase 中扫描的范围,从而提高查询的效率。


image

我们发现不常用维度放在了后面:


image

5 官方案例总览

发现总共使用了20个维度和6个度量,以及6个Lookup Table (包含别名表)


image

6 官方案例构建CUBE

image

7 官方案例测试

在进行Cube构建时,我没有选择连接键和衍生维度( KYLIN_SALES.PART_DT = KYLIN_CAL_DT.CAL_DT),那么能不能正常使用,让我们拭目以待,以下是维度剪枝优化的内容,基于此,我们来做测试:

image

7.1 测试一:Joint Dimension 缺少伙伴是否会报错?

7.1.1 订单事实表与订单类别表的联合查询:

image

7.1.2 结论

联合维度缺失同伴不会报错

image

7.2 测试二:衍生维度能否正常分组和条件过滤?

7.2.1 订单事实表与时间维度表进行衍生字段分组:

image

7.2.2 结论

衍生维度分组不会报错


image

7.2.2 订单事实表与时间维度表进行衍生字段分组+过滤:

image

7.2.3 结论

衍生维度过滤不会报错

image

7.3 测试三:Mandatory Dimensions 缺少是否报错?

如果强制维度是衍生维度所在表的连接键(即KYLIN_SALES 与 KYLIN_CAL_DT的连接键CAL_DT)在,那么查询时不包含强制维度也不会不错,参考7.2.1既可以看出来。

7.3.1 结论

KYLIN_SALES.PART_DT缺少不会报错,虽然被设置成强制维度

7.4 测试四:层次维度乱序会不会报错呢?

可以看到随便更改层次维度顺序,都不会报错,原则上已经做过优化:


image

看我和一个朋友的聊天记录,感谢我这位朋友技术支持:


image

7.4.1 结论

不会报错
应该这样理解,层次维度ABC,AB , A 包含了所有维度组合情况情况,所以就不用其他的组合了,计算BC实际上查的还是ABC,所以Kylin通过层次维度设置,缩减了cubeId的数量。

8 结语

至此 ,整个官方案例剖析完毕,而留给我们的思考又该什么时候结束呢?Kylin作为OLAP技术先驱,真正把HBASE的列式存储功能发挥到了极致,没有HBASE和SPARK作为技术支撑,KYLIN又该是什么呢?如果有机会,我会写一篇HBASE技术专栏,我们来看看Hbase如何基于LSM(Log-Structured Merge Tree)把索引即数据的思想给落地的。如对spark感兴趣请关注我的Spark技术架构剖析专栏。

秦凯新 于深圳 2018-10-30

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容