Dubbo 3.0
在服务注册领域,市面上有两种模型,一种是应用级注册(),一种是接口级注册,在Spring Cloud中,一个应用是一个微服务,而在Dubbo2.7中,一个接口是一个微服务。
应用名
以及应用所在服务器的IP地址
和应用所绑定的端口
注册到注册中心,相当于key是应用名
,value是ip+port
,接口名
以及对应应用的IP地址
和所绑定的端口
注册到注册中心,相当于key是接口名
,value是ip+port
。所以在Dubbo2.7中,一个应用如果提供了10个Dubbo服务,那么注册中心中就会存储10对keyvalue,而Spring Cloud就只会存一对keyvalue,所以以Spring Cloud为首的应用级注册是更加适合的。
所以Dubbo3.0中将注册模型也改为了应用级注册,提升效率节省资源的同时,通过统一注册模型,也为各个微服务框架的互通打下了基础。
服务消费者是通过发送一个Invocation对象来表示要调用的是哪个接口中的哪个方法,我们是直接把Invocation对象进行JDK序列化得到字节流后然后发送出去的,那如果现在不用JDK序列化呢,比如还有很多序列化的方式,比如JSON、Hessian等等。
此时服务消费者最好能在发送的请求中,能标记所使用的序列化方式,这个标记是不能放在Invocation对象中的,因为这个标记指的就是Invocation对象的序列化方法,服务端接收到字节流后,首先得能拿到序列化标记,看采用的是什么序列化方法,再解析反序列化。
如果我们通过HTTP协议(特指HTTP1.x,HTTP2后面分析),那实现起来就比较方便,把序列化标记放在请求头,Invocation对象序列化之后的结果放在请求体,服务端收到HTTP请求后,就先解析请求头得到序列化标记,再取请求体进行反序列化。
比如HTTP协议就描述了,从第一个字节开始,遇到第一个空格符时,那就是表示前面每个字节对应的字符组合成的字符串就表示请求方法(字符编码为ascii,一个字符对应一个字节),紧接着继续解析字节,将会按HTTP协议格式解析出请求行、请求头,解析完请求头之后,取出content-length对应的value,该value表示请求体的字节数,所以直接再获取content-length个字节,就表示获取到了请求体(Invocation对象序列化之后的字节),从而一个HTTP请求就获取出来,下一个字节就是另外一个HTTP请求了。
dubbo协议为了解决上面两个问题,协议,描述的就是一份数据长什么样子,HTTP协议也是一样,描述的就是一个HTTP请求长什么样子,以什么开始,到哪里结束。
dubbo协议在Dubbo框架内使用还是比较舒服的,并且dubbo协议相比于http1.x协议,性能会更好,因为请求中没有多余的无用的字节,都是必要的字节,并且每个Dubbo请求和响应中都有一个请求ID,这样可以基于一个Socket连接同时发送多个Dubbo请求,不用担心请求和响应对不上,所以dubbo协议成为了Dubbo框架中的默认协议。
而dubbo协议也有自己的格式,比如:
dubbo协议一旦涉及到跨RPC框架,比如一个Dubbo服务要调用gPRC服务,就比较麻烦了,因为发一个dubbo协议的请求给一个gPRC服务,gPRC服务只会按照gRPC的格式来解析字节流,最终肯定会解析不成功的。
dubbo协议虽好,但是不够通用,所以这就出现了Triple协议,Triple协议是基于HTTP2,没有性能问题,另外HTTP协议非常通用,全世界都认它,兼容起来也比较简单,而且还有很多额外的功能,比如流式调用。
HTTP2是HTTP1的升级版,完全兼容HTTP1,而且HTTP2协议从设计层面就解决了HTTP1性能低的问题,具体看https://www.cnblogs.com/mrliuzf/p/14596005.html
Google公司开发的gRPC,也基于的HTTP2,目前gRPC是云原生事实上协议标准,包括k8s/etcd等都支持gRPC协议
Dubbo3.0为了能够更方便的和k8s进行通信,在实现Triple的时候也兼容了gRPC,也就是可以用gPRC的客户端调用Dubbo3.0所提供的triple服务,也可以用triple服务调用gRPC的服务
利用HTTP2发送一个请求:
HPACK
算法进行压缩,最终把压缩之后的字节存在帧中的Payload区域,记录好StreamID,最后通过TCP连接把这个HEADERS 帧发送出去对于服务端而言:
对于Triple协议而言,我们主要理解HTTP2中的Stream、HEADERS 帧、DATA 帧就可以了。
QUIC代表”快速UDP Internet连接”,基于UDP的传输层协议,它本身就是Google尝试将TCP协议重写为一种结合了HTTP/2、TCP、UDP和TLS(用于加密)等多种技术的改进技术。
谷歌希望QUIC通信技术逐渐取代TCP和UDP,作为在Internet上移动二进制数据的新选择协议,QUIC 协议的主要目的,是为了整合 TCP 协议的可靠性和 UDP 协议的速度和效率。
由于 TCP 是在操作系统内核和中间件固件中实现的,因此对 TCP 进行重大更改几乎是不可能的(TCP 协议栈通常由操作系统实现,如 Linux、Windows 内核或者其他移动设备操作系统。修改 TCP 协议是一项浩大的工程,因为每种设备、系统的实现都需要更新)。但是,由于 QUIC 建立在 UDP 之上,因此没有这种限制。
采用多路复用 思想,一个连接可以同时承载多个流 ( stream ),同时发起多个请求。 请求间完全独立 ,某个请求阻塞甚至报文出错均不影响其他请求。
QUIC只需要1RTT(Round-Trip Time)的延迟就可以建立可靠安全的连接,相对于TCP+TLS的3次RTT要更加快捷。之后客户端可以在本地缓存加密的认证信息,再次与服务器建立连接时可以实现0-RTT的连接建立延迟。
TCP 采用 重传 机制,而 QUIC 采用 纠错 机制。
TCP 发生丢包时,需要一个等待延时判断发生了丢包,然后再启动重传机制,这个过程会造成一定的阻塞,影响传输时间。
而 QUIC 则采用一种更主动的方案,有点类似 RAID5 ,每 n 个包额外发一个 校验和包 。 如果这 n 个包中丢了一个包,可以通过其他包和校验和恢复出来,完全不需要重传。
QUIC 直接基于客户端(应用进程)实现,而非基于内核,可以快速迭代更新,不需要操作系统层面的改造,部署灵活。
连接保持
QUIC 在客户端保存连接标识,当客户端 IP 或者端口发生变化时,可以快速恢复连接 —— 客户端以标识请求服务端,服务端验证标识后感知客户端新地址端口并重新关联,继续通讯。 这对于改善移动端应用连接体验意义重大(从 WiFi 切换到流量)。
pom.xml
<dependency>
<groupId>com.demogroupId>
<artifactId>commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>3.0.7version>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-rpc-dubboartifactId>
<version>3.0.7version>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-rpc-tripleartifactId>
<version>3.0.7version>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-registry-zookeeperartifactId>
<version>3.0.7version>
dependency>
application.properties
server.port=7070
dubbo.application.name=provider-application
#dubbo.protocol.name=dubbo
#dubbo.protocol.port=20880
dubbo.protocol.name=tri
dubbo.protocol.port=50051
#
##dubbo.protocol.p1.name=dubbo
##dubbo.protocol.p1.port=20880
##
##dubbo.protocol.p2.name=rest
##dubbo.protocol.p2.port=8082
#
dubbo.registry.address=zookeeper://zookeeper.localhost.com:2181
log4j.properties
###set log levels###
log4j.rootLogger=info, stdout
###output to the console###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%d{dd/MM/yy hh:mm:ss:sss z}] %t %5p %c{2}: %m%n
ProviderApplication.java
UserServiceImpl.java
@SpringBootApplication
@EnableDubbo(scanBasePackages = "com.demo.provider.service")
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
@DubboService
public class UserServiceImpl implements UserService {
public User getUser(String uid) {
User user = new User(uid, "men-dd");
return user;
}
}
pom.xml
<dependency>
<groupId>com.demogroupId>
<artifactId>commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>3.0.7version>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-rpc-dubboartifactId>
<version>3.0.7version>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-rpc-tripleartifactId>
<version>3.0.7version>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-registry-zookeeperartifactId>
<version>3.0.7version>
dependency>
application.properties
server.port=7100
dubbo.application.name=consumer-application
dubbo.registry.address=zookeeper://zookeeper.localhost.com:2181
ConsumerApplication.java
OrderService.java
@SpringBootApplication
public class ConsumerApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
@Service
public class OrderService {
@Resource
private RestTemplate restTemplate;
@DubboReference
private UserService userService;
public String createOrder() {
// User user = restTemplate.getForObject("http://localhost:7070/user/1", User.class);
User user = userService.getUser("1");
System.out.println("用户创建订单" + user);
return user.toString() + " created success";
}
}
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-commonartifactId>
<version>3.0.7version>
dependency>
public interface UserService {
public User getUser(String uid);
// UNARY 正常使用
public User getUserTriple(String uid);
// SERVER_STREAM 系统
default void getUserServerStream(String name, StreamObserver<String> response) {
}
// CLIENT_STREAM / BI_STREAM 双端
default StreamObserver<String> getUserStream(StreamObserver<String> response) {
return response;
}
}
UserServiceImpl.java
@DubboService
public class UserServiceImpl implements UserService {
public User getUser(String uid) {
User user = new User(uid, "men-dd");
return user;
}
@Override
public User getUserTriple(String uid) {
User user = new User(uid, "triple-men-dd");
return user;
}
@Override
public void getUserServerStream(String name, StreamObserver<String> response) {
System.out.println("triple-server-处理请求1 :" + name);
response.onNext("triple-server-返回结果1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("triple-server-处理请求2 :" + name);
response.onNext("triple-server-返回结果2");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("triple-server-处理请求3 :" + name);
response.onNext("triple-server-返回结果3");
response.onCompleted();//所有的响应结果返回完毕
}
@Override
public StreamObserver<String> getUserStream(final StreamObserver<String> response) {
return new StreamObserver<String>() {
@Override
public void onNext(String data) {
System.out.println(DateUtil.nowDate() + " --- " + "triple-stream-接收到客户端请求数据: " + data);
response.onNext(DateUtil.nowDate() + " --- " + "triple-stream result: " + data);
}
@Override
public void onError(Throwable throwable) {
System.out.println(DateUtil.nowDate() + " --- " + "triple-stream throwable " + throwable.getMessage());
}
@Override
public void onCompleted() {
System.out.println(DateUtil.nowDate() + " --- " + "triple-stream completed");
}
};
}
}
OrderService.java
@Service
public class OrderService {
@Resource
private RestTemplate restTemplate;
@DubboReference
private UserService userService;
public String createOrder() {
// User user = restTemplate.getForObject("http://localhost:7070/user/1", User.class);
User user = userService.getUserTriple("1");
System.out.println("用户创建订单" + user);
return user.toString() + " created success";
}
//正常的调用方法
public String createOrderTriple() {
User user = userService.getUserTriple("2");
System.out.println("用户创建订单" + user);
return user.toString() + " created success";
}
//服务端流
public String createOrderTripleServer() {
String uid = "3";
final StringBuffer stringBuffer = new StringBuffer();
System.out.println(DateUtil.nowDate() + " --- " + uid);
stringBuffer.append(DateUtil.nowDate() + " --- " + uid + "\n");
userService.getUserServerStream(uid, new StreamObserver<String>() {
@Override
public void onNext(String data) {
System.out.println(DateUtil.nowDate() + " --- " + "triple-server-接收到响应数据: " + data);
stringBuffer.append(DateUtil.nowDate() + " --- " + data + "\n");
}
@Override
public void onError(Throwable throwable) {
System.out.println(DateUtil.nowDate() + " --- " + "triple-server-接收到响应数据异常:" + throwable.getMessage());
}
@Override
public void onCompleted() {
System.out.println(DateUtil.nowDate() + " --- " + "triple-server-接收到响应数据完毕" );
}
});
return stringBuffer + "created success";
}
//双端流
public String createOrderTripleStream() {
String uid = "4";
final StringBuffer stringBuffer = new StringBuffer();
System.out.println(DateUtil.nowDate() + " --- " + uid);
stringBuffer.append(DateUtil.nowDate() + " --- " + uid + "\n");
StreamObserver<String> streamObserver = userService.getUserStream(new StreamObserver<String>() {
@Override
public void onNext(String data) {
System.out.println(DateUtil.nowDate() + " --- " + "triple-stream-接收到服务端响应数据: " + data);
stringBuffer.append(DateUtil.nowDate() + " --- " + data + "\n");
}
@Override
public void onError(Throwable throwable) {
System.out.println(DateUtil.nowDate() + " --- " + "triple-stream-接收到响应数据异常:" + throwable.getMessage());
}
@Override
public void onCompleted() {
System.out.println(DateUtil.nowDate() + " --- " + "triple-stream-接收到响应数据完毕" );
}
});
streamObserver.onNext("request step 1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
streamObserver.onNext("request step 2");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
streamObserver.onNext("request step 3");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
streamObserver.onCompleted();
return stringBuffer + "created success";
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-dubbo-demoartifactId>
<groupId>com.demogroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>grpc-providerartifactId>
<name>grpc-providername>
<url>http://www.example.comurl>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.7maven.compiler.source>
<maven.compiler.target>1.7maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>3.0.7version>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-rpc-dubboartifactId>
<version>3.0.7version>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-rpc-tripleartifactId>
<version>3.0.7version>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-registry-zookeeperartifactId>
<version>3.0.7version>
dependency>
<dependency>
<groupId>com.google.protobufgroupId>
<artifactId>protobuf-javaartifactId>
<version>3.3.0version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.7.0version>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
<plugin>
<groupId>org.xolstice.maven.pluginsgroupId>
<artifactId>protobuf-maven-pluginartifactId>
<version>0.6.1version>
<configuration>
<protocExecutable>protocprotocExecutable>
<protoSourceRoot>../grpc-provider/src/protoprotoSourceRoot>
<protocArtifact>com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier}protocArtifact>
<outputDirectory>${project.basedir}/src/build/generated/source/proto/main/javaoutputDirectory>
<clearOutputDirectory>falseclearOutputDirectory>
<protocPlugins>
<protocPlugin>
<id>dubboid>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-compilerartifactId>
<version>0.0.3version>
<mainClass>org.apache.dubbo.gen.dubbo.Dubbo3GeneratormainClass>
protocPlugin>
protocPlugins>
configuration>
<executions>
<execution>
<goals>
<goal>compilegoal>
<goal>test-compilegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
project>
syntax = "proto3";
import "google/protobuf/wrappers.proto";
package api;
option go_package = "./;api";
option java_multiple_files = true;
option java_package = "com.demo";
option java_outer_classname = "UserServiceProto";
service UserService {
rpc GetUser (UserRequest) returns (User) {}
rpc ServerGetUser (UserRequest) returns (stream User) {}
rpc ClientGetUser (stream UserRequest) returns (User) {}
rpc BiGetUser (stream UserRequest) returns (stream User) {}
}
// The response message containing the greetings
message UserRequest {
string uid = 1;
}
// The response message containing the greetings
message User {
string uid = 1;
string username = 2;
}
@DubboService
public class UserServiceImpl implements UserService{
@Override
public User getUser(UserRequest request) {
User user = User.newBuilder().setUid(request.getUid()).setUsername("men-dd").build();
return user;
}
@Override
public void serverGetUser(UserRequest request, StreamObserver<User> responseObserver) {
}
@Override
public StreamObserver<UserRequest> clientGetUser(StreamObserver<User> responseObserver) {
return null;
}
@Override
public StreamObserver<UserRequest> biGetUser(StreamObserver<User> responseObserver) {
return null;
}
}
同grpc-provider 步骤1、2、3、4
@Service
public class OrderService {
@DubboReference
private UserService userService;
public String createOrder() {
User user = userService.getUser(UserRequest.newBuilder().setUid("1").build());
return user.toString() + " success";
}
}
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
go get -u github.com/dubbogo/tools/cmd/protoc-gen-go-triple
go install github.com/golang/protobuf/protoc-gen-go
go install github.com/dubbogo/tools/cmd/protoc-gen-go-triple
cp $GOPATH/bin/protoc-gen-go /usr/local/bin
cp $GOPATH/bin/protoc-gen-go-triple /usr/local/bin
protoc -I. test.proto --go_out=../api --go-triple_out=../api
go mod tidy
syntax = "proto3";
package api;
option go_package = "./;api";
option java_multiple_files = true;
option java_package = "com.demo.service";
option java_outer_classname = "UserServiceProto";
service UserService {
rpc GetUser (UserRequest) returns (User) {}
rpc ServerGetUser (UserRequest) returns (stream User) {}
rpc ClientGetUser (stream UserRequest) returns (User) {}
rpc BiGetUser (stream UserRequest) returns (stream User) {}
}
// The response message containing the greetings
message UserRequest {
string uid = 1;
}
// The response message containing the greetings
message User {
string uid = 1;
string username = 2;
}
dubbo:
registries:
demoZk:
protocol: zookeeper
address: zookeeper.local.com:2181
consumer:
references:
UserServiceClientImpl:
protocol: tri
interface: com.demo.service.UserService
consumer.go
package main
import (
"context"
"dubbo.apache.org/dubbo-go/v3/common/logger"
"dubbo.apache.org/dubbo-go/v3/config"
_ "dubbo.apache.org/dubbo-go/v3/imports"
"go-consumer/api"
)
var userServiceImpl = new(api.UserServiceClientImpl)
// export DUBBO_GO_CONFIG_PATH=conf/dubbogo.yml
func main() {
config.SetConsumerService(userServiceImpl)
config.Load()
logger.Info("start to test dubbo")
req := &api.UserRequest{
Uid: "1",
}
user, err := userServiceImpl.GetUser(context.Background(), req)
if err != nil {
logger.Error(err)
}
logger.Infof("client response result: %v\n", user)
}