Spark SQL 使用指北

簡(jiǎn)介

sparkSQL 是為了讓開(kāi)發(fā)人員擺脫 自己編寫 RDD 原生代碼而產(chǎn)生的,只需要寫一句 SQL語(yǔ)句或者調(diào)用API,進(jìn)行查詢 或?qū)崿F(xiàn)更復(fù)雜的數(shù)據(jù)分析,使得開(kāi)發(fā)變得更簡(jiǎn)潔。
Spark SQL 允許開(kāi)發(fā)人員直接處理RDD,同時(shí)也可查詢Hive、HBase等外部數(shù)據(jù)源。

從Shark說(shuō)起

Shark即Hive on Spark,為了實(shí)現(xiàn)與Hive兼容,Shark重用了Hive中的HiveQL解析,以近似認(rèn)為僅從MapReduce作業(yè) 替換成了 Spark作業(yè),通過(guò)Hive的HiveQL解析,把HiveQL翻譯成Spark上的RDD操作。

這樣設(shè)計(jì)導(dǎo)致兩個(gè)問(wèn)題:

  1. 執(zhí)行計(jì)劃優(yōu)化完全依賴于Hive,不方便添加新的優(yōu)化策略
  2. Spark是線程級(jí)并行,而MapReduce是進(jìn)程級(jí)并行,因此,Spark在兼容Hive的實(shí)現(xiàn)上存在線程安全問(wèn)題。

因此,在2014年的時(shí)候,Shark項(xiàng)目中止,并轉(zhuǎn)向Spark SQL的開(kāi)發(fā)。

Spark SQL設(shè)計(jì)

Spark SQL增加了Schema RDD(即帶有Schema信息的RDD,即DataFrame),使用戶可以在Spark SQL中執(zhí)行SQL語(yǔ)句。
數(shù)據(jù)既可以來(lái)自RDD,也可以來(lái)自Hive、HDFS、Cassandra等外部數(shù)據(jù)源,也可以是json數(shù)據(jù)。

Spark SQL目前支持Scala、Java、Python三種語(yǔ)言,
Spark SQL可以很好地支持SQL查詢,
一方面,可以編寫Spark應(yīng)用程序使用SQL語(yǔ)句進(jìn)行數(shù)據(jù)查詢,
另一方面,也可以使用標(biāo)準(zhǔn)的數(shù)據(jù)庫(kù)連接器(比如JDBC)連接Spark進(jìn)行SQL查詢。

DataFrame

DataFrame 是什么?

DataFrame的推出,讓Spark具備了處理大規(guī)模結(jié)構(gòu)化數(shù)據(jù)的能力,
Spark能夠輕松實(shí)現(xiàn)從 MySQL 到 DataFrame的轉(zhuǎn)化,并且支持SQL查詢。

與RDD的區(qū)別:


RDD是分布式的 Java對(duì)象的數(shù)據(jù)集
RDD[Person]是以Person為類型參數(shù),但是,Person類的內(nèi)部結(jié)構(gòu)對(duì)于RDD而言卻是不可知的。
DataFrame是一種以RDD為基礎(chǔ)的分布式數(shù)據(jù)集,也就是分布式的Row對(duì)象的集合(每個(gè)Row對(duì)象代表一行記錄),提供了詳細(xì)的結(jié)構(gòu)信息,也就是我們經(jīng)常說(shuō)的模式(schema),
Spark SQL可以清楚地知道該數(shù)據(jù)集中包含哪些列、每列的名稱和類型,
可以方便的通過(guò) Spark SQL 進(jìn)行查詢

Schema是什么?

DataFrame中提供了詳細(xì)的數(shù)據(jù)結(jié)構(gòu)信息,從而使得SparkSQL可以清楚地知道該數(shù)據(jù)集中包含哪些列,每列的名稱和類型各是什么,

DataFrame中的數(shù)據(jù)結(jié)構(gòu)信息,即為schema。

DataFrame 的創(chuàng)建

從Spark2.0以上版本開(kāi)始,Spark使用全新的SparkSession接口替代Spark1.6中的SQLContext及HiveContext接口來(lái)實(shí)現(xiàn)其對(duì)數(shù)據(jù)加載、轉(zhuǎn)換、處理等功能。
SparkSession實(shí)現(xiàn)了 SQLContext 及 HiveContext 所有功能。

先創(chuàng)建一個(gè) SparkSession:

import org.apache.spark.sql.SparkSession
val spark = SparkSession.builder().getOrCreate()

然后從json文件中讀取

 //使支持RDDs轉(zhuǎn)換為DataFrames及后續(xù)sql操作
import spark.implicits._ 
//后面用到的 spark 都是和上面的 spark 是同一個(gè)
val df = spark.read.json("file:///zyb/people.json")

json文件的內(nèi)容為:

{"name":"Michael"}
{"name":"Andy", "age":30}
{"name":"Justin", "age":19}

展示 Dataframe 數(shù)據(jù):

df.show()
//結(jié)果為:
+----+-------+
| age|   name|
+----+-------+
|null|Michael|
|  30|   Andy|
|  19| Justin|
+----+-------+

看一下 DataFrame 的 Schema

df.printSchema()  
//結(jié)果為:
root
  |-- age: long (nullable = true)
  |-- name: string (nullable = true)
通過(guò) sql 語(yǔ)句進(jìn)行查詢

//必須注冊(cè)為臨時(shí)表才能供下面的查詢使用
df.registerTempTable("peopleTempTab")
spark.sql("select name,age from peopleTempTab where age > 20").rdd

常用的 DataFrame操作
// 選擇多列,并為 age 的列 ,都加1
df.select(df("name"),df("age")+1).show()
// 條件過(guò)濾
df.filter(df("age") > 20 ).show()
// 分組聚合
df.groupBy("age").count().show()
從 RDD 轉(zhuǎn)換為 DataFrame

這里介紹兩種轉(zhuǎn)換方式:
1、利用反射機(jī)制推斷RDD模式
這種方式要先確定類的成員組成,即Schema信息。

先定義一個(gè) case class
case class Person(name: String, age: Long)
把從文件中讀的字符串轉(zhuǎn)換為 Person,然后調(diào)用 case class 的 toDF() 方法
val peopleDF = spark.sparkContext.textFile("file:///zyb/people.txt").map(_.split(",")).map(att => Person(att(0), att(1))).toDF()

2、使用編程方式定義RDD模式
先 生成一個(gè) schema,然后生成一個(gè) Row,然后調(diào)用 spark的 createDataFrame 方法把兩者組裝起來(lái)。

生成一個(gè) RDD:
val studentRDD = spark.sparkContext.parallelize(Array("3 Rongcheng M 26","4 Guanhua M 27")).map(_.split(" "))

創(chuàng)建 schema:
val schema = StructType(List(StructField("id", IntegerType, true),StructField("name", StringType, true), StructField("gender", StringType, true),StructField("age", IntegerType, true)))

創(chuàng)建Row對(duì)象,每個(gè)Row對(duì)象都是rowRDD中的一行:
val rowRDD = studentRDD.map(p => Row(p(0).toInt, p(1).trim, p(2).trim, p(3).toInt))

建立起Row對(duì)象和模式之間的對(duì)應(yīng)關(guān)系,也就是把數(shù)據(jù)和模式對(duì)應(yīng)起來(lái)
val studentDF = spark.createDataFrame(rowRDD, schema)
下面 往 MySql 寫數(shù)據(jù)時(shí),也會(huì)用到這個(gè) studentDF

相比較而言,還是第一種方式簡(jiǎn)單。

DataFrame 的兩種保存方式

從 json文件中加載數(shù)據(jù)得到 DataFrame,load方式:

val peopleDF = spark.read.format("json").load("file:///usr/local/spark/examples/src/main/resources/people.json")
或者 手動(dòng)指定 格式:
val peopleDF = spark.read.json("file:///usr/local/spark/examples/src/main/resources/people.json")
  1. 通過(guò) DataFrame 的 write 方法
    peopleDF.select("name", "age").write.format("csv").save("file:///usr/local/spark/mycode/newpeople.csv")
  2. 通過(guò) DataFrame 的 rdd方式
    peopleDF.rdd.saveAsTextFile("file:///usr/local/spark/mycode/newpeople.txt")
DataFrame 讀寫 parquet

Parquet是一種流行的列式存儲(chǔ)格式,可以高效地存儲(chǔ)具有嵌套字段的記錄。

讀 parquet 文件,返回的為 DataFrame
spark.read.parquet("file:///zyb/users.parquet")
通過(guò) DataFrame 寫成 parquet
peopleDF.write.parquet("file:///usr/local/spark/mycode/newpeople.parquet")

Spark SQL 支持連接到 MySql Server

1、創(chuàng)建一個(gè)表

//創(chuàng)建一個(gè)數(shù)據(jù)庫(kù) spark
create database spark;
//使用數(shù)據(jù)庫(kù)
use spark;
//創(chuàng)建一個(gè)表
create table student (id int(4), name char(20), gender char(4), age int(4));
//往表里插入數(shù)據(jù)
insert into student values(1,"Xueqian",'F',23);
insert into student values(2,"wegfw",'M',24);

//從表中查詢
select * from student

2、通過(guò) jdbc 從MySql 中讀數(shù)據(jù)
下載 jdbc 驅(qū)動(dòng)
mysql-connector-java-5.1.40.tar.gz

把 驅(qū)動(dòng) 放到 spark的 jars 目錄下
啟動(dòng) spark-shell,要附加一些參數(shù),告訴jar的位置
./bin/spark-shell \ --jars /usr/local/spark/jars/mysql-connector-java-5.1.40/mysql-connector-java-5.1.40-bin.jar \ --driver-class-path /usr/local/spark/jars/mysql-connector-java-5.1.40/mysql-connector-java-5.1.40-bin.jar

啟動(dòng) shell 后,執(zhí)行以下命令連接數(shù)據(jù)庫(kù),讀取數(shù)據(jù)
這里要告訴 MySql的 URL地址,drive是誰(shuí),表是哪個(gè),用戶和密碼。
val jdbcDF = spark.read.format("jdbc").option("url", "jdbc:mysql://localhost:3306/spark").option("driver","com.mysql.jdbc.Driver").option("dbtable", "student").option("user", "root").option("password", "hadoop").load()

顯示 DataFrame

jdbcDF.show()
//結(jié)果為:
+---+--------+------+---+
| id|    name|gender|age|
+---+--------+------+---+
|  1| Xueqian|     F| 23|
|  2|Weiliang|     M| 24|
+---+--------+------+---+

3、通過(guò) jdbc 往MySql 中寫數(shù)據(jù)
創(chuàng)建一個(gè) prop變量 用來(lái)保存 JDBC連接參數(shù)

val prop = new Properties()
prop.put("user", "root") //表示用戶名是root
prop.put("password", "hadoop") //表示密碼是hadoop
prop.put("driver","com.mysql.jdbc.Driver") //表示驅(qū)動(dòng)程序是com.mysql.jdbc.Driver

連接數(shù)據(jù)庫(kù),采用append模式,表示追加記錄到數(shù)據(jù)庫(kù)spark的student表中
studentDF.write.mode("append").jdbc("jdbc:mysql://localhost:3306/spark", "spark.student", prop)

Spark SQL 支持連接到 Hive
編譯Spark 源碼,使Spark 支持Hive

Spark官方提供的版本,通常是不包含Hive支持的,需要采用源碼編譯,編譯得到一個(gè)包含Hive支持的Spark版本。
編譯 支持 Hive 的 Spark 源碼步驟:

  1. 先到這個(gè)網(wǎng)址:http://spark.apache.org/downloads.html 下載源碼
  2. 傳到Linux,解壓:tar -zxvf ./spark-2.1.0.tgz -C /home/hadoop/
  3. 查看 Hadoop版本,下面要用到。查看命令:hadoop version
  4. 編譯
    cd /home/hadoop/spark-2.1.0 ./dev/make-distribution.sh —tgz —name h27hive -Pyarn -Phadoop-2.7 -Dhadoop.version=2.7.1 -Phive -Phive-thriftserver -DskipTests
    • -Phadoop-2.7 -Dhadoop.version=2.7.1
      指定安裝 spark 時(shí)的 hadoop 版本,要和電腦上安裝的Hadoop的版本對(duì)應(yīng)。
    • -Phive -Phive-thriftserver
      這兩個(gè)選項(xiàng)讓其支持Hive。
    • -DskipTests
      能避免測(cè)試不通過(guò)時(shí)發(fā)生的錯(cuò)誤。
    • h27hive
      只是我們給編譯以后的文件的一個(gè)名稱,最終編譯成功后會(huì)得到文件名“spark-2.1.0-bin-h27hive.tgz”

編譯要花幾個(gè)小時(shí),而且可能還會(huì)遇到各種錯(cuò)誤,建議直接在網(wǎng)上下載一個(gè)編譯好的 Spark,提供一個(gè)下載地址:https://pan.baidu.com/s/1nv8Y2hj

配置
  1. 用vim編輯器打開(kāi)了spark-env.sh文件,在這個(gè)文件增加一行內(nèi)容,重復(fù)的就不需要添加:

    export SPARK_DIST_CLASSPATH=$(/usr/local/hadoop/bin/hadoop classpath)
    export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
    export CLASSPATH=$CLASSPATH:/usr/local/hive/lib
    export SCALA_HOME=/usr/local/scala
    export HADOOP_CONF_DIR=/usr/local/hadoop/etc/hadoop
    export HIVE_CONF_DIR=/usr/local/hive/conf
    export SPARK_CLASSPATH=$SPARK_CLASSPATH:/usr/local/hive/lib/mysql-connector-java-5.1.40-bin.jar
    
  2. 為了讓Spark能夠訪問(wèn)Hive,需要把Hive的配置文件hive-site.xml拷貝到Spark的conf目錄下:

    cd /usr/local/sparkwithhive/conf
    cp /usr/local/hive/conf/hive-site.xml .
    
  3. vim spark-env.sh

創(chuàng)建 Hive 表

我們進(jìn)入Hive:

cd /usr/local/hive
./bin/hive

新建一個(gè)數(shù)據(jù)庫(kù)sparktest,并在這個(gè)數(shù)據(jù)庫(kù)下面創(chuàng)建一個(gè)表student,并錄入兩條數(shù)據(jù)。

hive> create database if not exists sparktest;//創(chuàng)建數(shù)據(jù)庫(kù)sparktest
hive> show databases; //顯示一下是否創(chuàng)建出了sparktest數(shù)據(jù)庫(kù)
//下面在數(shù)據(jù)庫(kù)sparktest中創(chuàng)建一個(gè)表student
hive> create table if not exists sparktest.student(
> id int,
> name string,
> gender string,
> age int);
hive> use sparktest; //切換到sparktest
hive> show tables; //顯示sparktest數(shù)據(jù)庫(kù)下面有哪些表
hive> insert into student values(1,'Xueqian','F',23); //插入一條記錄
hive> insert into student values(2,'Weiliang','M',24); //再插入一條記錄
hive> select * from student; //顯示student表中的記錄
讀取 Hive 中的數(shù)據(jù)
Scala> import org.apache.spark.sql.Row
Scala> import org.apache.spark.sql.SparkSession
 
Scala> case class Record(key: Int, value: String)
 
// warehouseLocation points to the default location for managed databases and tables
Scala> val warehouseLocation = "spark-warehouse" //"file:${system:user.dir}/spark-warehouse"
 
Scala> val spark = SparkSession.builder().appName("Spark Hive Example"). \
    config("spark.sql.warehouse.dir", warehouseLocation).enableHiveSupport().getOrCreate()
 
Scala> import spark.implicits._
Scala> import spark.sql
//下面是運(yùn)行結(jié)果
scala> sql("SELECT * FROM sparktest.student").show()
+---+--------+------+---+
| id|    name|gender|age|
+---+--------+------+---+
|  1| Xueqian|     F| 23|
|  2|Weiliang|     M| 24|
+---+--------+------+---+
向 Hive 中插入數(shù)據(jù)

先創(chuàng)建一個(gè) DataFrame 數(shù)據(jù):

scala> import java.util.Properties
scala> import org.apache.spark.sql.types._
scala> import org.apache.spark.sql.Row
 
//下面我們?cè)O(shè)置兩條數(shù)據(jù)表示兩個(gè)學(xué)生信息
scala> val studentRDD = spark.sparkContext.parallelize(Array("3 Rongcheng M 26","4 Guanhua M 27")).map(_.split(" "))
 
//下面要設(shè)置模式信息
scala> val schema = StructType(List(StructField("id", IntegerType, true),StructField("name", StringType, true),StructField("gender", StringType, true),StructField("age", IntegerType, true)))
 
//下面創(chuàng)建Row對(duì)象,每個(gè)Row對(duì)象都是rowRDD中的一行
scala> val rowRDD = studentRDD.map(p => Row(p(0).toInt, p(1).trim, p(2).trim, p(3).toInt))
 
//建立起Row對(duì)象和模式之間的對(duì)應(yīng)關(guān)系,也就是把數(shù)據(jù)和模式對(duì)應(yīng)起來(lái)
scala> val studentDF = spark.createDataFrame(rowRDD, schema)
往 Hive 中插入數(shù)據(jù):
scala> studentDF.show()
+---+---------+------+---+
| id|     name|gender|age|
+---+---------+------+---+
|  3|Rongcheng|     M| 26|
|  4|  Guanhua|     M| 27|
+---+---------+------+---+
//下面注冊(cè)臨時(shí)表:tempTable
scala> studentDF.registerTempTable("tempTable")
 //往 Hive 表:sparktest.student 中插入數(shù)據(jù)
scala> sql("insert into sparktest.student select * from tempTable")

對(duì) SQL 進(jìn)行查詢優(yōu)化

主要是對(duì)要查詢的 存儲(chǔ)的數(shù)據(jù)格式 進(jìn)行優(yōu)化,盡可能的使用列式格式 存儲(chǔ)數(shù)據(jù)。

什么是列存儲(chǔ)呢?
傳統(tǒng)的數(shù)據(jù)庫(kù)通常以行單位做數(shù)據(jù)存儲(chǔ),而列式存儲(chǔ)(后文均以列存儲(chǔ)簡(jiǎn)稱)以列為單位做數(shù)據(jù)存儲(chǔ),如下:

列存儲(chǔ)相比于行存儲(chǔ)主要有以下幾個(gè)優(yōu)勢(shì):

  • 數(shù)據(jù)即索引,查詢是可以跳過(guò)不符合條件的數(shù)據(jù),只讀取需要的數(shù)據(jù),降低 IO 數(shù)據(jù)量(行存儲(chǔ)沒(méi)有索引查詢時(shí)造成大量 IO,建立索引和物化視圖代價(jià)較大)
  • 只讀取需要的列,進(jìn)一步降低 IO 數(shù)據(jù)量,加速掃描性能(行存儲(chǔ)會(huì)掃描所有列)
  • 由于同一列的數(shù)據(jù)類型是一樣的,可以使用高效的壓縮編碼來(lái)節(jié)約存儲(chǔ)空間

當(dāng)然列存儲(chǔ)并不是在所有場(chǎng)景都強(qiáng)于行存儲(chǔ),當(dāng)查詢要讀取多個(gè)列時(shí),行存儲(chǔ)一次就能讀取多列,而列存儲(chǔ)需要讀取多次。Spark 原始支持 parquet 和 orc 兩個(gè)列存儲(chǔ),下文的實(shí)踐使用 parquet

使用 Parquet 加速 Spark SQL 查詢

使用 Parquet 格式的列存儲(chǔ)主要帶來(lái)三個(gè)好處:

  1. 大大節(jié)省存儲(chǔ)空間
    使用行存儲(chǔ)占用 44G,將行存儲(chǔ)轉(zhuǎn)成 parquet 后僅占用 5.6G,節(jié)省了 87.2% 空間,使用 Spark 將數(shù)據(jù)轉(zhuǎn)成列存儲(chǔ)耗時(shí)4分鐘左右(該值與使用資源相關(guān))
  2. 只讀取指定行
    select count(distinct f1) from tbInRow/tbInParquet
    行存儲(chǔ)耗時(shí): 119.7s
    列存儲(chǔ)耗時(shí): 3.4s
    加速 35 倍
  3. 跳過(guò)不符合條件數(shù)據(jù)
    select count(f1) from tbInRow/tbInParquet where f1 > 10000
    行存儲(chǔ)耗時(shí): 102.8s
    列存儲(chǔ)耗時(shí): 1.3s
    加速 78 倍
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評(píng)論 3 417
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,423評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,991評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,761評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,207評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,419評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,959評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,653評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,901評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,678評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,978評(píng)論 2 374

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