v3.0
提供了 RPC通信微服务治理 两大关键能力,即具备相互之间的远程发现与通信能力,利用 Dubbo 提供的服务治理能力,实现服务发现、负载均衡、流量调度等服务治理诉求。同时支持可扩展。
点对点的服务通信是 Dubbo 提供的另一项基本能力,Dubbo 以 RPC 的方式将请求数据(Request)发送给后端服务,并接收服务端返回的计算结果(Response)。RPC 通信对用户来说是完全透明的,使用者无需关心请求是如何发出去的、发到了哪里,每次调用只需要拿到正确的调用结果就行。同步的 Request-Response 是默认的通信模型,它最简单但却不能覆盖所有的场景,因此,Dubbo 提供更丰富的通信模型:

  • 消费端异步请求(Client Side Asynchronous Request-Response)
  • 提供端异步执行(Server Side Asynchronous Request-Response)
  • 消费端请求流(Request Streaming)
  • 提供端响应流(Response Streaming)
  • 双向流式通信(Bidirectional Streaming)

透明地址发现让 Dubbo 请求可以被发送到任意 IP 实例上,这个过程中流量被随机分配。当需要对流量进行更丰富、更细粒度的管控时,就可以用到 Dubbo 的流量管控策略,Dubbo 提供了包阔 负载均衡 流量路由 请求超时 流量降级 重试策略 等策略,基于这些基础能力可以轻松的实现更多场景化的路由方案,包括 金丝雀发布 A/B测试 权重路由 同区域优先 等,Dubbo 支持 流控策略 在运行态动态生效。
Dubbo 强大的服务治理能力不仅体现在核心框架上,还包括其优秀的扩展能力以及周边配套设施的支持。通过 Filter Router Protocol 等几乎存在于每一个关键流程上的扩展点定义,我们可以丰富 Dubbo 的功能或实现与其他微服务配套系统的对接,包括 Transaction Tracing 目前都有通过 SPI 扩展的实现方案。

1. 云原生

Kubernetes Service Mesh

1.1. Kubernetes

容器调度平台
dubbo 服务生命周期与容器生命周期对齐,包括 Dubbo 的 启动 销毁 服务注册 等生命周期事件。相比于以往 Dubbo 自行定义生命周期事件,并要求开发人员在运维实践过程中遵守约定,Kubernetes 底层基础设施定义了严格的组件生命周期事件 probe ,转而要求 Dubbo 去按约定适配。
Kubernetes Service 是另一个层面的适配,在这种模式下,用户不再需要搭建额外的注册中心组件,Dubbo 消费端节点能自动对接到 Kubernetes( API-ServerDNS ),根据服务名 Kubernetes Service Name 查询到实例列表 Kubernetes endpoints 。 此时服务是通过标准的 Kubernetes Service API 定义,并被调度到各个节点。

1.2. Service Mesh

解决透明升级、多语言、依赖冲突、流量治理等问题。Service Mesh 的典型架构是通过部署独立的 Sidecar 组件来拦截所有的出口与入口流量,并在 Sidecar 中集成丰富的流量治理策略如负载均衡、路由等,除此之外,Service Mesh 还需要一个控制面 Control Plane 来实现对 Sidecar 流量的管控,即各种策略下发。
Mesh 实施成本:

  1. 运维控制面 Control Plane
  2. 运维 Sidecar
  3. 如何从原有 SDK 迁移到 Sidecar
  4. 引入 Sidecar 后整个链路的性能损耗

为了解决 Sidecar 引入的相关成本问题,Dubbo 引入了另一种变相的 Mesh 架构 - Proxyless Mesh ,Proxyless Mesh 就是指没有 Sidecar 的部署,转而由 Dubbo SDK 直接与控制面交互。
未来以 Dubbo 构建的微服务将会有三种部署架构:、基于 Sidecar 的 Service Mesh、脱离 Sidecar 的 Proxyless Mesh。
基于 Sidecar 的 Service Mesh,即经典的 Mesh 架构,独立的 sidecar 运行时接管所有的流量,脱离 Sidecar 的 Proxyless Mesh,传统 SDK富 SDK 直接通过 xDS 与控制面通信。富 SDK 直接通过 xDS 与控制面通信。

2. 服务发现

Dubbo 提供 Client-Based 服务发现机制,通常还需要部署第三方注册中心组件来协调服务发现过程,例如: Nacos Consul Zookeeper … Dubbo 自身也提供了对多种注册中心组件对接。
截屏2021-06-22 11.11.35.png

2.1. 对比 Dubbo2

应用级服务发现,在工作原理、数据格式上已完全不能兼容老版本服务发现,Dubbo3 默认保持了接口级地址发现,如果要开启应用级服务发现,则需要通过配置显示开启(双注册、双订阅)。

  • Dubbo3 应用级 服务发现,以应用粒度组织地址数据。
  • Dubbo2 接口级 服务发现,以接口粒度组织地址数据。

    2.2. 释放压力

    例如:微服务应用定义了100个接口(Dubbo 中的服务),则需要向注册中心中注册100个服务,如果这个微服务被部署在了100台机器上,那这 100 个服务总共会产生 100 100 = 10000 个虚拟节点;而同样的应用,对于 Dubbo3 来说,新的注册发现模型只需要 1 个服务(只和应用有关和接口无关),只注册和机器实例数相等的 1 100 = 100 个虚拟节点到注册中心。 对于注册中心、订阅方的存储压力都是一个极大的释放。地址发现容量彻底与业务 RPC 定义解藕开来,整个集群的容量评估对运维来说将变得更加透明:部署多少台机器就会有多大负载,不会像 Dubbo2 一样, 因为业务 RPC 重构就会影响到整个集群服务发现的稳定性。

    3. RPC协议

    Triple(Dubbo3) Dubbo2 其他

    3.1. Triple

    Triple 协议是 Dubbo3 的主力协议,完整兼容 gRPC over HTTP/2,并在协议层面扩展了负载均衡和流量控制相关机制。推荐使用 protobuf 作为默认序列化,在性能和跨语言上的效果都会更好。 Triple 协议也已经支持其他序列化方式,例如 Hessian JSON 等。

    https://dubbo.apache.org/zh/docs/v3.0/references/protocols/tri/#protobuf

3.2. REST

支持远程调用方式:
dubbo RPC 二进制序列化 + tcp。
http invoker 二进制序列化 + http,在开源版本不支持文本序列化。
hessian 二进制序列化 + http协议。
WebServices 文本序列化 + http协议。

REST风格的远程调用,即RESTful Remoting(抽象的远程处理或者调用),而不是叫RESTful RPC(具体的远程“过程”调用),是因为REST和RPC本身可以被认为是两种不同的风格。在dubbo的REST实现中,可以说有两个面向,其一是提供或消费正常的REST服务,其二是将REST作为dubbo RPC体系中一种协议实现,而RESTful Remoting同时涵盖了这两个面向。

  1. public interface UserService {
  2. void registerUser(User user);
  3. }
  1. @Path("users")
  2. public class UserServiceImpl implements UserService {
  3. @POST
  4. @Path("register")
  5. @Consumes({MediaType.APPLICATION_JSON})
  6. public void registerUser(User user) {
  7. // save the user...
  8. }
  9. }

@Path(“users”) 指定访问UserService的URL相对路径是/users,例如:http://localhost:8080/users
@Path(“register”) 指定访问registerUser()方法的URL相对路径是/register,完整路径例如:http://localhost:8080/users/register
@POST 指定HTTP POST访问registerUser()
@Consumes({MediaType.APPLICATION_JSON}) 指定registerUser()接收JSON格式的数据。

  1. <!-- 用rest协议在8080端口暴露服务 -->
  2. <dubbo:protocol name="rest" port="8080"/>
  3. <!-- 声明需要暴露的服务接口 -->
  4. <dubbo:service interface="xxx.UserService" ref="userService"/>
  5. <!-- 和本地bean一样实现服务 -->
  6. <bean id="userService" class="xxx.UserServiceImpl" />

JAX-RS本身可以支持所有这些形式。在URL传递参数(http://localhost:8080/users/1001)更符合REST的一般习惯。

@GET
@Path("{id : \\d+}")
@Produces({MediaType.APPLICATION_JSON})
public User getUser(@PathParam("id") Long id) {
    // ...
}

@PathParam(“id”) 修饰的方法参数id。id:后面紧跟的\d+是一个正则表达式,指定了id参数必须是数字。

3.2.1. 等价声明

在 Dubbo 中开发 REST 服务主要通过 JAX-RS Annotation 配置,可以将 Annotation 放到服务接口或实现类上,这两种方式是完全等价的。建议将 Annotation 放到实现类。

@Path("users")
public interface UserService {
    @GET
    @Path("{id : \\d+}")
    @Produces({MediaType.APPLICATION_JSON})
    User getUser(@PathParam("id") Long id);
}

3.2.2. 多数据格式

@Path("users")
@Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
public class UserServiceImpl implements UserService {
    // ...
}

Dubbo 用 URL 后缀 .json / .xml 指定数据格式。例如,在添加上述 Annotation 后,直接访问 http://localhost:8888/users/1001.json 则表示用json格式,直接访问 http://localhost:8888/users/1002.xml 则表示用xml格式,比用HTTP Header更简单直观。Twitter、微博等的REST API都是采用这种方式。
如果无HTTP Header或后缀,则 dubbo REST 会优先启用在以上 Annotation 定义中排位最靠前的数据格式。

支持XML格式数据,Annotation 使用 MediaType.TEXT_XML 或 MediaType.APPLICATION_XML,TEXT_XML更常用,如果使用URL后缀方式来指定数据格式,只能配置TEXT_XML才生效。

@XmlRootElement 由于JAX-RS的实现一般都用标准的JAXB(Java API for XML Binding)来序列化和反序列化XML格式数据,所以我们需要为每一个要用XML传输的对象添加一个类级别的JAXB Annotation,否则序列化将报错。例如:

@XmlRootElement
public class User implements Serializable {
    // ...
}

中文支持

@Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
User getUser(@PathParam("id") Long id);

注意:如果service方法中的返回值是Java的 primitive类型(如int,long,float,double等),最好为它们添加一层wrapper对象,因为JAXB不能直接序列化primitive类型。

3.2.3. 定制序列化

XML username 映射 name属性:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class User implements Serializable {
    @XmlElement(name="username") 
    private String name;  
}

JSON username 映射 name属性:

public class User implements Serializable {
    @JsonProperty("username")
    private String name;
}

3.2.4. REST Server

jetty tomcat netty sunhttp servlet 默认选用jetty,如果 dubbo 系统部署到 Java 应用服务器建议采用servlet。

<dubbo:protocol name="rest" server="jetty ..."/>

配置为servlet,web.xml添加配置:

<web-app>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/classes/META-INF/spring/dubbo-demo-provider.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.apache.dubbo.remoting.http.servlet.BootstrapListener</listener-class>
    </listener>    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>    
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.apache.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>   
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

3.2.5. 获取上下文

@Context

public User getUser(@PathParam("id") Long id, @Context HttpServletRequest request) {
    System.out.println("Client address is " + request.getRemoteAddr());
}

当server=”tjws / tomcat / jetty / servlet”时生效,只有这几种REST server提供servlet容器。

Dubbo中常用的RpcContext:

public User getUser(@PathParam("id") Long id) {
    RpcContext.getContext().getRemoteAddressString();
}

Dubbo的RpcContext是一种比较有侵入性的用法,未来可能会做出重构,注意事项同上。

泛型获取Request / Response,不符合指定的类型返回null:

if (RpcContext.getContext().getRequest(HttpServletRequest.class) != null) {
    System.out.println("Client address is " + RpcContext.getContext().getRequest(HttpServletRequest.class).getRemoteAddr());
}

if (RpcContext.getContext().getResponse(HttpServletResponse.class) != null) {
    System.out.println("Response object from RpcContext: " + RpcContext.getContext().getResponse(HttpServletResponse.class));
}

3.2.6. 配置

Port & Context path

<dubbo:protocol name="rest" port="8888" contextpath="services" server="servlet" />

线程数

<dubbo:protocol name="rest" threads="500"/>

当server=”netty / tomcat / jetty / servlet”时生效,如果server=“servlet”,由于这时候启用的是外部应用服务器做rest server,不受dubbo控制,所以这里的线程池设置也无效。

IO 线程数

<dubbo:protocol name="rest" iothreads="5" threads="100"/>

如果是选用netty server,还可以配置Netty的IO worker线程数:

长连接

<dubbo:protocol name="rest" keepalive="false"/>

Dubbo中的rest服务默认都是采用http长连接来访问,上方为切换短连接

最大连接数

<dubbo:protocol name="rest" accepts="500" server="tomcat/>

服务端所能同时接收的最大HTTP连接数,作为一种最基本的自我保护机制,当server=”tomcat”生效。

<dubbo:reference id="xxx" interface="xxx" timeout="2000" connections="10"/>

通常我们建议配置在服务提供端提供此类配置。按照dubbo官方文档的说法:“Provider上尽量多配置Consumer端的属性,让Provider实现者一开始就思考Provider服务特点、服务质量的问题”。这个配置针对消费端生效的,也可以在消费端配置。

3.2.7. GZIP

Dubbo的REST支持用GZIP压缩请求和响应的数据,以减少网络传输时间和带宽占用,但这种方式会也增加CPU开销。

3.2.8. 自定义 Filter Interceptor

ContainerResponseFilter Filter主要用于访问和设置HTTP请求和响应的参数、URI等,该 filter 应用于 provider。

public class CacheControlFilter implements ContainerResponseFilter {
    public void filter(ContainerRequestContext req, ContainerResponseContext res) {
        if (req.getMethod().equals("GET")) {
            res.getHeaders().add("Cache-Control", "someValue");
        }
    }
}

ClientResponseFilter consumer 的 filter。

public class LoggingFilter implements ClientResponseFilter {
     public void filter(ClientRequestContext reqCtx, ClientResponseContext resCtx) throws IOException {
        // ...
    } 
}

WriterInterceptor 主要用于访问和修改输入与输出字节流,例如,手动添加GZIP压缩:

public class GZIPWriterInterceptor implements WriterInterceptor {
    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
                    throws IOException, WebApplicationException {
        OutputStream outputStream = context.getOutputStream();
        context.setOutputStream(new GZIPOutputStream(outputStream));
        context.proceed();
    }
}

**配置** Filter、Interceptor和DynamicFeature这三种类型的对象都添加到extension属性上,多个之间用逗号分隔。

<dubbo:protocol name="rest" port="8888" extension="xxx.TraceInterceptor, xxx.TraceFilter"/>

3.2.9. 自定义 Exception

ExceptionMapper

public class CustomExceptionMapper implements ExceptionMapper<NotFoundException> {
    public Response toResponse(NotFoundException e) {     
        return Response.status(Response.Status.NOT_FOUND)
            .entity("Oops! the requested resource is not found!").type("text/plain").build();
    }
}

配置

<dubbo:protocol name="rest" port="8888" extension="xxx.CustomExceptionMapper"/>

3.2.10. 参数检验

JSR 303来做输入校验 http://beanvalidation.org/

3.2.11. 透明传输

Thinking …

4. 流量控制

路由规则可以有多个,不同的路由规则之间存在优先级,一个路由规则可以路由到多个不同的应用服务,多个不同的路由规则可以路由到同一个应用服务。路由规则也可以不路由到任何应用服务,所有命中Router(m)的请求都会因为没有对应的应用服务处理而导致报错。
截屏2021-06-22 11.31.20.png

4.1. 蓝绿部署

蓝绿部署是让线上的老版本继续运行,直接部署新版本然后进行测试,当新版本测试通过以后,将流量切到新版本,最后将老版本同时也升级到新版本。整个过程无需停机,风险较小且可控。

4.2. AB测试

AB测试是用来测试应用功能的一种方案。通过科学的实验设计、样本采样、流量分割和校验等方式来获得具有代表性的实验结论,并确信该结论再推广到全部流量可信。

4.3. 金丝雀部署

金丝雀部署是在原有版本可用的情况下,同时部署一个新版本应用作为“金丝雀”,测试新版本的性能和表现,在保障整体系统稳定的前提下,尽早发现、及时调整。

5. 部署架构

注册中心
协调 Consumer 与 Provider 之间的地址注册与发现。
配置中心

  1. 存储 Dubbo 启动阶段的全局配置,保证配置的跨环境共享与全局一致性。
  2. 负责服务治理规则(路由规则、动态配置等)的存储与推送。

元数据中心

  1. 接收 Provider 上报的服务接口元数据,为 Admin 等控制台提供运维能力(如服务测试、接口文档等)。
  2. 作为服务发现机制的补充,提供额外的接口/方法级别配置信息的同步能力,相当于注册中心的额外扩展。

截屏2021-06-22 11.53.18.png

6. 手册

https://dubbo.apache.org/zh/docs/v3.0/references