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-Server
或 DNS
),根据服务名 Kubernetes Service Name
查询到实例列表 Kubernetes endpoints
。 此时服务是通过标准的 Kubernetes Service API 定义,并被调度到各个节点。
Service Mesh
解决透明升级、多语言、依赖冲突、流量治理等问题。Service Mesh 的典型架构是通过部署独立的 Sidecar
组件来拦截所有的出口与入口流量,并在 Sidecar
中集成丰富的流量治理策略如负载均衡、路由等,除此之外,Service Mesh 还需要一个控制面 Control Plane
来实现对 Sidecar 流量的管控,即各种策略下发。
Mesh 实施成本:
- 运维控制面
Control Plane
- 运维
Sidecar
- 如何从原有 SDK 迁移到
Sidecar
- 引入
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 自身也提供了对多种注册中心组件对接。
对比 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的实现
public interface UserService {
void registerUser(User user);
}
@Path("users")
public class UserServiceImpl implements UserService {
@POST
@Path("register")
@Consumes({MediaType.APPLICATION_JSON})
public void registerUser(User user) {
// save the user...
}
}
@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格式的数据。
<!-- 用rest协议在8080端口暴露服务 -->
<dubbo:protocol name="rest" port="8080"/>
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="xxx.UserService" ref="userService"/>
<!-- 和本地bean一样实现服务 -->
<bean id="userService" class="xxx.UserServiceImpl" />
## 通常情况下我们的实现
http://localhost:8080/users/load?id=1001
## Restful 风格的实现,采用 POST、DELETE、PUT、GET 增删改查
http://localhost:8080/users/1001
http://localhost:8080/users/1002
http://localhost:8080/users/1003
@GET
@Path("{id : \\d+}")
@Produces({MediaType.APPLICATION_JSON})
public User getUser(@PathParam("id") Long id) {
// ...
}
@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被直接忽略。
@Path("users")
public interface UserService {
@GET
@Path("{id : \\d+}")
@Produces({MediaType.APPLICATION_JSON})
User getUser(@PathParam("id") Long id);
}
JSON、XML等多数据格式的支持
@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({"application/json; charset=UTF-8", "text/xml; charset=UTF-8"})
@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类型。
定制序列化
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;
}
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>
获取上下文
@Context
当server=”tjws / tomcat / jetty / servlet”时生效,只有这几种REST server提供servlet容器。
public User getUser(@PathParam("id") Long id, @Context HttpServletRequest request) {
System.out.println("Client address is " + request.getRemoteAddr());
}
Dubbo中常用的RpcContext:
Dubbo的RpcContext是一种比较有侵入性的用法,未来可能会做出重构,注意事项同上。
public User getUser(@PathParam("id") Long id) {
RpcContext.getContext().getRemoteAddressString();
}
泛型获取Request / Response,不符合指定的类型返回null:
if (RpcContext.getContext().getRequest() != null && RpcContext.getContext().getRequest() instanceof HttpServletRequest) {
System.out.println("Client address is " + ((HttpServletRequest) RpcContext.getContext().getRequest()).getRemoteAddr());
}
if (RpcContext.getContext().getResponse() != null && RpcContext.getContext().getResponse() instanceof HttpServletResponse) {
System.out.println("Response object from RpcContext: " + RpcContext.getContext().getResponse());
}
配置
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服务特点、服务质量的问题”。这个配置针对消费端生效的,也可以在消费端配置。
GZIP
Dubbo的REST支持用GZIP压缩请求和响应的数据,以减少网络传输时间和带宽占用,但这种方式会也增加CPU开销。
自定义 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 {
System.out.println("status: " + resCtx.getStatus());
System.out.println("date: " + resCtx.getDate());
System.out.println("last-modified: " + resCtx.getLastModified());
System.out.println("location: " + resCtx.getLocation());
System.out.println("headers:");
for (Entry<String, List<String>> header : resCtx.getHeaders().entrySet()) {
System.out.print("\t" + header.getKey() + " :");
for (String value : header.getValue()) {
System.out.print(value + ", ");
}
System.out.print("\n");
}
System.out.println("media-type: " + resCtx.getMediaType().getType());
}
}
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"/>
自定义 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"/>
参数检验
dubbo的rest支持采用Java标准的bean validation annotation(JSR 303)来做输入校验 http://beanvalidation.org/
基准测试
流量控制
蓝绿部署
蓝绿部署是让线上的老版本继续运行,直接部署新版本然后进行测试,当新版本测试通过以后,将流量切到新版本,最后将老版本同时也升级到新版本。整个过程无需停机,风险较小且可控。
AB测试
AB测试是用来测试应用功能的一种方案。通过科学的实验设计、样本采样、流量分割和校验等方式来获得具有代表性的实验结论,并确信该结论再推广到全部流量可信。
金丝雀部署
金丝雀部署是在原有版本可用的情况下,同时部署一个新版本应用作为“金丝雀”,测试新版本的性能和表现,在保障整体系统稳定的前提下,尽早发现、及时调整。
部署架构
注册中心
协调 Consumer 与 Provider 之间的地址注册与发现。配置中心
- 存储 Dubbo 启动阶段的全局配置,保证配置的跨环境共享与全局一致性。
- 负责服务治理规则(路由规则、动态配置等)的存储与推送。
元数据中心
- 接收 Provider 上报的服务接口元数据,为 Admin 等控制台提供运维能力(如服务测试、接口文档等)。
- 作为服务发现机制的补充,提供额外的接口/方法级别配置信息的同步能力,相当于注册中心的额外扩展。