<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Dubbo中的連接控制,你真的理解嗎?

          共 13441字,需瀏覽 27分鐘

           ·

          2021-09-22 21:47

          作者 | kiritomoe

          來(lái)源 | https://mp.weixin.qq.com/s/6K9NKWkXEOKLleZV-8yLwA

          前言

          剛發(fā)現(xiàn)微信公眾號(hào)有了標(biāo)簽功能,于是乎,我將我 Dubbo 相關(guān)的文章都打上了標(biāo)簽,仔細(xì)一統(tǒng)計(jì),這已經(jīng)是我第 41 篇原創(chuàng)的 Dubbo 文章了,如果你希望看到我其他的 Dubbo 文章,可以從話題標(biāo)簽點(diǎn)擊進(jìn)入。

          這是一篇很久之前就想動(dòng)筆寫(xiě)的文章,最近正好看到群里有小伙伴分享了 Dubbo 連接相關(guān)的文章,才又讓我想起了這個(gè)話題。今天想跟大家聊的便是 Dubbo 中的連接控制這一話題。說(shuō)到“連接控制”,可能有讀者還沒(méi)反應(yīng)過(guò)來(lái),但你對(duì)下面的配置可能不會(huì)感到陌生:

          <dubbo:reference interface="com.foo.BarService" connections="10" />

          如果你還不了解 Dubbo 中連接控制的用法,可以參考官方文檔:https://dubbo.apache.org/zh/docs/advanced/config-connections/ ,話說(shuō)最近 Dubbo 官方文檔來(lái)了一次大換血,好多熟悉的文檔差點(diǎn)都沒(méi)找到在哪兒 Orz。

          眾所周知,dubbo 協(xié)議通信默認(rèn)是長(zhǎng)連接,連接配置功能用于決定消費(fèi)者與提供者建立的長(zhǎng)連接數(shù)。但官方文檔只給出了該功能的使用方法,卻并沒(méi)有說(shuō)明什么時(shí)候應(yīng)該配置連接控制,本文將主要圍繞該話題進(jìn)行探討。

          本文也會(huì)涉及長(zhǎng)連接相關(guān)的一些知識(shí)點(diǎn)。

          使用方式:

          先來(lái)看一個(gè) Dubbo 構(gòu)建的簡(jiǎn)單 demo,啟動(dòng)一個(gè)消費(fèi)者(192.168.4.226)和一個(gè)提供者(192.168.4.224),配置他們的直連。

          消費(fèi)者:

          <dubbo:reference id="userService" check="false"
              interface="org.apache.dubbo.benchmark.service.UserService"
              url="dubbo://192.168.4.224:20880"/>

          提供者:

          <dubbo:service interface="org.apache.dubbo.benchmark.service.UserService" ref="userService" />
          <bean id="userService" class="org.apache.dubbo.benchmark.service.UserServiceServerImpl"/>

          長(zhǎng)連接是看不見(jiàn)摸不著的東西,我們需要一個(gè)觀測(cè)性工作來(lái)”看到“它。啟動(dòng)提供者和消費(fèi)者之后,可以使用如下的命令查看 tcp 連接情況

          • Mac 下可使用:lsof -i:20880
          • Linux 下可使用:netstat -ano | grep 20880

          提供者:

          [root ~]# netstat -ano | grep 20880
          tcp6       0      0 192.168.4.224:20880      :::*                    LISTEN      off (0.00/0/0)
          tcp6    2502      0 192.168.4.224:20880      192.168.4.226:59100     ESTABLISHED off (0.00/0/0)

          消費(fèi)者:

          [root@ ~]# netstat -ano | grep 20880
          tcp6     320    720 192.168.4.226:59110     192.168.4.224:20880      ESTABLISHED on (0.00/0/0)

          通過(guò)上述觀察到的現(xiàn)象我們可以發(fā)現(xiàn)幾個(gè)事實(shí)。

          僅僅是啟動(dòng)了提供者和消費(fèi)者,上述的 TCP 連接就已經(jīng)存在了,要知道我并沒(méi)有觸發(fā)調(diào)用。也就是說(shuō),Dubbo 建連的默認(rèn)策略是在地址發(fā)現(xiàn)時(shí),而不是在調(diào)用時(shí)。當(dāng)然,你也可以通過(guò)延遲加載 lazy="true" 來(lái)修改這一行為,這樣可以將建聯(lián)延遲到調(diào)用時(shí)。

          <dubbo:reference id="userService" check="false"
              interface="org.apache.dubbo.benchmark.service.UserService"
              url="dubbo://${server.host}:${server.port}"
              lazy="true"/>

          除此之外,還可以發(fā)現(xiàn)消費(fèi)者和提供者之間只有一條長(zhǎng)連接,20880 是 Dubbo 提供者默認(rèn)開(kāi)放的端口,就跟 tomcat 默認(rèn)開(kāi)放的 8080 一個(gè)地位,而 59110 是消費(fèi)者隨機(jī)生成的一個(gè)端口。(我之前跟一些朋友交流過(guò),發(fā)現(xiàn)很多人不知道消費(fèi)者也是需要占用一個(gè)端口的)

          而今天的主角”連接控制“便可以控制長(zhǎng)連接的數(shù)量,例如我們可以進(jìn)行如下的配置

          <dubbo:reference id="userService" check="false"
              interface="org.apache.dubbo.benchmark.service.UserService"
              url="dubbo://192.168.4.224:20880"
              connections="2" />

          再啟動(dòng)一次消費(fèi)者,觀察長(zhǎng)連接情況

          提供者:

          [root@ ~]# netstat -ano | grep 20880
          tcp6       0      0 192.168.4.224:20880      :::*                    LISTEN      off (0.00/0/0)
          tcp6    2508     96 192.168.4.224:20880      192.168.4.226:59436     ESTABLISHED on (0.00/0/0)
          tcp6    5016    256 192.168.4.224:20880      192.168.4.226:59434     ESTABLISHED on (0.00/0/0)

          消費(fèi)者:

          [root@ ~]# netstat -ano | grep 20880
          tcp6       0   2520 192.168.4.226:59436     192.168.4.224:20880      ESTABLISHED on (0.00/0/0)
          tcp6      48   1680 192.168.4.226:59434     192.168.4.224:20880      ESTABLISHED on (0.00/0/0)

          可以看到,這里已經(jīng)變成兩條長(zhǎng)連接了。

          什么時(shí)候需要配置多條長(zhǎng)連接

          現(xiàn)在我們知道了如何進(jìn)行連接控制,但什么時(shí)候我們應(yīng)該配置多少條長(zhǎng)連接呢?這個(gè)時(shí)候我可以跟你說(shuō),具體視生產(chǎn)情況而定,但你如果你經(jīng)常看我的公眾號(hào),肯定會(huì)知道這不是我的風(fēng)格,我的風(fēng)格是什么?benchmark!

          寫(xiě)作之前,我跟幾個(gè)同事和網(wǎng)友對(duì)這個(gè)話題進(jìn)行了簡(jiǎn)單的討論,其實(shí)也沒(méi)有什么定論,無(wú)非是對(duì)單連接和多連接吞吐量高低不同的論調(diào)。參考既往 Dubbo github 中的 issue,例如:https://github.com/apache/dubbo/pull/2457,我也參與了這個(gè) pr 的討論,講道理,我是持懷疑態(tài)度的,我當(dāng)時(shí)的觀點(diǎn)是多連接不一定能夠提升服務(wù)的吞吐量(還是挺保守的,沒(méi)有這么絕對(duì))。

          那接下來(lái),還是用 benchmark 來(lái)說(shuō)話吧,測(cè)試工程還是我們的老朋友,使用 Dubbo 官方提供的 dubbo-benchmark 工程。

          • 測(cè)試工程地址:https://github.com/apache/dubbo-benchmark.git
          • 測(cè)試環(huán)境:2 臺(tái)阿里云 Linux 4c8g ECS

          測(cè)試工程在之前的文章介紹過(guò),這里就不過(guò)多贅述了,測(cè)試方案也非常簡(jiǎn)單,兩輪 benchmark,分別測(cè)試 connections=1 和 connections=2 時(shí),觀察測(cè)試方法的吞吐量。

          說(shuō)干就干,省略一堆測(cè)試步驟,直接給出測(cè)試結(jié)果。

          connections=1

          Benchmark           Mode  Cnt      Score      Error  Units
          Client.createUser  thrpt    3  22265.286 ± 3060.319  ops/s
          Client.existUser   thrpt    3  33129.331 ± 1488.404  ops/s
          Client.getUser     thrpt    3  19916.133 ± 1745.249  ops/s
          Client.listUser    thrpt    3   3523.905 ±  590.250  ops/s

          connections=2

          Benchmark           Mode  Cnt      Score      Error  Units
          Client.createUser  thrpt    3  31111.698 ± 3039.052  ops/s
          Client.existUser   thrpt    3  42449.230 ± 2964.239  ops/s
          Client.getUser     thrpt    3  30647.173 ± 2551.448  ops/s
          Client.listUser    thrpt    3   6581.876 ±  469.831  ops/s

          從測(cè)試結(jié)果來(lái)看,似乎單連接和多連接的差距是非常大的,近乎可以看做是 2 倍了!看起來(lái)連接控制的效果真是好呀,那么事實(shí)真的如此嗎?

          按照這種方案第一次測(cè)試下來(lái)之后,我也不太相信這個(gè)結(jié)果,因?yàn)槲抑鞍凑掌渌绞阶鲞^(guò)多連接的測(cè)試,并且我也參加過(guò)第三屆中間件挑戰(zhàn)賽,使得我對(duì)長(zhǎng)連接的認(rèn)知是:大多數(shù)時(shí)候,單連接往往能發(fā)揮出最優(yōu)的性能。即使由于硬件原因,這個(gè)差距也不應(yīng)該是兩倍。懷著這樣的疑問(wèn),我開(kāi)始研究,是不是我的測(cè)試場(chǎng)景出了什么問(wèn)題呢?

          發(fā)現(xiàn)測(cè)試方案的問(wèn)題

          經(jīng)過(guò)和閃電俠的討論,他的一席話最終讓我定位到了問(wèn)題的所在。

          不知道大家看完我和閃電俠的對(duì)話,有沒(méi)有立刻定位到問(wèn)題所在。

          之前測(cè)試方案最大的問(wèn)題便是沒(méi)有控制好變量,殊不知:在連接數(shù)變化的同時(shí),實(shí)際使用的 IO 線程數(shù)實(shí)際也發(fā)生了變化

          Dubbo 使用 Netty 來(lái)實(shí)現(xiàn)長(zhǎng)連接通信,提到長(zhǎng)連接和 IO 線程的關(guān)系,這里就要介紹到 Netty 的連接模型了。一言以蔽之,Netty 的設(shè)置 IO worker 線程和 channel 是一對(duì)多的綁定關(guān)系,即一個(gè) channel 在建連之后,便會(huì)完全由一個(gè) IO 線程來(lái)負(fù)責(zé)全部的 IO 操作。再來(lái)看看 Dubbo 是如何設(shè)置 NettyClient 和 NettyServer 的 worker 線程組的:

          客戶端 org.apache.dubbo.remoting.transport.netty4.NettyClient

              private static final EventLoopGroup NIO_EVENT_LOOP_GROUP = eventLoopGroup(Constants.DEFAULT_IO_THREADS, "NettyClientWorker");
              
              @Override
              protected void doOpen() throws Throwable {
                  final NettyClientHandler nettyClientHandler = new NettyClientHandler(getUrl(), this);
                  bootstrap = new Bootstrap();
                  bootstrap.group(NIO_EVENT_LOOP_GROUP)
                          .option(ChannelOption.SO_KEEPALIVE, true)
                          .option(ChannelOption.TCP_NODELAY, true)
                          .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
                ...
              }

          Constants.DEFAULT_IO_THREADS 在 org.apache.dubbo.remoting.Constants 中被寫(xiě)死了

          int DEFAULT_IO_THREADS = Math.min(Runtime.getRuntime().availableProcessors() + 1, 32);

          在我的 4c8g 的機(jī)器上,默認(rèn)等于 5。

          服務(wù)端 org.apache.dubbo.remoting.transport.netty4.NettyServer

          protected void doOpen() throws Throwable {
                  bootstrap = new ServerBootstrap();

                  bossGroup = NettyEventLoopFactory.eventLoopGroup(1, "NettyServerBoss");
                  workerGroup = NettyEventLoopFactory.eventLoopGroup(
                          getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
                          "NettyServerWorker");

                  final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
                  channels = nettyServerHandler.getChannels();

                  ServerBootstrap serverBootstrap = bootstrap.group(bossGroup, workerGroup)
                      .channel(NettyEventLoopFactory.serverSocketChannelClass());
                  .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                          .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                          .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
                          
           }

          服務(wù)端倒是可以配置,例如我們可以通過(guò) protocol 來(lái)控制服務(wù)端的 IO 線程數(shù):

          <dubbo:protocol name="dubbo" host="${server.host}" server="netty4" port="${server.port}" iothreads="5"/>

          如果不設(shè)置,則跟客戶端邏輯一致,是 core + 1 個(gè)線程。

          好了,問(wèn)題就在這兒,由于我并沒(méi)有進(jìn)行任何 IO 線程的設(shè)置,所以客戶端和服務(wù)端都會(huì)默認(rèn)開(kāi)啟 5 個(gè) IO 線程。當(dāng) connections=1 時(shí),Netty 會(huì)將 channel1 綁定到一個(gè) IO 線程上,而當(dāng) connections=2 時(shí),Netty 會(huì)將 channel1 和 channel2 按照順序綁定到 NettyWorkerThread-1和 NettyWorkerThread-2 上,這樣就會(huì)有兩個(gè) IO 線程在工作,這樣的測(cè)試結(jié)果當(dāng)然是不公平的。

          這里需要考慮實(shí)際情況,在實(shí)際生產(chǎn)中,大多數(shù)時(shí)候都是分布式場(chǎng)景,連接數(shù)一定都是大于 IO 線程數(shù)的,所以基本不會(huì)出現(xiàn)測(cè)試場(chǎng)景中的 channel 數(shù)少于 IO 線程數(shù)的場(chǎng)景。

          解決方案也很簡(jiǎn)單,我們需要控制變量,讓 IO 線程數(shù)一致,僅僅觀察連接數(shù)對(duì)吞吐量的影響。針對(duì)服務(wù)端,可以在 protocol 層配置 iothreads=1;針對(duì)客戶端,由于源碼被寫(xiě)死了,這里我只能通過(guò)修改源碼的方式,重新本地打了一個(gè)包,使得客戶端 IO 線程數(shù)也可以通過(guò) -D 參數(shù)指定。

          改造之后的,我們得到了如下的測(cè)試結(jié)果:

          1 IO 線程 1 連接

          Benchmark           Mode  Cnt      Score      Error  Units
          Client.createUser  thrpt    3  22265.286 ± 3060.319  ops/s
          Client.existUser   thrpt    3  33129.331 ± 1488.404  ops/s
          Client.getUser     thrpt    3  19916.133 ± 1745.249  ops/s
          Client.listUser    thrpt    3   3523.905 ±  590.250  ops/s

          1 IO 線程 2 連接

          Benchmark           Mode  Cnt      Score      Error  Units
          Client.createUser  thrpt    3  21776.436 ± 1888.845  ops/s
          Client.existUser   thrpt    3  31826.320 ± 1350.434  ops/s
          Client.getUser     thrpt    3  19354.470 ±  369.486  ops/s
          Client.listUser    thrpt    3   3506.714 ±   18.924  ops/s

          可以發(fā)現(xiàn),單純提升連接數(shù)并不會(huì)提升服務(wù)的吞吐量,這樣的測(cè)試結(jié)果也更加符合我認(rèn)知的預(yù)期。

          總結(jié)

          從上述測(cè)試的結(jié)果來(lái)看,一些配置參數(shù)并不是越大就代表了越好,類似的例子我也在多線程寫(xiě)文件等場(chǎng)景分析過(guò),唯有理論分析+實(shí)際測(cè)試才能得出值得信服的結(jié)論。當(dāng)然個(gè)人的測(cè)試,也可能會(huì)因?yàn)榫植啃躁P(guān)鍵信息的遺漏,導(dǎo)致誤差,例如,如果我最終沒(méi)有發(fā)現(xiàn) IO 線程數(shù)和連接數(shù)之間的隱性關(guān)聯(lián),很容易就得出連接數(shù)和吞吐量成正比的錯(cuò)誤結(jié)論了。當(dāng)然,也不一定就代表本文最終的結(jié)論是靠譜的,說(shuō)不定還是不夠完善的,也歡迎大家留言,提出意見(jiàn)和建議。

          最終回到最初的問(wèn)題,我們什么時(shí)候應(yīng)該配置 Dubbo 的連接控制呢?按照我個(gè)人的經(jīng)驗(yàn),大多數(shù)時(shí)候,生產(chǎn)環(huán)境下連接數(shù)是非常多的,你可以挑選一臺(tái)線上的主機(jī),通過(guò) netstat -ano| grep 20880| wc -l 來(lái)大概統(tǒng)計(jì)下,一般是遠(yuǎn)超 IO 線程數(shù)的,沒(méi)必要再多配置成倍的連接數(shù),連接數(shù)和吞吐量并不是一個(gè)線性增長(zhǎng)的關(guān)系。

          Dubbo 框架有這個(gè)能力和大家真的需要用這個(gè)能力完全是兩碼事,我相信大多數(shù)讀者應(yīng)該已經(jīng)過(guò)了技術(shù)新鮮感驅(qū)動(dòng)項(xiàng)目的階段了吧?如果有一天你需要控制連接數(shù),去達(dá)到一定特殊的用途,你就會(huì)真心感嘆,Dubbo 真是強(qiáng)呀,這個(gè)擴(kuò)展點(diǎn)都有。

          Dubbo 的連接控制真的完全沒(méi)有用嗎?也不盡然,我的測(cè)試場(chǎng)景還是非常有限的,可能在不同硬件上會(huì)跑出不一樣的效果,例如我在第三屆中間件性能挑戰(zhàn)賽中,就是用 2 連接跑出了最好的成績(jī),并非單連接。

          最后,你如果僅僅使用 Dubbo 去維系你們的微服務(wù)架構(gòu),大部分情況不需要關(guān)注到連接控制這個(gè)特性,多花點(diǎn)時(shí)間搬磚吧,就醬,我也去搬磚了。

          往期推薦

          笑出腹肌的注釋,都是被代碼耽誤的詩(shī)人!

          快速搭建Java 17環(huán)境并玩轉(zhuǎn)Record特性

          一個(gè)Bug能有多大影響:虧損30億、致6人死亡、甚至差點(diǎn)毀滅世界...

          六成大學(xué)生認(rèn)為自己畢業(yè)10年內(nèi)會(huì)年入百萬(wàn)!網(wǎng)友:知乎上多了,沒(méi)被社會(huì)毒打過(guò)吧!

          緩存核心知識(shí)小抄,面試必備,趕緊收藏!



          技術(shù)交流群

          最近有很多人問(wèn),有沒(méi)有讀者交流群,想知道怎么加入。加入方式很簡(jiǎn)單,有興趣的同學(xué),只需要點(diǎn)擊下方卡片,回復(fù)“加群,即可免費(fèi)加入我們的高質(zhì)量技術(shù)交流群!

          點(diǎn)擊閱讀原文,送你免費(fèi)Spring Boot教程!

          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  波多野结衣中文字幕一区二区 | 四虎成人电影 | 婷婷国产成人精品一区二区 | 伊人久操视频 | 红桃视频一区二区三区四区五区在线视频 |