click house 概览

clickhouse

what‘s it

面向列的数据库管理系统,可用于olap

ClickHouse允许在运行时创建表和数据库,加载数据和运行查询,而无需重新配置和重新启动服务器。

数据压缩

LZ4和ZSTD

好处:节省空间,降低网络IO,磁盘IO

坏处:内存中解压缩,增大cpu负载,查询同样不压缩的数据性能相比较会有所下降

参考文章

多核心并行处理

简言之就是支持任务并行处理,并行度取决整个集群的core的数量。利用可利用的一切资源去执行任务

较为支持sql

支持的查询包括 GROUP BY,ORDER BY,IN,JOIN以及非相关子查询。不支持窗口函数和相关子查询。

向量搜索引擎

这个比较复杂,采用knn,kdtree等算法。

好处就是在精度不做特别要求时,速度要快

坏处就是消耗更多的资源

适用于topn场景

参考

支持索引

按照主键对数据进行物理排序

在线计算

即现查现算

近似计算

用于近似计算的各类聚合函数,如:distinct values, medians, quantiles

基于数据的部分样本进行近似查询。这时,仅会从磁盘检索少部分比例的数据。

不使用全部的聚合条件,通过随机选择有限个数据聚合条件进行聚合。这在数据聚合条件满足某些分布条件下,在提供相当准确的聚合结果的同时降低了计算资源的使用。

限制

1.没有完整的事务支持。2.缺少高频率,低延迟的修改或删除已存在数据的能力。仅能用于批量删除或修改数据,但这符合 GDPR。3.稀疏索引使得ClickHouse不适合通过其键检索单行的点查询。

ps :ck是数据库管理系统不是数据库

how to use

安装

sudo yum install yum-utilssudo rpm --import https://repo.clickhouse.tech/CLICKHOUSE-KEY.GPGsudo yum-config-manager --add-repo https://repo.clickhouse.tech/rpm/clickhouse.reposudo yum install clickhouse-server clickhouse-clientsudo /etc/init.d/clickhouse-server startclickhouse-client

常用运维命令

clickhouse start|stop|restart

clickhouse-client

日志路径

/var/log/clickhouse-server/

常见问题

启动失败

· 端口冲突,需要修改,修改文件conf.xml

需要设置密码和登录端口

常用配置文件 user.xml conf.xml

· 开启远程访问需要修改conf.xml

· 设置密码需要修改user.xml

web访问

http://ui.tabix.io/#!/sql

接口层

jdbc/odbc

clickhouse-client

mysql

· 即通过mysql客户端工具链接ck

· 参考

http客户端

· 即restapi操作

· 参考

第三方

tabix

https://clickhouse.tech/docs/en/interfaces/third-party/integrations/

HouseOps

DBeaver

DataGrip

数据格式

最为常见的就是csv格式,其他的见官网

引擎

概述

· 一般在创建数据库的时候不指定引擎,默认使用的就是clickhouse自己的引擎,也可以指定使用mysql的引擎,就是可以在clickhouse中调用mysql 中的数据,进行查询,也可以使用 lazy引擎.

数据库引擎

· 延时引擎Lazy

• CREATE DATABASE testlazy ENGINE = Lazy(expiration_time_in_seconds);

• 日志引擎,在该数据库下只能创建 log 系列引擎的表

· MySQL

• MySQL引擎用于将远程的MySQL服务器中的表映射到ClickHouse中,并允许对表进行INSERT和SELECT查询MySQL数据库引擎会将对其的查询转换为MySQL语法并发送到MySQL服务器中,可以执行诸如SHOW TABLES或SHOW CREATE TABLE之类的操作。无法对其执行以下操作:RENAMECREATE TABLEALTER

• CREATE DATABASE [IF NOT EXISTS] db_name [ON CLUSTER cluster]ENGINE = MySQL('host:port', ['database' | database], 'user', 'password')

• host:port — 链接的MySQL地址。database — 链接的MySQL数据库。user — 链接的MySQL用户。password — 链接的MySQL用户密码

· Ordinary

• 默认引擎

• 在这种数据库下面的表可以是任意类型引擎。

· Dictionary

• 字典引擎,此类数据库会自动为所有数据字典创建它们的数据表(加载配置文件中配置的字段表信息和数据)

· Memory

• 内存引擎,用户存放临时数据,数据只会在内存中,不会涉及任何磁盘操作,当服务重启后数据会清空。并且对表引擎也有限制,只能使用memory类型表引擎。

• 所有数据只会保存在内存中,服务重启数据消失.

表引擎

· log系列

• log系列表引擎功能相对简单,主要用于快速写入小表(1百万行左右的表),然后全部读出的场景

• 表引擎的共性是:数据被顺序append写到磁盘上;不支持delete、update;不支持index;不支持原子性写;insert会阻塞select操作。

• TinyLog

• 不支持并发读取数据文件,查询性能较差;格式简单,适合用来暂存中间数据;

• create table test(a UInt16 ,b string) ENGINE =TinyLog

• StripLog

• 支持并发读取数据文件,查询性能比TinyLog好;将所有列存储在同一个大文件中,减少了文件个数;

• create table test(a UInt16 ,b string) ENGINE =StripLog

• Log

• 支持并发读取数据文件,查询性能比TinyLog好;每个列会单独存储在一个独立文件中

• create table test(a UInt16 ,b string) ENGINE =Log

· Integration系列

• Kafka

• 将Kafka Topic中的数据直接导入到ClickHouse;

• create table test(a UInt16 ,b string)ENGINE=Kafka SETTINGS kafka_broker_list='localhost:9092',kafak_topic_list='topic',kafka_group_name='groupname',kafka_formta='JSONEachRow',kafka_num_consumers=4

• MySQL

• 将Mysql作为存储引擎,直接在ClickHouse中对MySQL表进行select等操作;

• create table test(a UInt16 ,b string) ENGINE=MySQL('localhost:3306',''databases',username','password',)

• JDBC/ODBC

• 通过指定jdbc、odbc连接串读取数据源;

• create table test(a UInt16 ,b string)ENGINE =ODBC('DSN=mysqlconn','test','test')

• create table test(a UInt16 ,b string)ENGINE =JDBC('jdbc:mysql://localhost:3306/?'user=username&password=password ,'test','test')

• HDFS

• 直接读取HDFS上的特定格式的数据文件;

• create table test(a UInt16 ,b string) ENGINE=HDFS('hdfs://clustername:9000/data','TSV')

· MergeTree系列

• MergeTree

• ReplacingMergeTree

• CollapsingMergeTree

• VersionedCollapsingMergeTree

• SummingMergeTree

• AggregatingMergeTree

• 详情点击

· Special系列

• Memory

• 将数据存储在内存中,重启后会导致数据丢失。查询性能极好,适合于对于数据持久性没有要求的1亿一下的小表。在ClickHouse中,通常用来做临时表。

• Buffer

• 为目标表设置一个内存buffer,当buffer达到了一定条件之后会flush到磁盘。

• File

• 直接将本地文件作为数据存储;

• Null

• 写入数据被丢弃、读取数据为空;

SQL

https://clickhouse.tech/docs/zh/sql-reference/syntax/

how to work

架构

向量化执行引擎

[图片上传失败...(image-5f11cd-1609924266376)]

· 向量化执行,可以简单地看作一项消除程序中循环的优化。这里用一个形象的例子比喻。小胡经营了一家果汁店,虽然店里的鲜榨苹果汁深受大家喜爱,但客户总是抱怨制作果汁的速度太慢。小胡的店里只有一台榨汁机,每次他都会从篮子里拿出一个苹果,放到榨汁机内等待出汁。如果有8个客户,每个客户都点了一杯苹果汁,那么小胡需要重复循环8次上述的榨汁流程,才能榨出8杯苹果汁。如果制作一杯果汁需要5分钟,那么全部制作完毕则需要40分钟。为了提升果汁的制作速度,小胡想出了一个办法。他将榨汁机的数量从1台增加到了8台,这么一来,他就可以从篮子里一次性拿出8个苹果,分别放入8台榨汁机同时榨汁。此时,小胡只需要5分钟就能够制作出8杯苹果汁。为了制作n杯果汁,非向量化执行的方式是用1台榨汁机重复循环制作n次,而向量化执行的方式是用n台榨汁机只执行1次。

· 为了实现向量化执行,需要利用CPU的SIMD指令。SIMD的全称是Single Instruction Multiple Data,即用单条指令操作多条数据。现代计算机系统概念中,它是通过数据并行以提高性能的一种实现方式 ( 其他的还有指令级并行和线程级并行 ),它的原理是在CPU寄存器层面实现数据的并行操作。在计算机系统的体系结构中,存储系统是一种层次结构。典型服务器计算机的存储层次结构如图1所示。一个实用的经验告诉我们,存储媒介距离CPU越近,则访问数据的速度越快

· 从上图中可以看到,从左向右,距离CPU越远,则数据的访问速度越慢。从寄存器中访问数据的速度,是从内存访问数据速度的300倍,是从磁盘中访问数据速度的3000万倍。所以利用CPU向量化执行的特性,对于程序的性能提升意义非凡。

多线程与分布式

多主架构

数据分片与分布式查询

· 数据分片是将数据进行横向切分,这是一种在面对海量数据的场景下,解决存储和查询瓶颈的有效手段,是一种分治思想的体现。ClickHouse支持分片,而分片则依赖集群。每个集群由1到多个分片组成,而每个分片则对应了ClickHouse的1个服务节点。分片的数量上限取决于节点数量 ( 1个分片只能对应1个服务节点 )。

· ClickHouse并不像其他分布式系统那样,拥有高度自动化的分片功能。ClickHouse提供了本地表 ( Local Table ) 与分布式表 ( Distributed Table ) 的概念。一张本地表等同于一份数据的分片。而分布式表本身不存储任何数据,它是本地表的访问代理,其作用类似分库中间件。借助分布式表,能够代理访问多个数据分片,从而实现分布式查询。

· 这种设计类似数据库的分库和分表,十分灵活。例如在业务系统上线的初期,数据体量并不高,此时数据表并不需要多个分片。所以使用单个节点的本地表 ( 单个数据分片 ) 即可满足业务需求,待到业务增长、数据量增大的时候,再通过新增数据分片的方式分流数据,并通过分布式表实现分布式查询。

[图片上传失败...(image-69efb0-1609924266376)]

· Column与Field

• Column和Field是ClickHouse数据最基础的映射单元。作为一款百分之百的列式存储数据库,ClickHouse按列存储数据,内存中的一列数据由一个Column对象表示。Column对象分为接口和实现两个部分,在IColumn接口对象中,定义了对数据进行各种关系运算的方法,例如插入数据的insertRangeFrom和insertFrom方法、用于分页的cut,以及用于过滤的filter方法等。而这些方法的具体实现对象则根据数据类型的不同,由相应的对象实现,例如ColumnString、ColumnArray和ColumnTuple等。在大多数场合,ClickHouse都会以整列的方式操作数据,但凡事也有例外。如果需要操作单个具体的数值 ( 也就是单列中的一行数据 ),则需要使用Field对象,Field对象代表一个单值。与Column对象的泛化设计思路不同,Field对象使用了聚合的设计模式。在Field对象内部聚合了Null、UInt64、String和Array等13种数据类型及相应的处理逻辑。

• 这句话归纳如下:column与field是两中不同的数据结构,column定义了数据的处理逻辑,filed定义了特殊的数据类型以及相应的处理逻辑。column是列式操作,filed是单元格操作。

· DataType

• 数据的序列化和反序列化工作由DataType负责。IDataType接口定义了许多正反序列化的方法,它们成对出现,例如serializeBinary和deserializeBinary、serializeTextJSON和deserializeTextJSON等,涵盖了常用的二进制、文本、JSON、XML、CSV和Protobuf等多种格式类型。IDataType也使用了泛化的设计模式,具体方法的实现逻辑由对应数据类型的实例承载,例如DataTypeString、DataTypeArray及DataTypeTuple等。

• DataType虽然负责序列化相关工作,但它并不直接负责数据的读取,而是转由从Column或Field对象获取。在DataType的实现类中,聚合了相应数据类型的Column对象和Field对象。例如,DataTypeString会引用字符串类型的ColumnString,而DataTypeArray则会引用数组类型的ColumnArray,以此类推。

· Table

• 在数据表的底层设计中并没有所谓的Table对象,它直接使用IStorage接口指代数据表。表引擎是ClickHouse的一个显著特性,不同的表引擎由不同的子类实现,例如IStorageSystemOneBlock ( 系统表 )、StorageMergeTree ( 合并树表引擎 ) 和StorageTinyLog ( 日志表引擎 ) 等。IStorage接口定义了DDL ( 如ALTER、RENAME、OPTIMIZE和DROP等 ) 、read和write方法,它们分别负责数据的定义、查询与写入。在数据查询时,IStorage负责根据AST查询语句的指示要求,返回指定列的原始数据。后续对数据的进一步加工、计算和过滤,则会统一交由Interpreter解释器对象处理。对Table发起的一次操作通常都会经历这样的过程,接收AST查询语句,根据AST返回指定列的数据,之后再将数据交由Interpreter做进一步处理。

· Block与Block流

• Block对象可以看作数据表的子集。Block对象的本质是由数据对象、数据类型和列名称组成的三元组

· Parser与Interpreter

• Parser和Interpreter是非常重要的两组接口:Parser分析器负责创建AST对象;而Interpreter解释器则负责解释AST,并进一步创建查询的执行管道。它们与IStorage一起,串联起了整个数据查询的过程。Parser分析器可以将一条SQL语句以递归下降的方法解析成AST语法树的形式。不同的SQL语句,会经由不同的Parser实现类解析。例如,有负责解析DDL查询语句的ParserRenameQuery、ParserDropQuery和ParserAlterQuery解析器,也有负责解析INSERT语句的ParserInsertQuery解析器,还有负责SELECT语句的ParserSelectQuery等。

• Interpreter解释器的作用就像Service服务层一样,起到串联整个查询过程的作用,它会根据解释器的类型,聚合它所需要的资源。首先它会解析AST对象;然后执行"业务逻辑" ( 例如分支判断、设置参数、调用接口等 );最终返回IBlock对象,以线程的形式建立起一个查询执行管道

· Functions 与Aggregate Functions

• ClickHouse主要提供两类函数—普通函数和聚合函数。普通函数由IFunction接口定义,拥有数十种函数实现,例如FunctionFormatDateTime、FunctionSubstring等。除了一些常见的函数 ( 诸如四则运算、日期转换等 ) 之外,也不乏一些非常实用的函数,例如网址提取函数、IP地址脱敏函数等。普通函数是没有状态的,函数效果作用于每行数据之上。当然,在函数具体执行的过程中,并不会一行一行地运算,而是采用向量化的方式直接作用于一整列数据。

• 聚合函数由IAggregateFunction接口定义,相比无状态的普通函数,聚合函数是有状态的。以COUNT聚合函数为例,其AggregateFunctionCount的状态使用整型UInt64记录。聚合函数的状态支持序列化与反序列化,所以能够在分布式节点之间进行传输,以实现增量计算。

· Cluster与Replication

• ClickHouse的集群由分片 ( Shard ) 组成,而每个分片又通过副本 ( Replica ) 组成。这种分层的概念,在一些流行的分布式系统中十分普遍。例如,在Elasticsearch的概念中,一个索引由分片和副本组成,副本可以看作一种特殊的分片。如果一个索引由5个分片组成,副本的基数是1,那么这个索引一共会拥有10个分片 ( 每1个分片对应1个副本 )。如果你用同样的思路来理解ClickHouse的分片,那么很可能会在这里栽个跟头。ClickHouse的某些设计总是显得独树一帜,而集群与分片就是其中之一。这里有几个与众不同的特性。ClickHouse的1个节点只能拥有1个分片,也就是说如果要实现1分片、1副本,则至少需要部署2个服务节点。分片只是一个逻辑概念,其物理承载还是由副本承担的。

• 分片即表示安装clickhouse的节点数

参考

原理

列式存储

[图片上传失败...(image-8d94a5-1609924266376)]

· 分析场景中往往需要读大量行但是少数几个列。在行存模式下,数据按行连续存储,所有列的数据都存储在一个block中,不参与计算的列在IO时也要全部读出,读取操作被严重放大。而列存模式下,只需要读取参与计算的列即可,极大的减低了IO cost,加速了查询。

· 同一列中的数据属于同一类型,压缩效果显著。列存往往有着高达十倍甚至更高的压缩比,节省了大量的存储空间,降低了存储成本。

· 更高的压缩比意味着更小的data size,从磁盘中读取相应数据耗时更短。

· 高压缩比,意味着同等大小的内存能够存放更多数据,系统cache效果更好

数据有序存储

· ClickHouse支持在建表时,指定将数据按照某些列进行sort by。sort by是局部排序,在hive中表示在一个reduce内部排序排序后,保证了相同sort key的数据在磁盘上连续存储,且有序摆放。在进行等值、范围查询时,where条件命中的数据都紧密存储在一个或若干个连续的Block中,而不是分散的存储在任意多个Block, 大幅减少需要IO的block数量。另外,连续IO也能够充分利用操作系统page cache的预取能力,减少page fault。

• 简言之,就是where后面的谓词在建表时候要提前指定。由于集中存储在一个数据块内,就会减少扫描时间。io

**better to use **

环境要求

如果可能的话,使用10G或更高级别的网络。

至少4GB的RAM来执行重要的查询

RAM所需的体积取决于:查询的复杂性。查询中处理的数据量

监控体系

[图片上传失败...(image-d202b4-1609924266376)]

参考

集群模式

支持分布式集群部署

参考1

参考2

参考3

主键索引

ClickHouse支持主键索引,它将每列数据按照index granularity(默认8192行)进行划分,每个index granularity的开头第一行被称为一个mark行。主键索引存储该mark行对应的primary key的值。对于where条件中含有primary key的查询,通过对主键索引进行二分查找,能够直接定位到对应的index granularity,避免了全表扫描从而加速查询。但是值得注意的是:ClickHouse的主键索引与MySQL等数据库不同,它并不用于去重,即便primary key相同的行,也可以同时存在于数据库中。要想实现去重效果,需要结合具体的表引擎ReplacingMergeTree、CollapsingMergeTree、VersionedCollapsingMergeTree实现

稀疏索引

ClickHouse支持对任意列创建任意数量的稀疏索引。其中被索引的value可以是任意的合法SQL Expression,并不仅仅局限于对column value本身进行索引。之所以叫稀疏索引,是因为它本质上是对一个完整index granularity(默认8192行)的统计信息,并不会具体记录每一行在文件中的位置。目前支持的稀疏索引类型包括:minmax: 以index granularity为单位,存储指定表达式计算后的min、max值;在等值和范围查询中能够帮助快速跳过不满足要求的块,减少IO。set(max_rows):以index granularity为单位,存储指定表达式的distinct value集合,用于快速判断等值查询是否命中该块,减少IO。ngrambf_v1(n, size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed):将string进行ngram分词后,构建bloom filter,能够优化等值、like、in等查询条件。tokenbf_v1(size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed): 与ngrambf_v1类似,区别是不使用ngram进行分词,而是通过标点符号进行词语分割。bloom_filter([false_positive]):对指定列构建bloom filter,用于加速等值、like、in等查询条件的执行。

数据Sharding

ClickHouse支持单机模式,也支持分布式集群模式。在分布式模式下,ClickHouse会将数据分为多个分片,并且分布到不同节点上。不同的分片策略在应对不同的SQL Pattern时,各有优势。ClickHouse提供了丰富的sharding策略,让业务可以根据实际需求选用。1) random随机分片:写入数据会被随机分发到分布式集群中的某个节点上。2) constant固定分片:写入数据会被分发到固定一个节点上。3)column value分片:按照某一列的值进行hash分片。4)自定义表达式分片:指定任意合法表达式,根据表达式被计算后的值进行hash分片。数据分片,让ClickHouse可以充分利用整个集群的大规模并行计算能力,快速返回查询结果。更重要的是,多样化的分片功能,为业务优化打开了想象空间。比如在hash sharding的情况下,JOIN计算能够避免数据shuffle,直接在本地进行local join; 支持自定义sharding,可以为不同业务和SQL Pattern定制最适合的分片策略;利用自定义sharding功能,通过设置合理的sharding expression可以解决分片间数据倾斜问题等。另外,sharding机制使得ClickHouse可以横向线性拓展,构建大规模分布式集群,从而具备处理海量数据的能力。

数据Partitioning

ClickHouse支持PARTITION BY子句,在建表时可以指定按照任意合法表达式进行数据分区操作,比如通过toYYYYMM()将数据按月进行分区、toMonday()将数据按照周几进行分区、对Enum类型的列直接每种取值作为一个分区等。数据Partition在ClickHouse中主要有两方面应用:在partition key上进行分区裁剪,只查询必要的数据。灵活的partition expression设置,使得可以根据SQL Pattern进行分区设置,最大化的贴合业务特点。对partition进行TTL管理,淘汰过期的分区数据。

数据TTL

在分析场景中,数据的价值随着时间流逝而不断降低,多数业务出于成本考虑只会保留最近几个月的数据,ClickHouse通过TTL提供了数据生命周期管理的能力。ClickHouse支持几种不同粒度的TTL:1) 列级别TTL:当一列中的部分数据过期后,会被替换成默认值;当全列数据都过期后,会删除该列。2)行级别TTL:当某一行过期后,会直接删除该行。3)分区级别TTL:当分区过期后,会直接删除该分区。

why it's designed

将军点兵

如果将军点数一个方阵有多少人,是一个一个数方便还是先确定一个纵队(列)有多少人,在确定有多少排(行)方便呢?显然是后者。虽然例子不是很生动,但是思想是一致的,通过扫描同一个类型的列时(即面向列操作数据)在同等数据处理要比传统的按行读取数据速度快,减少了将军点兵时间(即io流),

在古代行军打仗中,两军对垒,军阵的排序往往对战争的成败起着非常作用,这里的军阵的排序就好比数据的有效排序(即sort by)数据的有效排序对查询的性能也会起着至关重要的作用

如果把将军比作cpu,那么是不是最开始执行将军计划的是不是身边近臣,然后是百夫长,在然后是士兵。(这个像极了向量的执行引擎,越靠近cpu的block被处理的速度越快)

同样,士兵都有名字或者编号,这个就好比主键索引,在众多的人中快速找到具体人

如果按照身手好,力气大,这些群体特征点兵就是稀疏索引的体现,快速过滤掉不符合此类特征的人

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

推荐阅读更多精彩内容