当前位置: 萬仟网 > IT编程>软件设计>面向对象 > 小林求职记(六)踩过Dubbo坑,回答印象深,干货整理

小林求职记(六)踩过Dubbo坑,回答印象深,干货整理

2020年08月01日  | 萬仟网IT编程  | 我要评论
小林求职记系列文章,归置到公众号菜单栏,欢迎查看历史篇前传小林求职记(五)上来就一连串的分布式缓存提问,我有点上头....终于,在小林的努力下,获得了王哥公司那边的offer,但是因...

小林求职记系列文章,归置到公众号菜单栏,欢迎查看历史篇

  前传

小林求职记(五)上来就一连串的分布式缓存提问,我有点上头....

终于,在小林的努力下,获得了王哥公司那边的offer,但是因为薪水没有谈妥,小林又重新进入了求职的旅途,在经历了多次求职过程之后,小林也大概地对求职的考点掌握地七七八八了,于是这次他重新书写了简历,投递了一家新的互联网企业。

距离面试开始还有大约十分钟,小林已经抵达了面试现场,并开始调整自己的状态。

过了不久,一个稍显消瘦,戴着黑色眼镜框的男人走了过来,估计这家伙就是小林这次的面试官了。

面试官:你好,请简单先做个自我介绍吧。

小林:嗯嗯,面试官你好,我是XXXX(此处省略200个字)

面试官:我看到你的项目里面有提及到dubbo,rpc技术这一技术栈正好和我们这边的匹配,我先问你些关于dubbo和rpc的技术问题吧。首先你能讲解下什么是rpc吗?

小林:好的,rpc技术其实简单地来理解就是不同计算机之间进行远程通信实现数据交互的一种技术手段吧。一个合理的rpc应该要分为server, client, server stub,client stub四个模块部分,

面试官:嗯嗯,你说的server stub,client stub该怎么理解呢?

小林:这个可以通过名字来识别进行理解,client stub就是将服务的请求的参数,请求方法,请求地址通过打包封装给成一个对象统一发送给server端。server stub就是服务端接收到这些参数之后进行拆解得到最终数据的结果。

图片

在以前的单机版架构里面,两个方法进行相互调用的时候都是先通过内存地址查询到对应的方法,然后调用执行,但是分布式环境下不同的进程是可能存在于不同的机器中的,因此在通过原先的寻址方式调用函数就不可行了,这个时候就需要结合网络io的手段来进行服务的”交流“。

面试官:了解,你对rpc本质还是有自己的理解。可以大致讲解下dubbo在工程中启动的时候的一些整体流程吗?

小林:嗯嗯(猛地想起了之前写的一些笔记内容)

在工程进行启动的时候(假设使用spring容器进行bean的托管),首先会将bean注册到spring容器中,然后再将对应的服务注册到zk中,实现对外暴露服务。

面试官:可以说说在源码里面的核心设计吗?假设说某个dubbo服务没有对外暴露成功,你会如何去做分析呢?

小林:嗯嗯。其实可以先通过阅读启动日志进行分析,dubbo的启动顺序并不是直接就进行zk的连接,而是先校验配置文件是否正确,然后是否已经将bean都成功注册到了Spring的ioc容器中,接下来才是连接zk并且将服务进行注册的环节。

图片

如果确保服务的配置无误,那么问题可能就是出在连接zk的过程了。

面试官:嗯嗯,有一定的逻辑依据,挺好的。你有了解过服务暴露的细节点吗?例如说dubbo是如何将自己的服务提供者信息写入到注册中心(zookeeper)的呢?

小林:我在阅读dubbo对外进行服务暴露的源代码时印象中对ServiceConfig这个类比较熟系。在实现对外做服务暴露的时候,这里面的有个加了锁的export函数,内部会先对dubbo的配置进行校验,首先判断是否需要对外暴露,然后是是否需要延迟暴露,如果需要延迟暴露则会通过ScheduledExecutorService去做延迟暴露的操作,否则立即暴露,即执行doExport方法

图片

在往源码里面分析,会看到一个叫做doExportUrls的函数,这里面写明了关于注册的细节点:

private void doExportUrls() {
    List<URL> registryURLs = loadRegistries(true);
    for (ProtocolConfig protocolConfig : protocols) {
        String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
        ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
        ApplicationModel.initProviderModel(pathKey, providerModel);
        //暴露对外的服务内容 核心
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

实现注册中心的服务暴露核心点:

doExportUrlsFor1Protocol内部的代码
Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
//这里有一个使用了委派模型的invoker
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
//服务暴露的核心点
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);

最终在org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doRegister函数里面会有一步熟系的操作,将dubbo的服务转换为url写入到zk中做持久化处理:

并且这里写入的数据节点还是非持久化的节点

图片

面试官: 看来你对服务注册的这些原理还是有过一定深入的理解啊。你以前的工作中是有遇到过源码分析的情况吗?对这块还蛮清晰的。

小林:嗯嗯,之前在工作中有遇到过服务启动异常,一直报错,但是又没人肯帮我,所以这块只好硬着头皮去学习。后来发现了解了原理以后,对于dubbo启动报错异常的分析还是蛮有思路的。

面试官:嘿嘿,挺好的,那你对于使用dubbo的时候又遇到过dubbo线程池溢出的情况吗?

小林:嗯嗯,以前工作中在做这块的时候有遇到过。

面试官: 嘿嘿,跟我讲讲你自己对于dubbo内部的线程池这块的分析吧。

小林:嗯嗯,可以的。

接下来小林进行了一番压测场景的讲解:

其实dubbo的服务提供者端一共包含了两类线程池,一类叫做io线程池,还有一类叫做业务线程池,它们各自有着自己的分工,如下图所示:

图片

dubbo在服务提供方中有io线程池和业务线程池之分。可以通过调整相关的dispatcher参数来控制将请求处理交给不同的线程池处理。(下边列举工作中常用的几个参数:)

all:将请求全部交给业务线程池处理(这里面除了日常的消费者进行服务调用之外,还有关于服务的心跳校验,连接事件,断开服务,响应数据写回等)

execution:会将请求处理进行分离,心跳检测,连接等非业务核心模块交给io线程池处理,核心的业务调用接口则交由业务线程池处理。

假设说我们的dubbo接口只是一些简单的逻辑处理,例如说下方这类:

@Service(interfaceName = "msgService")
public class MsgServiceImpl implements MsgService {
    @Override
    public Boolean sendMsg(int id, String msg)  {
            System.out.println("msg is :"+msg);
            return true;
    }
}

并没有过多的繁琐请求,并且我们手动设置线程池参数:

dubbo.protocol.threadpool=fixed
dubbo.protocol.threads=10
dubbo.protocol.accepts=5

当线程池满了的时候,服务会立马进入失败状态,此时如果需要给provider设置等待队列的话可以尝试使用queues参数进行设置。

dubbo.protocol.queues=100

但是这个设置项虽然看似能够增大服务提供者的承载能力,却并不是特别建议开启,因为当我们的provider承载能力达到原先预期的限度时,通过请求堆积的方式继续请求指定的服务器并不是一个合理的方案,合理的做法应该是直接抛出线程池溢出异常,然后请求其他的服务提供者。

测试环境:Mac笔记本,jvm:-xmx 256m -xms 256m

接着通过使用jmeter进行压力测试,发现一秒钟调用100次(大于实际的业务线程数目下,线程池并没有发生溢出情况)。这是因为此时dubbo接口中的处理逻辑非常简单,这么点并发量并不会造成过大影响。(几乎所有请求都能正常抗住)

图片
图片

但是假设说我们的dubbo服务内部做了一定的业务处理,耗时较久,例如下方:

@Service(interfaceName = "msgService")
public class MsgServiceImpl implements MsgService {
    @Override
    public Boolean sendMsg(int id, String msg) throws InterruptedException {
            System.out.println("msg is :"+msg);
            Thread.sleep(500);
            return true;
    }
}

此时再做压测,解果就会不一样了。

此时大部分的请求都会因为业务线程池中的数目有限出现堵塞,因此导致大量的rpc调用出现异常。可以在console窗口看到调用出现大量异常:

图片

将jmeter的压测报告进行导出之后,可以看到调用成功率大大降低,

图片

也仅仅只有10%左右的请求能够被成功处理,这样的服务假设进行了线程池参数优化之后又会如何呢?

1秒钟100个请求并发访问dubbo服务,此时业务线程池专心只处理服务调用的请求,并且最大线程数为100,服务端最大可接纳连接数也是100,按理来说应该所有请求都能正常处理

dubbo.protocol.threadpool=fixed
dubbo.protocol.dispatcher=execution
dubbo.protocol.threads=100
dubbo.protocol.accepts=100

还是之前的压测参数,这回所有的请求都能正常返回。

图片

ps:提出一个小问题,从测试报告中查看到平均接口的响应耗时为:502ms,也就是说其实dubbo接口的承载能力估计还能扩大个一倍左右,我又尝试加大了压测的力度,这次看看1秒钟190次请求会如何?(假设线程池100连接中,每个连接对请求的处理耗时大约为500ms,那么一秒时长大约能处理2个请求,但是考虑到一些额外的耗时可能达不到理想状态那么高,因此设置为每秒190次(190 <= 2*100)请求的压测)

但是此时发现请求的响应结果似乎并没有这么理想,这次请求响应的成功率大大降低了。

jmeter参数:

图片

请求结果:

图片

面试官:哦,看来你对线程池这块的参数还是有一定的研究哈。

面试官:你刚刚提到了请求其他服务提供者,那么你对于dubbo的远程调用过程以及负载均衡策略这块可以讲讲吗?最好能够将dubbo的整个调用链路都讲解一遍?

小林思考了一整子,在脑海中整理了一遍dubbo的调用链路,然后开始了自己的分析:

小林:这整个的调用链路其实是非常复杂的,但是我尝试将其和你阐述清楚。

衔接我上边的服务启动流程,当dubbo将服务暴露成功之后,会在zk里面记录相关的url信息

图片

此时我们切换视角回归到consumer端来分析。假设此时consumer进行了启动,启动的过程中,会触发一个叫做get的函数操作,这个操作位于ReferenceConfig中。

图片

首先是检查配置校验,然后再是进行初始化操作。在init操作中通过断点分析可以看到一个叫做createProxy的函数,在这里面会触发创建dubbo的代理对象。可以通过idea工具分析,此时会传递一个包含了各种服务调用方的参数进入该函数中。

图片

在createProxy这个方法的名字上边可以分析出,这时候主要是创建了一个代理对象。并且还优先会判断是否走jvm本地调用,如果不是的话,则会创建远程调用的代理对象,并且是通过jdk的代理技术进行实现的。

最终会在org.apache.dubbo.registry.support.ProviderConsumerRegTable#registerConsumer里面看到consumer调用服务时候的一份map关系映射。这里面根据远程调用的方法名称来识别对应provider的invoker对象

图片

最后当需要从consumer对provider端进行远程调用的时候,会触发一个叫做:DubboInvoker的对象,在这个对象内部有个叫做doInvoke的操作,这里面会将数据的格式进行封装,最终通过netty进行通信传输给provider。并且服务数据的写回主要也是依靠netty来处理。

ps:

dubbo的整体架构图

图片

面试官:嗯嗯,你大概讲解了一遍服务的调用过程,虽然源码部分讲解地挺细(面试官也听得有点点晕~~),但是我还是想问你一些关于更加深入的问题。你对于netty这块有过相关的了解吗?

小林:好像在底层中是通过netty进行通信的。这块的通信机制原理之前有简单了解过一些。

面试官:能讲解下netty里面的粘包和拆包有所了解吗?

小林:哈哈,可以啊。其实粘包和拆包是一件我们在研发工作中经常可能遇到的一个问题。一般只有在TCP网络上通信时才会出现粘包与拆包的情况。

正常的一次网络通信:

客户端和服务端进行网络通信的时候,client给server发送了两个数据包,分别是msg1,msg2,理想状态下可能数据的发送情况如下:

图片

但是在网络传输中,tcp每次发送都会有一个叫做Nagle的算法,当发送的数据包小于mss(最大分段报文体积)的时候,该算法会尽可能将所有类似的数据包归为同一个分组进行数据的发送。避免大量的小数据包发送,因为发送端通常都是收到前一个报文确认之后才会进行下个数据包的发送,因此有可能在网络传输数据过程中会出现粘包的情况,例如下图:

两个数据包合成一个数据包一并发送

图片

某个数据包的数据丢失了一部分,缺失部分和其他数据包一并发送

图片

为了防止这种情况发生,通常我们会在服务端制定一定的规则来防范,确保每次接收的数据包都是完整的数据信息。

netty里面对于数据的粘包拆包处理机制主要是通过ByteToMessageDecoder这款编码器来进行实现的。常见的手段有:定长协议处理,特殊分隔符,自定义协议方式。

面试官:哦,看来你对这块还是有些了解的哈。行吧,那先这样吧,后边是二面,你先在这等一下吧。

小林长舒一口气,瞬间感觉整个人都轻松多了。

(未完待续...)

琐碎时间想看一些技术文章,可以去公众号菜单栏翻一翻我分类好的内容,应该对部分童鞋有帮助。同时看的过程中发现问题欢迎留言指出,不胜感谢~。另外,有想多了解哪些方面内容的可以留言(什么时候,哪篇文章下留言都行),附菜单栏截图(PS:很多人不知道公众号菜单栏是什么)

END

我知道你 “在看”

本文地址:https://blog.csdn.net/weixin_36380516/article/details/108212034

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

  • 面向对象设计原则

    很久之前的读书整理内容,躺在草稿里很久了。。。 一、 "开放-封闭"原则(OCP) Open-Closed Principle原... [阅读全文]
  • Docker(二):微服务教程

    Docker(二):微服务教程

    Docker 是一个容器工具,提供虚拟环境。很多人认为,它改变了我们对软件的认识。 站在 Docker 的角度,软件就是容器的组合:业务逻辑容器、数... [阅读全文]
  • JS面向对象设计-创建对象

    Object构造函数和对象字面量都可以用来创建单个对象,但是在创建多个对象时,会产生大量重复代码. 1.工厂模式 工厂模式抽象了创建具体对象的过程... [阅读全文]
  • 关于软件设计与工匠精神

    关于软件设计与工匠精神

    最近与同仁讲一个简单的功能,我们过去的项目或系统可能开发过很多次了,当下需要反思软件设计怎么才能做得更好,这样我们自己才能成长。如果软件设计都做不好... [阅读全文]
  • Asp.net MVC 中的TempData对象的剖析

    Asp.net MVC 中的TempData对象的剖析

    另一篇文章,也对TempData 做了很详细的介绍,链接地址:https://www.jianshu.com/p/eb7a301bc536 。 MV... [阅读全文]
  • python语言线程标准库threading.local源码解读

    本段源码可以学习的地方: 1. 考虑到效率问题,可以通过上下文的机制,在属性被访问的时候临时构建; 2. 可以重写一些魔术方法,比如 __new_... [阅读全文]
  • django之models学习总结

    from django.db import models # Create your models here. class Classes(mode... [阅读全文]
  • 作业小结2

    作业小结2

    作业小结2 第五次作业 多线程电梯 设计策略 构造两个队列,PendingRequestList和ProcessedRequestList。 Pen... [阅读全文]
  • __getattribute__

    __getattribute__

    [TOC] \_\_getattr\_\_ 不存在的属性访问,触发\_\_getattr\_\_ 10 执行的是我 \_\_getattribute... [阅读全文]
  • angular 表单验证

    angular 表单验证

    最近在用angular写表单验证时 , 不小心把ng-model全替换删掉了, 然后发现之前写的验证都失效, 在查阅资料和反复修改摸索后, 发现an... [阅读全文]
验证码:
Copyright © 2017-2020  萬仟网 保留所有权利. 粤ICP备17035492号-1