09- QuartZ

Java中實現(xiàn)定時任務(wù)的幾種方式:

  • Timer: java.util.Timer, 一個JDK自帶的處理簡單的定時任務(wù)的工具存哲。
  • ScheduledExecutorService: java.util.concurrent.ScheduledExecutorService, JDK中的定時任務(wù)接口,可以將定時任務(wù)與線程池結(jié)合使用。
  • Sceduled: org.springframework.scheduling.annotation.Scheduled, Spring框架中基于注解來實現(xiàn)定時任務(wù)處理方式。
  • Quartz: 支持分布式調(diào)度任務(wù)的開源框架操漠。

Spring Task 與 Quartz 對比:

  • Quartz提供了完整的分布式并發(fā)支持官辈,Spring task的話還是需要自己手動配置線程池以及借助其他分布式工具實現(xiàn)援岩。
  • Quartz支持調(diào)度信息持久化稿茉,可以去Quartz官方網(wǎng)站下載建表sql锹锰,然后配置數(shù)據(jù)源等信息實現(xiàn)持久化芥炭, Spring task不支持持久化漓库。
  • Spring Task 使用起來非常簡單Quartz還需要寫其他配置相對來說比Spring Task復(fù)雜點。

需求簡單的話可以直接使用 Spring Task 园蝠,比如一個簡單的數(shù)據(jù)對接接口渺蒿。如果會有復(fù)雜的業(yè)務(wù)需求不如一步到位,直接使Quartz彪薛。

一茂装、Timer

Timer的schedulescheduleAtFixedRate在任務(wù)計劃時間和實際開始時間沒有異常的情況下,當(dāng)任務(wù)的執(zhí)行時間超過設(shè)置的執(zhí)行間隔善延,會丟失任務(wù)少态。schedule不受異常時間的控制。

public class App {
    public static void main(String[] args) {
        Timer t = new Timer();
        TimerTask task = new FooTimeTask("foo");
        Calendar c = Calendar.getInstance();
        c.set(Calendar.SECOND, c.get(Calendar.SECOND) - 10);
        Date planTime = c.getTime();
        System.out.println("計劃執(zhí)行時間" + planTime);
        System.out.println("實際執(zhí)行時間" + new Date());
        t.schedule(task, planTime, 3000);
    }
}

class FooTimeTask extends TimerTask {
    private String name;
    public FooTimeTask(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        try {
            System.out.println("name" + name + ",stime=" + new Date());
            Thread.sleep(2000);
           // Thread.sleep(4000);
            System.out.println("name" + name + ",etime=" + new Date());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
任務(wù)開始時間不正常-任務(wù)執(zhí)行2S-設(shè)置間隔時間3S-時間間隔時間3S
任務(wù)開始時間不正常-任務(wù)執(zhí)行4S-設(shè)置間隔時間3S-實際間隔時間4S
public class App {
    public static void main(String[] args) {
        Timer t = new Timer();
        TimerTask task = new FooTimeTask("foo");
        t.scheduleAtFixedRate(task, new Date(), 3000);
    }
}
class FooTimeTask extends TimerTask {
    private String name;

    public FooTimeTask(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try {
            System.out.println("name" + name + ",stime=" + new Date());
            Thread.sleep(2000);
           //Thread.sleep(4000);
            System.out.println("name" + name + ",etime=" + new Date());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
任務(wù)開始時間正常-任務(wù)執(zhí)行2S-設(shè)置間隔時間3S-實際間隔時間3S

任務(wù)開始時間正常-任務(wù)執(zhí)行4S-設(shè)置間隔時間3S-時間間隔時間4S

Timer的scheduleAtFixedRate在任務(wù)開始時間不正常情況下與schedule有區(qū)別:存在時間追趕易遣。

public class App {
    public static void main(String[] args) {
        Timer t = new Timer();
        TimerTask task = new FooTimeTask("foo");
        Calendar c = Calendar.getInstance();
        c.set(Calendar.SECOND, c.get(Calendar.SECOND) - 10);
        Date planTime = c.getTime();
        System.out.println("計劃執(zhí)行時間" + planTime);
        System.out.println("實際執(zhí)行時間" + new Date());
        t.scheduleAtFixedRate(task, planTime, 3000);
    }
}

class FooTimeTask extends TimerTask {
    private String name;

    public FooTimeTask(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        try {
            System.out.println("name" + name + ",stime=" + new Date());
            Thread.sleep(2000);
           //Thread.sleep(4000);
            System.out.println("name" + name + ",etime=" + new Date());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
任務(wù)開始時間不正常-任務(wù)執(zhí)行2S-設(shè)置間隔時間3S-時間間隔時間2S
任務(wù)開始時間不正常-任務(wù)執(zhí)行4S-設(shè)置間隔時間3S-時間間隔時間4S

二彼妻、ScheduledExecutorService

摘抄:java定時任務(wù)之Timer和ScheduledExecutorService
Timer的缺陷:
1、執(zhí)行多個任務(wù)的時候,必須第一個執(zhí)行完后才會執(zhí)行第二個侨歉。
2屋摇、timer是單線程執(zhí)行,因此一個任務(wù)拋異常幽邓,其它任務(wù)也不能執(zhí)行了炮温。

import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
    private static long start;
    public static void main(String[] args) throws Exception {

        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task1 invoked ! "+(System.currentTimeMillis() - start));
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task2 invoked ! "+ (System.currentTimeMillis() - start));
            }
        };
        Timer timer = new Timer();
        start = System.currentTimeMillis();
        timer.schedule(task1, 1000);
        timer.schedule(task2, 3000);
    }
}  
結(jié)果

newScheduledThreadPool:
1、可以多個線程同時執(zhí)行不同的任務(wù)牵舵。
2基跑、因為是多個線程所有,任務(wù)拋出異常策添,不影響其它任務(wù)的執(zhí)行息楔。
scheduleAtFixedRate : 0s后開始執(zhí)行任務(wù),前一次任務(wù)的開始時間和下次任務(wù)的開始時間中間間隔是5s , 如果前一次任務(wù)執(zhí)行時間大于5s 重斑,那么下次任務(wù)會在隊列中等待兵睛,直到前一次任務(wù)執(zhí)行完后,再執(zhí)行窥浪。
scheduleWithFixedDelay : 0s后開始執(zhí)行任務(wù)祖很,前一次任務(wù)的結(jié)束時間和后一次任務(wù)的開始時間中間間隔是5s。

public class Scheduled {
    static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
    static class Task implements Runnable {
        public void run() {
            try {
                Thread.sleep(2000L);
                System.out.println(Thread.currentThread().getName());
            } catch (Exception e) {

            }
        }
    }
    /**
     * command:執(zhí)行線程
     * initialDelay:初始化延時
     * period:兩次開始執(zhí)行最小間隔時間
     * delay:前一次執(zhí)行結(jié)束到下一次執(zhí)行開始的間隔時間(間隔執(zhí)行延遲時間)
     * unit:計時單位
     */
    public static void main(String[] args) {
        scheduledExecutorService.scheduleAtFixedRate(new Task(), 0L, 5000L, TimeUnit.MILLISECONDS);
        scheduledExecutorService.scheduleWithFixedDelay(new Task(), 0L, 5000L, TimeUnit.MILLISECONDS);
    }
}

三漾脂、QuartZ

可參考:Java定時框架Quartz

  • Quartz是功能強大的開源作業(yè)調(diào)度庫,可以集成到最小的獨立應(yīng)用程序到最大的電子商務(wù)程序中假颇。
  • Quartz可以通過創(chuàng)建簡單或者復(fù)雜的計劃來執(zhí)行成千上萬的任務(wù)。
  • 任何任務(wù)標(biāo)準(zhǔn)的Java組件,都可以執(zhí)行相應(yīng)的編程操作骨稿。
  • Quartz Scheduler支持JTA事務(wù)和集群笨鸡。
  • Quartz實現(xiàn)了任務(wù)和觸發(fā)器多對多的關(guān)系,可以將多個任務(wù)和不同的觸發(fā)器相關(guān)聯(lián)。

Quartz的核心

  • JobBuilder:創(chuàng)建Job和JobDetail坦冠。
  • JobDataMap:可以包含不限量的(序列化的)數(shù)據(jù)對象形耗,在job實例執(zhí)行的時候,可以使用其中的數(shù)據(jù)辙浑;將job加入到scheduler之前激涤,在構(gòu)建JobDetail時,可以將數(shù)據(jù)放入JobDataMap判呕。
  • Job:Job接口中只有一個方法 execute(JobExecutionContext context)倦踢;表示要執(zhí)行的具體內(nèi)容。
    Job的屬性有兩種: 兩者都是在值為true時表示任務(wù)被持久化或者保留侠草;一個Job可以關(guān)聯(lián)多個Trigger, 一個Trigger只能關(guān)聯(lián)一個Job辱挥。
    volatility : 表示任務(wù)是否被持久化到數(shù)據(jù)庫存儲;
    durability : 沒有trigger關(guān)聯(lián)的時候任務(wù)是否保留边涕;
  • JobDetail:表示一個具體的可執(zhí)行的調(diào)度程序晤碘,Job是這個可執(zhí)行程調(diào)度程序所要執(zhí)行的內(nèi)容愧哟,另外JobDetail還包含了這個任務(wù)調(diào)度的方案和策略。它本身可能是有狀態(tài)的哼蛆。
  • TriggerBuilder:創(chuàng)建Trigger蕊梧。
  • Trigger:代表一個調(diào)度參數(shù)的配置。
  • Scheduler:代表一個調(diào)度容器腮介,一個調(diào)度容器中可以注冊多個JobDetail和Trigger肥矢。當(dāng)Trigger與JobDetail組合,就可以被Scheduler容器調(diào)度了叠洗。

Quartz設(shè)計成JobDetail + Job的原因在于:

  • JobDetail用于定義任務(wù)數(shù)據(jù), 真正的執(zhí)行任務(wù)的邏輯在Job中甘改。
  • 因為任務(wù)有可能是并發(fā)執(zhí)行的,如果Scheduler直接使用Job, 會存在對同一個Job實例并發(fā)訪問的問題;通過JobDetail綁定Job的方式 ,Scheduler每次執(zhí)行,都會根據(jù)JobDetail創(chuàng)建一個新的Job實例,可以避免并發(fā)訪問的問題灭抑。
Quartz主要線程

Quartz的持久化:http://www.reibang.com/p/d2f7ad108ab2

Quartz數(shù)據(jù)存儲

3.1 QuartZ入門

//@DisallowConcurrentExecution   禁止并發(fā)執(zhí)行同一個Job定義的多個實例十艾。
//@PersistJobDataAfterExecution 多個Job實例共享JobDataMap;對Trigger中的JobDataMap無效
public class MyJob implements Job {
  //可以在JobDetail和Trigger中給Job中的屬性賦值
    private  String  name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
        //JobDetail的使用
        JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        JobDataMap triggerDataMap = jobExecutionContext.getTrigger().getJobDataMap();
        JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();
        System.out.println("MyJob exe " + new Date() +" || "+ jobDataMap.get("job") +" || " + triggerDataMap.get("trigger")+"||"+mergedJobDataMap.get("com") +"||"+name);
        System.out.println("=============");
        //1.scheduler每次執(zhí)行都會根據(jù)jobDetail創(chuàng)建一個新的Job實例。避免并發(fā)問題腾节。
        //  這樣存在的問題:多個Job無法共享JobDataMap忘嫉。
        System.out.println("jobDetail"+System.identityHashCode(jobExecutionContext.getJobDetail()));
        System.out.println("job"+System.identityHashCode(jobExecutionContext.getJobInstance()));
        jobDataMap.put("count",jobDataMap.getInt("count")+1);
        System.out.println("計數(shù):"+jobDataMap.getInt("count"));
        //2.默認(rèn)是并發(fā)執(zhí)行調(diào)度內(nèi)的Job;Scheduler不會理會Job中的任務(wù)執(zhí)行時間,嚴(yán)格按照調(diào)度執(zhí)行案腺。
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
    @Test
    void quartz() throws  Exception {
        int count = 0;
        JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                .withIdentity("job1","group1")       //定義屬性
                .usingJobData("job","myJobDetail")
                .usingJobData("com","jobcom")
                .usingJobData("name","ABO") // 會給job中的屬性自動賦值
                .usingJobData("count",count)
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1","trigger1") //定義屬性
                .usingJobData("trigger","myTrigger")
                .usingJobData("com","triggercom")
                .usingJobData("name","ABO-trigger") // 會給job中的屬性自動賦值
                .startNow()
                .withSchedule(                                   //定義作業(yè)執(zhí)行規(guī)則
                 SimpleScheduleBuilder.simpleSchedule()  //簡單的調(diào)度
                 .withIntervalInSeconds(1)                //間隔時間
                 .repeatForever()                          //一直執(zhí)行
                ).build();

        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.scheduleJob(jobDetail,trigger);
        //默認(rèn)是并發(fā)執(zhí)行調(diào)度內(nèi)的Job;不會理會Job中的任務(wù)執(zhí)行時間庆冕,嚴(yán)格按照調(diào)度執(zhí)行。
        scheduler.start();

        //測試使用
        Thread.sleep(10000);
    }
無DisallowConcurrentExecution和@PersistJobDataAfterExecution
有DisallowConcurrentExecution和@PersistJobDataAfterExecution

3.2 Trigger

通用的觸發(fā)器包括兩類:

  • SimpleTrigger: 根據(jù)Quartz框架的類中自定義的方法設(shè)置定時執(zhí)行規(guī)則劈榨。
  • CronTrigger: 基于Cron表達式實現(xiàn)觸發(fā)器访递。

misfire:任務(wù)錯過執(zhí)行的處理。
優(yōu)先級:同時觸發(fā)的Trigger之間優(yōu)先級高的先執(zhí)行同辣。
calendar:用于排除時間段拷姿。

misfire

錯過觸發(fā)的判斷條件

  • job到達觸發(fā)時間時候沒有執(zhí)行。
  • 被執(zhí)行的延遲時間超過了Quartz配置的misfireThreshold閾值旱函。

錯過觸發(fā)的產(chǎn)生原因

  • 當(dāng)job達到觸發(fā)時間時响巢,所有線程都被其他job占用了。
  • 當(dāng)job需要觸發(fā)的時間點陡舅,scheduler停止了抵乓。
  • job使用了@PersistJobDataAfterExecution伴挚。job不能并發(fā)執(zhí)行靶衍,當(dāng)?shù)竭_下一個job執(zhí)行點的時候,上一個任務(wù)還沒有完成茎芋。
  • job指定了過去的開始執(zhí)行時間(在閾值范圍內(nèi)颅眶,還可以被正常執(zhí)行)。

錯過觸發(fā)的應(yīng)對策略

  • SimpleTrigger:
    nowXXX相關(guān)的策略田弥,會立即執(zhí)行第一個misfire的任務(wù)涛酗,同時會修改startTime和repeatCount,因此finalFireTime會重新計算,原計劃執(zhí)行時間會被打亂。
    nextXXX相關(guān)的策略商叹,不會立即執(zhí)行misfire的任務(wù)燕刻,也不會修改startTime和repeatCount,finalFireTime也沒有改變剖笙,發(fā)生了misfire也還是按照原計劃進行執(zhí)行卵洗。
  • CronTrigger
    MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY:Quartz不會判斷發(fā)生了misfire,立即執(zhí)行所有發(fā)生了misfire的任務(wù),然后按照原計劃進行執(zhí)行弥咪。
    MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:立即執(zhí)行第一個發(fā)生misfire的任務(wù)过蹂,忽略其他發(fā)生misfire的任務(wù),按照原計劃繼續(xù)執(zhí)行聚至。
    MISFIRE_INSTRUCTION_DO_NOTHING:所有發(fā)生misfire的任務(wù)都被忽略酷勺,只是按照計劃繼續(xù)執(zhí)行。

默認(rèn)使用MISFIRE_INSTRUCTION_SMART_POLICY策略扳躬。

3.3 scheduler

SchedulerFactory:

  • 創(chuàng)建Scheduler脆诉。
  • DireSchedulerFactory:在代碼中定制Scheduler參數(shù)。
  • StdSchedulerFactory:讀取類路徑下quartz.properties配置實例化Scheduler贷币。

JobStore:

  • 存儲運行時的信息库说,包括:Trigger、Scheduler片择、JobDetail潜的、業(yè)務(wù)鎖等。
  • RAMJobStore:內(nèi)存實現(xiàn)字管。
  • JobStoreTX:JDBC啰挪,事務(wù)由Quartz管理。
  • JobStoreCMT:JDBC嘲叔,使用容器事務(wù)亡呵。
  • ClusteredJobStore:集群實現(xiàn)。
  • TerracottaJobStore:Terracotta中間件硫戈。

ThreadPool:

  • SimpleThreadPool锰什。
  • 自定義線程池。

3.4 SpringBoot整合Quartz

1.job類

public class QuartzDemo implements Job {
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        System.out.println("Execute..."+new Date());
    }
}

2.配置類丁逝,配置觸發(fā)器與任務(wù)調(diào)度器

/**
 * Quartz配置類
 */
@Configuration
public class QuartzConfig {
    /**
     * 1汁胆、創(chuàng)建Job對象
     */
    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean(){
        JobDetailFactoryBean factoryBean=new JobDetailFactoryBean();
        //關(guān)聯(lián)我們自己的Job類
        factoryBean.setJobClass(QuartzDemo.class);
        return factoryBean;
    }
    /**
     * 2、創(chuàng)建Trigger對象
     */
    @Bean
    public SimpleTriggerFactoryBean simpleTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean){
        SimpleTriggerFactoryBean factoryBean=new SimpleTriggerFactoryBean();
        //關(guān)聯(lián)JobDetail對象
        factoryBean.setJobDetail(jobDetailFactoryBean.getObject());
        //該參數(shù)表示一個執(zhí)行的毫秒數(shù)
        factoryBean.setRepeatInterval(2000); //每隔2秒執(zhí)行一次
        //重復(fù)次數(shù)
        factoryBean.setRepeatCount(5);
        return factoryBean;
    }
    /**
     * 3霜幼、創(chuàng)建Scheduler
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(SimpleTriggerFactoryBean simpleTriggerFactoryBean){
        SchedulerFactoryBean factoryBean=new SchedulerFactoryBean();
        //關(guān)聯(lián)trigger
        factoryBean.setTriggers(simpleTriggerFactoryBean.getObject());
        return factoryBean;
    }
}

3.啟動類

@SpringBootApplication
@EnableScheduling
public class QuartzApplication extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(QuartzApplication.class, args);
   }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嫩码,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子罪既,更是在濱河造成了極大的恐慌铸题,老刑警劉巖铡恕,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異丢间,居然都是意外死亡探熔,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門烘挫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祭刚,“玉大人,你說我怎么就攤上這事墙牌∥型裕” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵喜滨,是天一觀的道長捉捅。 經(jīng)常有香客問我,道長虽风,這世上最難降的妖魔是什么棒口? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮辜膝,結(jié)果婚禮上无牵,老公的妹妹穿的比我還像新娘。我一直安慰自己厂抖,他們只是感情好茎毁,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著忱辅,像睡著了一般七蜘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上墙懂,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天橡卤,我揣著相機與錄音,去河邊找鬼损搬。 笑死碧库,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的巧勤。 我是一名探鬼主播嵌灰,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼踢关!你這毒婦竟也來了伞鲫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤签舞,失蹤者是張志新(化名)和其女友劉穎秕脓,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體儒搭,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡吠架,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了搂鲫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片傍药。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖魂仍,靈堂內(nèi)的尸體忽然破棺而出拐辽,到底是詐尸還是另有隱情,我是刑警寧澤擦酌,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布俱诸,位于F島的核電站,受9級特大地震影響赊舶,放射性物質(zhì)發(fā)生泄漏睁搭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一笼平、第九天 我趴在偏房一處隱蔽的房頂上張望园骆。 院中可真熱鬧,春花似錦寓调、人聲如沸锌唾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸠珠。三九已至,卻和暖如春秋麸,著一層夾襖步出監(jiān)牢的瞬間渐排,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工灸蟆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留驯耻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓炒考,卻偏偏與公主長得像可缚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子斋枢,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

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