2024年7月


计划工具

上次介绍了需求工具。根据项目管理中的内容,五大过程组(启动、计划、执行、监控、收尾),以及软件工程里的内容,需求之后就是计划,所以这次介绍的是计划工具。

对于项目管理中的计划,主要是用Project来进行对需求分解,然后形成WBS内容,然后才对计划进行监控管理,这里Project的使用见以前写的博文:
https://www.cnblogs.com/lzhdim/p/3537808.html

编程的话,对于个人来说,主要是一些配套的软件系统,能够进行分类,然后能够进行任务提醒,然后根据提醒进行工作时间的分配和管理以及实现。市面上有一些待办列表的软件,推荐读者们能够去进行使用,这次介绍的是笔者使用的一款软件:PNotes.Net。

这个应用主要是小巧简单便捷,能够进行基本的记录内容,最主要的是其强大的定时功能,能够对具体的时间进行自定义,在指定的时间里进行提醒,然后对工作进行处理。下面对这个应用软件做个截图介绍。

1、
需要进行计划的内容;

2、
时间提醒的设置;

右键菜单,选择调整计划;

选择定时项;

设置定时时间;

上面介绍了这个能够整理和做计划的便签小应用工具,希望能够对读者有帮助。

提供软件下载:
https://download.csdn.net/download/lzhdim/89530828

之前MoneyPrinterPlus在批量混剪,一键AI生成视频这些功能上的语音合成功能都用的是云厂商的语音服务,比阿里云,腾讯云和微软云。

云厂商虽然提供了优质的语音服务,但是用起来还是要收费。

为了各位小伙伴的钱包,现在特意给MoneyPrinterPlus上线了本地chatTTS语音服务。

赶紧来体验吧。

软件准备

当然,前提条件就是你需要下载MoneyPrinterPlus软件啦。

下载地址:
https://github.com/ddean2009/MoneyPrinterPlus

用得好的朋友,不妨给个star支持一下。 在软件v4.0版本之后,MoneyPrinterPlus已经全面开始支持本地模型。

安装chatTTS

我们可以直接从chatTTS的官网上
https://github.com/2noise/ChatTTS
下载chatTTS的源代码:

git clone https://github.com/2noise/ChatTTS
cd ChatTTS

安装依赖:

pip install --upgrade -r requirements.txt

运行web-UI:

python examples/web/webui.py

运行api-server:

fastapi dev examples/api/main.py --host 0.0.0.0 --port 8000

这里注意,web-ui的默认端口是8080, api-server的端口是8000。

MoneyPrinterPlus需要连接的是8000端口的api-server。

web-UI只是为了展示怎么配置音色的一个展示用的。

我们打开
http://localhost:8080/
可以看到类似下面的页面:

image-20240715173317089

点击生成按钮,如果能够成功合成音频文件,那么说明你的chatTTS安装成功了。

在MoneyPrinterPlus中使用ChatTTS

回到MoneyPrinterPlus,我们启动MoneyPrinterPlus,在基本配置页面我们可以看到:本地语音TTS的选项。

image-20240715173442224

这里我们选择chatTTS,并且设置chatTTS api server的地址。

上面我们是以8000启动的api server,所以这里我们就输入:
http://127.0.0.1:8000/。

接下来点击视频混剪区,在视频配音区选择本地模型:

image-20240715173642900

我们可以得到下面的界面:

image-20240715173714939

解释一下各个参数的作用:

是否口语化对应chatTTS的口语化开关,如果启动口语化,chatTTS会自动对输入的文案进行口语化调整。所以默认是不开启的。

Text Seed是控制口语化模型处理的种子,你可以随意调整。

Audio Temperature控制音频情感波动性,范围为 0-1,数字越大,波动性越大

top_P :控制音频的情感相关性,范围为 0.1-0.9,数字越大,相关性越高

top_K :控制音频的情感相似性,范围为 1-20,数字越小,相似性越高

Refine text Prompt是指在口语化过程中添加的一些参数。如果不懂的话可以不修改。

本地chatTTS音色目录,默认在项目的chattts目录中。

现在我在chattts目录中预先放置了2种音色文件。

一种是txt文件,一种是pt文件。

你可以自行添加更多的音色文件到chattts目录中。

那么有小伙伴要问了,txt或者pt文件是怎么来的呢?

先讲一个简单的pt文件,你可以在
https://modelscope.cn/studios/ttwwwaa/ChatTTS_Speaker
这个空间中试听和下载对应的语音文件:

image-20240715174400937

把下载下来的pt文件,放在MoneyPrinterPlus中的chattts目录即可。

txt文件是怎么来的呢?

我们再次回到chatTTS的webUI界面:

image-20240715174512706

这个txt就是这里的Speaker Embedding的内容。

你可以点击右上角的拷贝按钮,新建一个utf-8编码的txt文件。 把这个txt文件放到MoneyPrinterPlus中的chattts目录即可。

如果你不想用已有的音色文件,那么可以点击使用随机声音按钮:

image-20240715174635106

会出现一个Audio Seed选项,这个seed就是用来控制Audio音色的。

有了这些配置之后,点击试听声音,如果能听到声音就说明你的chatTTS在MoneyPrinterPlus中配置成功了。

接下来就可以使用本地的chatTTS来合成语音啦。

点我查看更多精彩内容:www.flydean.com

前言

在开发一个新项目时遇到了共享库冲突的问题,因此在这里记录一下共享库的链接和加载过程中库路径的搜索优先级的相关知识。

共享库的链接

现在有一个
main.o
可重定位目标文件,其中需要用到开源库log4cpp。在链接的时候,我们可以这样链接:

g++ main.o -o a.out -L/path/to/libs -llog4cpp

其中:
-L/path/to/libs
表示链接器去哪个目录下查找库文件。
注意:通过-L指定的路径的搜索优先级是要高于系统默认的库文件路径的。

编译时库搜索优先级:
-L指定的路径
>
LIBRARY_PATH
>
默认库路径

-llog4cpp
表示要链接的库,在链接阶段,链接器一般会按照搜索优先级,从库路径中查找名为
liblog4cpp.so

liblog4cpp.a
的文件进行链接。若不特殊说明,默认优先链接共享库。

共享库的加载

在程序开始运行前的加载阶段,动态链接器会读取可执行文件,查找并加载所有必须的共享库。注意此时的搜索路径并不包含
/path/to/libs
,因为它只作用于链接阶段。链接器搜索的路径有:
LD_LIBRARY_PATH
环境变量,
/etc/ld.so.cache
文件,默认库路径(
/usr/lib
,
/lib
等)。

如果需要从指定的路径加载共享库,则可以使用下述方法:

  • 手动设置
    LD_LIBRARY_PATH
    环境变量:在程序运行前设置环境变量,指定共享库的搜索路径:
    export LD_LIBRARY_PATH=/path/to/runtime_libs:$LD_LIBRARY_PATH
    

    注意,此方法设置环境变量是临时的,一旦终端关闭设置就会失效。若想使其持久化,需要特殊设置,方法包括但不限于:修改.bashrc文件,修改rc.loacl文件。
  • 修改
    /etc/ld.so.conf

    /etc/ld.so.conf.d/*
    配置:
    这些文件提供了系统级别的库搜索路径。系统管理员可以在这些文件中定义默认的库搜索路径,这些设置对所有用户和应用程序都有效。
  • 在链接阶段使用
    -rpath
    :
    在链接阶段,设置RPATH或RUNPATH,将库路径硬编码到可执行文件中
    g++ main.o -o a.out -L/path/to/libs -llog4cpp -Wl,-rpath,/path/to/runtime_libs
    

    注意若可执行文件中同时包含RPATH和RUNPATH,那么RPATH将会被动态链接器忽略。

共享库的搜索优先级为:
RPATH
>
LD_LIBRARY_PATH
>
RUNPATH
>
/etc/ld.so.conf

/etc/ld.so.conf.d/*
>
默认库路径

本文介绍基于
R
语言中的
UBL
包,读取
.csv
格式的
Excel
表格文件,实现
SMOTE
算法与
SMOGN
算法,对机器学习、深度学习回归中,
训练数据集不平衡
的情况加以解决的具体方法。

在之前的文章
SMOGN算法Python实现:解决回归分析中的数据不平衡
中,我们介绍了基于
Python
语言中的
smogn
包,实现
SMOGN
算法,对机器学习、深度学习回归中
训练数据集不平衡
的情况加以解决的具体方法;而我们也在上述这一篇文章中提到了,
SMOGN
算法的
Python
实现实在是太慢了,且
Python
还无法较为方便地实现回归数据的
SMOTE
算法。因此,我们就在本文中介绍一下基于
R
语言中的
UBL
包,实现
SMOTE
算法与
SMOGN
算法的方法。对于这两种算法的具体介绍与对比,大家参考上述提到的这一篇文章即可,这里就不再赘述了。

首先,我们配置一下所需用到的
R
语言
UBL
包。包的下载方法也非常简单,我们输入如下的代码即可。

install.packages("UBL")

输入代码后,按下
回车
键,运行代码;如下图所示。

image

接下来,我们即可开始代码的撰写。在这里,我们最好通过如下的方式新建一个
R
语言脚本(我这里是用的
RStudio
);因为后期执行算法的时候,我们往往需要对比多种不同的参数搭配效果,通过脚本来运行代码会比较方便。

其中,我们需要的代码如下所示。

library(UBL)
csv_path <- r"(E:\01_Reflectivity\99_Model_Training\00_Data\02_Extract_Data\26_Train_Model_New\Train_Model_0710.csv)"
result_path <- r"(E:\01_Reflectivity\99_Model_Training\00_Data\02_Extract_Data\26_Train_Model_New\Train_Model_0710_smote_nir.csv)"
data <- read.csv(csv_path)
data_nona <- na.omit(data)
data_nona$PointType <- as.factor(data_nona$PointType)
data_nona$days <- as.factor(data_nona$days)

data_smote <- SmoteRegress(inf_dif~., data_nona, dist = "HEOM", C.perc = "balance")
data_smogn <- SMOGNRegress(inf_dif~., data_nona, thr.rel = 0.6, dist = "HEOM", C.perc = "extreme")

hist(data_nona$inf_dif, breaks = 50)
hist(data_smote$inf_dif, breaks = 50)
hist(data_smogn$inf_dif, breaks = 50)

write.csv(data_smogn, file = result_path, row.names = FALSE)
write.csv(data_smote, file = result_path, row.names = FALSE)

其中,上述代码的具体含义如下。

首先,通过
library(UBL)
将我们刚刚配置好的
UBL
包加以加载,该包提供了处理不平衡数据的函数和算法;随后,我们可以设置输入的
.csv
格式文件的路径,这一文件中存储了我们需要加以处理的数据;随后,我们设置输出的
.csv
格式文件的路径,这一文件就是我们加以处理后的结果数据。

接下来,我们使用
read.csv
函数读取输入的
.csv
格式文件,并将其存储在变量
data
中。其后的
data_nona <- na.omit(data)
代码表示,去除数据中的缺失值,将处理后的数据保存在
data_nona
中。随后,这里需要注意,由于我们的输入数据中含有
数值型的类别变量
,因此需要将其转换为因子(
factor
)类型,这样才可以被
UBL
包识别为类别变量。

接下来,第一个函数
SmoteRegress()
就是使用
SMOTE
算法对
data_nona
进行回归任务的不平衡处理——其中
inf_dif
是目标变量(因变量),
~.
表示使用所有其他列作为特征(自变量),
dist = "HEOM"
表示使用
HEOM
(Heterogeneous Euclidean-Overlap Metric)距离度量(注意,只要我们的输入数据中有类别变量,那么就需要用这一种距离表示方式),最后的
C.perc = "balance"
表示平衡类别比例。

随后的
SMOGNRegress()
函数,则是使用
SMOGN
算法对
data_nona
进行回归任务的不平衡处理——其中
thr.rel = 0.6
表示设置相对阈值为
0.6
,这个参数设置的越大,算法执行的程度越深;其他参数则和前一个函数类似。这里如果大家需要对两个函数的参数加以更进一步的理解,可以直接访问其
官方网站

最后,为了比较一下我们执行
SMOTE
算法与
SMOGN
算法的结果,可以绘制一下
data_nona
中,目标变量
inf_dif
的直方图,
breaks = 50
表示将直方图分成
50
个条块。

如果通过直方图确定我们算法处理后的数据可以接受,那么就可以将处理结果数据写入到输出的
.csv
格式文件,
row.names = FALSE
表示不保存行索引。

执行上述代码后,我们可以实际看一下三个直方图的结果情况。首先,是处理前的数据,如下图所示。

其次,是
SMOTE
算法处理后的数据,如下图所示。

最后,是
SMOGN
算法处理后的数据,如下图所示。

基于以上图片可以很清楚地看出,
SMOTE
算法与
SMOGN
算法确实对于原始的数据分布而言,有着明显的改变作用。

至此,大功告成。

大家好,我是三友~~

今天继续探秘系列,扒一扒一次RPC请求在Dubbo中经历的核心流程。

本文是基于Dubbo3.x版本进行讲解


一个简单的Demo

这里还是老样子,为了保证文章的完整性和连贯性,方便那些没有使用过的小伙伴更加容易接受文章的内容,这里快速讲一讲Dubbo一个简单的Demo

如果你已经使用过了,可直接跳过本节,直接进入下一节

在Dubbo中RPC调用过程中主要分为以下两个角色:

  • 服务提供者:提供一个接口给消费者远程调用
  • 服务消费者:调用生产者提供的接口

于是一个简单的Dubbo示例工程就如下所示:

示例工程的创建步骤、使用配置、第三方的依赖等详细内容可参考官网:https://cn.dubbo.apache.org/zh-cn/overview/quickstart/java/spring-boot/

接口层,提供者消费者都需要依赖,服务提供者实现,服务消费者调用

服务提供者单独一个工程,实现DemoService接口,通过@DubboService表明提供DemoService这个服务

服务消费者单独一个工程,这里使用单元测试,通过@DubboReference注解表明消费DemoService这个服务接口

启动服务提供者,运行消费者单元测试,结果如下:

成功实现远程服务调用


服务提供者暴露

所谓的服务提供者暴露,主要就是指在项目启动时服务提供者去做的两件事

第一件事就是,由于需要对外提供调用服务,接受消费者的请求

所以在启动时需要根据使用协议,以及协议对应的端口启动一个对应的服务

就拿前面DemoService来举例,由于@DubboService注解没有指定任何信息

所以DemoService默认就是使用Dubbo框架自己写的通信协议,也就是Dubbo协议,这个协议默认使用的端口就是20880

之后如果要调用DemoService的方法时,就可以按照Dubbo协议要求组装数据格式

向20880端口发送请求,从而就实现远程服务调用,如下图所示:

当然除了默认的Dubbo协议之外,Dubbo还支持其它的通信协议,后面会详细介绍

虽然第一件事成功让接口可以对外提供访问,但是对于消费者来说,它其实还是无法访问接口

因为消费者并不知道接口使用的是哪个通信协议、端口,也不知道接口所在的服务器的ip

于是,在启动时就会去做第二件事

第二件事是将每个接口的详细信息,包括接口的全限定名、方法名称、方法参数、服务器的ip、端口、通信协议等等按照一定的格式组装好

存放到元数据中心和服务提供者本地缓存中

注意这是3.x版本时的存储情况,跟2.x有点不同。并且元数据中心其实就是使用的Nacos或者Zookeeper来实现的,所以你可以认为就是存储在Nacos或者Zookeeper中

之后消费者需要调用接口时,就可以从元数据中心或者服务提供者本地缓存中获取到接口的详细信息(具体从哪取决于配置,默认是从本地缓存中获取)

这里你肯定有疑问消费者是如何从服务提供者本地缓存获取,这就涉及到Dubbo3.x应用级服务注册的逻辑了,所以就不详细展开了,不过立个flag,如果本篇文章点赞达到38个,就再来一篇,单独讲一讲Dubbo3.x应用级服务注册的原理。

当需要发起调用时,就可以按照接口使用的协议组装数据,向接口所在的服务器ip和端口发送请求

所以总的来说,服务提供者暴露主要就是这两件事:

  • 根据接口使用协议和端口开启服务,对外提供接口访问
  • 将当前服务支持的接口,以及每个接口使用的协议、端口、服务器ip等信息存到元数据中心或者本地缓存,供消费者获取


消费者引用

前面提到,如果消费者想引用远程服务,可以通过@DubboReference注解触发引用的逻辑

消费者引用也会去做两件事

第一件事我们都知道,那就是创建接口的动态代理

由于消费者使用的DubboService是一个接口,所以会给DubboService创建一个动态代理

这个动态代理最终也会发送请求RPC请求

Dubbo支持两种动态搭理生成方式:

  • JDK动态搭理
  • Javassist动态生成字节码

默认使用的Javassist动态生成字节码的方式

除了创建动态搭理之外,还会去获取服务提供者的接口详细信息

上面一节说了,可以从元数据中心或者是服务提供者本地缓存中获取到

当获取到接口详情数据之后,会为之后的RPC调用做一些准备工作

比如如果接口使用的是Dubbo通信协议的话,准备工作就是消费者会跟服务提供者机器建立长连接

好了,到这里我们就把服务者暴露和消费者引用都讲完了

接下来就会进入本文的主题,一次RPC调用,也就是调用动态代理之后在Dubbo中会经历哪些环节


参数封装

熟悉JDK动态代理的同学肯定知道,当调用动态代理方法时,最终会走到InvocationHandler的实现

在Dubbo中,调用消费者动态代理的时候,不论是JDK动态代理还是使用Javassist方式生成动态代理

最终都会走到InvokerInvocationHandler这个InvocationHandler的实现

所以这个整个RPC调用的起点就是invoke方法的实现

如图所示,首先将RPC调用的接口、方法名、参数封装到RpcInvocation中

接着会走到下面这行代码

invoker.invoke(rpcInvocation)

而这看似简简单单一行代码就会触发RPC调用的整个核心流程


ClusterFilter过滤

当参数封装完成之后,接下来就会走到ClusterFilter过滤环节

ClusterFilter本质是一种责任链模式,是Dubbo提供的一个重要扩展点

通过实现invoke方法对请求进行自定义预处理操作

Dubbo默认提供了几种实现

比如就拿MonitorClusterFilter来说

这个实现主要是去统计每个接口的每个方法调用成功多少次,调用失败多少次等等调用的信息

除了默认实现之外,很多我们熟悉的一些框架也是通过这个扩展点跟Dubbo进行整合的

就比如常见的流控框架Sentinel


集群调用逻辑决策

当走完ClusterFilter之后,接下来就会来到集群调用逻辑决策的环节

这个集群调用逻辑决策是什么意思呢?

在实际生产环境中,一般服务都会以集群的方式来部署

这就会产生一个问题,面对多服务情况下,怎么去调用?

举个例子,按图上所示,有三个服务

那么集群调用逻辑就是去决定

应该每个服务都去调用一次,还是只去调用其中一个?

如果只调用其中一个,比如调用服务1,如果失败了,那么此时是直接抛异常还是选择继续去调用服务2,还是做其它的事?

所以集群调用逻辑就是解决多服务实例下,应该怎样合理地调用服务实例

Dubbo提供了以下几种集群调用逻辑:

  • 广播,也就是每个服务都调用(broadcast)
  • 调用前会去判断服务是不是可用,如果可用,那么就直接进行调用(available)
  • 调用失败,会开启定时任务进行重试调用,最大重试3次(failback)
  • 调用失败就直接抛出异常(failfast)
  • 调用失败直接调用其它服务进行重试,故障转移(failover)
  • 调用失败不会抛异常,而是直接返回(failsafe)
  • 同时调用指定个数的服务,直接最快返回结果当做这个调用的结果(forking)
  • 调用每个服务,合并服务返回的数据作为调用的结果,结果怎么合并需要我们自定义相关逻辑(mergeable)

在默认情况下使用的就是failover,也就是出现异常时会调用其它的服务再返回结果

当然我们也可以按照如下的方式指定调用策略


路由策略

上一节是解决集群中众多实例时应该如何调用的问题

而路由策略其实是选择允许调用哪些服务实例

因为并不是所有的服务实例都符合调用要求

什么意思呢?

举个例子,现在有个灰度发布的场景

假设所有的服务都处于同一套环境中,有一群机器运行者之前正式版本的服务,有一群机器运行着灰度版本的服务,如下图所示

那么对于处于灰度的消费者肯定要调用处于灰度的服务提供者

但是由于在同一套环境,那么处于灰度的消费者其实是能获取到处于之前正式环境的服务接口信息

如果就这么直接调用,那么处于灰度的消费者就可能调用非灰度的服务提供者

这肯定是不允许的

所以必须在调用前过滤掉非灰度发布的服务

而这种情况就可以交给路由来过滤

假设如果想做到灰度区分,可以使用Dubbo提供了一种叫tag的路由策略

灰度的服务提供者可以指定自己的tag属性为
gray
(灰色的意思),如下所示

而对于处于灰度的消费者,只需要指定消费tag为
gray
的服务提供者,如下所示

这样在真正调用前就会通过tag路由的方式过滤出处于灰度的服务提供者

所以集群调用逻辑所能使用的服务实例只能是经过路由策略选择出来

除了tag路由策略之外,Dubbo还提供了以下几种路由策略

  • 条件路由,可以指定某些条件下可以调用哪些服务实例
  • 脚本路由,可以写一段JavaScript脚本,更加灵活地选择哪些服务实例

顺带说一句,这个路由功能可以用来实现一个更高大上的功能,叫做
流量管控


负载均衡

所谓的负载均衡就是说,面对多个服务实例,我们应该按照何种算法选择一个供我们调用

Dubbo提供了以下几种负载均衡策略:

  • 随机(random),随机选择一个
  • 轮询(roundrobin),每次调用按照顺序选择一个
  • 最少活跃优先(leastactive),优先选择被最少调用的服务
  • 最短响应优先(shortestresponse),优先选择响应时间断的服务调用
  • 一致性Hash(consistenthash)

在没有指定的情况下,默认使用的就是随机(random)算法

如果想进行修改,可以按照如下方式:

这里你肯定有疑问

这个负载均衡和集群调用策略有什么关系?感觉这两者有点像,又感觉这两者有点冲突。

其实集群调用策略的优先级会大于负载均衡

比如说如果集群调用策略选择默认,也就是故障转移(failover)

那么对于路由策略过滤出来的服务实例,会根据负载均衡算法选择一个进行调用

但是如果集群调用策略选择的是广播调用(broadcast)

那么对于路由策略过滤出来的服务实例,实际上每个都需要去调用

所以此时压根不需要走负载均衡策略,因为没有意义,即使你配置了,也不会生效

所以需不需要负载均衡这件事,取决于使用什么集群调用策略

总的来说,集群调用策略、路由策略、负载均衡策略它们一步一步去决定本次RPC调用具体应该调用哪个或者哪些服务实例

三者关系入下图所示:


Filter过滤

经过上面的几步,终于知道本地RPC请求需要请求哪个或者哪些具体的服务实例

接下来只需要向对应的服务实例发送请求就可以了

不过在发送请求前,Dubbo还预留了一个扩展点,叫做Filter

本质也是一种责任链模式

通过Filter,我们可以在RPC调用前对整个请求再进行自定义扩展

这里你肯定又会有一个疑问

Filter和前面提到的ClusterFilter有什么区别?

的确它两真的很像,甚至都继承同一个接口BaseFilter,但是它两还有一些区别

第一点,两者作用时机不同

通过讲解顺序我们可以看出,ClusterFilter作用在路由和负载均衡前,而Filter在路由和负载均衡后

所以只要我们愿意,我们可以通过ClusterFilter去影响后面的路由和负载均衡,而Filter是做不到的

第二点就是Filter是跟服务实例走的

在调用每个服务实例之前,Filter一定会都会重新调用一遍

比如假设这次RPC最终需要选择调用两个服务实例,那么Filter会走两遍

但是对于ClusterFilter,在整个调用过程中它仅仅只会执行一次

所以官方也是建议,在无特殊情况下,优先选择使用ClusterFilter而不是Filter

到这,画一张图总结一下前面整个调用环节用


通信协议

当Filter责任链走完之后,接下来就到了向服务实例发送请求的时候了

一旦涉及到服务与服务之间的调用,那么就离不开通信协议

所谓的通信协议,讲的简单点就是发送方把需要发送的数据按照一定的格式组装好之后再发送给接收方

Dubbo需要发送数据包括调用但不限于接口全限定名、调用的方法名、调用参数等等

而接收方在获取到数据时再使用对应的格式去解析,从而获取到请求数据

前面提到,Dubbo默认使用的通信协议是Dubbo自己的写的,叫做Dubbo协议

除了Dubbo协议之外,Dubbo还支持以下几种通信协议:

  • Rest
  • gRPC
  • Triple
  • ...

Rest
,就是我们说的Http协议

当使用这种协议的时候,Dubbo在启动的时候会去创建一个Http的服务

默认使用的是Jetty,当然也支持切换成Tomcat

gRPC
,谷歌开源的高性能RPC框架

当然使用gRPC的时候,服务提供者会启动一个gRPC的服务端

这里你可能有疑问,Dubbo是RPC框架,gRPC也是RPC框架,为什么要集成gRPC

其实这是因为Dubbo和gRPC定位不同

Dubbo其实不仅仅是一个RPC框架,它其实是一套微服务解决方案,会承担更多的服务治理相关的逻辑

而gRPC的定位是通信协议与实现,是一款纯粹的RPC框架

Triple
协议就比较厉害了,它是Dubbo在3.x时发布的通信协议

Triple完全兼容gRPC协议,可同时运行在HTTP/1和HTTP/2传输协议之上,让你可以直接使用curl、浏览器访问后端Dubbo服务

如果要想使用上面的这些协议,代码可能需要进行一些改动,这里就不演示了


序列化协议

上一节提到,数据在发送的时候需要根据通信协议按照要求去组装数据

但是我们都知道,数据在网络中传输使用的是二进制

所以在实际开发中,要想发送数据,一般都是先将需要传输的数据转换成字节序列(数组),之后再交由操作系统转换成二进制进行传输

于是就有了一个问题,比如我们想传输一个对象的数据,那么我们应该按照什么样的格式将对象的数据转换成字节序列呢?

而这个
按照什么样的格式
就被称为序列化协议

整个转换过程就被称为序列化,也可以被称为编码

既然有序列化,那么就有反序列化

所谓反序列化就是根据序列化协议将字节序列转换成数据,也被称为解码

当通信协议使用Dubbo协议时,Dubbo支持以下几种序列化协议:

  • Java原生
  • Hessian2
  • Fastjson2
  • ...

Dubbo在3.2.0版本之前默认使用的Hessian2协议,3.2.0之后默认使用Fastjson2作为序列化协议

到这里其实就算讲完了消费者整个调用的过程了

因为当序列化完成之后,接下来就只需要将字节序列通过网络发送出去即可


服务提供者处理请求

当服务提供者监听到有请求时,会获取到请求的字节序列

然后根据通信协议,序列化协议反序列化出传输的数据

从而获取到消费者需要调用的、接口、方法以及入参等数据

之后就可以找到调用接口对应的实现,通过反射进行调用,获取结果

然后再将结果序列化成字节数组,返回给消费者

这样服务提供者就处理完成了一次请求

不过这里面有一个小细节,那就是在调用接口的实现之前,也会经过Filter过滤

所以Filter过滤其实在提供者和消费者两者都有

但是需要注意的是,两边的Filter不一定相同,具体取决于这个Filter是作用在消费者端还是提供者端,可通过如下方式配置


总结

到这终于讲完了一次RPC请求在Dubbo中经历整个核心流程

不知道你看完有什么感受

这里我再来画一张图总结整个调用过程

值得注意是,上面提到的所有调用环节,注意说的是所有,Dubbo都留了对应的扩展点

也就是说,小到一个Filter,大到整个通信协议你都可以进行自定义扩展

从这也可以看出,Dubbo在设计上的优秀之处。

好了,本文就讲到这里,如果觉得本文对你有所帮助,欢迎点赞、在看、收藏、转发分享给其他需要的人

你的支持就是我更新的最大动力,感谢感谢!

扫码或者搜索关注公众号
三友的java日记
,及时干货不错过,公众号致力于通过画图加上通俗易懂的语言讲解技术,让技术更加容易学习,回复 面试 即可获得一套面试真题。

让我们下期再见,拜拜!


往期热门文章推荐

如何去阅读源码,我总结了18条心法

如何写出漂亮代码,我总结了45个小技巧

三万字盘点Spring/Boot的那些常用扩展点

三万字盘点Spring 9大核心基础功能

两万字盘点那些被玩烂了的设计模式

万字+20张图探秘Nacos注册中心核心实现原理

万字+20张图剖析Spring启动时12个核心步骤

1.5万字+30张图盘点索引常见的11个知识点