linux性能分析之系统平均负载

你真的了解系统平均负载吗?

Brendan Gregg's Blog

通常我们利用uptimetop分析系统平均负载情况,那如何通过该值分析系统繁忙程度?

Linux的平均负载不仅追踪可运行的任务,还追踪处于不可中断睡眠状态的任务。

Linux平均负载是"系统平均负载",它显示系统上运行的线程(任务)需求,即运行线程的平均数量加上等待线程。(非CPU平均负载)

大多数工具显示了平均负载三个平均值,分别为1分钟、5分钟和15分钟:

$ uptime
 16:48:24 up  4:11,  1 user,  load average: 25.25, 23.40, 23.46

top - 16:48:42 up  4:12,  1 user,  load average: 25.25, 23.14, 23.37

$ cat /proc/loadavg 
25.72 23.19 23.35 42/3411 43603

关于平均负载数值的一些解释:

  • 如果平均值是0.0,说明系统处于空闲状态
  • 如果1分钟的平均值高于5或15分钟的平均值,那么负载处于降低状态
  • 如果1分钟的平均值低于5或15分钟的平均值,那么负载处于升高状态
  • 当平均值高于系统的CPU计数,那么系统可能会遇到性能问题

通过上面的解释,我们了解到:通过系统1分钟、5分钟和15分钟的平均负载值,可以分析出系统繁忙程度及趋势。
将平均负载值与系统CPU核数相比较,可以判断出系统是否存在性能问题。

历史回溯

起初平均负载只显示CPU需求: 运行的进程数加上等待运行的进程数。

在1973年8月RFC 546 的《TENEX平均负载》中有一个很好的描述:

TENEX平均负载是衡量CPU需求的一个指标。平均负载是给定时间段内可运行进程数量的平均值。
例如,每小时平均负载为10意味着(对于单个CPU系统)在这一小时内的任何时候,都可能看到1个进程在运行,另外9个进程准备运行(即,没有阻塞I/O)等待CPU。

下面是一张1973年7月手工绘制的TENEX平均负载图表的PDF扫描

load-scan.png

旧操作系统的源代码也可以在网上找到。下面内容是TENEX(20世纪70年代早期)SCHED.DEC宏汇编的摘要:

NRJAVS==3               ;NUMBER OF LOAD AVERAGES WE MAINTAIN
GS RJAV,NRJAVS          ;EXPONENTIAL AVERAGES OF NUMBER OF ACTIVE PROCESSES
[...]
;UPDATE RUNNABLE JOB AVERAGES

DORJAV: MOVEI 2,^D5000
        MOVEM 2,RJATIM          ;SET TIME OF NEXT UPDATE
        MOVE 4,RJTSUM           ;CURRENT INTEGRAL OF NBPROC+NGPROC
        SUBM 4,RJAVS1           ;DIFFERENCE FROM LAST UPDATE
        EXCH 4,RJAVS1
        FSC 4,233               ;FLOAT IT
        FDVR 4,[5000.0]         ;AVERAGE OVER LAST 5000 MS
[...]
;TABLE OF EXP(-T/C) FOR T = 5 SEC.

EXPFF:  EXP 0.920043902 ;C = 1 MIN
        EXP 0.983471344 ;C = 5 MIN
        EXP 0.994459811 ;C = 15 MIN

下面内容摘自现代Linux(include/linux/sched/loadavg.h):

#define EXP_1           1884            /* 1/exp(5sec/1min) as fixed-point */
#define EXP_5           2014            /* 1/exp(5sec/5min) */
#define EXP_15          2037            /* 1/exp(5sec/15min) */

Linux依旧硬编码了平均负载1分钟、5分钟和15分钟常量。

在旧系统中也有类似的平均负载指标,包括Multics,它具有指数级调度队列平均值。

平均负载的三个数字

这三个数字分别是系统过去1、5和15分钟的平均负载。但它们并不是平均值,也不是1分钟、5分钟或15分钟。

从上面的资料中可以看出,1、5和15分钟是函数中使用的常数,用于计算5秒平均值的指数阻尼移动。得到的1、5和15分钟的平均负载实际远远超过1、5和15分钟的负载。

如果您使用一个空闲的系统,然后开启一个单线程cpu绑定的工作负载(循环中有一个线程), 那么60秒后一分钟的平均负载是多少?
如果它是一个普通的平均值,它应该是1.0。

下面为实验的图表:

load-time-average.png

所谓的"一分钟平均值"在一分钟的时候只能达到0.62左右。

关于这个等式和类似实验的更多信息,Neil Gunther博士写了一篇关于平均负载的文章:How It Works,另外在loadavg.c中有许多Linux源代码块注释。

Linux TASK_UNINTERRUPTIBLE

当平均负载第一次出现在Linux中时,与其他操作系统一样它们反映了系统对CPU实际需求。

随着Linux的发展,平均负载不仅包括可运行的任务,还包括不可中断状态的任务(TASK_UNINTERRUPTIBLE或nr_uninterruptible)。

不可中断状态由代码路径使用,以避免信号的中断,其中包括在磁盘I/O和一些锁上阻塞的任务。
您以前可能见过这种状态: 它在输出pstop中显示为D状态。ps(1)手册页称其为不间断睡眠(通常为IO)

添加不可中断状态意味着Linux平均负载可能会由于磁盘(或NFS) I/O工作负载而增加,而不仅仅是CPU需求。
对于熟悉其他操作系统及其平均CPU负载的人来说,添加这种状态一开始会让人感到非常困惑。

那么Linux为什么要这么做呢?

很多关于平均负载的文章都指出了Linux nr_uninterruptible问题,
但却没有任何文章中解释或猜测为什么Linux nr_uninterruptible会被包括在内。

或许系统平均负载反映更广泛意义上的需求,而不仅仅是CPU需求。

“不可中断”的起源

下面为oldlinux.org上的一个1993年的压缩邮件文件内容,
邮件中阐述了系统平均负载应该包含不可中断任务的必要性:

From: Matthias Urlichs <urlichs@smurf.sub.org>
Subject: Load average broken ?
Date: Fri, 29 Oct 1993 11:37:23 +0200


The kernel only counts "runnable" processes when computing the load average.
I don't like that; the problem is that processes which are swapping or
waiting on "fast", i.e. noninterruptible, I/O, also consume resources.

It seems somewhat nonintuitive that the load average goes down when you
replace your fast swap disk with a slow swap disk...

Anyway, the following patch seems to make the load average much more
consistent WRT the subjective speed of the system. And, most important, the
load is still zero when nobody is doing anything. ;-)

--- kernel/sched.c.orig Fri Oct 29 10:31:11 1993
+++ kernel/sched.c  Fri Oct 29 10:32:51 1993
@@ -414,7 +414,9 @@
    unsigned long nr = 0;

    for(p = &LAST_TASK; p > &FIRST_TASK; --p)
-       if (*p && (*p)->state == TASK_RUNNING)
+       if (*p && ((*p)->state == TASK_RUNNING) ||
+                  (*p)->state == TASK_UNINTERRUPTIBLE) ||
+                  (*p)->state == TASK_SWAPPING))
            nr += FIXED_1;
    return nr;
 }
--
Matthias Urlichs        \ XLink-POP N|rnberg   | EMail: urlichs@smurf.sub.org
Schleiermacherstra_e 12  \  Unix+Linux+Mac     | Phone: ...please use email.
90491 N|rnberg (Germany)  \   Consulting+Networking+Programming+etc'ing      42

这证实了平均负载是故意更改的,以反映对其他系统资源的需求,而不仅仅是cpu。Linux从"平均CPU负载"变成了"平均系统负载"。

邮件提及的使用较慢交换磁盘的例子是有意义的: 随着系统性能的降低,系统的负载需求(以运行+排队的方式测量)应该会增加。
但是,平均负载下降了,因为它们只跟踪CPU运行状态,而不跟踪交换状态。马提亚斯认为这是不直观的,事实也是如此,所以他修正了它。

现如今的“不可中断”

但是Linux的平均负载有时不是太高了吗,超过了磁盘I/O所能解释的范围?

是的,在Linux 0.99.14中,有13个代码路径直接设置TASK_UNINTERRUPTIBLETASK_SWAPPING(交换状态后来从Linux中删除)。
现在,在Linux 4.12中,有将近400个代码路径设置TASK_UNINTERRUPTIBLE,包括一些锁原语。

原文作者给上文提到的马提亚斯发了邮件,问他如何看待20多年后平均负载的变化。马提亚斯回复道:

平均负载的意义是从人的角度得出一个与系统繁忙程度相关的数字。
TASK_UNINTERRUPTIBLE意味着(或曾经意味着?)进程正在等待类似磁盘读取的操作,这会增加系统负载。
大量使用磁盘的系统可能非常缓慢,但TASK_RUNNING的平均值只有0.1,这对任何人都没有帮助

所以马提亚斯仍然认为它是有意义的,至少考虑到TASK_UNINTERRUPTIBLE过去的含义。

不间断测量任务

下面是一个生产机的Off-CPU火焰图,跨度为60秒,只显示内核堆栈,过滤后只包括处于TASK_UNINTERRUPTIBLE状态(即下图内容)的内核堆栈。
它提供了许多不可中断代码路径的例子:

[图片上传失败...(image-e2c4d0-1657865246125)]

如果您之前不了解off-CPU火焰图:

您可以单击帧来放大,查看显示为一个帧塔的完整堆栈。x轴大小与off-CPU阻塞所花费的时间成正比,排序顺序(从左到右)没有实际意义。
非cpu栈的颜色是蓝色(我对cpu栈使用暖色),饱和度有随机方差来区分帧。

火焰图通过bcc 生成(需要Linux 4.8+ 内核eBPF特性支持)

$ ./bcc/tools/offcputime.py -K --state 2 -f 60 > out.stacks
$ awk '{ print $1, $2 / 1000 }' out.stacks | ./FlameGraph/flamegraph.pl --color=io --countname=ms > out.offcpu.svgb>

从上面的火焰图可以看出: 60秒中只有926毫秒处于不可中断睡眠状态。这只会让我们的平均负载增加0.015。
在一些cgroup路径上是时间,但是这个服务器没有做太多的磁盘I/O。

下面是跨度为10秒的Off-CPU火焰图:

[图片上传失败...(image-cfa3ea-1657865246125)]

右边的宽塔在proc_pid_cmdline_read()(读取/proc/PID/cmdline)中显示systemd-journal,被阻塞,平均负载为0.07。
左侧还有一个更宽的页面错误塔,它也以rwsem_down_read_failed()结束(平均负载0.23)。

下面是rwsem_down_read_failed()的摘录:

/* wait to be given the lock */
while (true) {
    set_task_state(tsk, TASK_UNINTERRUPTIBLE);
    if (!waiter.task)
        break;
    schedule();
}

这是获取使用TASK_UNINTERRUPTIBLE锁代码。

Linux有不可中断和可中断版本的互斥锁获取函数(例如,mutex_lock() vs mutex_lock_interruptible(),以及用于信号量的down()down_interruptible())。
可中断版本允许任务被一个信号中断,然后在获得锁之前被唤醒处理它。不可中断锁睡眠中的时间通常不会对平均负载增加太多,但在本例中增加了0.30。
如果这个值较高就需要进行分析,分析如何减少锁争用(例如上面的:systemd-journalproc_pid_cmdline_read()),这将可能提高系统性能并降低平均负载。

将这些代码路径包含在平均负载中有意义吗? 我觉得是有意义的。

那些正在工作的线程碰巧阻塞在一个锁上。
Those threads are in the middle of doing work, and happen to block on a lock. They aren't idle. They are demand on the system, albeit for software resources rather than hardware resources.

分解Linux平均负载

能否将Linux负载平均值完全分解为组件? 下面是一个示例:

在一个空闲的8核CPU系统上,启动tar来归档一些未缓存的文件。该操作会花费几分钟的时间,主要是在读取磁盘时被阻塞。以下是来自三个不同终端窗口的统计数据:

  • 窗口-terma
terma$ pidstat -p `pgrep -x tar` 60
Linux 4.9.0-rc5-virtual (bgregg-xenial-bpf-i-0b7296777a2585be1)     08/01/2017  _x86_64_    (8 CPU)

10:15:51 PM   UID       PID    %usr %system  %guest    %CPU   CPU  Command
10:16:51 PM     0     18468    2.85   29.77    0.00   32.62     3  tar
  • 窗口-termb
termb$ iostat -x 60
[...]
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           0.54    0.00    4.03    8.24    0.09   87.10

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
xvdap1            0.00     0.05   30.83    0.18   638.33     0.93    41.22     0.06    1.84    1.83    3.64   0.39   1.21
xvdb            958.18  1333.83 2045.30  499.38 60965.27 63721.67    98.00     3.97    1.56    0.31    6.67   0.24  60.47
xvdc            957.63  1333.78 2054.55  499.38 61018.87 63722.13    97.69     4.21    1.65    0.33    7.08   0.24  61.65
md0               0.00     0.00 4383.73 1991.63 121984.13 127443.80    78.25     0.00    0.00    0.00    0.00   0.00   0.00
  • 窗口-termc
termc$ uptime
 22:15:50 up 154 days, 23:20,  5 users,  load average: 1.25, 1.19, 1.05
[...]
termc$ uptime
 22:17:14 up 154 days, 23:21,  5 users,  load average: 1.19, 1.17, 1.06

下图为Off-CPU火焰图

[图片上传失败...(image-d5a169-1657865246125)]

最后一分钟的平均负载是1.19。我们来分解一下:

  • 0.33来自tarCPU时间(pidstat)
  • 0.67是来自tar的不可中断磁盘读取,推断(offcpu火焰图在0.69,我怀疑因为它开始收集有点晚,跨越的时间范围略有不同)
  • 0.04是来自其他CPU消费者(iostat用户+系统,减去来自pidstat的tar的CPU)
  • 0.11是来自内核worker的不间断磁盘I/O时间,刷新磁盘写(offcpu火焰图,左边的两个塔)

加起来是1.15,缺少0.04,其中一些可能是四舍五入和测量间隔偏移误差,但很多可能是由于负载平均值是指数阻尼移动,而我使用的其他平均值(pidstat, iostat)是正常平均值。
在1.19之前,一分钟平均水平是1.25,所以这部分仍将把我们拉高。 从我之前的图表中可以看出,在1分钟这一标记处,62%的指标来自这一分钟,其余的则更早。
所以0.62 x 1.15 + 0.38 x 1.25 = 1.18。这与报告中的1.19非常接近。

在这个系统中,一个用户线程(tar)加上一些系统线程(在内核工作线程中)处于工作状态,Linux的平均负载为1.19。
如果Linux平均负载测量的是CPU平均负载,上述值应为0.37(从mpstat的摘要中推断)而非1.19
此时该值只能描述CPU计算资源需求,但是隐藏了一个事实,即该操作需要超过一个线程的系统资源。

理解Linux的平均负载

不同版本Linux系统的平均负载指代内容并不明确。也许问题根源来自平均负载这个词和I/O一样含糊不清。I/O指的到底是磁盘I/O? 还是文件系统I/O? 还是网络I/O ?

同样,平均负载指的是? CPU负载平均? 系统平均负载? 或许下面的总结更容易理解:

  • Linux平台上,平均负载指的是系统平均负载,用于整个系统,
    度量正在工作和等待工作的线程数量(CPU、磁盘、不可中断锁)。换句话说,它度量非完全空闲的线程的数量。优点: 反应对不同系统资源的需求。
  • 在其他操作系统上,平均负载指的是CPU平均负载,衡量的是CPU运行线程数 + CPU可运行线程数。优点:可以更容易理解和推理(仅针对cpu)。

如何评估系统平均负载的值?

有些人已经找到了适合他们的系统和工作负载的值:他们知道当负载超过X时,应用程序延迟就会很高,客户就会开始抱怨。当然,这个值并不唯一。

评估CPU平均负载时,可以将系统平均负载值除以CPU计数,然后如果该比率超过1.0,则表示CPU运行于饱和状态,这可能会导致性能问题。
这有点模棱两可,因为这是一个长期平均值(至少一分钟),可能会隐藏变化。
一个比率为1.5的系统可能运行得很好,而另一个比率为1.5的系统在几分钟内突然爆发,可能运行得很糟糕。

评估Linux的系统平均负载时,由于涉及不同的资源类型,所以这些数据更加模糊,所以不能只除以CPU计数。
它对于相对以下场景比较更有用: 如果您知道系统在负载为20时运行良好,现在是40,那么就该排查下系统存在的问题了。

更好的负载指标

当Linux平均负载增加时,您知道对资源(cpu、磁盘和一些锁)的需求增加了,但不确定是哪一个。您可以使用其他指标进行说明。例如,对于cpu:

  • 每个cpu利用率: 如使用mpstat -P ALL 1
  • 每个进程的CPU利用率: 如top, pidstat <pid>
  • 每个线程运行队列(调度器)延迟: 例如,cat /proc/<pid>/schedstats, delaystats, perf sched
  • CPU运行队列延迟: 例如,cat /proc/schedstat, perf sched
  • CPU运行队列长度: 例如,使用vmstat 1和'r'列

前两个是利用率指标,后三个是饱和度指标。利用率度量对于工作负载表征很有用,饱和度量对于识别性能问题很有用。
最好的CPU饱和指标是度量运行队列(或调度器)延迟: 任务/线程处于可运行状态,但必须等待轮到CPU运行它的时间。
通过上述指标可以计算出性能问题的大小,例如,线程花费在调度器延迟上的时间百分比。

Linux 4.6中,schedstats功能被设置为内核可调特性(sysctl kernel.sched_schedstats),默认关闭。

$ awk 'NF > 7 { if ($1 == "task") { if (h == 0) { print; h=1 } } else { print } }' /proc/sched_debug
            task   PID         tree-key  switches  prio     wait-time             sum-exec        sum-sleep
         systemd     1      5028.684564    306666   120        43.133899     48840.448980   2106893.162610 0 0 /init.scope
     ksoftirqd/0     3 99071232057.573051   1109494   120         5.682347     21846.967164   2096704.183312 0 0 /
    kworker/0:0H     5 99062732253.878471         9   100         0.014976         0.037737         0.000000 0 0 /
     migration/0     9         0.000000   1995690     0         0.000000     25020.580993         0.000000 0 0 /
   lru-add-drain    10        28.548203         2   100         0.000000         0.002620         0.000000 0 0 /
      watchdog/0    11         0.000000   3368570     0         0.000000     23989.957382         0.000000 0 0 /
         cpuhp/0    12      1216.569504         6   120         0.000000         0.010958         0.000000 0 0 /
          xenbus    58  72026342.961752       343   120         0.000000         1.471102         0.000000 0 0 /
      khungtaskd    59 99071124375.968195    111514   120         0.048912      5708.875023   2054143.190593 0 0 /
[...]
         dockerd 16014    247832.821522   2020884   120        95.016057    131987.990617   2298828.078531 0 0 /system.slice/docker.service
         dockerd 16015    106611.777737   2961407   120         0.000000    160704.014444         0.000000 0 0 /system.slice/docker.service
         dockerd 16024       101.600644        16   120         0.000000         0.915798         0.000000 0 0 /system.slice/
[...]

除了CPU指标之外,您还可以查找磁盘设备的利用率和饱和度指标。

虽然有更明确的指标,但这并不意味着平均负载无用。它们被成功地用于云计算微服务的弹性扩展策略,以及其他指标。
这有助于微服务响应不同类型的负载增加,如CPU或磁盘I/O。

总结

1993年,一位Linux工程师发现了一个关于平均负载的不够直观的例子,并通过一个三行补丁将其从平均CPU负载永久地更改为人们可能称之为平均系统负载
他的更改包括了处于不可中断状态的任务,这样平均负载就反映了对磁盘资源的需求,而不仅仅是cpu的需求。
这些系统负载平均值为正在工作和等待工作的线程数量,并总结为指数阻尼移动和平均值的三元组,在方程中使用1、5和15分钟作为常数。
这三个数字可以让您看到负载是增加还是减少,它们的最大值可能用于与它们本身的相对比较。

此后,不可中断状态的使用在Linux内核中得到了发展,现在包括了不可中断锁原语。
如果平均负载是运行和等待线程(严格来说不是需要硬件资源的线程)的需求度量,那么它们仍然按照我们希望的方式工作。

所谓原语,一般是指由若干条指令组成的程序段,用来实现某个特定功能,在执行过程中不可被中断。

在这篇文章中,原作者挖掘了1993年的Linux平均加载补丁——这是令人惊讶的难以找到的——包含了作者最初的解释。
并在现代Linux系统上使用bcc/eBPF测量了不可中断状态下的堆栈跟踪和时间,并可视化为off-CPU火焰图。
可视化提供了许多不可中断睡眠的例子,并且可以在需要解释异常高的平均负载时生成。并且他还提出了其他指标,可以用来更详细地了解系统负载,而不是平均负载。

参考文献

linux-performance-analysis
Linux Load Averages: Solving the Mystery

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

推荐阅读更多精彩内容