1000道互联网java面试题(三)Dubbo面试题

唔,继上一篇贼鸡儿痛苦的zookeeper面试题的整理之后,迎来了下一个知识点。dubbo的面试题整理。其实我仔细回读的时候才发现上一篇zookeeper的大多数知识点都是zookeeper集群的问题。而我单机还在开发中,所以直接上来啃集群的知识确实是有点难。不过本篇的dubbo的面试题还好大多数属于知识范围内的。嘿嘿,顺便打个广告java技术交流群130031711招人,欢迎各位萌新大佬踊跃加入!群文件里好多学习资料,走过路过别错过!接下来是面试题的整理。

1. 为什么要用 Dubbo?

其实我觉得这个题更好的问法叫做dubbo是什么?简单来说dubbo是什么呢?
随着服务化的进一步发展,服务越来越多服务之间的调用和依赖关系也越来越复杂,诞生了面向服务的架构体系(SOA),也因此衍生出了一系列相应的技术,如对服务提供、服务调用、连接处理、通信协议、序列化方式、服务发现、服务路由、日志输出等行为进行封装的服务框架。就这样为分布式系统的服务治理框架就出现了,Dubbo 也就这样产生了。
Dubbo是一款高性能轻量级开源Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用智能容错和负载均衡,以及服务自动注册和发现。简单来说 Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
这个文字中我觉得的重点都有加粗。首先dubbo是个RPC框架,其优点是高性能,轻量级,开源。
至于RPC的概念:简单理解就是远程调用。比如A电脑想调用B电脑上的东西。用http当然可以,但是如果调用频繁则性能堪忧,反应比较慢。但是rpc则不然。其实我看了好多关于rpc的介绍,越看越有点像http的过程。但是其实好多是简化了的。
这里复习个知识:
http的交互:显示tcp分解报文,然后根据ip找到交互的另一台电脑(中间经过了多少跳板不说),然后三次握手传数据,四次挥手数据传完了。这边tcp再把数据解析成最开始的样子,然后展示出来。我这个说的有点笼统和不准确,但是我的简单理解是这样的。由此叙述就能看出来一次http的交互有多麻烦。
所以说频繁调用用http肯定是不好滴,于是出现了rpc(附上两中方式交互的图解)。

http交互流程

rpc交互流程

继续回来这个话题,说dubbo。dubbo是个rpc框架,这个框架的最大用处就是进行rpc调用。而rpc是不同电脑间的交互。由此说Dubbo是一个分布式的服务框架。
另外一个名词就是微服务。
何谓微服务?微就是小的意思。把一个大的服务拆分成一个小块。每一块单独起一个服务。继续拿58做例子。58同城有招聘模块,房产模块,圈子等。如果每一个模块是一个单独的服务部署在不一样的服务器上,起码做到房产模块有问题了但是招聘模块还可以正常使用。这样使得服务之间的耦合性比较低,出问题了影响也小。其实同时也减少了服务器的压力。
额,我发现这个话题不能展开了,越说越多。反正总体而言,不管出于什么原因的考虑,dubbo是一个分布式的服务框架。其核心功能也是远程调用,实现注册中心,并且有一定的容错和负载均衡等减压服务器的功能。剩下的会一点点在后面的问答中细化。

2. Dubbo 的整体架构设计有哪些分层?

接口服务层(Service):该层与业务逻辑相关,根据 provider 和consumer 的业务设计对应的接口和实现。
配置层(Config):对外配置接口,以 ServiceConfig 和 ReferenceConfig 为中心。
服务代理层(Proxy):服务接口透明代理,生成服务的客户端 Stub 和 服务端的 Skeleton,以 ServiceProxy 为中心,扩展接口为 ProxyFactory。
服务注册层(Registry):封装服务地址的注册和发现,以服务 URL 为中心,扩展接口为 RegistryFactory、Registry、RegistryService。
路由层(Cluster):封装多个提供者的路由和负载均衡,并桥接注册中心,以Invoker 为中心,扩展接口为 Cluster、Directory、Router 和 LoadBlancce。
监控层(Monitor):RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory、Monitor 和 MonitorService。
远程调用层(Protocal):封装 RPC 调用,以 Invocation 和 Result 为中心,扩展接口为 Protocal、Invoker 和 Exporter。
信息交换层(Exchange):封装请求响应模式,同步转异步。以 Request 和Response 为中心,扩展接口为 Exchanger、ExchangeChannel、ExchangeClient 和 ExchangeServer。
网络传输层(Transport):抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel、Transporter、Client、Server 和 Codec。
数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、ObjectInput、ObjectOutput 和 ThreadPool。

虽然使用过这个dubbo,并且现在还在做一个dubbox的项目,但是这些个所谓的分层有的我都是第一次听说。
怎么说呢,说一下自己理解的吧。我感觉上面所谓的层,好多都是一个简单的配置。这么说吧,大多数上面的功能我用过,但是说是说不出来的、接下来我一个个说。下面这个图片是比上面叙述更好理解的说明:


image.png

其中的registry,cluster,甚至monitor等好多都是配置文件中的东西。简单的说一下比如Cluster是路由层其实也可以说是容错层。Cluster 是外围概念,目的是将多个 Invoker 伪装成一个 Invoker。打个比方,某一个服务启动了两个实例。我们通过配置负载均衡策略进行合理路由,使得两个实例被合理的访问,减少了单个服务器的压力,同时如果一个宕机也可以保证程序的正常运行。加上 Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要 Cluster 的。
registry 注册中心模块,基于注册中心下发地址的集群方式,以及对各种注册中心的抽象。大多数时候我们也是用配置文件的形式注册服务。
monitor 是监控模块,统计服务调用次数,调用时间的,调用链跟踪的等服务的模块,不过说真的,我目前的项目没有用这块。
这些个理论知识呦,莫名想起了osi七层模型。最理论的情况,现实中不可能出现的。呵呵。

3. 默认使用的是什么通信框架,还有别的选择吗?

默认也推荐使用 netty 框架,可选mina。
其实这个通信框架我也是随着时间越来越有清晰的认识的。就是dubbo中各个服务和注册中心之间的连接是使用什么。下面是一个网上盗的图:


image.png

而dubbo默认使用的通信框架是netty。这个netty其实对于我来说不陌生。毕竟以前用过netty-socketio这个工具包,相当好用。
然后答案中说可选mina,对于这个陌生的名词肯定是不懂就百度。于是多少有这么点点印象了。下面是一个简单的截图文字对比:


netty和mina区别

总结一下:二者都是通信框架,其次mina比netty要老(相比于netty出来的时间)。netty的维护性更好,文档清晰。Netty比Mina使用起来更简单,可以自定义连接断开事件。但是Mina对UDP进行了高级层次的抽象,更像是”面向连接”的协议。
但是我其实只用过netty,emmm...上面说的那么多都是百度整理来的,对,就是这样。下一题了。

4. 服务调用是阻塞的吗?

默认是阻塞的,可以异步调用,没有返回值的可以这么做。
Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象。
其实我觉得这些题目都是一个扣一个的,因为dubbo默认是netty通信框架。而netty的显著特点就是异步。这个题目的答案其实可以看看上个题目中对netty的简介。

5. 一般使用什么注册中心?还有别的选择吗?

推荐使用 Zookeeper 作为注册中心,还有 Redis、Multicast、Simple 注册中心,但不推荐。
这个怎么说呢,百度上百分之八十的例子都是用的zookeeper,所以我也直接用了zookeeper,但是具体原因我以前也不是十分清楚,正好借此机会百度了好多知识帖子,在此说说自己的理解。
首先注册中心于dubbo相当于动物园与动物。注册中心是dubbo实例的一个平台和汇集。为什么推荐zookeeper呢?其最真实和主要是原因就是zookeeper相比于其他三个来讲,是功能最完善,最健全的!我们想看动物要去动物园,一般选择什么样的?动物最多的?和动物距离最近的?或者说离家最近的?一般来讲都不是的,应该选择最正规的,安全措施最好的。同样推荐zookeeper而不是其他三个的原理也是如此。 Redis、Multicast、Simple虽然都是可以作为注册中心,但是其各种各样的机制比如redis的单纯key-value存储来讲,都使得使用的时候不如zookeeper方便。

6. 默认使用什么序列化框架,你知道的还有哪些?

推荐使用 Hessian 序列化,还有 Duddo、FastJson、Java 自带序列化。
说到这个题先要简单的墨迹一下什么是序列化:将 Java 对象转换成字节流的过程。而数据的传输有些对象各种属性肯定是不能直接传输,都是分解成字节流再还原。比如java中我们接口传递一个对象,而前端是不识别这个对象的,一般都是用key-value对获取数据,这个就算是一个序列化的过程。(反序列化是指将字节流还原成java对象。前端获取的是json,注意这个不是反序列化!)
然后序列化的框架其实挺多的,在RPC应用中,进行跨进程远程调用的时候,需要使用特定的序列化技术,需要对进行网络传输的对象进行序列化和反序列化。影响序列化选择有两个因素
1. 序列化之后码流的大小,如果太大,那么将会影响网络传输的性能。
2. 序列化和反序列化过程的性能
我自己再去做测试啥的就没必要了,网上好多这种测试的demo。简单来讲java自带的序列化是指JDK的序列化,是性能什么的最差的一个。优点是不用额外引用什么依赖的,不然上面说到的FastJson,Hessian等都需要额外引用jar包。下面是一个常用序列化的性能比较测试,感兴趣的可以自己去看看。https://www.cnblogs.com/lgjlife/p/10731099.html
在Dubbo中支持多种序列化。但是

  • dubbo序列化,阿里尚不成熟的java序列化实现。
  • hessian2序列化:hessian是一种跨语言的高效二进制的序列化方式,但这里实际不是原生的hessian2序列化,而是阿里修改过的hessian lite,它是dubboRPC默认启用的序列化方式。
  • json序列化:目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自已实现的简单json库,一般情况下,json这种文本序列化性能不如二进制序列化。
  • java序列化:主要是采用JDK自带的java序列化实现,性能很不理想。
    基于上述原因,默认使用的就是这个hessian。

7. 服务提供者能实现失效踢出是什么原理?

服务失效踢出基于 zookeeper 的临时节点原理。在ZK中,很多数据和状态都是和会话绑定的,一旦会话失效,那么ZK就开始清除和这个会话有关的信息,包括这个会话创建的临时节点和注册的所有Watcher。(所以其实这个dubbo的一个实例挂了会将其提成是zookeeper的功能,而不是dubbo!)

8. 服务上线怎么不影响旧版本?

采用多版本开发,不影响旧版本。
其实这个我没做过,特意百度了一下是什么玩意。。结果发现其实就是旧版本仍然运行,挺无语的。差不多理解成同时启动两个新老项目。下面是个简单的介绍:https://blog.csdn.net/weixin_34270606/article/details/91440961,然后就酱吧。

9. 说说核心的配置有哪些?

  • dubbo:service 服务配置
  • dubbo:reference 引用配置
  • dubbo:protocol 协议配置
  • dubbo:application应用配置
  • dubbo:module 模块配置
  • dubbo:registry 注册中心配置
  • dubbo:monitor 监控中心配置
  • dubbo:provider 提供方配置
  • dubbo:consumer 消费方配置
  • dubbo:method 方法配置
  • dubbo:argument 参数配置

10. Dubbo 推荐用什么协议?

服务消费者和服务提供者可以不通过注册中心,而直接两者相互连接调用服务,这时使用的就是底层的协议,有八种。
以下是8种协议:

                ①dubbo://

                ②hessian://

                ③rmi://

                ④http://

                ⑤webservice://

                ⑥thrift://

                ⑦memcached://

                ⑧redis://

不过dubbo默认的肯定是自己的dubbo。可以使用dubbo直连而不是使用注册中心,如下示例:


image.png
  • Dubbo协议底层默认使用的是netty,性能非常优秀,官方推荐使用此协议。
  • Dubbo协议不适合传输大数据量的服务,如传输文件,传视频等,除非请求量很低。
  • <dubbo:protocol name="dubbo" port="20880" />,使用此标签来设置其他协议,默认端口为20880。
    协议设置的方式如下图:


    协议设置

11. 服务注册与发现的流程图

服务注册与发现的流程图

12. Dubbo 集群容错有几种方案?

(最上面我就简单说了容错有好几种方案,终于到了正文,具体介绍的时候了)

  • Failover Cluster 失败自动切换,自动重试其它服务器(默认)
  • Failfast Cluster 快速失败,立即报错,只发起一次调用
  • Failsafe Cluster 失败安全,出现异常时,直接忽略
  • Failback Cluster 失败自动恢复,记录失败请求,定时重发
  • Forking Cluster 并行调用多个服务器,只要一个成功即返回
  • Broadcast Cluster 广播逐个调用所有提供者,任意一个报错则报错

13. Dubbo 服务降级,失败重试怎么做?

可以通过 dubbo:reference 中设置 mock="return null"。mock 的值也可以修
改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口
名称+Mock” 后缀。然后在 Mock 类里实现自己的降级逻辑。

14. Dubbo Monitor 的实现原理?

Consumer 端在发起调用之前会先走 filter 链;provider 端在接收到请求时也是
先走 filter 链,然后才进行真正的业务逻辑处理。
默认情况下,在 consumer 和 provider 的 filter 链中都会有 Monitorfilter。

  1. MonitorFilter 向 DubboMonitor 发送数据
  2. DubboMonitor 将数据进行聚合后(默认聚合 1min 中的统计数据)暂存到ConcurrentMap<Statistics, AtomicReference> statisticsMap,然后使用一个含有 3 个线程(线程名字:DubboMonitorSendTimer)的线程池每隔 1min钟,调用 SimpleMonitorService 遍历发送 statisticsMap 中的统计数据,每发送完毕一个,就重置当前的 Statistics 的 AtomicReference
  3. SimpleMonitorService 将这些聚合数据塞入 BlockingQueue queue 中(队列大写为 100000)
  4. SimpleMonitorService 使用一个后台线程(线程名为:DubboMonitorAsyncWriteLogThread)将 queue 中的数据写入文件(该线程以死循环的形式来写)
  5. SimpleMonitorService 还会使用一个含有 1 个线程(线程名字:DubboMonitorTimer)的线程池每隔 5min 钟,将文件中的统计数据画成图表

这个说的很具体了,其实简单理解就是每一步调用都记录并定时整理成可视的方式让使用者可以看到。

15. Dubbo 用到哪些设计模式?

Dubbo 框架在初始化和通信过程中使用了多种设计模式,可灵活控制类加载、权限控制等功能。

  • 工厂模式
    Provider 在 export 服务时,会调用 ServiceConfig 的 export 方法。ServiceConfig中有个字段:
private static final Protocol protocol=ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

Dubbo 里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了JDK SPI 的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在 classpath下增加个文件就可以了,代码零侵入。另外,像上面的 Adaptive 实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。

  • 装饰器模式
    Dubbo 在启动和调用阶段都大量使用了装饰器模式。以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper 的 buildInvokerChain 完成的,具体是将注解中含有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是:
EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter ->
ExecuteLimitFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter ->
ExceptionFilter

更确切地说,这里是装饰器责任链模式的混合使用。例如,EchoFilter 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter 则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式。

  • 观察者模式
    Dubbo 的 Provider 启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个 listener。注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息,provider 接受到 notify 消息后,即运行 NotifyListener 的 notify 方法,执行监听器方法。
  • 动态代理模式
    Dubbo 扩展 JDK SPI 的类 ExtensionLoader 的 Adaptive 实现是典型的动态代理实现。Dubbo 需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader 的 createAdaptiveExtensionClassCode 方法。代理类的主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key。
    (ps:上面的模式拆开看每个我都很熟,合在一起大部分单词都不认识了,我觉得这块单纯的看文字有点问题 ,打算有空去撸一下dubbo的源码,这个题就酱了)

16. Dubbo 配置文件是如何加载到 Spring 中的?

Spring 容器在启动的时候,会读取到 Spring 默认的一些 schema 以及 Dubbo 自定义的 schema,每个 schema 都会对应一个自己的 NamespaceHandler,NamespaceHandler 里面通过 BeanDefinitionParser 来解析配置信息并转化为需要加载的 bean 对象!

17. Dubbo SPI 和 Java SPI 区别?

  • JDK SPI
    JDK 标准的 SPI 会一次性加载所有的扩展实现,如果有的扩展很耗时,但也没用上,很浪费资源。所以只希望加载某个的实现,就不现实了
  • DUBBO SPI
  1. 对 Dubbo 进行扩展,不需要改动 Dubbo 的源码
  2. 延迟加载,可以一次只加载自己想要加载的扩展实现。
  3. 增加了对扩展点 IOC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
  4. Dubbo 的扩展机制能很好的支持第三方 IoC 容器,默认支持 Spring Bean。

18. Dubbo 支持分布式事务吗?

目前暂时不支持,可与通过 tcc-transaction 框架实现
介绍:tcc-transaction 是开源的 TCC 补偿性分布式事务框架
Git 地址:https://github.com/changmingxie/tcc-transaction
TCC-Transaction 通过 Dubbo 隐式传参的功能,避免自己对业务代码的入侵。

19. Dubbo可以对结果进行缓存么?

为了提高数据访问的速度。Dubbo 提供了声明式缓存,以减少用户加缓存的工作量<dubbo:reference cache="true" />其实比普通的配置文件就多了一个标签 cache="true"

19. 服务上线怎么兼容旧版本?

可以用版本号(version)过渡,多个不同版本的服务注册到注册中心,版本号不同的服务相互间不引用。这个和服务分组的概念有一点类似。

20. Dubbo 必须依赖的包有哪些?

Dubbo 必须依赖 JDK,其他为可选。

21. Dubbo 和 Dubbox 之间的区别?

Dubbox 是继 Dubbo 停止维护后,当当网基于 Dubbo 做的一个扩展项目,如加了服务可 Restful 调用,更新了开源组件等。

22. Dubbo 和 Spring Cloud 的区别?

(首先因为cloud和dubbo我都用过,所以都多少了解了一点,也因此我看的这套资料应该是很老的整理了,放到现在有很大的不全面甚至误差。但是!!我觉得还是很有意思也很有道理的,这里直接贴对比图)


dubbo和cloud对比图

使用 Dubbo 构建的微服务架构就像组装电脑,各环节我们的选择自由度很高,但是最终结果很有可能因为一条内存质量不行就点不亮了,总是让人不怎么放心,但是如果你是一名高手,那这些都不是问题;而 Spring Cloud 就像品牌机,在Spring Source 的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础有足够的了解。(其实别的不说,起码cloud来讲,zuul换成getway了,eureka也是可换的,但是整体两个微服务框架的特点就是cloud是全家桶,dubbo是自助拼盘)

这篇dubbo的面试题整理就到这里了,如果稍微帮到你了记得点个喜欢点个关注,另外如果我哪里说得不精准或者说错了欢迎指出(其实在看资料的过程中发现资料中也有很多错误了),然后大部分是我能理解的,小部分是对我来说涉及到了知识盲区的,整理这么一篇文章各种查资料,看源码片段等。挺不容易的,给自己点个心心~哈哈,另外java技术交流群130031711,欢迎各位萌新大佬踊跃加入!

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