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 扩展的实现方案。

云原生

Kubernetes Service Mesh

Kubernetes

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

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 与控制面通信。

服务发现

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

image.png

对比 Dubbo2

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

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

释放压力

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

RPC协议

Triple

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

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

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同时涵盖了这两个面向。

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

快速入门-HTTP POST/GET的实现

  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" />
  1. ## 通常情况下我们的实现
  2. http://localhost:8080/users/load?id=1001
  3. ## Restful 风格的实现,采用 POST、DELETE、PUT、GET 增删改查
  4. http://localhost:8080/users/1001
  5. http://localhost:8080/users/1002
  6. http://localhost:8080/users/1003
  1. @GET
  2. @Path("{id : \\d+}")
  3. @Produces({MediaType.APPLICATION_JSON})
  4. public User getUser(@PathParam("id") Long id) {
  5. // ...
  6. }

@GET:指定用HTTP GET方法访问
@Path("{id : \d+}"):根据上面的功能需求,访问getUser()的URL应当是“http://localhost:8080/users/ + 任意数字”,并且这个数字要被做为参数传入getUser()方法。 这里的annotation配置中,@Path 中间的{id: xxx}指定URL相对路径中包含了名为id参数,而它的值也将被自动传递给下面用@PathParam(“id”) 修饰的方法参数id。{id:后面紧跟的\d+是一个正则表达式,指定了id参数必须是数字。
@Produces({MediaType.APPLICATION_JSON}):指定getUser()输出JSON格式的数据。框架会自动将User对象序列化为JSON数据。

等价声明

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

如果接口和实现类都同时添加了annotation,则实现类的annotation配置会生效,接口上的annotation被直接忽略。

  1. @Path("users")
  2. public interface UserService {
  3. @GET
  4. @Path("{id : \\d+}")
  5. @Produces({MediaType.APPLICATION_JSON})
  6. User getUser(@PathParam("id") Long id);
  7. }

JSON、XML等多数据格式的支持

  1. @Path("users")
  2. @Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
  3. @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
  4. public class UserServiceImpl implements UserService {
  5. // ...
  6. }

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,否则序列化将报错。例如:

  1. @XmlRootElement
  2. public class User implements Serializable {
  3. // ...
  4. }
  5. // 中文支持
  6. // @Produces({"application/json; charset=UTF-8", "text/xml; charset=UTF-8"})
  7. @Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_XML_UTF_8})
  8. User getUser(@PathParam("id") Long id);

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

定制序列化

XML username 映射 name属性:

  1. @XmlRootElement
  2. @XmlAccessorType(XmlAccessType.FIELD)
  3. public class User implements Serializable {
  4. @XmlElement(name="username")
  5. private String name;
  6. }

JSON username 映射 name属性:

  1. public class User implements Serializable {
  2. @JsonProperty("username")
  3. private String name;
  4. }

REST Server

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

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

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

  1. <web-app>
  2. <context-param>
  3. <param-name>contextConfigLocation</param-name>
  4. <param-value>/WEB-INF/classes/META-INF/spring/dubbo-demo-provider.xml</param-value>
  5. </context-param>
  6. <listener>
  7. <listener-class>org.apache.dubbo.remoting.http.servlet.BootstrapListener</listener-class>
  8. </listener>
  9. <listener>
  10. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  11. </listener>
  12. <servlet>
  13. <servlet-name>dispatcher</servlet-name>
  14. <servlet-class>org.apache.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class>
  15. <load-on-startup>1</load-on-startup>
  16. </servlet>
  17. <servlet-mapping>
  18. <servlet-name>dispatcher</servlet-name>
  19. <url-pattern>/*</url-pattern>
  20. </servlet-mapping>
  21. </web-app>


获取上下文

@Context

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

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

Dubbo中常用的RpcContext:

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

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

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

  1. if (RpcContext.getContext().getRequest() != null && RpcContext.getContext().getRequest() instanceof HttpServletRequest) {
  2. System.out.println("Client address is " + ((HttpServletRequest) RpcContext.getContext().getRequest()).getRemoteAddr());
  3. }
  4. if (RpcContext.getContext().getResponse() != null && RpcContext.getContext().getResponse() instanceof HttpServletResponse) {
  5. System.out.println("Response object from RpcContext: " + RpcContext.getContext().getResponse());
  6. }


配置

Port & Context path

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

线程数

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

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

IO 线程数

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

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

长连接

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

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

最大连接数

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

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

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

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


GZIP

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

自定义 Filter Interceptor

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

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

ClientResponseFilter consumer 的 filter。

  1. public class LoggingFilter implements ClientResponseFilter {
  2. public void filter(ClientRequestContext reqCtx, ClientResponseContext resCtx) throws IOException {
  3. System.out.println("status: " + resCtx.getStatus());
  4. System.out.println("date: " + resCtx.getDate());
  5. System.out.println("last-modified: " + resCtx.getLastModified());
  6. System.out.println("location: " + resCtx.getLocation());
  7. System.out.println("headers:");
  8. for (Entry<String, List<String>> header : resCtx.getHeaders().entrySet()) {
  9. System.out.print("\t" + header.getKey() + " :");
  10. for (String value : header.getValue()) {
  11. System.out.print(value + ", ");
  12. }
  13. System.out.print("\n");
  14. }
  15. System.out.println("media-type: " + resCtx.getMediaType().getType());
  16. }
  17. }

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

  1. public class GZIPWriterInterceptor implements WriterInterceptor {
  2. @Override
  3. public void aroundWriteTo(WriterInterceptorContext context)
  4. throws IOException, WebApplicationException {
  5. OutputStream outputStream = context.getOutputStream();
  6. context.setOutputStream(new GZIPOutputStream(outputStream));
  7. context.proceed();
  8. }
  9. }

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

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

自定义 Exception

ExceptionMapper

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

配置

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

参数检验

dubbo的rest支持采用Java标准的bean validation annotation(JSR 303)来做输入校验 http://beanvalidation.org/

基准测试

image.png

流量控制

蓝绿部署

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

AB测试

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

金丝雀部署

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

部署架构

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

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

元数据中心

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

简介 V3.0 - 图3