Spring Cloud入門教程(七):分布式鏈路跟蹤(Sleuth)

上一篇:《Spring Cloud入門教程(六):API服務(wù)網(wǎng)關(guān)(Zuul) 下》

本人和同事撰寫的《Spring Cloud微服務(wù)架構(gòu)開發(fā)實(shí)戰(zhàn)》一書也在京東、當(dāng)當(dāng)?shù)葧晟霞芾炷Γ蠹铱梢渣c(diǎn)擊這里前往購買,多謝大家支持和捧場累提!


當(dāng)我們進(jìn)行微服務(wù)架構(gòu)開發(fā)時(shí),通常會(huì)根據(jù)業(yè)務(wù)來劃分微服務(wù)庄呈,各業(yè)務(wù)之間通過REST進(jìn)行調(diào)用鸯乃。一個(gè)用戶操作蠢涝,可能需要很多微服務(wù)的協(xié)同才能完成辆童,如果在業(yè)務(wù)調(diào)用鏈路上任何一個(gè)微服務(wù)出現(xiàn)問題或者網(wǎng)絡(luò)超時(shí),都會(huì)導(dǎo)致功能失敗惠赫。隨著業(yè)務(wù)越來越多,對于微服務(wù)之間的調(diào)用鏈的分析會(huì)越來越復(fù)雜故黑。

Spring Cloud Sleuth為服務(wù)之間調(diào)用提供鏈路追蹤儿咱。通過Sleuth可以很清楚的了解到一個(gè)服務(wù)請求經(jīng)過了哪些服務(wù),每個(gè)服務(wù)處理花費(fèi)了多長场晶。從而讓我們可以很方便的理清各微服務(wù)間的調(diào)用關(guān)系混埠。此外Sleuth可以幫助我們:

  • 耗時(shí)分析: 通過Sleuth可以很方便的了解到每個(gè)采樣請求的耗時(shí),從而分析出哪些服務(wù)調(diào)用比較耗時(shí);
  • 可視化錯(cuò)誤: 對于程序未捕捉的異常诗轻,可以通過集成Zipkin服務(wù)界面上看到;
  • 鏈路優(yōu)化: 對于調(diào)用比較頻繁的服務(wù)钳宪,可以針對這些服務(wù)實(shí)施一些優(yōu)化措施。

1. Sleuth+Log 示例代碼

我們先用最簡單的方式集成Sleuth扳炬,把Sleuth所跟蹤到的信息輸出到日志中吏颖。基礎(chǔ)代碼采用之前所構(gòu)建的商城項(xiàng)目恨樟。

1.1 改造Mall-Web

增加bootstrap.properties文件

為了能夠讓日志文件可以獲取到服務(wù)名稱半醉,我們需要將原來配置在application.properties中的部分內(nèi)容移入到bootstrap.properties配置文件中,這是因?yàn)镾pringBoot在啟動(dòng)時(shí)會(huì)優(yōu)先掃描bootstrap配置源劝术,從而能夠讓日志可以獲取到服務(wù)名稱缩多。

server.port=8080

spring.application.name=MALL-WEB

修改application.properties文件

eureka.client.service-url.defaultZone=http://localhost:8260/eureka

logging.level.org.springframework=INFO
logging.level.org.springframework.web.servlet.DispatcherServlet=DEBUG

這里主要是把DispatcherServlet的日志級別修改為DEBUG

修改Logback配文件

resources目錄中增加一個(gè)名稱為: logback-spring.xml的文件养晋,內(nèi)容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    ?
    <springProperty scope="context" name="springAppName" source="spring.application.name"/>

    <!-- Example for logging into the build folder of your project -->
    <property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>?

    <property name="CONSOLE_LOG_PATTERN"
              value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p})
            %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>
                %d{yyyy-MM-dd HH:mm:ss SSS} [%thread] %-5level %logger{36} - %msg%n
            </Pattern>
        </layout>
    </appender>

    <!-- Appender to log to console -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!-- Minimum logging level to be presented in the console logs-->
            <level>DEBUG</level>
        </filter>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- Appender to log to file -->?
    <appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>
    ?    ?
    <root level="INFO">
        <appender-ref ref="console"/>
        <!-- uncomment this to have also JSON logs -->
        <!--<appender-ref ref="logstash"/>-->
        <!--<appender-ref ref="flatfile"/>-->
    </root>
</configuration>

SpringCloud的參考手冊中提到:SLF4J MDC總是會(huì)自動(dòng)進(jìn)行設(shè)置衬吆,并且如果使用logback,那么trace/span的id則會(huì)立即顯示在日志中绳泉。其他的日志系統(tǒng)需要配置各自的格式來達(dá)到這樣的效果逊抡。默認(rèn)的logging.pattern.level設(shè)置為%clr(%5p) %clr([${spring.application.name:},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]){yellow} (這也是一個(gè)Spring Boot整合logback時(shí)有的特性)。 這就意味著零酪,如果使用SLF4J時(shí)不需要手工配置該格式秦忿,而其它日志系統(tǒng)則必須手工進(jìn)行配置,否則不會(huì)輸出蛾娶。

修改POM文件

pom.xml文件中增加如下依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

1.2 改造Product-Service

改造方式與上面相同灯谣。

1.3 啟動(dòng)測試

按照先后順序分別啟動(dòng)Service-discoveryProduct-ServiceMall-Web工程蛔琅。然后在瀏覽器中輸入: http://localhost:8080/products胎许。然后我們分別觀察Mall-WebProduct-Service控制臺(tái)中日志輸出,可以看到類似下面輸出:

2017-07-10 21:36:24.802 DEBUG [MALL-WEB,e23abdb6268af95d,e23abdb6268af95d,false] [MALL-WEB,e23abdb6268af95d,e23abdb6268af95d,,false] 92827 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet        : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/products]

2017-07-10 21:36:24.838 DEBUG [PRODUCT-SERVICE,e23abdb6268af95d,c68a9b1c2ab8a025,false] [PRODUCT-SERVICE,e23abdb6268af95d,c68a9b1c2ab8a025,e23abdb6268af95d,false] 92782 --- [nio-2100-exec-3] o.s.web.servlet.DispatcherServlet        : DispatcherServlet with name 'dispatcherServlet' processing GET request for [/products]

日志中類似 [MALL-WEB,e23abdb6268af95d,e23abdb6268af95d,false]、[PRODUCT-SERVICE,e23abdb6268af95d,c68a9b1c2ab8a025,false] 的日志內(nèi)容它們的格式為: [appname,traceId,spanId,exportable]辜窑,也就是Sleuth的跟蹤數(shù)據(jù)钩述。其中:

  • appname: 為微服務(wù)的服務(wù)名稱;
  • traceId\spanId: 為Sleuth鏈路追蹤的兩個(gè)術(shù)語,后面我們再仔細(xì)介紹;
  • exportable 是否是發(fā)送給Zipkin穆碎。

2. Sleuth術(shù)語

因?yàn)镾leuth是根據(jù)Google的Dapper’s論文而來的牙勘,所以在術(shù)語上也借鑒了Dapper。

  • Span: 最基本的工作單元所禀。例如: 發(fā)送一個(gè)RPC就是一個(gè)新的span方面,同樣一次RPC的應(yīng)答也是。Span通過一個(gè)唯一的色徘,長度為64位的ID來作為標(biāo)識恭金,另外,再使用一個(gè)64位ID用于服務(wù)調(diào)用跟蹤褂策。Span也可以帶有其他數(shù)據(jù)横腿,例如:描述,時(shí)間戳斤寂,鍵值對標(biāo)簽耿焊,起始Span的ID,以及處理ID(通常使用IP地址)等等遍搞。 Span有起始和結(jié)束搀别,它們用于跟蹤時(shí)間信息。Span應(yīng)該都是成對出現(xiàn)的尾抑,有始必有終歇父,所以一旦創(chuàng)建了一個(gè)span,那就必須在未來某個(gè)時(shí)間點(diǎn)結(jié)束它再愈。

提示: 起始的Span通常被稱為:root span榜苫。它的id通常也被作為一個(gè)跟蹤記錄的id。

  • Trace: 一個(gè)樹結(jié)構(gòu)的Span集合翎冲。例如:在分布式大數(shù)據(jù)存儲(chǔ)中垂睬,可能每一次請求都是一次跟蹤記錄。
  • Annotation: 用于記錄一個(gè)事件的時(shí)間信息抗悍。一些基礎(chǔ)核心的Annotation用于記錄請求的起始和結(jié)束時(shí)間驹饺,例如:
    • cs: 客戶端發(fā)送(Client Sent的縮寫)。這個(gè)annotation表示一個(gè)span的起始;
    • sr: 服務(wù)端接收(Server Received的縮寫)缴渊。表示服務(wù)端接收到請求赏壹,并開始處理。如果減去cs的時(shí)間戳衔沼,則可以計(jì)算出網(wǎng)絡(luò)傳輸耗時(shí)蝌借。
    • ss: 服務(wù)端完成請求處理昔瞧,應(yīng)答信息被發(fā)回客戶端(Server Sent的縮寫)。如果減去sr的時(shí)間戳菩佑,則可以計(jì)算出服務(wù)端處理請求的耗時(shí)自晰。
    • cr: 客戶端接收(Client Received的縮寫)。標(biāo)志著Span的結(jié)束稍坯〕贶瘢客戶端成功的接收到服務(wù)端的應(yīng)答信息。如果減去cs的時(shí)間戳瞧哟,則可以計(jì)算出請求的響應(yīng)耗時(shí)混巧。

下圖,通過可視化的方式描述了Span和Trace的概念:

trace-id

圖中每一個(gè)顏色都表示著一個(gè)span(總共7個(gè)span绢涡,從A到G)。它們都有以下這些數(shù)據(jù)信息:

Trace Id = X
Span Id = D
Client Sent

表示該Span的Trace-IdX遣疯,Span-IdD雄可。相應(yīng)的事件為Client Sent

這些Span的上下級關(guān)系可以通過下圖來表示:

parents

3. 整合Zipkin服務(wù)

Zipkin是一個(gè)致力于收集分布式服務(wù)的時(shí)間數(shù)據(jù)的分布式跟蹤系統(tǒng)缠犀。其主要涉及以下四個(gè)組件:

  • collector: 數(shù)據(jù)采集;
  • storage: 數(shù)據(jù)存儲(chǔ);
  • search: 數(shù)據(jù)查詢;
  • UI: 數(shù)據(jù)展示.

Zipkin提供了可插拔數(shù)據(jù)存儲(chǔ)方式:In-Memory数苫、MySql、Cassandra以及Elasticsearch辨液。接下來的測試為方便直接采用In-Memory方式進(jìn)行存儲(chǔ)虐急,個(gè)人推薦Elasticsearch,特別是后續(xù)當(dāng)我們需要整合ELK時(shí)滔迈。

ZipKin在Github源碼地址為:https://github.com/openzipkin/zipkin止吁。

ZipKin運(yùn)行環(huán)境需要Jdk8支持。

在本篇中我們僅通過Http的方式向Zipkin提供跟蹤數(shù)據(jù)燎悍,關(guān)于使用stream的方式后續(xù)講到Spring Cloud Bus的時(shí)候再說明敬惦。我們所要搭建的系統(tǒng)架構(gòu)如下(做了精簡):

zipkin-070

3.1 構(gòu)建Zipkin-Server

編寫pom.xml文件

還是繼承自我們之前的parent:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>twostepsfromjava.cloud</groupId>
        <artifactId>twostepsfromjava-cloud-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../parent</relativePath>
    </parent>
    
    <artifactId>zipkin-server</artifactId>
    <name>Spring Cloud Sample Projects: Zipkin Server</name>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-server</artifactId>
        </dependency>
        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-autoconfigure-ui</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

這里需要說明的時(shí)zipkin-autoconfigure-ui包提供了可視化界面。

編寫啟動(dòng)類

/**
 * TwoStepsFromJava Cloud -- Zipkin Server Project
 *
 * @author CD826(CD826Dong@gmail.com)
 * @since 1.0.0
 */
@SpringBootApplication
@EnableZipkinServer
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

這里在Application的注解中增加@EnableZipkinServer谈山,開啟Zipkin服務(wù)俄删。

編寫bootstrap.properties配置文件

server.port=8240

spring.application.name=ZIPKIN-SERVER

我們把Zipkin服務(wù)的端口設(shè)置為:8240

3.2 修改Mall-Web工程

修改pom.xml文件

在pom文件中增加以下依賴:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

同時(shí)可以刪除之前的:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>

應(yīng)為奏路,在spring-cloud-starter-zipkin中已經(jīng)包含了對spring-cloud-starter-sleuth的依賴畴椰。

修改application.properties配置文件

application.properties增加以下內(nèi)容:

spring.zipkin.base-url=http://localhost:8240
spring.sleuth.sampler.percentage=1.0

spring.zipkin.base-url指定了Zipkin服務(wù)器的地址,spring.sleuth.sampler.percentage將采樣比例設(shè)置為1.0鸽粉,也就是全部都需要斜脂。關(guān)于采樣可以參考下面的說明。

3.3 修改Product-Service工程

改造方式與上面相同触机。

3.4 啟動(dòng)測試

按照先后順序分別啟動(dòng)Service-discovery秽褒、Zipkin-Server壶硅、Product-ServiceMall-Web工程。

查看Zipkin服務(wù)器

啟動(dòng)后我們可以訪問:http://localhost:8240销斟,可以看到如下界面:

zipkin-010

說明Zipkin服務(wù)器啟動(dòng)成功庐椒。

訪問幾次Mall-Web所提供的服務(wù)

我們在瀏覽器中訪問幾次Mall-Web所提供的服務(wù),然后轉(zhuǎn)到Zipkin服務(wù)器蚂踊,可以看到如下界面:

zipkin-020

可以看到约谈,Zipkin已經(jīng)獲取到幾次服務(wù)的調(diào)用跟蹤信息了。我們可以點(diǎn)擊其中的一個(gè)請求犁钟,可以看到如下界面:

zipkin-030

該界面對本次請求進(jìn)行了更詳細(xì)的展現(xiàn)棱诱。同樣我們還可以再點(diǎn)擊,以查看更為詳細(xì)的數(shù)據(jù)涝动,可以看到如下界面:

zipkin-040

在該界面中我們可以看到之前所講的各個(gè)時(shí)間跟蹤信息迈勋。

在Zipkin界面中我們還可以點(diǎn)擊[Dependencies]查看各服務(wù)之間的依賴關(guān)系,如下圖:

zipkin-050

錯(cuò)誤信息

Zipkin可以在跟蹤記錄中顯示錯(cuò)誤信息醋粟。當(dāng)異常拋出并且沒有捕獲靡菇,Zipkin就會(huì)自動(dòng)的換個(gè)顏色顯示。在跟蹤記錄的清單中米愿,當(dāng)看到紅色的記錄時(shí)厦凤,就表示有異常拋出了。如上面圖中的第一個(gè)根據(jù)數(shù)據(jù)就顯示了錯(cuò)誤信息育苟。我們還可以點(diǎn)擊進(jìn)去以獲取更詳細(xì)的錯(cuò)誤信息较鼓。

3.5 采樣率

在生成環(huán)境中,由于業(yè)務(wù)量比較大违柏,所產(chǎn)生的跟蹤數(shù)據(jù)可能會(huì)非常大博烂,如果全部采集一是對業(yè)務(wù)有一定影響,二是對存儲(chǔ)壓力也會(huì)比較大漱竖,所以采樣變的很重要脖母。一般來說,我們也不需要把每一個(gè)發(fā)生的動(dòng)作都進(jìn)行記錄闲孤。

Spring Cloud Sleuth有一個(gè)Sampler策略谆级,可以通過這個(gè)實(shí)現(xiàn)類來控制采樣算法。采樣器不會(huì)阻礙span相關(guān)id的產(chǎn)生讼积,但是會(huì)對導(dǎo)出以及附加事件標(biāo)簽的相關(guān)操作造成影響肥照。 Sleuth默認(rèn)采樣算法的實(shí)現(xiàn)是Reservoir sampling,具體的實(shí)現(xiàn)類是PercentageBasedSampler勤众,默認(rèn)的采樣比例為: 0.1(即10%)舆绎。不過我們可以通過spring.sleuth.sampler.percentage來設(shè)置,所設(shè)置的值介于0.0到1.0之間们颜,1.0則表示全部采集吕朵。

也可以通過實(shí)現(xiàn)bean的方式來設(shè)置采樣為全部采樣(AlwaysSampler)或者不采樣(NeverSampler):如

@Bean public Sampler defaultSampler() {
    return new AlwaysSampler();
}

這也是為何之前我們需要修改Mall-WebProduct-Service中的spring.sleuth.sampler.percentage配置猎醇,如果是默認(rèn)值很可能我們在Zipkin服務(wù)器上根本看不到。

你可以到這里下載本篇的代碼努溃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末硫嘶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子梧税,更是在濱河造成了極大的恐慌沦疾,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件第队,死亡現(xiàn)場離奇詭異哮塞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)凳谦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門忆畅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尸执,你說我怎么就攤上這事家凯。” “怎么了剔交?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵肆饶,是天一觀的道長改衩。 經(jīng)常有香客問我岖常,道長,這世上最難降的妖魔是什么葫督? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任竭鞍,我火速辦了婚禮,結(jié)果婚禮上橄镜,老公的妹妹穿的比我還像新娘偎快。我一直安慰自己,他們只是感情好洽胶,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布晒夹。 她就那樣靜靜地躺著,像睡著了一般姊氓。 火紅的嫁衣襯著肌膚如雪丐怯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天翔横,我揣著相機(jī)與錄音读跷,去河邊找鬼。 笑死禾唁,一個(gè)胖子當(dāng)著我的面吹牛效览,可吹牛的內(nèi)容都是我干的无切。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼丐枉,長吁一口氣:“原來是場噩夢啊……” “哼哆键!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起矛洞,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤洼哎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后沼本,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體噩峦,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年抽兆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了识补。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辫红,死狀恐怖凭涂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贴妻,我是刑警寧澤切油,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站名惩,受9級特大地震影響澎胡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜娩鹉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一攻谁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弯予,春花似錦戚宦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至呼寸,卻和暖如春艳汽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背等舔。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工骚灸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人慌植。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓甚牲,卻偏偏與公主長得像义郑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子丈钙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

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