Hadoop: 完全分布式环境搭建

说明:本文所有操作均在 64位 ubuntu 16.04 操作系统下进行

准备

通过物理机器虚拟化 4 台虚拟机:1 个 Master 节点,3 个 Slave 节点。为了实现节点间在同一局域网上定向通信,配置使用静态地址,各节点的 IP 分布如下:

节点主机名 静态 IP 地址 主要角色
master 192.168.1.200 namenode 节点
slave01 192.168.1.201 datanode 节点
slave02 192.168.1.202 datanode 节点
slave03 192.168.1.203 datanode 节点

Master 节点机器主要配置 NameNode 和 JobTracker 角色,总体负责分布式数据和分解任务的执行;Slave 节点机器配置 DataNode 和 TaskTracker 的角色,负责分布式数据存储以及任务的执行。

需要的软件:

软件名称 描述
VirtualBox 用于虚拟化主机
Ubuntu Server 16.04.2 LTS 服务器版的 ubuntu 操作系统
jdk-8u121-linux-x64.tar.gz Hadoop 需要 JDK 的支持
hadoop-2.7.3.tar.gz hadoop 的安装文件

配置一个单节点环境

安装 VirtualBox

使用 apt 安装 virtual box

$ sudo apt install virtualbox

新建虚拟机

打开 VirtualBox,点击 New,Name 填 master (为方便区分),Type 选择 Linux,Version 选择 Ubuntu(64-bit),点击下一步。

一路 Next,最终如下图。

为虚拟机安装操作系统

选择镜像

点击 Settings,选中 Storage -> Controller:IDE -> Empty


右上方有一个光盘的小图标,点击之后选择下载好的 Ubuntu Server 镜像


点击 OK 确定。

安装

点击 Start 运行虚拟机开始安装


注意:语言请选择 "English",选择 "中文" 安装过程中会报错


选择 "Install Ubuntu Server"

一路回车,到创建新用户时停止。


这里我将用户名设置为 spark (最头疼的就是起名字了,你可以根据自己的喜好随意起名 _ )

continue,continue,到设置密码的时候停止。


注意:密码输入完成后还需重新输入一次以确认两次密码是否一致

如果密码强度太弱系统会发出如下提示:


手动选择 Yes

然后一路回车。注意,在此过程中系统会自动配置时区时间,就不要 cancel 了,让它自己配一会儿。

接下来是给磁盘分区,依旧回车,到如图所示的时候停下来,选择 Yes

继续,到这一步,选择 Yes


现在静静的等一小会儿_

到这一步,直接 continue


然后系统会连接网络更新软件,这一步不是必须的(且比较耗时),所以选择 cancel,然后继续回车,到下图所示的步骤停止。

这里我们选择安装 standard system utilitiesOpenSSH serverstandard system utilities 不安装的话机器基本无法正常使用,安装 OpenSSH server 是为了方便集群之间的通信。

好了,剩下的所有对话框都可以用回车解决了。
等待安装完成,系统会自动重启。

关闭虚拟机,并设置虚拟机的网络为桥接(bridged adapter)模式。

对安装的操作系统进行配置

启动虚拟机,并进行如下配置:

  1. 设置主机名
  2. 设置静态 IP
  3. 安装 JDK
  4. 安装 Hadoop

1. 设置主机名

打开文件 hostname

$ sudo vim /etc/hostname

将内容修改为 master

2. 设置静态 IP

打开文件 interfaces

$ sudo vim /etc/network/interfaces

我的 interfaces 文件中的内容如下:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto enp0s3
iface enp0s3 inet dhcp

dhcp 修改为 static,并添加 ip 地址,子网掩码,网关

修改完成后的文件内容如下:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto enp0s3
iface enp0s3 inet static
address 192.168.1.200
netmask 255.255.255.0
gateway 192.168.1.1

完成后继续配置 DNS 解析

$ sudo vim /etc/resolvconf/resolv.conf.d/base 

添加如下内容:

nameserver 192.168.1.1
nameserver 114.114.114.114

重启使配置生效

此时,主机名已经是 master 了,运行 ifconfig 查看一下 ip 是否正确修改

spark@master:~$ ifconfig
...
enp0s3    Link encap:Ethernet  HWaddr 08:00:27:0f:f6:ec  
          inet addr:192.168.1.200  Bcast:192.168.1.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fe0f:f6ec/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:86 errors:0 dropped:0 overruns:0 frame:0
          TX packets:86 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:12088 (12.0 KB)  TX bytes:12102 (12.1 KB)
...

看到 inet addr 已经修改为 192.168.1.200

在进行接下来的操作之前我们先配置一下本机(物理机)的 hosts 文件,使 ip 和主机名绑定,方便后续操作

$ sudo vim /etc/hosts

在文件末尾添加如下内容:

192.168.1.200 master
192.168.1.201 slave01
192.168.1.202 slave02
192.168.1.203 slave03

现在我们就可以通过主机名来连接主机了

打开终端,使用 ssh 连接虚拟机

$ ssh spark@master 

spark 为虚拟机中创建的用户名,master 为刚才配置的主机名
中间会有个小提示,输入 yes 回车,然后输入 spark 用户的密码

3. 安装 JDK

参考 JDK 的正确安装姿势

4. 安装 Hadoop

我已经提前下载好了 hadoop-2.7.3.tar.gz,直接传到虚拟机里就可以了
这里有个小技巧,可以用 scp 命令在 linux 主机之间传文件,例如:

$ scp hadoop-2.7.3.tar.gz spark@master:/home/spark/

这条命令表示将文件 hadoop-2.7.3.tar.gz 从本机复制到 master 主机的 /home/spark/ 目录下

在虚拟机中进行安装 hadoop 的操作

解压安装文件

在用户家目录下新建 apps 目录,将 hadoop-2.7.3.tar.gz 移动到 apps 目录中进行解压

$ mkdir apps && mv hadoop-2.7.3.tar.gz apps && cd apps && tar zxvf hadoop-2.7.3.tar.gz 

配置

  1. 配置 hadoop 环境变量
export HADOOP_HOME="/home/spark/apps/hadoop-2.7.3"
export HADOOP_LOG_DIR="$HADOOP_HOME/logs"
export YARN_LOG_DIR="$HADOOP_LOG_DIR"
export PATH="$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin"

使配置立即生效

$ source ~/.bashrc 

执行 hadoop 命令,出现如下信息则说明环境配置成功

$ hadoop
Usage: hadoop [--config confdir] [COMMAND | CLASSNAME]
 CLASSNAME            run the class named CLASSNAME
...
  1. 修改 hadoop 配置文件
    2.1 修改 ~/apps/hadoop-2.7.3/etc/hadoop 目录下的 hadoop-env.sh yarn-env.sh mapred-env.sh 文件中 JAVA_HOME 的值为 /opt/java/jdk1.8.0_121
    2.2 修改 ~/apps/hadoop-2.7.3/etc/hadoop/slaves 文件内容如下:
slave01
slave02
slave03

2.3 配置 ~/apps/hadoop-2.7.3/etc/hadoop/core-site.xml
添加如下内容:

  <property>
    <name>fs.defaultFS</name>
    <value>hdfs://master:9000</value>
    <description>设定 namenode 的主机名及端口</description>
  </property>
  <property>
    <name>hadoop.tmp.dir</name>
    <value>/home/spark/apps/hadoop-2.7.3/tmp/hadoop-${user.name}</value>
    <description>存储临时文件的目录</description>
  </property>

  <!-- 用户 "spark" 可以代理所有主机上的所有用户 -->
  <property>
    <name>hadoop.proxyuser.spark.hosts</name>
    <value>*</value>
  </property>
  <property>
    <name>hadoop.proxyuser.spark.groups</name>
    <value>*</value>
  </property>

2.4 配置 ~/apps/hadoop-2.7.3/etc/hadoop/hdfs-site.xml
添加如下内容:

 <property>
    <name>dfs.namenode.http-address</name>
    <value>master:50070</value>
    <description> NameNode 地址和端口 </description>
</property>

<property>
    <name>dfs.namenode.secondary.http-address</name>
    <value>slave01:50090</value>
    <description> SecondNameNode 地址和端口 </description>
</property>

<property>
    <name>dfs.replication</name>
    <value>3</value>
    <description> 设定 HDFS 存储文件的副本个数,默认为 3 </description>
</property>

<property>
    <name>dfs.namenode.name.dir</name>
    <value>file:///home/spark/apps/hadoop-2.7.3/dfs/name</value>
    <description> namenode 用来持续存储命名空间和交换日志的本地文件系统路径 </description>
</property>

<property>
    <name>dfs.datanode.data.dir</name>
    <value>file:///home/spark/apps/hadoop-2.7.3/dfs/data</value>
    <description> DateNode 在本地存储块文件的目录列表 </description>
</property>

<property>
    <name>dfs.namenode.checkpoint.dir</name>
    <value>file:///home/spark/apps/hadoop-2.7.3/dfs/namesecondary</value>
    <description> 
       设置 secondarynamenode 存储临时镜像的本地文件系统路径,
       如果这是一个用逗号分隔的列表,则镜像将会冗余复制到所有目录 
   </description>
</property>

<property>
    <name>dfs.webhdfs.enabled</name>
    <value>true</value>
    <description> 是否允许网页浏览 HDFS 文件 </description>
</property>

<property>
    <name>dfs.stream-buffer-size</name>
    <value>131072</value>
    <description> 
       默认 4KB,作为 hadoop 缓冲区,用于 hadoop 读 HDFS 的文件和写 HDFS 的文件,
       还有 map 的输出都用到了这个缓冲区容量,对于现在的硬件,可以设置为 128KB(131072),
       甚至是 1MB(太大了 map 和 reduce 任务可能会内存溢出) 
   </description>
</property>

2.5 配置 ~/apps/hadoop-2.7.3/etc/hadoop/mapred-site.xml
该文件需要通过模板复制一份

$ cp mapred-site.xml.template mapred-site.xml

添加如下内容:

<property>
    <name>mapreduce.framework.name</name>
    <value>yarn</value>
</property>

<property>
    <name>mapreduce.jobhistory.address</name>
    <value>master:10020</value>
</property>

<property>
    <name>mapreduce.jobhistory.webapp.address</name>
    <value>master:19888</value>
</property>

2.6 配置 ~/apps/hadoop-2.7.3/etc/hadoop/yarn-site.xml
添加如下内容:

<property>
   <name>yarn.resourcemanager.hostname</name>
   <value>master</value>
</property>

<property>
    <name>yarn.nodemanager.aux-services</name>
    <value>mapreduce_shuffle</value>
</property>

<property>
    <name>yarn.nodemanager.aux-services.mapreduce.shuffle.class</name>
    <value>org.apache.hadoop.mapred.ShuffleHandler</value>
</property>

<property>
    <name>yarn.resourcemanager.address</name>
    <value>master:8032</value>
</property>

<property>
    <name>yarn.resourcemanager.scheduler.address</name>
    <value>master:8030</value>
</property>

<property>
    <name>yarn.resourcemanager.resource-tracker.address</name>
    <value>master:8031</value>
</property>

<property>
    <name>yarn.resourcemanager.admin.address</name>
    <value>master:8033</value>
</property>

<property>
    <name>yarn.resourcemanager.webapp.address</name>
    <value>master:8088</value>
</property>

绑定 hostnameip
之前我们在物理机中绑定过 iphostname。现在,在虚拟机中进行同样的操作

$ sudo vim /etc/hosts

在文件末尾添加如下内容:

192.168.1.200 master
192.168.1.201 slave01
192.168.1.202 slave02
192.168.1.203 slave03

通过 ping master 检查是否修改成功

$ ping master
PING master (192.168.1.200) 56(84) bytes of data.
64 bytes from master (192.168.1.200): icmp_seq=1 ttl=64 time=0.016 ms
64 bytes from master (192.168.1.200): icmp_seq=2 ttl=64 time=0.022 ms
64 bytes from master (192.168.1.200): icmp_seq=3 ttl=64 time=0.021 ms

至此,我们已经完成了一台主机上 hadoop 的配置,接下来,我们通过 master 主机克隆 3 个 slave 主机

克隆多个虚拟主机

关闭虚拟机,选中列表中的 master,右键选择 clone

将其命名为 slave01,并把下面的对勾打上(用于重新初始化 MAC 地址)

继续创建 slave02, slave03,方法同上

现在,我们就有了 4 台虚拟主机

配置 slave 主机

修改 slave01 的 hostname

$ sudo vim /etc/hostname 

内容如下:

slave01

修改 slave01 的 ip

$ sudo vim /etc/network/interfaces

address 修改为 192.168.1.201

对 slave02 和 slave03 做类似操作

配置 SSH 免密码登录

在 master 主机上运行如下命令(一路回车):

$ ssh-keygen -t rsa

此时 ~/.ssh 目录下会生成两个文件:id_rsa id_rsa.pub,前者是私钥,后者是公钥

id_rsa.pub 写入 authorized_keys 文件并测试是否可以对本机进行 SSH 无密码登录

$ cat .ssh/id_rsa.pub >> .ssh/authorized_keys
$ chmod 600 .ssh/authorized_keys 
$ ssh localhost // 接下来会询问是否继续,输入 yes 回车

对 3 台 slave 主机进行上述操作均会生成自己的 authorized_keys

将 slave 主机的公钥依次复制到 master 主机

$ ssh-copy-id -i ~/.ssh/id_rsa.pub spark@master

此时 master 主机的 authorized_keys 文件内容如下:

$ cat .ssh/authorized_keys 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPdIlGd1POJtarQAVIos5GuZKs64wRQfDXBT/p8WtLshKQ2NN+HGqOqwOY42VjtaEAhoWiWUaT4DPuMgMVso7N90oLVADOu8kEqE8EErrZKTbD1qW4HJOA5LNdNt6ExrTQQMQ8W8gdxN209/e/z7ZIblnT2ZXfw0Y5bQHYf37RI9+vo//NTPFLTP07YoKc9LYTx4YxQxw7PJRm7+G6B2wY5x/YIqYlhwWCqDut/lfoRFR0iMbifsjyOyNMSDF2/f6DQvTgx+EZ3UAxZFSQhE1HSdxpTsWA/SbRctF7MlTWrDYP6eknVfHjdD15CBcqEdkVbiKNJk6WjobeUy8XSWyz spark@master
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC20+5tTSIoj+cLVN9lhWtyyJudzKJP7lEz/bP3fecX00nNAeWD0YJnWMOVibw3ISMZ8lHu4YUqZjzN6taDesqGWtI57o7cRr5PeeuU2Iwruy48MOPHlx4kgXPjLN4APapTJ5m5UPZYjERt3RnEGfHMU5dowZ8QKAl4rk0yApctXoD3N1Kz2avNqKiREdzGo7bJm/HrEzt5uUqKqEQqVpFGOZRhZ7t2Ad/mu5/CvgB/weA24wC3noXliGTxdX6x0/SHKBdIBCWnTzgX6xAbs/l/Gix2Se70/Xl23pJRp4GK/ulOB4zKasxJOTfe0/33BhNT48AUCVoYXRUNuoq7Um3L spark@slave01
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7To6Xa58/2EPR0OOHFpsy5n9acp+a/cwoYXI9rIvO8ISG52FBHahtimEN2RgIon0oU+r32+E3wcZRB/4uwfBQriKxGHpII3WVTEj8qZoDywqITvbhM08EXBISzIIOfIc6s3zCZb6fqQW1Vsw5Toz6QteEB8g9Eq6AXrvM4PKILq+aZeV8qnjFZOWccjz+sHRXyNcP1v111hZbrrOBvAvh5Q8P79ReLDyWjJodfS6MTuQe6kMnTscvwA+2x/qxeYthkXDlp7+ETogYrP/LBcfd+LUX6A2fzFahQSy/Ok1Dzq0N/BRPnpEWD920GwpN0VhjOQMn7relKHxl0I4HUwAz spark@slave02
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC54wC6D1TckbScYKtErVdM8a332bIyyaFDTCoL7kyvHubx4ZE2uY9CxBxosKk/eC9GLYc3gIOfVrXotGBzafQxUUVf/AxsuP2cMoSQU4yjLmyFE5D/HISIG+1kt9wGE6RqL0HmQ7BAOMBwv0rvI4GeEpnEo3O7cwRmEh42r5mS7kAHYKg2f8sLqMeA8UXrh4YUqzs8r64lEphOgd4UBmAdc1xKrM9j95P3dKeFfRANSfckJkNHgdzGYWioaY/qSfYczjR8JLXT6veC822xF6Q6Za05fYMzVVW9f/DfNdXba01rb20RJmQ1lC68ClqgR5EDFqSt5xAaVn4aSuH57IOv spark@slave03

复制此文件到 3 台 slave 主机即可实现节点之间的免密码登录

$ scp .ssh/authorized_keys spark@slave01:/home/spark/.ssh/
$ scp .ssh/authorized_keys spark@slave02:/home/spark/.ssh/
$ scp .ssh/authorized_keys spark@slave03:/home/spark/.ssh/

Hadoop 的启动和测试

格式化文件系统

$ hdfs namenode -format

输出如下信息:

...
17/03/03 21:11:54 INFO util.ExitUtil: Exiting with status 0
17/03/03 21:11:54 INFO namenode.NameNode: SHUTDOWN_MSG: 
/************************************************************
SHUTDOWN_MSG: Shutting down NameNode at master/192.168.1.200
************************************************************/

提示 Exiting with status 0 则表示格式化成功

启动 HDFS

启动 Hadoop HDFS 服务

$ start-dfs.sh

验证是否启动成功,在浏览器地址栏输入 http://master:50070 ,出现如下图所示的信息则表示启动成功:

也可以通过 jps 命令查看各节点是否正常启动

启动 Yarn

$ start-yarn.sh

验证是否启动成功,在浏览器地址栏输入 http://master:8088 ,出现如下图所示的信息则表示启动成功:

同样的,也可以使用 jps 命令查看各节点启动的进程
此时可以看到 master 节点多了 ResourceManager 进程,slave 节点多了 NodeManager 进程

集群验证

使用 Hadoop 自带的 WordCount 例子进行集群验证

  1. 在 HDFS 上创建目录
$ hadoop fs -mkdir -p /data/input
  1. 在本地创建 wordcount.txt 文件并上传到 HDFS
    wordcount.txt中的内容如下:
Hello laowang
hello kviccn
hello jack

上传到文件到 HDFS

$ hadoop fs -put wordcount.txt /data/input
  1. 运行 WordCount
$ hadoop jar ${HADOOP_HOME}/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.3.jar wordcount /data/input /data/output

等待运行结束,输出如下信息:

17/03/03 21:48:46 INFO client.RMProxy: Connecting to ResourceManager at master/192.168.1.200:8032
17/03/03 21:48:48 INFO input.FileInputFormat: Total input paths to process : 1
17/03/03 21:48:48 INFO mapreduce.JobSubmitter: number of splits:1
17/03/03 21:48:48 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1488547769795_0001
17/03/03 21:48:49 INFO impl.YarnClientImpl: Submitted application application_1488547769795_0001
17/03/03 21:48:49 INFO mapreduce.Job: The url to track the job: http://master:8088/proxy/application_1488547769795_0001/
17/03/03 21:48:49 INFO mapreduce.Job: Running job: job_1488547769795_0001
17/03/03 21:49:00 INFO mapreduce.Job: Job job_1488547769795_0001 running in uber mode : false
17/03/03 21:49:00 INFO mapreduce.Job:  map 0% reduce 0%
17/03/03 21:49:09 INFO mapreduce.Job:  map 100% reduce 0%
17/03/03 21:49:20 INFO mapreduce.Job:  map 100% reduce 100%
17/03/03 21:49:21 INFO mapreduce.Job: Job job_1488547769795_0001 completed successfully
17/03/03 21:49:21 INFO mapreduce.Job: Counters: 49
...

查看结果:

$ hadoop fs -cat /data/output/*
Hello   1
hello   2
jack     1
kviccn   1
laowang  1

至此,Hadoop 完全分布式环境搭建成功。

其他:

Ubuntu16.04换源 参考 这篇文章
中文乱码 参考 这篇文章

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

推荐阅读更多精彩内容