一文給你道破Eureka服務(wù)發(fā)現(xiàn)慢的原因突琳,深度剖析Eureka客戶端服務(wù)發(fā)現(xiàn)原理以及 Eureka服務(wù)端服務(wù)剔除原理

我的博客他巨,原文出處http://www.deepinblog.com/eureka/175/

我的博客地址账阻,原文出處

先直接給出配置讓你嘗鮮

EurekaServer端配置

eureka:
  server:
    #Eureka Server會(huì)定時(shí)(間隔值是eureka.server.eviction-interval-timer-in-ms蒂秘,默認(rèn)60s)進(jìn)行檢查,如果發(fā)現(xiàn)實(shí)例在在一定時(shí)間
    #(此值由客戶端設(shè)置的eureka.instance.lease-expiration-duration-in-seconds定義,默認(rèn)值為90s)內(nèi)沒有收到心跳淘太,則會(huì)注銷此實(shí)例姻僧。
    #我們這里配置每秒鐘去檢測(cè)一次规丽,驅(qū)除失效的實(shí)例
    eviction-interval-timer-in-ms: 1000
    #關(guān)閉一級(jí)緩存,讓客戶端直接從二級(jí)緩存去讀取撇贺,省去各緩存之間的同步的時(shí)間
    use-read-only-response-cache: false

EurekaClient端(應(yīng)用端)配置

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    # EurekaClient每隔多久從EurekaServer拉取一次服務(wù)列表赌莺,默認(rèn)30秒,這里修改為2秒鐘從注冊(cè)中心拉取一次
    registry-fetch-interval-seconds: 2
  #租約期限以及續(xù)約期限的配置
  instance:
    #租約到期松嘶,服務(wù)失效時(shí)間雄嚣,默認(rèn)值90秒,服務(wù)超過90秒沒有發(fā)生心跳喘蟆,EurekaServer會(huì)將服務(wù)從列表移除
    #這里修改為6秒
    lease-expiration-duration-in-seconds: 6
    #租約續(xù)約間隔時(shí)間缓升,默認(rèn)30秒,這里修改為3秒鐘
    lease-renewal-interval-in-seconds: 3

#這里是Ribbon緩存實(shí)例列表的刷新間隔蕴轨,默認(rèn)30秒鐘港谊,這里修改為每秒鐘刷新一次實(shí)例信息
ribbon:
  ServerListRefreshInterval: 1000

下面給你剖析原理

Eureka服務(wù)端詳解

服務(wù)端緩存

如圖所示

file

服務(wù)注冊(cè)到注冊(cè)中心后,服務(wù)實(shí)例信息是存儲(chǔ)在注冊(cè)表中的橙弱,也就是內(nèi)存中歧寺。但Eureka為了提高響應(yīng)速度,在內(nèi)部做了優(yōu)化棘脐,加入了兩層的緩存結(jié)構(gòu)斜筐,將Client需要的實(shí)例信息,直接緩存起來蛀缝,獲取的時(shí)候 直接從緩存中拿數(shù)據(jù)然后響應(yīng)給 Client顷链。

第一層緩存是readOnlyCacheMap,readOnlyCacheMap是采用ConcurrentHashMap來存儲(chǔ)數(shù)據(jù)的屈梁,主要負(fù)責(zé)定時(shí)與readWriteCacheMap進(jìn)行數(shù)據(jù)同步嗤练,默認(rèn)同 步時(shí)間為 30 秒一次。

第二層緩存是readWriteCacheMap在讶,readWriteCacheMap采用Guava來實(shí)現(xiàn)緩存煞抬。緩存過期時(shí)間默認(rèn)為180秒,當(dāng)服務(wù)下線构哺、過期革答、注冊(cè)、狀態(tài)變更等操作都會(huì)清除此緩存中的數(shù)據(jù)曙强。

第三層是數(shù)據(jù)存儲(chǔ)層残拐。

Client獲取服務(wù)實(shí)例數(shù)據(jù)時(shí),會(huì)先從一級(jí)緩存中獲取旗扑,如果一級(jí)緩存中不存在蹦骑,再從二級(jí)緩存中獲取,如果二級(jí)緩存也不存在臀防,會(huì)觸發(fā)緩存的加載眠菇,從存儲(chǔ)層拉取數(shù)據(jù)到緩存中边败,然后再返回給 Client。

Eureka 之所以設(shè)計(jì)二級(jí)緩存機(jī)制捎废,也是為了提高 Eureka Server 的響應(yīng)速度笑窜,缺點(diǎn)是緩存會(huì)導(dǎo)致 Client 獲取不到最新的服務(wù)實(shí)例信息,然后導(dǎo)致無法快速發(fā)現(xiàn)新的服務(wù)和已下線的服務(wù)登疗。

了解了服務(wù)端的實(shí)現(xiàn)后排截,想要解決這個(gè)問題就變得很簡(jiǎn)單了,我們可以縮短只讀緩存的更新時(shí)間 (eureka.server.response-cache-update-interval-ms)讓服務(wù)發(fā)現(xiàn)變得更加及時(shí)辐益,或者直接將只讀緩 存關(guān)閉(eureka.server.use-read-only-response-cache=false)断傲,多級(jí)緩存也導(dǎo)致Client層面(數(shù)據(jù)一致性)很薄弱。

客戶端緩存

客戶端緩存主要分為兩塊內(nèi)容智政,一塊是 Eureka Client 緩存认罩,一塊是 Ribbon 緩存。

Eureka Client緩存 续捂,EurekaClient負(fù)責(zé)跟EurekaServer進(jìn)行交互垦垂,在EurekaClient中的 com.netflix.discovery.DiscoveryClient.initScheduledTasks() 方法中,初始化了一個(gè) CacheRefreshThread 定時(shí)任務(wù)專?用來拉取 Eureka Server 的實(shí)例信息到本地牙瓢。所以我們需要縮短這個(gè)定時(shí)拉取服務(wù)信息的時(shí)間間隔(此值在客戶端配置eureka.client.registryFetchIntervalSeconds) 來快速發(fā)現(xiàn)新的服務(wù)

Ribbon緩存劫拗,Ribbon會(huì)從EurekaClient中獲取服務(wù)信息,ServerListUpdater是Ribbon中負(fù)責(zé)服務(wù)實(shí)例 更新的組件矾克,默認(rèn)的實(shí)現(xiàn)是PollingServerListUpdater页慷,通過線程定時(shí)去更新實(shí)例信息。定時(shí)刷新的時(shí) 間間隔默認(rèn)是30秒聂渊,當(dāng)服務(wù)停止或者上線后差购,這邊最快也需要30秒才能將實(shí)例信息更新成最新的。我們可以將這個(gè)時(shí)間調(diào)短一點(diǎn)汉嗽,比如 3 秒。

刷新間隔的參數(shù)是通過 getRefreshIntervalMs 方法來獲取的找蜜,方法中的邏輯也是從 Ribbon的配置中進(jìn)行取值的饼暑。所以我們需要縮短這個(gè)更新間隔(此值在客戶端配置ribbon.ServerListRefreshInterval)來快速的更新Ribbon緩存實(shí)例列表

將這些服務(wù)端緩存和客戶端緩存的時(shí)間全部縮短后,跟默認(rèn)的配置時(shí)間相比洗做,快了很多弓叛。我們通過調(diào)整 參數(shù)的方式來盡量加快服務(wù)發(fā)現(xiàn)的速度,但是還是不能完全解決報(bào)錯(cuò)的問題诚纸,間隔時(shí)間設(shè)置為3秒撰筷,也還是會(huì)有間隔。所以我們一般都會(huì)開啟重試功能畦徘,當(dāng)路由的服務(wù)出現(xiàn)問題時(shí)毕籽,可以重試到另一個(gè)服務(wù)來 保證這次請(qǐng)求的成功抬闯。

服務(wù)端緩存部分源碼如下:

/**
 *The class that is responsible for caching registry information that will be
 *queried by the clients.
 */
public class ResponseCacheImpl implements ResponseCache {
    //一級(jí)緩存
    private final ConcurrentMap<Key, Value> readOnlyCacheMap = new ConcurrentHashMap<Key, Value>();
    //二級(jí)緩存(Guava實(shí)現(xiàn))
    private final LoadingCache<Key, Value> readWriteCacheMap;
    //數(shù)據(jù)存儲(chǔ)層
    private final AbstractInstanceRegistry registry;
}

客戶端緩存更新部分源碼如下:

Eureka Client緩存刷新部分源碼

    //Eureka Client緩存刷新部分源碼
    /**
     * Initializes all scheduled tasks.
     * 在實(shí)例化com.netflix.discovery.DiscoveryClient被調(diào)用
     */
    private void initScheduledTasks() {
        if (clientConfig.shouldFetchRegistry()) {
            // registry cache refresh timer
            int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
            int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "cacheRefresh",
                            scheduler,
                            cacheRefreshExecutor,
                                //從哪個(gè)Eureka客戶端拉取實(shí)例列表的間隔時(shí)間
                                        //通過eureka.client.registryFetchIntervalSeconds可配置
                            registryFetchIntervalSeconds,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                                //執(zhí)行刷新的Runable定時(shí)任務(wù)
                            new CacheRefreshThread()
                    ),
                        //從哪個(gè)Eureka客戶端拉取實(shí)例列表的間隔時(shí)間
                        //通過eureka.client.registryFetchIntervalSeconds可配置
                    registryFetchIntervalSeconds, TimeUnit.SECONDS);
        }

        if (clientConfig.shouldRegisterWithEureka()) {
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

            // Heartbeat timer
            scheduler.schedule(
                    new TimedSupervisorTask(
                            "heartbeat",
                            scheduler,
                            heartbeatExecutor,
                            renewalIntervalInSecs,
                            TimeUnit.SECONDS,
                            expBackOffBound,
                            new HeartbeatThread()
                    ),
                    renewalIntervalInSecs, TimeUnit.SECONDS);

            // InstanceInfo replicator
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize

            statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
                @Override
                public String getId() {
                    return "statusChangeListener";
                }

                @Override
                public void notify(StatusChangeEvent statusChangeEvent) {
                    if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
                            InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
                        // log at warn level if DOWN was involved
                        logger.warn("Saw local status change event {}", statusChangeEvent);
                    } else {
                        logger.info("Saw local status change event {}", statusChangeEvent);
                    }
                    instanceInfoReplicator.onDemandUpdate();
                }
            };

            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }

            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }
    }   

Ribbon緩存刷新部分源碼

   //PollingServerListUpdater,Ribbon緩存刷新部分源碼
   @Override
    public synchronized void start(final UpdateAction updateAction) {
        if (isActive.compareAndSet(false, true)) {
            final Runnable wrapperRunnable = new Runnable() {
                @Override
                public void run() {
                    if (!isActive.get()) {
                        if (scheduledFuture != null) {
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
                        updateAction.doUpdate();
                        lastUpdated = System.currentTimeMillis();
                    } catch (Exception e) {
                        logger.warn("Failed one update cycle", e);
                    }
                }
            };

            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,
                    initialDelayMs,
                    refreshIntervalMs,//默認(rèn)30秒,通過ribbon.ServerListRefreshInterval來配置更小的值來快速更新Ribbon實(shí)例緩存
                    TimeUnit.MILLISECONDS
            );
        } else {
            logger.info("Already active, no-op");
        }
    }

服務(wù)下線

  1. 當(dāng)服務(wù)正常關(guān)閉操作時(shí)关筒,會(huì)發(fā)送服務(wù)下線的REST請(qǐng)求給EurekaServer溶握。
  2. 服務(wù)中心接受到請(qǐng)求后,將該服務(wù)置為下線狀態(tài)

失效剔除

Eureka Server 中會(huì)有定時(shí)任務(wù)去檢測(cè)失效的服務(wù)蒸播,將服務(wù)實(shí)例信息從注冊(cè)表中移除睡榆,也可以將這個(gè)失效檢測(cè)的時(shí)間縮短,這樣服務(wù)下線后就能夠及時(shí)從注冊(cè)表中清除袍榆。

  1. Eureka Server會(huì)定時(shí)(間隔值是eureka.server.eviction-interval-timer-in-ms胀屿,默認(rèn)60s)進(jìn)行檢查,
    如果發(fā)現(xiàn)實(shí)例在在一定時(shí)間
    (此值由客戶端設(shè)置的eureka.instance.lease-expiration-duration-in-seconds定義包雀,默認(rèn)值為90s)內(nèi)沒有收到心跳碉纳,
    則會(huì)注銷此實(shí)例。

Eureka客戶端

服務(wù)提供者(也是Eureka客戶端)

  • 服務(wù)提供者(也是Eureka客戶端)要向EurekaServer注冊(cè)服務(wù)馏艾,并完成服務(wù)續(xù)約等工作
  • 服務(wù)注冊(cè)詳解(服務(wù)提供者)
    1. 當(dāng)我們導(dǎo)入了eureka-client依賴坐標(biāo)劳曹,配置Eureka服務(wù)注冊(cè)中心地址
    2. 服務(wù)在啟動(dòng)時(shí)會(huì)向注冊(cè)中心發(fā)起注冊(cè)請(qǐng)求,攜帶服務(wù)元數(shù)據(jù)信息
    3. Eureka注冊(cè)中心會(huì)把服務(wù)的信息保存在Map中琅摩。
  • 服務(wù)續(xù)約詳解(服務(wù)提供者)
    1. 服務(wù)每隔30秒會(huì)向注冊(cè)中心續(xù)約(心跳)一次(也稱為報(bào)活)铁孵,如果沒有續(xù)約,租約在90秒后到期房资,然后服務(wù)會(huì)被失效蜕劝。
      每隔30秒的續(xù)約操作我們稱之為心跳檢測(cè)往往不需要我們調(diào)整這兩個(gè)配置
#向Eureka服務(wù)中心集群注冊(cè)服務(wù) 
eureka:
  #租約期限以及續(xù)約期限的配置
  instance:
    #租約到期,服務(wù)失效時(shí)間轰异,默認(rèn)值90秒岖沛,服務(wù)超過90秒沒有發(fā)生心跳,EurekaServer會(huì)將服務(wù)從列表移除
    #這里修改為6秒
    lease-expiration-duration-in-seconds: 6
    #租約續(xù)約間隔時(shí)間搭独,默認(rèn)30秒婴削,這里修改為3秒鐘
    lease-renewal-interval-in-seconds: 3

服務(wù)消費(fèi)者(也是Eureka客戶端)

  • 服務(wù)消費(fèi)者每隔30秒服務(wù)會(huì)從注冊(cè)中心中拉取一份服務(wù)列表,這個(gè)時(shí)間可以通過配置修改牙肝。往往不需要我們調(diào)整
    1. 服務(wù)消費(fèi)者啟動(dòng)時(shí)唉俗,從 EurekaServer服務(wù)列表獲取只讀備份,緩存到本地;
    2. 默認(rèn)每隔30秒配椭,會(huì)重新獲取并更新數(shù)據(jù)虫溜;
    3. 每隔30秒的時(shí)間可以通過配置eureka.client.registry-fetch-interval-seconds修改,如下股缸。
#向Eureka服務(wù)中心集群注冊(cè)服務(wù) 
eureka:
    client:
      # EurekaClient每隔多久從EurekaServer拉取一次服務(wù)列表衡楞,默認(rèn)30秒,這里修改為2秒鐘從注冊(cè)中心拉取一次
        registry-fetch-interval-seconds: 2

客戶端緩存見Eureka服務(wù)端詳情章節(jié)

至此您應(yīng)該明白Eureka的服務(wù)發(fā)現(xiàn)機(jī)制了吧

最后再說說Eureka自我保護(hù)機(jī)制

服務(wù)提供者 —> 注冊(cè)中心

  • 定期的續(xù)約(服務(wù)提供者和注冊(cè)中心通信)敦姻,假如服務(wù)提供者和注冊(cè)中心之間的網(wǎng)絡(luò)有點(diǎn)問題瘾境,
    不代表 服務(wù)提供者不可用歧杏,不代表服務(wù)消費(fèi)者無法訪問服務(wù)提供者,所以有自我保護(hù)的機(jī)制
  • 如果在15分鐘內(nèi)超過85%的客戶端節(jié)點(diǎn)都沒有正常的心跳寄雀,那么Eureka就認(rèn)為客戶端與注冊(cè)中心出現(xiàn)了網(wǎng)絡(luò)故障得滤,
    Eureka Server自動(dòng)進(jìn)入自我保護(hù)機(jī)制。
  • 為什么會(huì)有自我保護(hù)機(jī)制?
    • 默認(rèn)情況下盒犹,如果Eureka Server在一定時(shí)間內(nèi)(默認(rèn)90秒)沒有接收到某個(gè)微服務(wù)實(shí)例的心跳懂更, Eureka Server將會(huì)移除該實(shí)例。
      但是當(dāng)網(wǎng)絡(luò)分區(qū)故障發(fā)生時(shí)急膀,微服務(wù)與Eureka Server之間無法正常通信沮协,而微服務(wù)本身是正常運(yùn)行的,此時(shí)不應(yīng)該移除這個(gè)微服務(wù)卓嫂,
      所以引入了自我保護(hù)機(jī)制慷暂。

服務(wù)中心?面會(huì)顯示如下提示信息

file

當(dāng)處于自我保護(hù)模式時(shí)

  1. 不會(huì)剔除任何服務(wù)實(shí)例(可能是服務(wù)提供者和EurekaServer之間網(wǎng)絡(luò)問題),保證了大多數(shù)服務(wù)依 然可用
  2. Eureka Server仍然能夠接受新服務(wù)的注冊(cè)和查詢請(qǐng)求晨雳,但是不會(huì)被同步到其它節(jié)點(diǎn)上行瑞,保證當(dāng)前節(jié)點(diǎn)依然可用,
    當(dāng)網(wǎng)絡(luò)穩(wěn)定時(shí)餐禁,當(dāng)前Eureka Server新的注冊(cè)信息會(huì)被同步到其它節(jié)點(diǎn)中血久。
  3. 在Eureka Server工程中通過eureka.server.enable-self-preservation配置可用關(guān)停自我保護(hù),默認(rèn)值是打開
eureka:
  server:
    enable-self-preservation: false # 關(guān)閉自我保護(hù)模式(缺省為打開)

如果有大批量的集群且存在網(wǎng)絡(luò)分區(qū)帮非,強(qiáng)烈建議開啟自我保護(hù)機(jī)制

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末氧吐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子末盔,更是在濱河造成了極大的恐慌筑舅,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陨舱,死亡現(xiàn)場(chǎng)離奇詭異翠拣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)隅忿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門心剥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人背桐,你說我怎么就攤上這事〔踝幔” “怎么了链峭?”我有些...
    開封第一講書人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)又沾。 經(jīng)常有香客問我弊仪,道長(zhǎng)熙卡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任励饵,我火速辦了婚禮驳癌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘役听。我一直安慰自己颓鲜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開白布典予。 她就那樣靜靜地躺著甜滨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瘤袖。 梳的紋絲不亂的頭發(fā)上衣摩,一...
    開封第一講書人閱讀 52,807評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音捂敌,去河邊找鬼艾扮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛占婉,可吹牛的內(nèi)容都是我干的泡嘴。 我是一名探鬼主播,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼锐涯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼磕诊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纹腌,我...
    開封第一講書人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤霎终,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后升薯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體莱褒,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年涎劈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了广凸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蛛枚,死狀恐怖谅海,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹦浦,我是刑警寧澤扭吁,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響侥袜,放射性物質(zhì)發(fā)生泄漏蝌诡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一枫吧、第九天 我趴在偏房一處隱蔽的房頂上張望浦旱。 院中可真熱鬧,春花似錦九杂、人聲如沸颁湖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爷狈。三九已至,卻和暖如春裳擎,著一層夾襖步出監(jiān)牢的瞬間涎永,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工鹿响, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留羡微,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓惶我,卻偏偏與公主長(zhǎng)得像妈倔,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绸贡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361