Dubbo 学习笔记

发布时间 2023-11-01 19:46:38作者: LARRY1024

Dubbo 介绍

Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。

Dubbo 与 gRPC、Spring Cloud、Istio 的关系

Dubbo 与 Spring Cloud

image

从上图我们可以看出,Dubbo 和 Spring Cloud 有很多相似之处,它们都在整个架构图的相同位置并提供一些相似的功能。

  • Dubbo 和 Spring Cloud 都侧重在对分布式系统中常见问题模式的抽象(如服务发现、负载均衡、动态配置等),同时对每一个问题都提供了配套组件实现,形成了一套微服务整体解决方案,让使用 Dubbo 及 Spring Cloud 的用户在开发微服务应用时可以专注在业务逻辑开发上。
  • Dubbo 和 Spring Cloud 都完全兼容 Spring 体系的应用开发模式,Dubbo 对 Spring 应用开发框架、Spring Boot 微服务框架都做了很好的适配,由于 Spring Cloud 出自 Spring 体系,在这一点上自然更不必多说。

Dubbo 与 gRPC

image

Dubbo 与 gRPC 最大的差异在于两者的定位上:

  • gRPC 定位为一款 RPC 框架,Google 推出它的核心目标是定义云原生时代的 rpc 通信规范与标准实现;

  • Dubbo 定位是一款微服务开发框架,它侧重解决微服务实践从服务定义、开发、通信到治理的问题,因此 Dubbo 同时提供了 RPC 通信、与应用开发框架的适配、服务治理等能力。

Dubbo 不绑定特定的通信协议,即 Dubbo 服务间可通过多种 RPC 协议通信并支持灵活切换。因此,你可以在 Dubbo 开发的微服务中选用 gRPC 通信,Dubbo 完全兼容 gRPC,并将 gRPC 设计为内置原生支持的协议之一。

Dubbo 与 Istio

image

Service Mesh 是近年来在云原生背景下诞生的一种微服务架构,在 Kubernetes 体系下,让微服务开发中的更多能力如流量拦截、服务治理等下沉并成为基础设施,让微服务开发、升级更轻量。Istio 是 Service Mesh 的开源代表实现,它从部署架构上分为数据面与控制面,从这一点上与 Dubbo 总体架构 是基本一致的,Istio 带来的主要变化在于:

  • 数据面,Istio 通过引入 Sidecar 实现了对服务流量的透明拦截,Sidecar 通常是与 Dubbo 等开发的传统微服务组件部署在一起

  • 控制面,将之前抽象的服务治理中心聚合为一个具有统一实现的具体组件,并实现了与底层基础设施如 Kubernetes 无缝适配

Dubbo 已经实现了对 Istio 体系的全面接入,可以用 Istio 控制面治理 Dubbo 服务,而在数据面部署架构上,针对 Sidecar 引入的复杂性与性能问题,Dubbo 还支持无代理的 Proxyless 模式。 除此之外,Dubbo Mesh 体系还解决了 Istio 架构落地过程中的很多问题,包括提供更灵活的数据面部署架构、更低的迁移成本等。

Dubbo 微服务生态

基于扩展点的微服务生态

众多的扩展点与抽象,是 Dubbo 与众多微服务生态组件对接、实现微服务治理能力的基础。

  • 全链路追踪

  • 数据一致性

  • 限流降级

Dubbo 的各语言 sdk 实现都是采用的 “微内核+插件” 的设计模式,几乎所有流程中的核心节点都被定义为扩展点,官方发布的组件也是以扩展点的实现形式发布,因此 Dubbo 可以平等的对待所有官方与第三方组件扩展。

  • 扩展适配能力是实现 Dubbo 微服务生态的关键,Dubbo 生态组件如全链路追踪、注册中心实现等的适配都是基于 Filter、Registry、DynamicConfiguration 等扩展点实现。

  • 扩展适配给用户带来最大的灵活性,开发者可以随时接入公司内部组件、按需定制核心能力等。

image

以上是按架构层次划分的 Dubbo 内的一些核心扩展点定义及实现,从三个层次来展开:

  • 协议通信层

  • 流量管控层

  • 服务治理层

协议通信层

用户可以选择 Triple、gRPC、Dubbo2、REST、自定义协议等任一 RPC 远程通信协议,除此之外,RPC 协议之上的数据编码格式 (即序列化协议) 也是通过扩展点定义,用户可以灵活选择 RPC 与序列化的通信协议组合。

image

Dubbo 框架不绑定任何通信协议,在实现上 Dubbo 对多协议的支持也非常灵活,它可以让你在一个应用内发布多个使用不同协议的服务,并且支持用同一个 port 端口对外发布所有协议。

流量管控层

Dubbo 在服务调用链路上预置了大量扩展点,通过这些扩展点用户可以控制运行态的流量走向、改变运行时调用行为等,包括 Dubbo 内置的一些负载均衡策略、流量路由策略、超时等很多流量管控能力都是通过这类扩展点实现的。

image

Filter

Filter 流量拦截器是 Dubbo 服务调用之上的 AOP 设计模式,Filter 用来对每次服务调用做一些预处理、后处理动作,使用 Filter 可以完成访问日志、加解密、流量统计、参数验证等任务,Dubbo 中的很多生态适配如限流降级 Sentinel、全链路追踪 Tracing 等都是通过 Fitler 扩展实现的。一次请求过程中可以植入多个 Filter,Filter 之间相互独立没有依赖。

  • 从消费端视角,它在请求发起前基于请求参数等做一些预处理工作,在接收到响应后,对响应结果做一些后置处理;

  • 从提供者视角则,在接收到访问请求后,在返回响应结果前做一些预处理。

Router

Router 路由器是 Dubbo 中流量管控的关键组件,它将符合一定条件的流量转发到特定分组的地址子集,是 Dubbo 流量管控中一些关键能力如按比例流量转发、流量隔离等的基础。每次服务调用请求都会流经一组路由器 (路由链),每个路由器根据预先设定好的规则、全量地址列表以及当前请求上下文计算出一个地址子集,再传给下一个路由器,重复这一过程直到最后得出一个有效的地址子集。

Dubbo 官方发布版本预置了丰富的流量管控规则与 router 实现,如 流量管控 一文中阐述的,用户通过下发规则即可实现各种模式的流量管控。如果有其他流量管控诉求,可以通过提供自定义的 router 扩展实现。

Load Balance

在 Dubbo 中,Load Balance 负载均衡工作在 router 之后,对于每次服务调用,负载均衡负责在 router 链输出的地址子集中选择一台机器实例进行访问,保证一段时间内的调用都均匀的分布在地址子集的所有机器上。

Dubbo 官方提供了加权随机、加权轮询、一致性哈希、最小活跃度优先、最短响应时间优先等负载均衡策略,还提供了根据集群负载自适应调度的负载均衡算法。

服务治理层

以下是 Dubbo 部署的经典架构图,由注册中心 (服务发现)、配置中心和元数据中心构成了整个服务治理的核心。

image

Registry

注册中心是 Dubbo 实现服务发现能力的基础,Dubbo 官方支持 Zookeeper、Nacos、Etcd、Consul、Eureka 等注册中心。

通过对 Consul、Eureka 的支持,Dubbo 也实现了与 Spring Cloud 体系在地址和通信层面的互通,让用户同时部署 Dubbo 与 Spring Cloud,或者从 Spring Cloud 迁移到 Dubbo 变得更容易。

接口级服务发现

Dubbo 接口级服务发现的机制:

image

使用这种方式时,Provider 启动时,会将接口级的数据注册到注册中心。

应用级服务发现

Dubbo 应用级服务发现的机制:

image

它通过应用名 (配置项 dubbo.application.name) 对整个集群的实例地址进行聚合,每个对外提供服务的实例将自身的应用名、实例 ip:port 地址信息 (通常还包含少量的实例元数据,如机器所在区域、环境等) 注册到注册中心。

image

目前,业界主流的微服务模型都是采用这种方式,如:Spring Cloud、Kubernetes Service 等。

Config Center

配置中心是用户实现动态控制 Dubbo 行为的关键组件,我们在 流量管控 任务中下发的所有规则,都是先下发到配置中心保存起来,进而 Dubbo 实例通过监听配置中心的变化,收到路由规则并达到控制流量的行为。

Dubbo 官方支持 Zookeeper、Nacos、Etcd、Redis、Apollo 等配置中心实现。

Metadata Center

与配置中心相反,从用户视角来看元数据中心是只读的,元数据中心唯一的写入方是 Dubbo 进程实例,Dubbo 实例会在启动之后将一些内部状态(如服务列表、服务配置、服务定义格式等)上报到元数据中心,供一些治理能力作为数据来源,如服务测试、文档管理、服务状态展示等。

Dubbo 官方支持 Zookeeper、Nacos、Etcd、Redis 等元数据中心实现。

观测服务(监控和运维)

Dubbo 内部维护了多个纬度的可观测指标,并且支持多种方式的可视化监测。可观测性指标从总体上来说分为三个度量纬度:

  • Admin: Admin 控制台可视化展示了集群中的应用、服务、实例及依赖关系,支持流量治理规则下发,同时还提供如服务测试、mock、文档管理等提升研发测试效率的工具。

  • Metrics: Dubbo 统计了一系列的流量指标如 QPS、RT、成功请求数、失败请求数等,还包括一系列的内部组件状态如线程池数、服务健康状态等。

  • Tracing: Dubbo 与业界主流的链路追踪工作做了适配,包括 Skywalking、Zipkin、Jaeger 都支持 Dubbo 服务的链路追踪。

  • Logging: Dubbo 支持多种日志框架适配。以 Java 体系为例,支持包括 Slf4j、Log4j2、Log4j、Logback、Jcl 等,用户可以基于业务需要选择合适的框架;同时 Dubbo 还支持 Access Log 记录请求踪迹。

Dubbo Admin

Dubbo Admin 控制台可视化展示了集群中的应用、服务、实例及依赖关系,支持流量治理规则下发,同时还提供如服务测试、mock、文档管理等提升研发测试效率的工具

image

Metrics

我们可以通过 Grafana 可视化的查看 Metrics 指标。Dubbo 运行时统计了包括 qps、rt、调用总数、成功数、失败数,失败原因统计等在内的核心服务指标,同时,为了更好的监测服务运行状态,Dubbo 还提供了对核心组件状态的监控,如线程池数量、服务健康状态等。

Tracing

全链路追踪对于监测分布式系统运行状态具有非常重要的价值,Dubbo 通过 Filter 拦截器实现了请求运行时的埋点跟踪,通过将跟踪数据导出到一些主流实现,如:Zipkin、Skywalking、Jaeger 等,可以实现全链路跟踪数据的分析与可视化展示。

基于 Spring Boot 开发微服务应用

dubbo 支持的配置方式,支持:属性配置、API 配置、XML 配置、Annotation 配置。

这里,我们直接介绍如何使用注解的方式,将 Dubbo 集成到 Spring Boot 项目中。

核心注解

Provider

Maven 依赖:

<properties>
    <java.version>17</java.version>
    <spring.version>3.1.3</spring.version>
    <aspect.version>1.8.10</aspect.version>
    <dubbo.version>3.2.7</dubbo.version>
    <protobuf.version>3.19.6</protobuf.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${aspect.version}</version>
    </dependency>

    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>${dubbo.version}</version>
    </dependency>
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>${protobuf.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
        <version>${dubbo.version}</version>
        <type>pom</type>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>${spring.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

配置文件 application.yaml 的配置如下:

server:
  port: 18082

dubbo:
  application:
    name: athena-handle-service
    qos-port: 33333
  registry:
    address: 7.225.85.25:2181,7.225.82.14:2181,7.225.88.88:2181
    group: dev
    protocol: zookeeper
  provider:
    timeout: 5000
    token: false
  protocol:
    name: dubbo
    port: 20881

定义一个接口:

package com.athena.cloud.service;

import com.athena.cloud.common.pojo.User;

public interface UserService {
    User getUserById(String userId);
}

其中,对象 User 的定义如下:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class User implements Serializable {
    private String userName;
    private String userId;

    public User(String userId) {
        this.userId = userId;
        this.userName = "defaultName";
    }
}

注意,User 需要实现 Serializable 接口,即是一个可序列化对象。

发布一个接口级的服务:

package com.athena.cloud.service.rpc;

import com.athena.cloud.common.pojo.User;
import com.athena.cloud.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.stereotype.Service;

@Service
@DubboService
@Slf4j
public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(String userId) {
        log.info("handle: start to query user " + userId);
        return new User(userId);
    }
}

使能 Dubbo:

package com.athena.cloud.config;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableDubbo(scanBasePackages = {"com.athena.cloud"})
public class DubboConfiguration {
}

Consumer

Maven 依赖:

<properties>
    <java.version>17</java.version>
    <spring.version>3.1.3</spring.version>
    <aspect.version>1.8.10</aspect.version>
    <dubbo.version>3.2.7</dubbo.version>
    <protobuf.version>3.19.6</protobuf.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${aspect.version}</version>
    </dependency>

    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>${dubbo.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
        <version>${dubbo.version}</version>
        <type>pom</type>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>${spring.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

配置:

server:
  port: 18083

dubbo:
  application:
    name: athena-mgmt-service
    qos-port: 33334
    logger: slf4j
  registry:
    address: 7.225.85.25:2181,7.225.82.14:2181,7.225.88.88:2181
    group: dev
    protocol: zookeeper
  consumer:
    timeout: 5000

启用 Dubbo:

package com.athena.cloud.config;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableDubbo(scanBasePackages = {"com.athena.cloud.bootes"})
public class DubboConfig {
}

在controllor 中调用,RPC接口

package com.athena.cloud.controller;

import com.athena.cloud.common.pojo.User;
import com.athena.cloud.common.pojo.request.UserRequest;
import com.athena.cloud.service.UserService;
import com.athena.cloud.common.pojo.response.GenericResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class HandleController {

    @DubboReference
    private UserService userService;

    @PostMapping("/getUser")
    public GenericResponse<User> getUser(@RequestBody UserRequest request) {
        log.info("start to get user " + request);
        User user = userService.getUserById(request.getUserId());
        return GenericResponse.response(user);
    }
}

验证

注册中心

应用启动成功后,provider 和 consumer 均会将接口级服务注册到注册中心,可以查询 zookeeper 接口级服务对应的目录,可以看到服务已经注册成功:

[zk: localhost:2181(CONNECTED) 460] ls /dev/com.athena.cloud.service.UserService/providers 
[dubbo%3A%2F%2F10.169.30.81%3A20881%2Fcom.athena.cloud.service.UserService%3Fapplication%3Dathena-handle-service%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.athena.cloud.service.UserService%26methods%3DgetUserById%26prefer.serialization%3Dfastjson2%2Chessian2%26release%3D3.2.7%26service-name-mapping%3Dtrue%26side%3Dprovider%26timeout%3D5000%26timestamp%3D1698828275205%26token%3Dfalse]
[zk: localhost:2181(CONNECTED) 461] ls /dev/com.athena.cloud.service.UserService/consumers 
[consumer%3A%2F%2F10.169.30.81%2Fcom.athena.cloud.service.UserService%3Fapplication%3Dathena-mgmt-service%26background%3Dfalse%26category%3Dconsumers%26check%3Dfalse%26dubbo%3D2.0.2%26executor-management-mode%3Disolation%26file-cache%3Dtrue%26interface%3Dcom.athena.cloud.service.UserService%26logger%3Dslf4j%26methods%3DgetUserById%26pid%3D44060%26qos.port%3D33334%26release%3D3.2.7%26side%3Dconsumer%26sticky%3Dfalse%26timeout%3D5000%26timestamp%3D1698837015465%26unloadClusterRelated%3Dfalse]

接口验证

通过 postman 调用消费端的接口测试:

POST /getUser HTTP/1.1
Host: 127.0.0.1:18083
Content-Type: application/json
Content-Length: 23

{
    "userId": "2"
}

调用成功后,响应如下:

{
    "msg": "OK",
    "code": "0",
    "data": {
        "userName": "defaultName",
        "userId": "2"
    }
}

参考: