redis主从+哨兵+vip实现高可用

一. redis主从配置

1.三台机器部署redis
主机名称            IP地址            redis版本
redis-master    192.168.2.32      redis 5.0.5(主)
redis-slave01   192.168.2.34      redis 5.0.5(从)
redis-slave02   192.168.2.35     redis 5.0.5(从)
2.修改主从redis的配置文件
master的主要配置文件
[root@base-master redis]# grep -Ev "^$|#" redis.conf 
bind 0.0.0.0
protected-mode yes
port 6379
tcp-keepalive 300
daemonize yes
pidfile "/var/run/redis_6379.pid"
loglevel notice
logfile "/opt/redis/logs/redis.log"
dbfilename "dump.rdb"
dir "/opt/redis"
slave1的配置文件
bind 0.0.0.0
protected-mode yes
port 6379
tcp-keepalive 300
daemonize yes
pidfile "/var/run/redis_6379.pid"
loglevel notice
logfile "/opt/redis/logs/redis.log"
dbfilename "dump.rdb"
dir "/opt/redis"
replicaof 192.168.2.32 6379   # 添加master的内网ip和端口
masterauth 123456  # 如果master上有密码配置,这里配置master的密码
slave2的配置文件
bind 0.0.0.0
protected-mode yes
port 6379
tcp-keepalive 300
daemonize yes
pidfile "/var/run/redis_6379.pid"
loglevel notice
logfile "/opt/redis/logs/redis.log"
dbfilename "dump.rdb"
dir "/opt/redis"
replicaof 192.168.2.32 6379   #添加master的内网ip和端口
masterauth 123456  #如果master上有密码配置,这里配置master的密码
启动主从redis

三台都启动redis

[root@base-master redis]# ./src/redis-server ./redis.conf
[root@base-slave1 redis]# ./src/redis-server ./redis.conf
[root@base-slave2 redis]# ./src/redis-server ./redis.conf
[root@base-master redis]# ps -ef | grep redis
root      1135     1  0 9月21 ?       00:00:50 ./src/redis-server 0.0.0.0:6379
[root@base-slave1 redis]# ps -ef | grep redis
root      1135     1  0 9月21 ?       00:00:50 ./src/redis-server 0.0.0.0:6379
[root@base-slave2 redis]# ps -ef | grep redis
root      1135     1  0 9月21 ?       00:00:50 ./src/redis-server 0.0.0.0:6379
主从测试
1.在master上面执行 
[root@redis-master redis]# cd src/ 
[root@redis-master src]# ./redis-cli 
127.0.0.1:6379> ping 
PONG 
127.0.0.1:6379> set name xuan
 OK
127.0.0.1:6379> get name
 "xuan" 
127.0.0.1:6379> 
2.分别在slave-1和slave-2上面执行: 
[root@redis-slave-1 redis]# cd src/ 
[root@redis-slave-1 src]# ./redis-cli 
127.0.0.1:6379> ping 
PONG 
127.0.0.1:6379> get name 
"xuan" 
127.0.0.1:6379> 
[root@redis-slave-2 src]# ./redis-cli 
127.0.0.1:6379> ping 
PONG 
127.0.0.1:6379> get name "xuan" 
127.0.0.1:6379>
3.查看复制状态 
master执行:
127.0.0.1:6379>  info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.2.34,port=6379,state=online,offset=482568,lag=0
slave1:ip=192.168.2.35,port=6379,state=online,offset=482427,lag=1
master_replid:a67fca6ff5a6910f3f23b800a0bf973d2ae52cfc
master_replid2:e92bde36e46617f457b68275e4ea6df7ed46c381
master_repl_offset:482850
second_repl_offset:21196
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:482850
slave上面执行:
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:192.168.2.32
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:503405
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:a67fca6ff5a6910f3f23b800a0bf973d2ae52cfc
master_replid2:e92bde36e46617f457b68275e4ea6df7ed46c381
master_repl_offset:503405
second_repl_offset:21196
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:3013
repl_backlog_histlen:500393

主从同步部署完成!

二、Redis-sentinel哨兵模式

1、哨兵简介:Redis Sentinel
Sentinel(哨兵)是用于监控redis集群中Master状态的工具,其已经被集成在redis2.4+的版本中是Redis官方推荐
的高可用性(HA)解决方案。
2、作用
1):Master状态检测
2):如果Master异常,则会进行Master-Slave切换,将其中一个Slave作为Master,将之前的Master作为Slave
3):Master-Slave切换后,master_redis.conf、slave_redis.conf和sentinel.conf的内容都会发生改变,即master_redis.conf中会多一行slaveof的配置,sentinel.conf的监控目标会随之调换
3、工作模式
1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令
2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线。
3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线
4、主观下线和客观下线
主观下线:Subjectively Down,简称 SDOWN,指的是当前 Sentinel 实例对某个redis服务器做出的下线判断。
客观下线:Objectively Down, 简称 ODOWN,指的是多个 Sentinel 实例在对Master Server做出 SDOWN 判断,并且通过 SENTINE SENTINEL is-master-down-by-addr 命令互相交流之后,得出的Master Server下线判断,然后开启failover

5、配置哨兵模式
主机名称               IP地址                 
redis-master    192.168.2.32:6379   redis 5.0.5(主)
redis-slave01   192.168.2.34:6379   redis 5.0.5(从)
redis-slave02   192.168.2.35:6379   redis 5.0.5(从)
redis-master    192.168.2.32:26379  Sentinel01
redis-slave01   192.168.2.34:26379  Sentinel02
redis-slave02   192.168.2.35:26379  Sentinel03

Sentinel.conf配置文件主要参数解析:

# 端口
port 26379

# 是否后台启动
daemonize yes

# pid文件路径
pidfile /var/run/redis-sentinel.pid

# 日志文件路径
logfile "/var/log/sentinel.log"

# 定义工作目录
dir /tmp

# 定义Redis主的别名, IP, 端口,这里的2指的是需要至少2个Sentinel认为主Redis挂了才最终会采取下一步行为
sentinel monitor mymaster 127.0.0.1 6379 2

# 如果mymaster 30秒内没有响应,则认为其主观失效
sentinel down-after-milliseconds mymaster 30000

# 如果master重新选出来后,其它slave节点能同时并行从新master同步数据的台数有多少个,显然该值越大,所有slave节点完成同步切换的整体速度越快,但如果此时正好有人在访问这些slave,可能造成读取失败,影响面会更广。最保守的设置为1,同一时间,只能有一台干这件事,这样其它slave还能继续服务,但是所有slave全部完成缓存更新同步的进程将变慢。
sentinel parallel-syncs mymaster 1

# 该参数指定一个时间段,在该时间段内没有实现故障转移成功,则会再一次发起故障转移的操作,单位毫秒
sentinel failover-timeout mymaster 180000

# 不允许使用SENTINEL SET设置notification-script和client-reconfig-script。
sentinel deny-scripts-reconfig yes

修改三台Sentinel的配置文件,如下

[root@base-master redis]#  grep -Ev "^$|#"  sentinel.conf
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel.pid"
logfile "/opt/redis/logs/sentinel.log"
dir "/tmp"
sentinel deny-scripts-reconfig yes
sentinel monitor mymaster 192.168.2.32 6379 2
sentinel failover-timeout mymaster 10000
sentinel auth-pass mymaster 123456  # 如果redis设置了密码,配置一下
[root@base-slave1 redis]#  grep -Ev "^$|#"  sentinel.conf
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel.pid"
logfile "/opt/redis/logs/sentinel.log"
dir "/tmp"
sentinel deny-scripts-reconfig yes
sentinel monitor mymaster 192.168.2.32 6379 2
sentinel failover-timeout mymaster 10000
sentinel auth-pass mymaster 123456  # 如果redis设置了密码,配置一下
[root@base-slave2 redis]#  grep -Ev "^$|#"  sentinel.conf
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel.pid"
logfile "/opt/redis/logs/sentinel.log"
dir "/tmp"
sentinel deny-scripts-reconfig yes
sentinel monitor mymaster 192.168.2.32 6379 2
sentinel failover-timeout mymaster 10000
sentinel auth-pass mymaster 123456  # 如果redis设置了密码,配置一下

每台机器启动哨兵服务:

[root@redis-master redis]# ./src/redis-sentinel sentinel.conf
[root@redis-slave1 redis]# ./src/redis-sentinel sentinel.conf
[root@redis-slave2 redis]# ./src/redis-sentinel sentinel.conf

查看信息

[root@base-master redis]# redis-cli -p 26379
127.0.0.1:26379> ping
PONG
127.0.0.1:26379>  sentinel master mymaster #主节点的状态信息
 1) "name"
 2) "mymaster"
 3) "ip"
 4) "192.168.2.32"
 5) "port"
 6) "6379"
 7) "runid"
 8) "d92a445b7a680df2e0d931edb1b4d1f42471d08d"
 9) "flags"
10) "master"
........
127.0.0.1:26379> sentinel slaves mymaster   #查看mymaster的从信息,可以看到有2个从节点
1)  1) "name"
    2) "192.168.2.35:6379"
    3) "ip"
    4) "192.168.2.35"
    5) "port"
    6) "6379"
    7) "runid"
    8) "23ace834a24e91d81a4ee162ffff123bc0b3754a"
    9) "flags"
   10) "slave"
......
2)  1) "name"
    2) "192.168.2.34:6379"
    3) "ip"
    4) "192.168.2.34"
    5) "port"
    6) "6379"
    7) "runid"
    8) "e48433d94642f8774846e4a235f8500ccc18a860"
    9) "flags"
   10) "slave"
.....
127.0.0.1:26379> sentinel sentinels mymaster             #查看其它sentinel信息
1)  1) "name"
    2) "fb12c5486b6ce0995a89f4aaadf69a9800056aa2"
    3) "ip"
    4) "192.168.2.34"
    5) "port"
    6) "26379"
    7) "runid"
    8) "fb12c5486b6ce0995a89f4aaadf69a9800056aa2"
    9) "flags"
   10) "sentinel"
   11) "link-pending-commands"
   12) "0"
   13) "link-refcount"
   14) "1"
   15) "last-ping-sent"
   16) "0"
   17) "last-ok-ping-reply"
   18) "564"
   19) "last-ping-reply"
   20) "564"
   21) "down-after-milliseconds"
   22) "30000"
   23) "last-hello-message"
   24) "976"
   25) "voted-leader"
   26) "?"
   27) "voted-leader-epoch"
   28) "18"
2)  1) "name"
    2) "2daa2da905fbb00c126ab2882d8903b34142e71b"
    3) "ip"
    4) "192.168.2.35"
    5) "port"
    6) "26379"
    7) "runid"
    8) "2daa2da905fbb00c126ab2882d8903b34142e71b"
    9) "flags"
   10) "sentinel"
   11) "link-pending-commands"
   12) "0"
   13) "link-refcount"
   14) "1"
   15) "last-ping-sent"
   16) "0"
   17) "last-ok-ping-reply"
   18) "564"
   19) "last-ping-reply"
   20) "564"
   21) "down-after-milliseconds"
   22) "30000"
   23) "last-hello-message"
   24) "615"
   25) "voted-leader"
   26) "?"
   27) "voted-leader-epoch"
   28) "18"

哨兵测试

停掉master的redis
[root@base-master redis]# ps -ef | grep redis
root      4076     1  0 9月17 ?       00:07:16 ./src/redis-sentinel *:26379 [sentinel]
root     22406     1  0 08:34 ?        00:00:03 ../src/redis-server 0.0.0.0:6379
root     28813 32006  0 09:54 pts/0    00:00:00 grep --color=auto redis
[root@base-master redis]# kill -9 22406
[root@base-master redis]# ps -ef | grep redis
root      4076     1  0 9月17 ?       00:07:16 ./src/redis-sentinel *:26379 [sentinel]
root     28842 32006  0 09:54 pts/0    00:00:00 grep --color=auto redis
查看哨兵日志
[root@base-slave1 logs]# tailf sentinel.log
19142:X 22 Sep 2020 09:55:11.405 # +sdown master mymaster 192.168.2.32 6379        #master主服务已宕机
19142:X 22 Sep 2020 09:55:11.471 # +odown master mymaster 192.168.2.32 6379 #quorum 2/2
19142:X 22 Sep 2020 09:55:11.471 # +new-epoch 19
19142:X 22 Sep 2020 09:55:11.471 # +try-failover master mymaster 192.168.2.32 6379   # 开始故障恢复
19142:X 22 Sep 2020 09:55:11.472 # +vote-for-leader fb12c5486b6ce0995a89f4aaadf69a9800056aa2 19
19142:X 22 Sep 2020 09:55:11.476 # 2daa2da905fbb00c126ab2882d8903b34142e71b voted for fb12c5486b6ce0995a89f4aaadf69a9800056aa2 19                                          #投票选举哨兵leader
19142:X 22 Sep 2020 09:55:11.476 # a928076f96ca0580be30eb089b63d2a189dc87e5 voted for fb12c5486b6ce0995a89f4aaadf69a9800056aa2 19
19142:X 22 Sep 2020 09:55:11.543 # +elected-leader master mymaster 192.168.2.32 6379   # 选中哨兵leader
19142:X 22 Sep 2020 09:55:11.543 # +failover-state-select-slave master mymaster 192.168.2.32 6379 #选中其中一个slave当作master
19142:X 22 Sep 2020 09:55:11.602 # +selected-slave slave 192.168.2.35:8700 192.168.2.35 6379 @ mymaster 192.168.2.32 6379  #选中192.168.2.35
19142:X 22 Sep 2020 09:55:11.602 * +failover-state-send-slaveof-noone slave 192.168.2.35:6379 192.168.2.35 6379 @ mymaster 192.168.2.32 6379     #发送slaveof no one 命令
19142:X 22 Sep 2020 09:55:11.678 * +failover-state-wait-promotion slave 192.168.2.35:6379 192.168.2.35 6379 @ mymaster 192.168.2.32 6379
19142:X 22 Sep 2020 09:55:11.737 # +promoted-slave slave 192.168.2.35:6379 192.168.2.35 6379 @ mymaster 192.168.2.32 6379  #等待35升级为master
19142:X 22 Sep 2020 09:55:11.737 # +failover-state-reconf-slaves master mymaster 192.168.2.32 6379
19142:X 22 Sep 2020 09:55:11.820 * +slave-reconf-sent slave 192.168.2.34:6379 192.168.2.34 6379 @ mymaster 192.168.2.32 6379   #34正在设置新的master
19142:X 22 Sep 2020 09:55:12.149 * +slave-reconf-inprog slave 192.168.2.34:6379 192.168.2.34 6379 @ mymaster 192.168.2.32 6379
19142:X 22 Sep 2020 09:55:12.568 # -odown master mymaster 192.168.2.32 6379
19142:X 22 Sep 2020 09:55:13.171 * +slave-reconf-done slave 192.168.2.34:6379 192.168.2.34 6379 @ mymaster 192.168.2.32 6379  
19142:X 22 Sep 2020 09:55:13.223 # +failover-end master mymaster 192.168.2.32 6379   #故障转移完成
19142:X 22 Sep 2020 09:55:13.223 # +switch-master mymaster 192.168.2.32 6379 192.168.2.35 6379 #master从32转移到35
19142:X 22 Sep 2020 09:55:13.224 * +slave slave 192.168.2.34:6379 192.168.2.34 6379 @ mymaster 192.168.2.35 6379    #添加32为35的从节点
19142:X 22 Sep 2020 09:55:13.224 * +slave slave 192.168.2.32:6379 192.168.2.32 6379 @ mymaster 192.168.2.35 6379 3   #添加34为35的从节点
4076:X 22 Sep 2020 10:12:42.471 # +sdown slave 192.168.2.32:8700 192.168.2.32 8700 @ mymaster 192.168.2.35 8700   #32已宕机  等待修复

从上面的日志可以看到master已经down,并切换为192.168.2.35为master节点

三 . 用vip实现实现高可用

客户端程序连接redis时需要ip和port,但redis-server进行故障转移时,主redis是变化的,所以ip地址也是变化的。客户端程序如何感知当前主redis的ip地址和端口呢?
redis-sentinel提供了接口,请求任何一个sentinel,发送SENTINEL get-master-addr-by-name <master name>就能得到当前主redis的ip和port。

获取当前主redis的ip和port:
[root@base-master redis]# redis-cli -p 26379
127.0.0.1:26379> SENTINEL get-master-addr-by-name mymaster
1) "192.168.2.35"
2) "6379"

VIP方案是,redis系统对外始终是同一ip地址,当redis进行故障转移时,需要做的是将VIP从之前的redis服务器漂移到现在新的主redis服务器上。
这里可以使用redis sentinel的一个参数client-reconfig-script,这个参数配置执行脚本,sentinel在做failover的时候会执行这个脚本,并且传递6个参数<master-name>、 <role>、 <state>、 <from-ip>、 <from-port>、 <to-ip> 、<to-port>,其中<to-ip>是新主redis的IP地址,可以在这个脚本里做VIP漂移操作

sentinel client-reconfig-script mymaster /scripts/notify_master.sh
修改三个服务器的redis-sentinel配置文件sentinel.conf,增加上面一行。然后在/scripts目录下创建notify_master.sh脚本文件,这个脚本做VIP漂移操作,内容如下:

脚本内容

#!/bin/bash
MASTER_IP=$6  #第六个参数是新主redis的ip地址
LOCAL_IP='192.168.2.35'  #本机ip
VIP='192.168.2.201'  
NETMASK='24'
INTERFACE='eth0'
if [ ${MASTER_IP} = ${LOCAL_IP} ];then   
    /sbin/ip  addr  add ${VIP}/${NETMASK}  dev ${INTERFACE}  #将VIP绑定到该服务器上
    /sbin/arping -q -c 3 -U ${VIP} -I ${INTERFACE}
    exit 0
else 
   /sbin/ip  addr del  ${VIP}/${NETMASK}  dev ${INTERFACE}   #将VIP从该服务器上删除
   exit 0
fi
exit 1  #如果返回1,sentinel会一直执行这个脚本

现在当前主redis是192.168.2.35,需要手动绑定VIP到该服务器上。

/sbin/ip  addr add 192.168.2.201/24 dev eth0
/sbin/arping -q   -c 3 -A 192.168.2.201 -I eth0 

查看vip

[root@base-slave2 redis]# ip a | grep inet
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host 
    inet 192.168.2.35/24 brd 192.168.2.255 scope global noprefixroute eth0
    inet 192.168.2.201/24 scope global secondary eth0

下面关闭这台redis服务,看看VIP是否漂移到另一台服务器上

vip漂移到了32上
[root@base-master redis]# ip a | grep inet
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host 
    inet 192.168.2.32/24 brd 192.168.2.255 scope global noprefixroute eth0
    inet 192.168.2.201/24 scope global secondary eth0

通过vip连接redis也可以成功

[root@localhost ~]# redis-cli -h 192.168.2.201 
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
192.168.2.201:6379> ping
PONG
192.168.2.201:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.2.34,port=6379,state=online,offset=1320728,lag=0
slave1:ip=192.168.2.35,port=6379,state=online,offset=1320728,lag=0
master_replid:d3b972b1e664c0856093deea8d23b7c0bb0f3413
master_replid2:67c6248cb3e6f07e8d8f99d22c6984920e8e16e3
master_repl_offset:1320869
second_repl_offset:1241693
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1231871
repl_backlog_histlen:88999

搭建完成

参考:
https://www.cnblogs.com/linuxk/p/10718153.html
https://www.cnblogs.com/-wenli/p/11379606.html
https://www.cnblogs.com/maybesuch/p/10257374.html

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