暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

微服务专栏(十):基本技能之Dubbo的原理讲解

修电脑的杂货店 2021-11-17
434

 宝剑锋从磨砺出,梅花香自苦寒来。——佚名


1、前言

如果没有 Dubbo,那么我们的项目如何去实现远程调用呢?很多同学应该都能想到,那就是通过 HttpClient 模拟 Http 发起请求即可,那么这种模式到底会存在什么样的问题呢?

第一种,服务迁移,导致原来发服务不可用,还得手工修改代码里面的 IP,打包重新部署

第二种情况,服务宕机,请求出错,对应订单服务无法感知商品服务是否宕机、并且没有容错机制

第三种情况,集群模式下,需要手工实现负载均衡算法。

通过上面三种情况,我们发现服务之间的调用,需要解决以下技术问题

  • 远程调用:服务之间的通讯,可以基于 Http+json、WebService、TCP 等方式进行通讯

  • 负载均衡:如果商品服务器集群部署的时候,订单服务可以根据一定的算法选择其中一台进行调用

  • 服务容错:如果商品服务挂掉时,订单服务不需要每次都去调用它,如果每次都调用会导致每次都请求超时

  • 动态感知:对于订单服务来说,它应该能实时的感知商品服务是否宕机,否则每次都会发起请求

  • 地址管理:商品服务如果新增集群节点,那么对应订单服务来说,它是无法知道的,每次都得重新修改代码,重新打包部署

因此,我们需要一款 RPC 框架,Dubbo 就是 RPC 框架里面比较经典的一款,它经过阿里内部项目的检验,活跃的社区,国内很多大型互联网公司的大型项目都使用它。因此,目前行业也有很多人讨论到底使用 Dubbo 好还是 SpringCloud 好?需要明确的是 Dubbo 和 SpringCloud 不是一个对等的东西,Dubbo 是一个具体的 RPC 技术中间件,SpringCloud 是一个全家桶,内置好多解决方案。选择的话,根据项目的实际情况来选择,没有说一定选择哪个是好的,选择哪个是不好的。


2、Dubbo 核心原理

Dubbo 核心架构图:

简单介绍一下上图的几个节点:

  • Registry,注册中心,通常使用 Zookeeper 作为注册中心,其主要功能是服务地址管理和服务监听(动态感知)

  • Provider,服务提供者,顾名思义,就是提供接口给别人调用的工程

  • Consumer,服务消费者,顾名思义,就是需要调用被人接口的工程

  • Monitor,监控中心,这个对应整个架构来说不是必须的。

Dubbo 整个调用流程详细分析说明:

  • ①Provider 工程在启动项目的时候,连接 Zookeeper,把相关信息上传 Zookeeper,主要信息:服务名称、所在服务 IP、通讯端口、接口信息等。

  • ②Consumer 工程在启动项目的时候,连接 Zookeeper,并且订阅 dubbo
     节点,获取该节点下面的信息,并且缓存到本地一份。

  • ③Provider 在 Zookeeper 创建的是临时节点,如果 Provider 宕机了,则连接断开,临时节点被删除

  • ④Consumer 订阅 Zookeeper 上的 Dubbo
     节点,如果该节点有变动(新增节点、删除节点),Consumer 会受到通知,并且更新本地缓存

  • ⑤Consumer 调用接口,首先根据接口的名称通过动态代理技术生成一个代理类,代理类根据接口名称从本地缓存获取其对应的服务器信息(ip/port),基于 Netty 发起请求,并且获取返回接口。

相同传统的 HttpClient,Dubbo 的好处到底体现在哪里呢?

  • 如果采用 HttpClient 模式,则代码本身需要维护大量的 url 地址,并且一旦其中的一些服务变更服务器,则需要手工修改代码里面的地址,并且重新部署,非常的麻烦。引入 Dubbo 之后,服务不需要在手工维护大量的 url 地址,服务提供者把地址往 Zookeeper 发送,服务消费者从 Zookeeper 获取地址,服务提供者变更服务器也没关系,只要它重新往 Zookeeper 注册,消费者正常获取其信息。

  • 如果被调用的服务(提供方)做集群部署,服务调用方(消费者)会通过接口名称获取其对应信息,如果发现有多个对应信息,则 Dubbo 已经封装好了负载均衡算法。

  • 如果被调用的服务(提供方)宕机了怎么办?Zookeeper 起到一个监听的作用,双方和 Zookeeper 都保持长连接,那么消费方会动态维护一份地址,并且 Dubbo 内置有容错机制、超时机制等,让整个服务治理更加的安全和完善。


3、Dubbo 快速入门


3.1、Zookeeper 安装

Zookeeper 在 Linux 安装步骤如下:

#Zookeeper是基于Java开发的,所以部署起来非常的简单。
#1.解压
tar -zxvf zookeeper-3.4.6.tar.gz -C /usr/local

#2.进入并创建data目录
cd /usr/local/zookeeper-3.4.6
mkdir data

#3.修改配置文件的名称
cd conf
mv zoo_sample.cfg zoo.cfg

#4.配置zookeeper
vim zoo.cfg
dataDir=/usr/local/zookeepr-3.4.6/data #数据存放目录
dataLogDir=/root/zookeeper-3.4.6/log #日志存放目录

#5.启动
cd /usr/local/zookeeper-3.4.6/bin
./zkServer.sh start

Zookeeper 是一个分布式协调的组件,Hadoop 生态圈里面的一个技术组件,它通常用来做注册中心、分布式协调、分布式锁等,可以简单理解为一颗树,每个节点都可以存储数据。


3.2、服务提供者

第一步:导入坐标

<!--Dubbo坐标-->
<dependency>
<groupId>com.alibaba.spring.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!--Zookeeper客户端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.5.0</version>
</dependency>

第二步:配置文件

#服务名称,最好和工程名称一致
spring.dubbo.application.name=privoder
#Zookeeper连接地址
spring.dubbo.registry.address=zookeeper://10.254.55.210:2181
#通讯端口,自己自定义
spring.dubbo.protocol.port=28071
#扫描包路径,项目启动时候扫描该包路径,把对应的类信息注册到注册中心
spring.dubbo.scan=com.test.service

第三步:入门启动类

@EnableDubboConfiguration //启动注解
@SpringBootApplication
public class ProviderApp{
public static void main(String[] args){
SpringApplication.run(ProviderApp.class, args);
}
}

第四步:发布服务

@Service(interfaceClass=TestService.class)//Dubbo提供的注解,在服务提供端使用
@Component//Spring提供的注解
public class TestServiceImpl implements TestService{
@Override
public void save(){

}
}


3.3、服务消费者

第一步:导入坐标

<!--Dubbo坐标-->
<dependency>
<groupId>com.alibaba.spring.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!--Zookeeper客户端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.5.0</version>
</dependency>
<!--依赖API-->
<dependency>
<groupId>com.test</groupId>
<artifactId>provider-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

第二步:配置文件

#服务名称,最好和工程名称一致
spring.dubbo.application.name=consumer
#Zookeeper连接地址
spring.dubbo.registry.address=zookeeper://10.254.55.210:2181
#通讯端口,自己自定义
spring.dubbo.protocol.port=28072
#扫描包路径,项目启动时候扫描该包路径,把对应的类信息注册到注册中心
spring.dubbo.scan=com.test.controller

第三步:入口启动类

@EnableDubboConfiguration //启动注解
@SpringBootApplication
public class ConsumerApp{
public static void main(String[] args){
SpringApplication.run(ConsumerApp.class, args);
}
}

第四步:调用服务

@RestController
public class TestController{
//Dubbo提供的注解,在服务消费端使用
@Reference(check=false)
public TestService testService;

public void test(){
testService.save();//跟本地调用服务用法一样
}
}

提示:服务提供者一般需要提供一个 API 接口工程给消费端集成


4、手写 RPC 框架


4.1、RPC 架构梳理

RPC 整体架构图如下所示:


架构说明

  • 服务提供方,在项目启动的时候,连接 Zookeeper 并且把接口信息注册上去,并且维持心跳

  • 服务提供方,内置使用 Netty 启动一个服务端

  • 注册中心,它起到解耦服务提供方和服务消费方的作用,它主要是维护了服务提供方的接口信息,并且和服务提供方保持心跳,实时感知其是否存在,并且动态更新接口信息。它存储的数据结构其实就是一颗树形状的,请看上面的图形。

    协议格式如下所示:

    dubbo://192.168.1.2:27055/com.imooc.system.service.DocInfoService?anyhost=true&application=micro-system-web&dubbo=2.6.0&generic=false&interface=com.micro.system.service.DocInfoService&methods=save,findMarkdown,findHtml&pid=9420&side=provider&timestamp=1571889929371, dubbo version: 2.6.0, current host: 127.0.0.1

  • 服务消费方,在项目启动的时候,连接 Zookeeper 并且获取接口信息到本地,并且维持心跳

  • 服务消费方,在接口调用的时候,由动态代理生成代理类,并且通过接口名称
    负载算法
    获取具体的服务器信息,并且发起远程调用

RPC 通讯细节架构图如下所示:

架构说明

  • 服务消费者,通过动态代理生成代理类,简化了调用的过程;通过负载算法和接口名称去获取服务端的 ip 和 port。

  • 服务消费方,自定义一个协议,根据协议来封装请求参数,并且对协议进行序列化成字节数组,放到网络中传输

  • 服务提供者,从网络中获取字节数组,对字节数组进行反序列化成可以识别的自定义协议格式的数据

  • 服务提供方,根据协议获取请求的数据(接口名称、方法名称、参数类型、参数数据等),通过反射的方式进行调用,并且响应给客户端


4.2、开发思路梳理

开发思路梳理

  • ①服务提供方,在启动项目的时候,扫描含有某个包下面并且含有某个注解的类
    的类;获取它们的接口全名,并且在 Zookeeper 上创建相应的节点,同时给每个节点创建子节点,子节点的名称是服务所在的服务器的 ip:port

  • ②服务提供方,使用 Netty 开发服务,服务的 ip 和 port 就是注册到 Zookeeper 的 ip 和 port

  • ③服务消费方,在启动项目的时候,连接 Zookeeper 并且获取服务地址,并且在本地缓存一份

  • ④服务消费方,调用接口方法的时候,首先使用动态代理的方式去对接口生成一个代理类,由代理类去做以下步骤的处理

  • ⑤代理类,首先根据接口全
    名去 Zookeeper 获取其对应的子节点(先从本地缓存获取),并且根据负载算法获取其中一个 ip:port

  • ⑤代理类,获取到 ip:port 之后发起 Netty 客户端请求,并且把参数封装成 RpcRequest
     对象,通过序列化,把对象以字节流的方式通过网络传输

  • ⑥服务提供方,反序列化获得 RpcRequest
    ,通过反射机制调用接口,并且返回结果

  • ⑦思考:为什么需要自定义协议呢?这里的协议有两层意思,第一指的是封装数据格式;第二指的是长连接情况下,如果不自定义协议可能会出现粘包问题。

服务提供方,把接口信息发布到 Zookeeper 的格式如下所示:

  • 说明:这里使用简单的 ip:port 即可,不需要像 dubbo 那样复杂的协议格式了

  • 一级目录,/myrpc 是根节点,它是一个持久化节点

  • 二级目录,是接口的全路径名称

  • 三级目录,接口所在服务器的信息,如果接口是集群部署,则对应多个子目录

第一步: 新建相关项目和相关类

RpcApi
|-- src/main/java
| |-- com.micro.busi.service
| | |-- TestService.java (业务接口)
|-- pom.xml

RpcServer
|-- src/main/java
| |-- com.micro.registry (服务发布)
| | |-- IRegistryCenter.java
| | |-- RegistryCenterImpl.java
| |-- com.micro.netty (Netty服务端)
| | |-- RpcNettyServer.java
| | |-- RpcNettyServerHandler.java
| | |-- RpcRequest.java
| |-- com.micro.busi.service.impl (业务实现类)
| | |-- TestServiceImpl.java
|-- pom.xml

RpcClient
|-- src/main/java
| |-- com.micro.resistry (服务发现)
| | |-- IServiceDiscovery.java
| | |-- ServiceDiscoveryImpl.java
| |-- com.micro.blance (负载均衡)
| | |-- LoadBlance.java
| | |-- RandomLoadBlance.java
| |-- com.micro.netty (Netty客户端)
| | |-- RpcNettyClient.java
| | |-- RpcNettyClientHandler.java
| | |-- RpcRequest.java
| |-- com.micro.proxy (动态代理)
| | |-- RpcProxy.java
| | |-- MyInvocationHandler.java
|-- pom.xml

  • ①主要分三个工程,分别是 RpcApi(API 工程)、RpcServer(提供者工程)、RpcClient(消费者工程)

  • ②RpcServer

    • com.micro.registry 包下的类注意是在项目启动的时候,获取接口信息,并且在 Zookeeper 上创建对应的目录

    • com.micro.netty 包主要是接受客户端请求并且通过反射来处理

    • com.micro.busi.service.impl 包主要是普通的业务代码

  • ③RpcClient

    • com.micro.resistry 包下的类主要是根据接口名称去 Zookeeper 获取其子目录(ip:port)

    • com.micro.blance 包主要实现负载均衡

    • com.micro.netty 包主要实现客户端请求

    • com.micro.proxy 包主要实现动态代理,通过代理类发起客户端请求

第二步: RpcServer 和 RpcClient 两个工程的 pom.xml 的配置

<!--curator-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.5.0</version>
</dependency>
<!--Netty-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
<!-- Api -->
<dependency>
<groupId>com.micro</groupId>
<artifactId>RpcApi</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

第三步: 功能实现

由于代码较多,我把它单独放到代码托管中心,具体地址如下:https://gitee.com/zwyyf/rpc.git


5、小结

本节主要讲解如果没有 Dubbo 情况下远程调用会遇到什么问题;Dubbo 的核心原理讲解,它主要是通过 Zookeeper 来管理服务地址和服务监听,服务消费端在调用服务接口时底层通过动态代理去发起请求;讲解 SpringBoot 如何集成 Dubbo 并且使用;以及手写一个简单 RPC 框架,让大家更加能理解 Dubbo 的原理。

纸上得来终觉浅,绝知此事要coding...

文章转载自修电脑的杂货店,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论