HDFS入门介绍—(6千字总结)

目录:
一、HDFS入门介绍
二、HDFS三大组件(NameNode、DataNode、Secondary Namenode)
三、HDFS高可用介绍
四、HDFS联邦介绍
五、HDFS三大机制(心跳机制、安全模式、副本存放策略)
六、HDFS读写原理
七、HDFS的启动流程
八、HDFS的shell操作
九、我的问题—默认文件地址

一、HDFS入门介绍

HDFS产生背景:
随着数据量越来越大,在一个操作文件系统存不下所有的数据,那么就分配到更多的操作系统管理磁盘中,但是不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。HDFS只是分布式文件管理系统中的一种
HDFS定义
HDFS(Hadoop Distributed File System)它是一个文件系统,用于存储文件,通过目录树来定位文件;其次他是分布式的,由很多服务器联合起来实现功能,集群中的服务器由各自的角色。

HDFS使用场景
适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适合用来做网盘应用。

HDFS的核心设计思想

1、一个大的文件想要进行存储,借助这种分布式的文件存储系统。把大文件切分的存储、分散存储
2、整个HDFS的从节点DataNode存在故障问题,但是还要保证数据的安全。冗余存储

HDFS优缺点:

1、HDFS优点
1)高容错性:

(1)数据自动保存多个副本,它通过增加副本的形式,提高容错性
(2)某一个副本丢失以后,它可以自动恢复

2)适合处理大数据

(1)数据规模:能够处理数据规模达到GB、TB、甚至PB级别的数据
(2)文件规模:能够处理百万规模以上的文件数量,数量相当之大

3)可构建在廉价的机器上,通过多副本机制,提高可靠性

在设计之初,就考虑到廉价的服务器有可能会挂掉,于是在设计的时候为可能挂掉的情况做了预备处理,比如存储上通过多副本的机制保证了数据的安全性

2、HDFS缺点
1)不适合低延时数据访问,比如毫秒级的存储数据,是做不到的
2)无法高效的对大量小文件进行存储

(1)存储大量小文件的话,它会占用NameNode大量的内存来存储文件目录和块信息。这样是不可取的,因为NnameNode的内存总是有限的
(2)小文件存储的寻址时间会超过读取时间,它违反了HDFS的设计目标

3)不支持并发写入、文件随机修改

(1)一个文件只能有一个写,不允许多个线程同时写(同一个文件有一个进程在使用,第二个进程无法写入该文件)
(2)仅支持数据追加(往文件里面加内容),不支持文件的随机修改

1.5、HDFS组成架构:

整个HDFS集群由Namenode、Datanode和SecondaryNamenode构成master-worker(主从)模式。Namenode负责构建命名空间,管理文件的元数据等,而Datanode负责实际存储数据,负责读写工作。

image.png

二、HDFS三大组件

HDFS集群包括,NameNodeDataNode以及Secondary NamenodeNameNode负责管理整个文件系统的元数据,以及每一个路径(文件)所对应的数据块信息;DataNode 负责管理用户的文件数据块,每一个数据块都可以在多个datanode上存储多个副本。

但事实上,只有在普通的伪分布式集群和分布式集群中才有会 SecondaryNamenode 这个角色, 在高可用或者联邦集群中都不再出现该角色。在高可用和联邦集群中,都是有standby namenode承担。

1、NameNode

1.1、NameNode职责
1.2、NameNode 元数据管理
1.3、磁盘当中的元数据文件位置
1.4、磁盘当中的元数据文件分类
1.5、元数据合并的目的

1.1、NameNode职责

1、维护元数据
2、响应客户端的读写数据请求

1.2、NameNode 元数据管理

WAL(Write ahead Log): 预写日志系统
MYSQL的每次事物操作,都会被日志记录下来,为什么这么做?如果某张表的数据丢失之后,可以对应日志恢复出来。

如果我们一个存储系统实现了WAL,那就意味着事物操作会被记录日志。在客户端每次事物操作的时候会先记录日志,在进行真正的执行事物,当事物执行成功之后我们才提交事物。
提交事务在HDFS中,如果有需要把HDFS预写日志的元数据更新到内存,只有在事务执行操作成功之后,才会更新到内存.
EG:如果我在上传一个文件的时候,上传到80%的时候失败了。前面上传80%成功的元数据信息不会更新到内存当中

预写日志就是把我们所有的操作都做日志记录,把这些记录记录到磁盘的文件中,最新的磁盘文件在edits_inprogress中。

1.3、磁盘当中的元数据文件位置

(我还不知道怎么找)

1.4、磁盘当中的元数据文件分类

磁盘当中的元数据文件包括三类:fsimage + edits + edits_inprogress

edits_inprogress包括目录树结构 + 文件块映射;目录树结构就是我们文件的目录地址信息,文件块映射就是文件分成哪几个块,这个块的id信息叫什么

edits是edits_inprogress分裂出来的,fsimage是edits_inprogress合并出来的。

问题:我们在执行操作的时候,会往edits_inprogress不停的写日志,文件就会不停的增大。这时候怎么办?
edits_inprogress会分裂出其他的edits文件

元数据文件,上面是拆分文件,下面是合并文件,合并的是拆分文件
1.4.1、元数据合并的目的

A、大大的缩小了操作日志的大小

有些操作是无用的操作(比如对下面的操作会进行合并) 第一次操作:插入一条记录 第二次操作:插入一条记录 第三次操作:删除前面两次操作插入的记录

B、合并之后的磁盘镜像文件可以被namenode快速的加载到内存当中

什么时候合并,cheack point来做(HDFS在重启的时候会加载元数据,SecondaryNamenode将切分的数据进行合并,合并后结果格式:fsimage)

1.5、NameNode 元数据存储机制

A、内存中有一份完整的元数据(内存 metadata)
B、磁盘有一个元数据镜像(fsimage)文件(在 namenode 的工作目录中)
C、用于衔接内存 metadata 和持久化元数据镜像 fsimage 之间的操作日志(edits 文件)
当客户端对hdfs中的文件进行新增或者修改操作,操作记录首先被记入edits日志
文件中,当客户端操作成功后,相应的元数据会更新到内存 metadata 中

存放HDFS集群的版本信息

[root@hadoop0 current]# cat VERSION 
    #Sat Sep 05 20:02:39 CST 2020
    namespaceID=1618169485 #文件系统唯一的标识符
    clusterID=CID-2b25a790-4268-430a-91bc-47bcecb44eaf #集群唯一的标识符
    cTime=0 #fsimage创建的时间,初始化的时候为0,随着版本的更新而更新
    storageType=NAME_NODE #节点的类型
    blockpoolID=BP-359584305-192.168.22.128-1594828513525 #数据块池的id
    layoutVersion=-63 #hdfs持久化数据结构的版本号

2、DataNode

2.1、DataNode职责:

1、存储管理用户的文件块数据
2、定期向 namenode 汇报自身所持有的 block 信息(通过心跳信息上报)
3、真正的为客户端读写数据提供

讲datanode机制,主要是看datanode是如何管理数据块的
数据块两个重要的参数: 块的大小 、副本的个数
上传之前块的大小可以指定,上传之后块的大小不能改变
Datanode的工作机制就是看块的表现

2.2、Datanode上线和下线

A、Datanode上线(节点启动)
举个例子:如果一个现有的HDFS集群有500个节点,现在再增加10个节点。HDFS会如何表现?
1、新加的datanode启动之后,会按照配置文件去寻找HDFS集群的namenode进行报道
2、新上线的datanode,没有任何的数据块,只有自身的状态信息

原来的datanode和新来的datanode存在数据倾斜问题,原来的datanode压力比较大,如何解决磁盘数据之间倾斜(不是hive):
需要做一件事负载均衡
1、服务器之间的负载均衡
2、磁盘之间的负载均衡
举个例子:假如一个节点4个磁盘,每个磁盘块大小为2t,结果整个这个节点只存储了1t的数据

B、Datanode下线(节点宕机)
举个例子:如果一个现有的HDFS集群有500个节点,宕机 10个节点。10个节点存储的数据块都丢失了,HDFS集群会利用自身的恢复机制来恢复原来的副本个数。

问题:

1、Datanode的工作机制是怎样的?
2、原来的datanode和新来的datanode存在数据倾斜问题,这个数据倾斜解决的过程是怎样的?

3、SecondaryNameNode

3.1、SecondaryNameNode职责

1、分担namenode的合并元数据的压力

在配置SecondaryNameNode 不要和 namenode在一个节点上面,SecondaryNameNode在HDFS中扮演着辅助的作用,负责辅助NameNode管理工作。

但事实上,只有在普通的伪分布式集群和分布式集群中才有会 SecondaryNamenode 这个角色, 在HA或者联邦集群中都不再出现该角色。在HA和联邦集群中,都是有standby namenode承担。

三、HDFS高可用介绍

NameNode单点故障问题: 高可用解决

架构改成了高可用之后,我们再次使用了另外一套集群来存储元数据信息,不把数据存放在NameNode 上面。

1、背景

在hadoop2.0.0之前,HDFS集群中存在NameNode单点故障的问题,只要集群中一个NameNode不可用时,整个cluster将不可用,直到nameNode被重启或者或者备份到了另一个独立的机器才能使用。

造成问题的原因可能是:
NameNode所在机器突然宕机,集群将陷入瘫痪,直到NameNode服务重启
NameNode所在机器软硬件升级,将会导致集群在期间无法提供服务

2、HA 架构设计

在典型的HA架构中,two or more独立的机器被配置为Namenode,任何时刻,只有一个Namenode处于Active状态,其他都处于standby状态;Active Namenode负责接收Client端请求,Standby Namenode节点作为备胎并没资格和客户端交互,它只是不停地备份数据,努力与Active Namenode保持一致,争取在主机点挂掉后切换为主节点。

HDFS HA的实现方式是在每个HDFS集群中运行两个NameNode(or more),他们以主/从模式运行,Active NameNode负责接收客户端读写操作,将操作保存在JNS集群中,从节点会不断地读取JNS集群中的操作信息,从而实现主从NameNode数据同步,当Active Namenode节点不可用时,把从节点切换为主节点。

为了让Standby Namenode与Active Namenode保持同步,这两个Node都与一组称为JNS的独立的进程通信(Journal Nodes)。当Active Namenode接受客户端请求修改了namespace,它将把修改log写入到到JNS集群的大多数节点。
Standby Namenode会不断地监控JNS集群上edit log的改变。当Standby Namenode看到写入完成的edit log后,会将log同步到自己的namespace中。这样在主从节点切换时,Standby可以确保已经从从JNS中读取到了Standbye Namenode记录的所有edits。

为了支持快速切换,Standby Namenode必须持有集群中所有block的最新位置。为此,所有Datanode都配置了Namenodes的地址(主从都配置),并把block位置上报给它们。

3、HDFS的HA实现主要包括两部分:NameNode主从切换、NameNode数据同步

3.1、NameNode主从切换

还不会,以后补充把

3.2、NameNode数据同步

Active NameNode负责接收客户端的读写操作,将操作保存在JNS集群中,从节点会不断地读取JNS集群中的操作信息,从而实现主从NameNode数据同步。

4、分布式改成高可用之后,有事情要解决

1、我们必须要实现一些程序来监控所有的namenode的状态
2、 实现一个选举算法
3、所有的NameNode所在的节点的元数据信息保持一致
解决方案: ZooKeeper 动物园管理员

四、HDFS联邦介绍

NameNode内存不足问题: 联邦解决

图解:
1、A+b+c=集群中所有的元数据信息,第一个节点只保存a的元数据信息,为防止a节点宕机,有一个节点为a节点备份
2、横着是联邦,竖着是高可用
3、联邦中的集群节点是管理整个集群中元数据信息的一部分高可用所有节点的元数据信息一模一样

虽然HDFS HA解决了“单点故障”问题,但是在系统扩展性、整体性能和隔离性方面仍然存在问题。
(1)系统扩展性方面,元数据存储在NameNode内存中,受内存上限的制约。
(2) 整体性能方面,吞吐量受单个NameNode的影响。
(3)隔离性方面,一个程序可能会影响其他运行的程序,如一个程序消耗过多资源导致其他程序无法顺利运行。HDFS HA本质上还是单名称节点。

HDFS联邦可以解决以上三个方面问题。

在HDFS联邦中,设计了多个相互独立的NameNode,使得HDFS的命名服务能够水平扩展,这些NameNode分别进行各自命名空间和块的管理,不需要彼此协调。每个DataNode要向集群中所有NameNode注册,并周期性的发送心跳信息和块信息,报告自己的状态。

HDFS联邦拥有多个独立的命名空间,其中,每一个命名空间管理属于自己的一组块,这些属于同一个命名空间的块组成一个“块池”。每个DataNode会为多个块池提供块的存储,块池中的各个块实际上是存储在不同DataNodeN中的。

五、HDFS三大机制

1、心跳机制

1.1、心跳机制原理

集群节点之间必须做时间同步。NameNode是集群的老大,负责集群上任务的分工,如果要进行分工,则必须知道各个从节点存活状况
NameNode怎么知道?通过DataNode定期的向NameNode发送心跳报告,DataNode每隔3秒向NameNode发送一次心跳报告,告诉其自己的存活状态,默认情况下心跳间隔的参数由hdfs-default.xml中的下面参数决定:

datanode每隔3秒向NameNode发送一次心跳报告,当NameNode连续10次没有收到DataNode的心跳报告,会觉得datanode可能死了,并没有断定死了,此时NameNode向DataNode主动发送一次检查,发送一次检查的时间时5分钟,由hdfs.default.xml中的下面属性决定。

如果一次检查没有返回信息,这时候NameNode会再进行一次检查,如果还是没有读取不到DataNode的信息,此时判定死亡。也就是说NameNode最终判断DataNode死亡需要103s+25min=630s,也就是说NameNode在连续630s中没有得到DataNode的信息才认为当前的DataNode宕机。

心跳机制让NameNode能够识别到当前的集群中还有多少的DataNode存活
如果DataNode存活才能对外提供服务,如果DataNode宕机,HDFS集群会利用自身的恢复机制来恢复原来的副本个数

1.2、datanode死亡判断

timeout(超时时长) = 10 * 心跳时长 + 2 * 检查心跳机制是否正常工作的时间
心跳时长:3秒
检查心跳机制是否正常工作的时间 5分钟=300秒
timeout(超时时长) = 630秒

1.4、NameNode通过心跳上报的作用

1、让namenode识别到datanode的存活状态
2、心跳数据包
心跳数据包包括当前DataNode自身的状态DataNode节点所保存的所有的block块的信息

A、当前datanode自身的状态
磁盘的使用量、block的数量、block的状态

通过命令 hdfs dfsadmin -report  在主节点上查看

B、datanode节点所保存的所有的block块的信息
当namenode接收到datanode发送的汇报信息之后,就能统计出来,哪些文件的哪些数据块多个副本分布在哪些节点上

1.3、数据库的计算

以后涉及到数据库的计算的时候namenode会给datanode发送命令,也是通过心跳机制
汇报:是datanode给namenode发信息
命令:是namenode给datanode发信息

Last contact:0 1 2 正常

问题:找不到datanode上面存放的块的地址

/software/hadoop/data/datanode/current/BP-359584305-192.168.22.128-1594828513525/current/finalized/subdir0/subdir3

2、安全模式

安全模式是hadoop的一种保护机制,用于保证集群中的数据块的安全性。

当集群启动的时候,会首先进入安全模式,在安全模式当中,HDFS集群不能对外提供服务。
当系统处于安全模式时会检查数据块的完整性。假设我们设置的副本数(即参数dfs.replication)是5,那么在datanode上就应该有5个副本存在,假设只存在3个副本,那么比例就是3/5=0.6。在配置文件hdfs-default.xml中定义了一个最小的副本的副本率0.999,如图

image.png

我们的副本率0.6明显小于0.99,因此系统会自动的复制副本到其他的dataNode,使得副本率不小于0.999.如果系统中有8个副本,超过我们设定的5个副本,那么系统也会删除多余的3个副本。

2.1、什么情况下进入安全模式?

1、当HDFS集群中的部分的datanode宕机之后,HDFS会启动一些服务,做自我恢复
2、当丢失的数据库的比例超过0.1%的时候,就会自动进入安全模式
3、当集群启动的时候

超过比例仍然要提供服务的解决方案:手动强制退出安全模式,就是发送命令

hdfs dfsadmin -safemode leave  //强制namenode退出安全模式
hdfs dfsadmin -safemode enter  //强制进入安全模式
hdfs dfsadmin -safemode get  //查看安全模式的状态
hdfs dfsadmin -safemode wait //等待,一直等到安全模式结束

2.2、什么情况下退出安全模式?

1、找出问题所在,进行修复
2、手动强制退出安全模式

hdfs dfsadmin -safemode leave  //强制namenode退出安全模式

3、副本存放策略

副本存放策略:就是决定一个数据块的多个副本(默认是3)到底应该选取哪些服务器的节点进行存储

3.1、副本存放策略原则

(1)任意一个节点上面不可能存储两个一样的副本块
(2)如果一个数据库要保存完整的3个副本块,那么必须至少要3个节点

3.2、副本存放策略

(1)第一个副本块选取和客户端相同的节点上
(2)第二个副本块选取跟第一个副本的存储节点相邻的机架的任意一个节点
(3)第三个副本存储在和第二个副本块所在的机架的不同的节点上

六、HDFS读写原理

1、HDFS写入数据原理

客户端要向 HDFS 写数据,首先要跟 NameNode 通信以确认可以写文件并获得接收文件 block 的DataNode ,然后,客户端按顺序将文件逐个 block 传递给相应 DataNode ,并由接收到 block 的
DataNode 负责向其他 DataNode 复制 block 的副本


总结
(1)客户端向namenode请求上传nx.txt
(2)namenode 检查权限并做出响应
(3)客户端拿到namenode的响应之后,请求上传第一个block块,并请namenode返回可用的datanode列表
(4)namenode依据客户端的请求返回可用的datanode的列表
(5)客户端与可用的datanode列表中的节点建立数据传输管道,进行数据传输
(6)datanode依次返回响应,正式建立起来数据传输管道
(7)客户端以package为单位,进行数据的传输,知道第一个block块传输完毕
(8)其他的block块以此类推,传输完全部的数据

2、HDFS读取数据原理

客户端将要读取的文件路径发送给 NameNode ,NameNode 获取文件的元信息(主要是 block的存放位置信息)返回给客户端,客户端根据返回的信息找到相应 DataNode 逐个获取文件 的 block
并在客户端本地进行数据追加合并从而获得整个文件

image.png

总结
(1)客户端向namenode请求下载文件nx.txt
(2)namenode返回目标文件的元数据信息,{blk1:dn1 blk2:dn2 blk3:dn1}
(3)客户端和每一个block块所在的主机建立管道
(4)以package为单位进行数据的传输
(5)读取完毕之后,将block块合并成完整的文件

七、HDFS的启动流程

1、先启动namenode
2、加载namenode的文件夹中的磁盘的元数据(fsimage + edits_inprogress)
3、namnode在启动完毕之后,等到datanode的汇报
4、datanode一旦上线,就会通过心跳机制把自身的所有的block块的信息全部汇报给namenode
5、只有当namenode等到了所有的datanode的上线,datanode把所有的block块的信息全部汇报完毕。最后namenode才能得知,整个集群当中数据库的副本的存在的地方。

元数据信息:
1、目录树结构
2、每个文件的数据库存放的位置

假设HDFS的集群非常的大,就会出现两种情况:
1、元数据fsimage文件大,加载到内存中的时间长
2、datanode节点多,并且每个节点的数据库的个数也多
随着HDFS的集群的增大,最终HDFS集群的启动消耗的时间越来越长

八、HDFS的shell操作

start-dfs.sh  //启动hdfs
hdfs dfs -mkdir dir  //创建文件夹
hdfs dfs -rmr dir //删除文件夹dir
hdfs dfs -ls //查看目录文件信息

九、我的问题(默认文件地址)

所有的默认文件除了元数据文件,我都找不到路径,太可怜勒。
1、数据块大小、及副本个数在哪个文件修改?
副本个数

/home/bigdata/apps/hadoop-2.7.7/etc/hadoop/hdfs-site.xml  //查看副本个数文件hdfs-site.xml
查看副本个数文件位置

2、找不到datanode上面存放的块的地址?
3、心跳机制的DataNode发送心跳报告参数时间,文件在哪看?
4、检查心跳机制是否正常工作的时间,文件在哪看?
5、HDFS在重启的时候在启动,SecondaryNamenode合并的是edits_inprogress文件,还是将edits_inprogress、edits_inprogress合并成fsimage?
6、如何查看namanode元数据文件?
我磁盘当中的元数据文件位置:/home/bigdata/data/hadoopdata/name/current
7、然后我集群间的免密协议还有点问题(hadoop2,hadoop3不能免密传文件到hadoop1。我已经重新配置2次了)
8、补补DFSOutputStream
9、明天得学会hdfs的api(在难也得学,不然就跟不上了,今天老师手敲了3个小时的代码)


10.在什么地方将服务器端口修改成地址
/home/bigdata/apps/hadoop-2.7.7/etc/hadoop/core-site.xml ---我电脑地址

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