Mysql 主從復制配置和程序讀寫分離配置

一滑负、主從復制配置

首先费尽,準備兩臺服務(wù)器,master:192.168.174.10叛薯,slave:192.168.174.11浑吟。
然后兩臺服務(wù)器上安裝Mysql 5.5.5以上版本,因為其默認的執(zhí)行引擎是支持事務(wù)的Innodb耗溜。
Centos上安裝Mysql组力。

配置主服務(wù)器:

進入 /etc/my.cnf,在[mysqld]標識符下新增以下配置:
server-id =10 # 主服務(wù)器標識抖拴,唯一即可
log_bin=/usr/local/mysql/data/sql_log/mysql-bin #二進制日志存放路徑
binlog-do-db=miaosha # 需要主從復制的數(shù)據(jù)庫燎字,不寫默認為全部庫
binlog-ignore-db=mysql # 不需要復制的數(shù)據(jù)庫
max_binlog_size = 1000M # 二進制日志文件的最大容量
binlog_format = row # 它不記錄sql語句上下文相關(guān)信息,僅保存哪條記錄被修改
expire_logs_days = 7 # 日志文件保存天數(shù)
sync_binlog = 1

bin-log文件:

基本定義:二進制日志,也稱為二進制日志轩触,記錄對數(shù)據(jù)發(fā)生或潛在發(fā)生更改的SQL語句寞酿,并以二進制的形式保存在磁盤中;
作用:可以用來查看數(shù)據(jù)庫的變更歷史(具體的時間點所有的SQL操作)脱柱、數(shù)據(jù)庫增量備份和恢復(增量備份和基于時間點的恢復)伐弹、MySQL的復制(主主數(shù)據(jù)庫的復制、主從數(shù)據(jù)庫的復制)榨为。
文件位置:默認存放位置為數(shù)據(jù)庫文件所在目錄下惨好,也可以自定義,但是必須是mysql用戶的包下随闺。
文件的命名方式: 名稱為hostname-bin.xxxxx (重啟mysql一次將會自動生成一個新的binlog)

sync_binlog:

sync_binlog=0為默認情況日川,表示MySQL不控制binlog的刷新,由文件系統(tǒng)自己控制它的緩存的刷新矩乐。這時候的性能是最好的龄句,但是風險也是最大的。因為一旦系統(tǒng)Crash散罕,在binlog_cache中的所有binlog信息都會被丟失分歇。
如果sync_binlog>0,表示每sync_binlog次事務(wù)提交欧漱,MySQL調(diào)用文件系統(tǒng)的刷新操作將緩存刷下去职抡。最安全的就是sync_binlog=1了,表示每次事務(wù)提交误甚,MySQL都會把binlog刷下去缚甩,是最安全但是性能損耗最大的設(shè)置。

配置從服務(wù)器:

同樣進入 /etc/my.cnf窑邦,在[mysqld]標識符下新增以下配置:
server-id = 11 # 從服務(wù)器標識擅威,唯一即可
relay_log=/usr/local/mysql/data/sql_log/mysqld-relay-log #relay_log 存放位置,必須是mysql用戶的包下
master_info_repository = TABLE
relay_log_info_repository =TABLE
read_only=on # 只讀
relay_log_recovery = on # 開啟

relay log 文件:

由IO thread線程從主庫讀取的二進制日志事件組成奕翔,該日志被Slave上的SQL thread線程執(zhí)行裕寨,從而實現(xiàn)數(shù)據(jù)的復制。

master_info_repository :

該文件保存slave連接master的狀態(tài)以及配置信息派继,如用戶名,密碼捻艳,日志執(zhí)行的位置驾窟。master_info_repository 配置為TABLE,這些信息會被寫入mysql.slave_master_info 表中,代替原來的master.info文件了认轨。使用表來代替原來的文件绅络,主要為了crash-safe replication,從而大大提高從庫的可靠性。

relay_log_info_repository :

該文件保存slave上relay log的執(zhí)行位置恩急。設(shè)置為TABLE可以避免relay.info更新不及時杉畜,SLAVE 重啟后導致的主從復制出錯。

relay_log_recovery :

當slave從庫宕機后衷恭,假如relay-log損壞了此叠,導致一部分中繼日志沒有處理,則自動放棄所有未執(zhí)行的relay-log随珠,并且重新從master上獲取日志灭袁,這樣就保證了relay-log的完整性。

數(shù)據(jù)備份與傳輸:

使用mysqldump 從主庫中備份數(shù)據(jù)保存到all.sql文件窗看,然后將all.sql文件傳輸給從數(shù)據(jù)庫茸歧,最后從數(shù)據(jù)庫執(zhí)行該all.sql,從而實現(xiàn)數(shù)據(jù)傳輸显沈。
mysqldump -u用戶名-p密碼 數(shù)據(jù)庫 > sql腳本文件路徑全名软瞎,
上述命令將指定數(shù)據(jù)庫備份到某dump文件(轉(zhuǎn)儲文件)中,比如:
mysqldump -uroot -p123456 miaosha > all.sql拉讯;
然后將all.sql傳輸給從服務(wù)器:scp all.sql root@192.168.174.11:/home涤浇;
最后從服務(wù)器執(zhí)行all.sql文件,實現(xiàn)數(shù)據(jù)傳輸:mysql –u用戶名 –p密碼 –D數(shù)據(jù)庫 < sql腳本文件路徑全名遂唧,
示例:mysql -uroot -p123456 -Dmiaosha < /home/all.sql

在主服務(wù)器上創(chuàng)建一個用戶user芙代,并且賦予REPLICATION SLAVE 權(quán)限:

登錄主服務(wù)器的Mysql數(shù)據(jù)庫,執(zhí)行以下語句:
mysql> CREATE USER user@'192.168.174.%' IDENTIFIED BY '123456';
mysql> GRANT REPLICATION SLAVE ON * . * TO user@'192.168.174.%';
192.168.174.%:表示192.168.174.0~192.168.174.255范圍內(nèi)的服務(wù)器都可以使用user用戶盖彭。
這樣子纹烹,從服務(wù)器就可以通過user用戶訪問主服務(wù)器的二進制日志文件,從而實現(xiàn)數(shù)據(jù)的復制召边。
同時铺呵,使用 show master status命令,查看主服務(wù)器日志文件信息隧熙,后續(xù)從服務(wù)器上創(chuàng)建復制鏈路需要這些參數(shù)片挂。


主服務(wù)器日志文件信息

從服務(wù)器上創(chuàng)建復制鏈路:

登錄從服務(wù)器的Mysql數(shù)據(jù)庫,執(zhí)行以下語句:
CHANGE MASTER TO
-> MASTER_HOST='192.168.174.10', # 主服務(wù)器ip
-> MASTER_USER='user', # 主服務(wù)器上創(chuàng)建的user用戶
-> MASTER_PASSWORD='123456', # user用戶密碼
-> MASTER_LOG_FILE='mysql-bin.000001', # 為master中的二進制日志文件贞盯,與上面show master status結(jié)果的File一致
-> MASTER_LOG_POS=501; # master中二進制日志文件的起始復制位置音念,與上面show master status結(jié)果的Position一致
然后,開啟從服務(wù)器的鏈路躏敢,執(zhí)行 start slave 命令;至此闷愤,我們就實現(xiàn)了主從復制,我們可以通過執(zhí)行 show slave status\G 命令來查詢是否開啟成功件余。


從服務(wù)器連接狀態(tài)

二讥脐、程序讀寫分離配置

本文主從數(shù)據(jù)庫的切換需要用到AOP遭居,所以直接引用jar包;

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

配置文件application.yml的數(shù)據(jù)庫配置:

spring:
  datasource:
    master:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://192.168.174.10:3306/miaosha?useUnicode=true&characterEncoding=utf8&useSSL=true
      username: root
      password: 123456
      type: com.alibaba.druid.pool.DruidDataSource
      max-active: 1000
      initial-size: 100
      min-idle: 500
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: select 1
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      max-open-prepared-statements: 20
      #對于長時間不使用的連接強制關(guān)閉
      remove-abandoned: true
      #超過5分鐘開始關(guān)閉空閑連接
      remove-abandoned-timeout: 300
    slave:
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://192.168.174.11:3306/miaosha?useUnicode=true&characterEncoding=utf8&useSSL=true
      username: root
      password: 123456
      type: com.alibaba.druid.pool.DruidDataSource
      max-active: 1000
      initial-size: 100
      min-idle: 500
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: select 1
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      max-open-prepared-statements: 20
      #對于長時間不使用的連接強制關(guān)閉
      remove-abandoned: true
      #超過5分鐘開始關(guān)閉空閑連接
      remove-abandoned-timeout: 300

數(shù)據(jù)庫配置類 DataSourceConfiguration:

@Configuration
@Slf4j
public class DataSourceConfiguration {

    /**
     * 主數(shù)據(jù)源
     * @return
     */
    @Bean(name = "writeDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource writeDataSource(){
        log.info("---------------writeDataSource init -----------------");
        return DataSourceBuilder.create().type(DruidDataSource.class).build();
    }

    /**
     * 從數(shù)據(jù)源
     * @return
     */
    @Bean(name = "readDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource readDataSource(){
        log.info("---------------readDataSource init -----------------");
        return DataSourceBuilder.create().type(DruidDataSource.class).build();
    }
}

自定義一個數(shù)據(jù)庫注解旬渠,用于標識讀寫數(shù)據(jù)庫 @DataSource俱萍。

package com.imooc.miaosha.config;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Retention(RUNTIME)
@Target(METHOD)
public @interface DataSource {
    String value() default "";
}

@DataSource的value有兩種,一種是“write”寫庫告丢,另一種是"read"讀庫枪蘑,所以我們寫一個枚舉類。

package com.imooc.miaosha.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum DataSourceType {
    READ("read"),
    WRITE("write");

    private String type;
}

使用ThreadLocal來存放當前線程的讀寫類型(write或者read)芋齿,后續(xù)會采用AOP獲取@DataSource的value值腥寇,往ThreadLocal中set進去。

package com.imooc.miaosha.config;

import com.imooc.miaosha.enums.DataSourceType;

/**
 * 本地線程全部變量-數(shù)據(jù)源
 */
public class DataSourceContextHolder {

    private static final ThreadLocal<String> dataSourceContext = new ThreadLocal<>();

    public static void read(){
        dataSourceContext.set(DataSourceType.READ.getType());
    }

    public static void write(){
        dataSourceContext.set(DataSourceType.WRITE.getType());
    }

    public static String getJdbcType(){
        return dataSourceContext.get();
    }
}

創(chuàng)建MyAbstractRoutingDataSource觅捆,繼承AbstractRoutingDataSource赦役,并重寫determineCurrentLookupKey()方法。每次訪問數(shù)據(jù)庫栅炒,都會調(diào)用getConnection()方法掂摔,去獲取數(shù)據(jù)庫連接,該方法里面調(diào)用了determineTargetDataSource()方法赢赊,然后在determineTargetDataSource()方法里面調(diào)用了AbstractRoutingDataSource類里的抽象方法determineCurrentLookupKey()乙漓。這時候我們需要重寫該抽象方法來通過ThreadLocal獲取當前的數(shù)據(jù)庫類型的標識(write或者read),從而決定采用哪種數(shù)據(jù)庫释移。

package com.imooc.miaosha.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 重寫determineCurrentLookupKey方法叭披,因為每次獲取數(shù)據(jù)庫連接時,會調(diào)用getConnection()方法玩讳,
 * 該方法里面調(diào)用了determineTargetDataSource()方法涩蜘,然后determineTargetDataSource()方法里面調(diào)用了
 * AbstractRoutingDataSource類里的抽象方法determineCurrentLookupKey()。
 */
public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getJdbcType();
    }
}

Mybatis配置類 MyBatisConfiguration :

package com.imooc.miaosha.config;

import com.google.common.collect.Maps;
import com.imooc.miaosha.enums.DataSourceType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Map;

@Configuration
public class MyBatisConfiguration {

    @Resource(name = "writeDataSource")
    private DataSource writeDataSource;
    @Resource(name = "readDataSource")
    private DataSource readDataSource;

    @Bean
    public DataSource dynamicDataSource(){
        MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource();
        Map<Object, Object> targetDataSources = Maps.newHashMap();
        targetDataSources.put(DataSourceType.READ.getType(), readDataSource);
        targetDataSources.put(DataSourceType.WRITE.getType(), writeDataSource);
        proxy.setDefaultTargetDataSource(writeDataSource);
        proxy.setTargetDataSources(targetDataSources);
        return proxy;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        org.apache.ibatis.session.Configuration configuration = sqlSessionFactoryBean.getObject().getConfiguration();
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setUseGeneratedKeys(true);
        return sqlSessionFactoryBean.getObject();
    }

}

這里將讀寫數(shù)據(jù)源的bean放入AbstractRoutingDataSource 中熏纯,其中key為數(shù)據(jù)庫標識write或者read同诫,value為對應的讀寫數(shù)據(jù)源bean。這樣就可以通過ThreadLocal獲取到的當前數(shù)據(jù)庫標識樟澜,去取對應的數(shù)據(jù)源bean了误窖,從而實現(xiàn)讀寫分離。
添加事務(wù)管理配置 DataSourceTransactionManager ,因為只有寫庫涉及到事務(wù)秩贰,所以只需要將寫數(shù)據(jù)源放入事務(wù)管理即可:

package com.imooc.miaosha.config;

import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
public class DataSourceTransactionManager extends DataSourceTransactionManagerAutoConfiguration {

    @Resource(name = "writeDataSource")
    private DataSource writeDataSource;

    @Bean
    public org.springframework.jdbc.datasource.DataSourceTransactionManager transactionManager(){
        return new org.springframework.jdbc.datasource.DataSourceTransactionManager(writeDataSource);
    }
}

使用AOP霹俺,讀取指定包下@DataSource的值:

package com.imooc.miaosha.aop;

import com.imooc.miaosha.config.DataSource;
import com.imooc.miaosha.config.DataSourceContextHolder;
import com.imooc.miaosha.enums.DataSourceType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Slf4j
@Component
public class DataSourceAop {
    /**
     * @annotation(com.imooc.miaosha.config.DataSource)
     * 所有含有@DataSource 注解的方法都將匹配到
     */
    @Before("@annotation(com.imooc.miaosha.config.DataSource)")
    public void setDataSourceType(JoinPoint joinPoint) {
        Object target = joinPoint.getTarget();
        // 獲取方法名稱
        String methodName = joinPoint.getSignature().getName();
        // 獲取方法參數(shù)類型
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
        try {
            Method method = target.getClass().getMethod(methodName, parameterTypes);
            DataSource dataSource = method.getAnnotation(DataSource.class);
            // 為空什么都不做,因為一開始我們設(shè)置了master為默認數(shù)據(jù)庫
            if (dataSource == null) return;
            String value = dataSource.value();
            if (DataSourceType.READ.getType().equals(value)){
                DataSourceContextHolder.read();
            }else if (DataSourceType.WRITE.getType().equals(value)){
                DataSourceContextHolder.write();
            }
            log.info("dataSource切換到:{}", value);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    /**
     * 方法走完要清空ThreadLocal毒费,否則下次如果沒有指定@DataSource注解吭服,
     * 則不會使用默認的master數(shù)據(jù)源(即寫數(shù)據(jù)源),而是上一次的數(shù)據(jù)源
     * @param joinPoint
     */
    @After("@annotation(com.imooc.miaosha.config.DataSource)")
    public void afterSetDataSourceType(JoinPoint joinPoint) {
        DataSourceContextHolder.clearDB();
    }

}

以上配置完成之后蝗罗,就實現(xiàn)了數(shù)據(jù)庫的讀寫分離艇棕,是不是很給力!

package com.imooc.miaosha.service.impl;

import java.util.List;

import com.imooc.miaosha.config.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.imooc.miaosha.mapper.GoodsMapper;
import com.imooc.miaosha.model.Goods;
import com.imooc.miaosha.model.MiaoshaGoods;
import com.imooc.miaosha.service.GoodsService;
import com.imooc.miaosha.vo.GoodsVO;

@Service
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    private GoodsMapper goodsMapper;

    @Override
    @DataSource("read")
    public List<GoodsVO> getAllGoodsInfo() {
        return goodsMapper.findAllGoodsInfo();
    }

    @Override
    public Goods findOne(Long goodsId) {
        return goodsMapper.findOne(goodsId);
    }

    @Override
    @DataSource("write")
    public boolean reduceMiaoshaStock(Long goodsId) {
        int i = goodsMapper.decreaseStock(goodsId);
        return i == 1;
    }

小結(jié):當一個客戶端請求過來串塑,會調(diào)用impl包下的service實現(xiàn)類沼琉,aop通過掃描實現(xiàn)類中方法上的@DataSource注解,如果沒有該注解桩匪,則采用默認的寫數(shù)據(jù)源打瘪;如果有該注解,則獲取注解中的value值傻昙,并且set進去ThreadLocal闺骚。接著去獲取數(shù)據(jù)源,即調(diào)用getConnection()方法妆档,會調(diào)用我們重寫的determineCurrentLookupKey()方法從ThreadLocal中獲取當前的數(shù)據(jù)源類型僻爽,AbstractRoutingDataSource會根據(jù)當前的數(shù)據(jù)源類型,取出對應的數(shù)據(jù)源贾惦,從而執(zhí)行SQL語句胸梆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市须板,隨后出現(xiàn)的幾起案子碰镜,更是在濱河造成了極大的恐慌,老刑警劉巖习瑰,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绪颖,死亡現(xiàn)場離奇詭異,居然都是意外死亡甜奄,警方通過查閱死者的電腦和手機柠横,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贺嫂,“玉大人滓鸠,你說我怎么就攤上這事〉谠” “怎么了糜俗?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長曲饱。 經(jīng)常有香客問我悠抹,道長,這世上最難降的妖魔是什么扩淀? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任楔敌,我火速辦了婚禮,結(jié)果婚禮上驻谆,老公的妹妹穿的比我還像新娘卵凑。我一直安慰自己庆聘,他們只是感情好,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布勺卢。 她就那樣靜靜地躺著伙判,像睡著了一般。 火紅的嫁衣襯著肌膚如雪黑忱。 梳的紋絲不亂的頭發(fā)上宴抚,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機與錄音甫煞,去河邊找鬼菇曲。 笑死,一個胖子當著我的面吹牛抚吠,可吹牛的內(nèi)容都是我干的常潮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼埃跷,長吁一口氣:“原來是場噩夢啊……” “哼蕊玷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起弥雹,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤垃帅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后剪勿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贸诚,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年厕吉,在試婚紗的時候發(fā)現(xiàn)自己被綠了酱固。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡头朱,死狀恐怖运悲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情项钮,我是刑警寧澤班眯,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站烁巫,受9級特大地震影響署隘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亚隙,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一磁餐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧阿弃,春花似錦诊霹、人聲如沸羞延。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肴楷。三九已至,卻和暖如春荠呐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背砂客。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工泥张, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鞠值。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓媚创,卻偏偏與公主長得像,于是被迫代替她去往敵國和親彤恶。 傳聞我的和親對象是個殘疾皇子钞钙,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容