大数据实践温故知新

第一讲

大数据的基本特征是什么?

  • 数据规模巨大(Volume)

传统数据 GB->TB
大数据 TB->PB

  • 数据类型多样(Variety)

传统数据:结构化数据
大数据:结构化、半结构化、非结构化数据

  • 生成和处理速度极快(Velocity)

传统数据:数据量稳定,增长不快
大数据:实时产生处理、年增长率超60%

  • 价值巨大但密度较低(value)

传统数据:统计报表
大数据:机器学习,深度学习


Hadoop经历了几个发展阶段,各有什么特点?

前hadoop时代

2003.10 Google发表了Google File System论文
2004.10 Google发表了MapReduce论文
2006.02 Apache Hadoop项目正式启动,并支持MapReduce和HDFS独立发展
2006.11 Google发表了Bigtable论文

hadoop时代

2008.01Hadoop成为Apache顶级项目
2009.03Cloudera推出世界上首个Hadoop发行版——CDH,并完全开放源码
2012.03HDFS NameNode HA加入Hadoop主版本

后hadoop时代

2013.11 星环科技发布了国内首个全面支持Spark和Hadoop2.0的大数据基础平台软件——TDH
2014.02Spark代替MapReduce成为Hadoop的缺省计算引擎,并成为Apache顶级项目


大数据技术体系大致分为几层?每层包含哪些技术?

七层
数据源、数据采集、数据存储与管理、资源管理、通用计算、数据分析、数据展现


Apache Hadoop项目包含哪些子项目?简述一下它们的功能。

1、分布式文件系统 HDFS(数据存储与管理)
2、批处理计算框架 MapReduce (面向批处理的分布式计算框架)
3、高性能计算框架 Spark
(2、3都是用于计算,其中mapreduce有两个功能 <计算框架+资源管理>,所以后面又出现yarn来分离功能资源管理)
4、分布式资源管理系统 YARN (另一种资源管理器)专注于资源管理和作业调度
5、容器引擎 docker 打包应用及依赖包到一个可移植的容器中,然后任意发布到linux上
6、容器化集群操作系统 Kubernetes 容器化集群管理引擎、生产级容器编排工具
7、hadoop数据仓库&SQL引擎 Hive
Hadoop数据仓库:企业决策支持
SQL引擎:对海量结构化数据进行高性能SQL查询
8、分布式NoSQL数据库 HBase 主要用于半结构化、非结构化数据
9、分布式搜索引擎 ElastisSearch PB级以上基于Lucene实现全文数据的快速存储、搜索和分析


Spark包含哪些组件?简述一下它们的功能。

(高性能分布式通用计算框架)

Spark Core:基础计算框架(批处理、交互式分析)
Spark SQL:SQL引擎(海量结构化数据的高性能查询)
Spark Streaming:实时流处理(微批)
Spark MLlib:机器学习
Spark GraphX:图计算


第二讲

HDFS架构中包含哪几种角色?各自承担什么功能?

Client
• 将文件切分为Block
• 与NameNode交互,获取文件访问计划和相关元数据 • 与DataNode交互,读取或写入数据
• 管理HDFS

NameNode (master层)包括active 和 standby两种形式standby是avtive的热备节点

  • active namenode 活动master管理结点(一般只有一个,微小可能存在2个成为“脑裂”)它管理命名空间、管理元数据、管理block副本策略、处理客户端请求,为DataNode分配任务
    *standby namenode AN的热备节点(允许多个)AN当机后通过法定人数选举法快速升级为新的AN、同步元数据,即周期性下载edits编辑日志,生成fsimage镜像检查点文件

DataNode(slave层)
• Slave工作节点(可大规模扩展)
• 存储Block和数据校验和
• 执行客户端发送的读写操作
• 通过心跳机制定期(默认3秒)向NameNode汇报运行状态和Block列表信息 • 集群启动时,DataNode向NameNode提供Block列表信息

PS:master/slave:也就是我们所说的主从复制,主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。
可以实现读写分离等


为什么HDFS不合适存储大量的小文件?(上课的时候讲过!!!)

  • 元数据占用NameNode大量内存空间
    • 每个文件、目录和Block的元数据都要占
      用150Byte
    • 存储1亿个元素,大约需要20GB内存
    • 如果一个文件为10KB,1亿个文件大小仅
      有1TB,却要消耗掉20GB内存
  • 磁盘寻道时间会超过读取时间,大文件可以使磁盘寻道时间不超过读取时间

Block副本的放置策略是什么?如何理解?

  • 副本1:放在Client所在节点 -对于远程Client,系统会随机选择节点
  • 副本2:放在不同的机架节点上
  • 副本3:放在与第二个副本同一机架的不同节点上
  • 副本N:随机选择
  • 节点选择:同等条件下优先选择空闲节点

HDFS离开安全模式的条件是什么?

  • Block上报率:DataNode上报的可用Block个数 / NameNode元数据记录的Block个数
  • 当Block上报率 >= 阈值时,HDFS才能离开安全模式,默认阈值为0.999
  • 不建议手动强制退出安全模式

HDFS是如何实现高可用的?(上课的时候也讲过!!!)(法定人数机制)单数

利用QJM实现元数据高可用
  • QJM机制(Quorum Journal Manager)
    • 只要保证Quorum(法定人数)数量的操作成功,就认为这是一次最终成功的操作
  • QJM共享存储系统
    • 部署奇数(2N+1)个JournalNode
    • JournalNode负责存储edits编辑日志 -写edits的时候,只要超过半数(>=N+1)的JournalNode返回成功,就代表本次写入成功
    • 最多可容忍N个JournalNode宕机
    • 基于Paxos算法实现

ps:“高可用(High Availability)“是系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。

第三讲

简述YARN与MapReduce的关系。

YARN的出现为了处理MapReduce的缺陷(身兼两职:计算框架 + 资源管理系统。它的JobTracker :既做资源管理,又做任务调度 、任务太重,开销过大 、存在单点故障)yarn是分布式通用资源管理系统,可以让mapreduce只做计算框架一件事,而且可以将JobTracker的资源管理、任务调度功能分离。


为什么要设计ApplicationMaster这一角色?

它用来管理应用程序实例的整个生命周期,包括任务调度和资源申请
主要功能是:管理应用程序实例 、向ResourceManager申请任务执行所需的资源 、任务调度和监管
ApplicationMaster 的职责有:向调度器索要适当的资源容器,运行任务,跟踪应用程序的状态和监控它们的进程,处理任务的失败原因。


Zookeeper在YARN中承担了哪些功能?

Active节点选举
恢复Active RM的原有状态信息

在项目实践中,如何部署YARN的ResourceManager、NodeManager和HDFS的NameNode、DataNode?

(1)HDFS集群:负责海量数据的存储,集群中的角色主要有 NameNode / DataNode/SecondaryNameNode。

(2)YARN集群:负责海量数据运算时的资源调度,集群中的角色主要有 ResourceManager /NodeManager

可以把Hadoop想象成单机操作系统扩展到一个集群的情况,其中的NameNode就是文件系统的中央管理枢纽,ResourceManager就相当于单机中负责管理机器中的内存、cpu的那个操作系统的调度系统。


队列在资源调度中起什么作用?

将需要调度的资源放在队列中,然后进行不同资源调度策略时,对不同队列中的资源可以进行不同的处理。


容量调度器与公平调度器的区别是什么?

容器调度器的每个队列都要预设资源分配的比列(提前做预算),而公平调度器通过平分的方式,动态分配资源,无需预先设定资源分配比例


容量调度器会严格按预设比例分配资源吗?

弹性分配:空闲资源可以分配给任何队列,当多个队列争用时,会按比例进行平衡


简述公平调度器中队列权重和资源抢占的含义。

队列权重:当队列中有任务等待,并且集群中有空闲资源时,每个队列可 以根据权重获得不同比例的空闲资源

资源抢占:终止其他队列的任务,使其让出所占资源,然后将资源分配给占用资源量少于最小资源量限制的队列


第四讲

简述MR Split与HDFS Block的关系。

没有关系,Split的划分方式由程序设定,Split与HDFS Block没有严格的对应关系


为什么MapReduce要求输入输出必须是key-value键值对?

MapReduce框架只操作键值对<key, value>,因此这个框架中任务的输入和输出都是键值对形式<key, value>
(废话?)


简述Shuffle的工作原理。

• Map端
-Map任务将中间结果写入专用内存缓冲区Buffer(默认100M),同时进行Partition和Sort(先按“key
hashcode % reduce task number”对数据进行分区,分区内再按key排序)
-当Buffer的数据量达到阈值(默认80%)时,将数据溢写(Spill)到磁盘的一个临时文件中,文件内数据先分区后排序
-Map任务结束前,将多个临时文件合并(Merge)为一个Map输出文件,文件内数据先分区后排序
• Reduce端
-Reduce任务从多个Map输出文件中主动抓取(Fetch)属于自己的分区数据,先写入Buffer,数据量达到阈值后,溢写到磁盘的一个临时文件中
-数据抓取完成后,将多个临时文件合并为一个Reduce输入文件,文件内数据按key排序


从编程模型的视角,MapReduce有哪些优缺点?

1、优点

  • 高容错:任务失败,自动调度到其他节点重新执行
  • 高扩展:计算能力随着节点数增加,近似线性递增
  • 适用于海量数据的离线批处理
  • 降低了分布式编程的门槛

2、缺点(不适合)

• OLAP
-要求毫秒或秒级返回结果
• 流计算
-流计算的输入数据集是动态的,而MapReduce是静态的
• DAG计算
-多个任务之间存在依赖关系,后一个的输入是前一个的输出,构成有向无环图DAG -每个MapReduce作业的输出结果都会落盘,造成大量磁盘IO,导致性能非常低下


RDD的“弹性”主要体现在哪里?

失效后自动重构
1.自动进行内存和磁盘切换
2.基于lineage的高效容错
3.task如果失败会特定次数的重试
4.stage如果失败会自动进行特定次数的重试,而且只会只计算失败的分片
5.checkpoint【每次对RDD操作都会产生新的RDD,如果链条比较长,计算比较笨重,就把数据放在硬盘中】和persist 【内存或磁盘中对数据进行复用】(检查点、持久化)
6.数据调度弹性:DAG TASK 和资源管理无关
7.数据分片的高度弹性repartion


RDD宽依赖为什么又称为Shuffle依赖?

子RDD的部分或全部分区数据丢失或损坏,从所有父RDD分区重新计算,必须进行Shuffle


Spark运行模式有几种?Driver的主要功能是什么?

• Local模式

• 单机运行,通常用于测试
• Spark程序以多线程方式直接运行在本地

• Standalone模式

• Spark集群独立运行,不依赖于第三方资源
管理系统,如:YARN、Mesos
• 采用Master/Slave架构
• Driver在Worker中运行,Master只负责集群
管理
• ZooKeeper负责Master HA,避免单点故障
• 适用于集群规模不大,数据量不大的情况

• YARN/Mesos模式

• YARN-Client模式:适用于交互和调试
• YARN-Cluster模式:适用于生产环境

Driver主要功能

  • 一个Spark程序有一个Driver,一个Driver创建一个SparkContext,程序的main函数运行在Driver中
  • 负责解析Spark程序、划分Stage、调度任务到Executor上执行

简述Spark的程序执行过程。

• Driver -一个Spark程序有一个Driver,一个Driver创建一个SparkContext,程序的main函数运行在Driver中 -负责解析Spark程序、划分Stage、调度任务到Executor上执行
• SparkContext -负责加载配置信息,初始化运行环境,创建DAGScheduler和TaskScheduler
• Executor -负责执行Driver分发的任务,一个节点可以启动多个Executor,每个Executor通过多线程运行多个任务
• Task -Spark运行的基本单位,一个Task负责处理若干RDD分区的计算逻辑


DAGScheduler是如何划分Task的?

• 根据任务的依赖关系建立DAG

• 根据依赖关系是否为宽依赖,即是否存在Shuffle,将DAG划分为不同的阶段(Stage)
• 将各阶段中的Task组成的TaskSet提交到TaskScheduler


第七讲

为什么要对Consumer进行分组?

为了加快读取速度,多个Consumer可划分为一个组(Consumer Group, CG),并行消费同 一个Topic


为什么Kafka分了Topic之后,还要分Partition?

一个Topic可分为多个分区,相当于把一个数据集分成多份,分别存储不同的分区中,然后分区可以设置多个副本,副本存储在不同的Broker中,这样就可以避免Kafka早期版本没有Replication概念,一旦某个Brocker宕机,其上的分区数据就可能丢失,这样的错误。


Partition Leader和Follower是如何分工合作的?

从一个分区的多个副本中选举一个Partition Leader,由Leader负责读写,其他副本作为Follower从Leader同步消息


*为什么Zookeeper不亲自负责Partition Leader选举?

Kafka Controller Leader负责管理Kafka集群的分区和副本状态,避免分区副本直接在Zookeeper上注册Watcher和竞争创建临时Znode,导致Zookeeper集群负载过重


第十一讲

如何定位Inceptor?它与Hive有什么区别?

定位

• 分布式通用SQL引擎 -支持Slipstream、ArgoDB、Hyperbase和Search -构建星环新一代逻辑数据仓库
• 分布式数据仓库系统
• 基于Hive和Spark打造
• 用于离线分析和交互式分析(Holodesk -> ArgoDB)

它是在hive和spark的基础上进一步优化的,它是hadoop领域中SQL支持最完善的。与Apache Hive相比,数据分析处理速度有显著提升


如何理解Inceptor读时模式。

• 含义:数据写入数据仓库时,不检查数据的规范性,而是在查询时再验证
•特点
-数据写入速度快,适合处理大规模数据
-查询时处理尺度很宽松(弱校验),尽可能恢复各种错误


分区的目的是什么?分区有几种类型?如何将数据导入分区表?

目的:减少不必要的全表扫描,缩小查询范围,提升查询效率

类型:
单值分区:一个分区对应分区键的一个值
-单值静态分区:导入数据时,必须手动指定目标分区
-单值动态分区:导入数据时,系统可以动态判断目标分区

范围分区:一个分区对应分区键的一个范围(区间)
• 每个分区对应分区键的一个区间,凡是落在指定区间内的记录都被存储在对应的分区下
• 各范围分区按顺序排列,前一个分区的最大值即为后一个分区的最小值
• 创建范围分区


分桶的目的是什么?如何将数据导入分桶表?

• 含义:按分桶键哈希取模的方式,将表中数据随机、均匀地分发到若干桶文件中
• 目的:通过改变数据的存储分布,提升取样、Join等特定任务的执行效率
• 将数据写入分桶表
-分桶表在创建的时候只定义Schema,且数据写入时系统不会自动分桶,所以需要先人工分桶再写入
-写入分桶表只能通过Insert,而不能通过Load,因为Load只导入文件,并不分桶
-如果分桶表创建时定义了排序键,那么数据不仅要分桶,还要排序
-如果分桶键和排序键不同,且按降序排列,使用Distribute by ... Sort by分桶排序
-如果分桶键和排序键相同,且按升序排列(默认),使用Cluster by分桶排序


第十二讲

事件驱动模式与微批模式有什么不同?

微批(Micro-batch)模式:将Input Stream按时间划分成若干小数据块(Batch)来处理,即在由若干单位时间组成的时间间隔内,将接收的数据放到一个Batch中(Batch的时间长度称为Batch Duration)
-事件驱动(Event-driven)模式:以单条数据被Input Stream接收为事件,逐条读取并处理


两种处理模式下的窗口变形有什么不同?

-微批模式
窗口变形(多Batch变形)
*对一个时间窗口(Window)内的多个Batch进行计算得到新Batch的过程
*Window Stream:通过窗口变形得到的Derived Stream
*两个重要参数:Length和Slide,Length是窗口持续时间,Slide是两个相邻窗口之间的间隔时间, Length和Slide必须是Batch Duration的倍数

窗口变形(多数据变形):对一个时间窗口(Window)内的多条数据进行计算得到新数据的过程


简述一下SteamJob的主要作用。

对一个或多个Stream进行计算,并将结果写入一张表的任务
StreamJob是触发StreamSQL执行的Action,一般具有插入结果表语义
StreamJob主要存储StreamJob Level的配置参数,以及对应的SQL


StreamSQL与普通SQL有什么区别?

  • DML语句的运行机制不同
    • 普通SQL:阻塞式运行
    -提交SQL后,用户需等待SQL执行结束,期间命令被持续阻塞,无法执行其他命令
    • StreamSQL:背景运行
    -计算任务持续在后台运行
    -执行StreamSQL的DML语句会立即返回结果
  • 查询结果的输出不同
    • 普通SQL:查询结果或者显示在Console,或者通过JDBC读取
    • StreamSQL:用户必须显式地指定查询结果输出到某个地方
    -后台持续运行的SQL无法直接跟Console交互

-查询结果通常会插入到表中,如:Insert Into result_table Select ...


第十三讲

Search的数据模型与关系数据库有怎样的对应关系?

index(索引)->table(表)

document(文档)->row(行)
field(字段)->column(列)


Seach包含哪几类节点,它们各自负责哪些工作?

节点:一个运行中的ElasticSearch实例

主节点(MasterNode)
• 负责管理集群内的所有变更,如增删节点、增删索引、分配分片等,不负责文档更新和搜索
• 每个集群只有一个主节点,默认情况下任何节点都可能被选为主节点
• 硬件配置:普通服务器(CPU、内存消耗一般)

数据节点(DataNode)
• 负责存储数据,即文档的增删改查
• 分离主节点和数据节点是一个比较好的选择,因为索引和搜索操作会消耗大量资源
• 硬件配置:较高配置服务器(主要消耗磁盘和内存)

客户端节点(ClientNode / 路由节点)
• 负责路由请求,实现集群访问的负载均衡
• 集群规模较大时非常有用,协调主节点和数据节点,根据集群状态直接路由请求


简述Index、Document、Shard与副本Shard的关系。

• Shard分为主Shard和副本Shard,后者是前者的精确复制,每个Shard可有零个或多个副本
• Index的任意一个Document都归属于一个主Shard,主Shard的数量决定了Index的最大数据量
• Index建立时就必须明确主Shard数且不能修改,但副本Shard数可以随时修改
• 写操作只能被主Shard处理,读操作可同时被主Shard或副本Shard处理
-对于读操作,理论上拥有更多的副本,将拥有更高的吞吐量,但如果只在相同节点数目的集群上增加
副本并不能提高性能,因为每个Shard获得的资源会变少,这时需要增加更多的硬件资源来提升吞吐 量


简述Search更新文档的基本流程。

更新文档
(1)客户端向Node1(路由节点)发送新建、索引或删除文档请求

(2)通过文档id确定该文档属于分片0,请求被转发到Node3,因为分片0的主分片在Node3上
(3)Node3在主分片上执行更新操作,如果成功了,Node3将请求并行转发到Node1和Node2的 副本分片上,一旦所有副本分片都报告同步成功,Node3将向Node1报告更新成功, 最后 Node1向客户端报告成功


第十四讲

为什么可以将Hyperbase表看作是一张四维表?

因为它首先第一列是RowKey,下是每一行(包含多行),然后第二列是列族,下面包括很多列限定符,再是时间戳,表示存入时间所以是一张四维表。


为什么说Hyperbase是一个Key-Value数据库?


简述Table、Region、Store和StoreFile的关系。

一个table由多行组成,而系统将表水平划分(按行)为多个Region,每个Region保存表的一段连续数据,默认每张表开始只有一个Region,随着数据不断写入,Region不断增大,当Region大小超过阀值时,当前Region会分裂成两个子Region,而一个Region由多个Store组成,每个Store存储一个列族,而store由内存中的MemStore和磁盘中的若干StoreFile组成,MemStore是Store的内存缓冲区,数据读写都先访问MemStore,StoreFile是MemStore的磁盘溢写文件,在HDFS中被称为HFile


为什么要进行Region Split和StoreFile Compaction?

StoreFile Compaction
• 含义:将Store中的全部或部分StoreFile合并为一个StoreFile的过程
• 目的:减少StoreFile数量,提升数据读取效率

Region Split
• 含义:根据一定的触发条件和分裂策略,将Region划分为两个子Region的过程
• 目的:实现数据访问的负载均衡


简述HBase BulkLoad的基本过程。

• 抽取:从数据源中抽取数据
-对于MySQL,运行mysqldump命令导出数据
• 转换:利用MapReduce,将数据转换为HFile文件
-对于TSV或CSV文件,使用HBase ImportTsv工具将其转换成HFile文件
-每个输出文件夹中的每个区域都会创建一个HFile文件
-HDFS中的可用磁盘空间至少为原始输入文件的两倍。例如:对于100GB的mysqldump导出文件,HDFS中至少预留不少于200GB的磁盘空间,可在任务结束后删除原始输入文件
• 加载:将HFile文件加载到HBase
-利用HBase CompleteBulkLoad工具,将HFile文件移动到HBase表的相应目录中,完成加载



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

推荐阅读更多精彩内容