Dubbo实战开发

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
云原生网关 MSE Higress,422元/月
简介: 之前写给团队小伙伴的Dubbo快速入坑指南,上手快、疗效好,一文即够!

一、Dubbo介绍


Dubbo是阿里2011年开源的一款服务化开发框架,中间经过一段时间的停止维护后,终于在2017重启维护,目前已捐献给Apache基金会,称为全球顶级项目。大家通常会将它和SpringCloud做对比,实际上笔者认为,它们虽然能做同样的事情,但各有侧重点和优缺点,本章我们在学习Dubbo的同时,也会点出它们的不同。


1.1 Dubbo架构路线路

目前大型软件工程基本已经告别单体应用,逐步转向服务化、分布式集群,当然这种转向并非一日之功,它是经历了阶段性变化的。


image.png


1.2 Dubbo调用模型

任何服务化都离不开远程调用(通信),下面这个图可以基本说明Java RMI的基本过程,有过早期EJB的同学肯定对其有深刻印象:

image.png



但是这个过程对于工程师来说稍显复杂,而且并非业务核心,目前大部分服务框架(Dubbo、SpringCloud等)都能做到屏蔽远程细节,减轻了工程师的心智负担。对于Dubbo来讲,我们在实际开发中,大部分就跟两个概念打交道(稍显武断),即Consumer、Provider:

image.png

Consumer即消费(调用)端,Provider即服务提供端。


二、注册中心之Zookeeper

通常情况下,Provider会以多服务集群的形式存在,那么Consumer该如何找到Provider呢?答案就是注册中心,Dubbo中推荐的注册中心是Zookeeper(后面简称zk)。


image.png


服务提供者:provider

服务消费者:consumer

服务注册中心:registry

我们可以从官网直接下载其安装包,然后打开conf/zoo.cfg做些配置:

               tickTime=2000 dataDir=/var/lib/zookeeper clientPort=2181              

启动命令:

               bin/zkServer.sh start              

或者

               bin/zkServer.cmd              

zk不是本次学习的重点,我们暂时知道其作用并且会应用即可。


三、Dubbo开发初试


3.1 Dubbo核心代码讲解

首先我们看下Dubbo开发的基础过程及核心代码:

第一步,服务定义及实现:


publicinterfaceGreetingsService {
StringsayHi(Stringname);
}

publicclassGreetingsServiceImplimplementsGreetingsService {
@OverridepublicStringsayHi(Stringname) {
return"hi "+name+" ,welcome to dubbo";
    }
}

第二步,注册服务 为 provider:

privatestaticStringzookeeperHost=System.getProperty("zookeeper.address", "127.0.0.1");
publicstaticvoidmain(String[] args) throwsException {
ServiceConfig<GreetingsService>service=newServiceConfig<>();
service.setApplication(newApplicationConfig("first-dubbo-provider"));
service.setRegistry(newRegistryConfig("zookeeper://"+zookeeperHost+":2181"));
service.setInterface(GreetingsService.class);
service.setRef(newGreetingsServiceImpl());
service.export();
System.out.println("dubbo service started");
newCountDownLatch(1).await();
 }


第三步,定义消费者consumer:


privatestaticStringzookeeperHost=System.getProperty("zookeeper.address", "127.0.0.1");
publicstaticvoidmain(String[] args) {
ReferenceConfig<GreetingsService>reference=newReferenceConfig<>();
reference.setApplication(newApplicationConfig("first-dubbo-consumer"));
reference.setRegistry(newRegistryConfig("zookeeper://"+zookeeperHost+":2181"));
reference.setInterface(GreetingsService.class);
GreetingsServiceservice=reference.get();
Stringmessage=service.sayHi("dubbo");
System.out.println(message);
    }

3.2 Dubbo工程实践

上述代码示例主要暂时开发一款Dubbo应用的主要步骤,但在【实际项目中】,我们会把服务接口放在一个单独的公共包里,这样可以提高重用性,也可使工程结构更加清晰规范。下面我们按照这个格式来做项目结构:


image.png



其中:

dubbo-demo-interface是接口提供方,它是所有【即将暴露出去提供业务逻辑】的接口集合;

dubbo-demo-provider是服务提供方,它负责实现并发布所有业务服务,以便外部接入;

dubbo-demo-consumer是服务消费方,即调用方;

当然,大家在工作中可以根据实际情况来定项目结构,后面我们将以该结构进行延展。

DubboDemo根目录下的父parent中,我们需要将依赖配置成,以便让子模块中可以直接继承依赖版本:


<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><dubbo.version>3.0.3</dubbo.version></properties><dependencyManagement><dependencies><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo</artifactId><version>${dubbo.version}</version></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-dependencies-zookeeper</artifactId><version>${dubbo.version}</version><type>pom</type></dependency></dependencies></dependencyManagement>

3.3 实现服务并注册

首先我们在dubbo-demo-interface定义服务接口:

publicinterfaceHelloService {
publicStringsayHello(Stringname);
}


然后打开dubbo-demo-provider,在pom中引入dubbo-demo-interface及dubbo/zk相关依赖:

<dependencies><dependency><groupId>com.learn.dubbo</groupId><artifactId>dubbo-demo-interface</artifactId><version>${project.parent.version}</version></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo</artifactId></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-dependencies-zookeeper</artifactId><type>pom</type></dependency></dependencies>

现在,我们可以实现业务接口:

@DubboServicepublicclassHelloServiceImplimplementsHelloService {
@OverridepublicStringsayHello(Stringname) {
System.out.println("call sayHello "+name);
return"hello "+name;
    }
}

其中@DubboService是用于定义dubbo服务的注解。然后,新建配置类:

@Configuration@EnableDubbo(scanBasePackages="com.learn.dubbo.provider")
@PropertySource("classpath:/spring/dubbo-provider.properties")
publicclassProviderConfiguration {
}

@EnableDubbo 启用Dubbo注解扫描,scanBasePackages配置扫描包路径

@PropertySource 设置provider常见配置信息,内容如下:

dubbo.application.name=hello-provider 
dubbo.registry.address=zookeeper://127.0.0.1:2181 
dubbo.protocol.name=dubbo 
dubbo.protocol.port=20881


这里指定了zk的地址信息,以便服务能注册进去。dubbo.protocol.port设置成-1,则表示当前服务随机端口,这对服务集群时非常方便(在无须手动设置端口的情况下,避免端口占用问题)


然后,我们可以先启动本项目,使服务注册到注册中心,以便让消费者接入调用:

AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(ProviderConfiguration.class);
context.start();
System.in.read();



3.4 消费服务

打开dubbo-demo-consumer,同样加入如下依赖:

<dependencies><dependency><groupId>com.learn.dubbo</groupId><artifactId>dubbo-demo-interface</artifactId><version>${project.parent.version}</version></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo</artifactId></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-dependencies-zookeeper</artifactId><type>pom</type></dependency></dependencies>

配置类与配置信息

@Configuration@EnableDubbo(scanBasePackages="com.learn.dubbo.consumer")
@PropertySource("classpath:/spring/dubbo-consumer.properties")
@ComponentScan(value= {"com.learn.dubbo.consumer"})
publicclassConsumerConfiguration {
}


和前面的ProviderConfiguration类似,不过配置内容不一样:


dubbo.application.name=hello-consumer 
dubbo.registry.address=zookeeper://127.0.0.1:2181 
dubbo.consumer.timeout=3000


为了让调用逻辑更加清晰,我们新增一个对外的服务类:


@ComponentpublicclassHelloAction {
@DubboReferenceprivateHelloServicehelloService;
publicStringsayHelloAction(Stringname){
returnhelloService.sayHello(name);
    }
}

不难看出,我们使用 @DubboReference注解来做注入,最后,启动该服务并调用之:


AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(ConsumerConfiguration.class);
context.start();
helloService.sayHelloAction("dubbo "+i)

在实际项目中,微服务实例不可能只有一个,也就是说provider一般是以集群的形式出现的,在consumer调用时,请求会路由到不同实例。验证方法也比较简单,首先多次启动provider(注意端口问题),然后让consumer循环调用,即可看出轮询结果。


3.5 负载均衡策略

Random LoadBalance:随机,调用量越大分布越均匀,这是默认配置

RoundRobin LoadBalance:轮询

LeastActive LoadBalance:最少活跃调用数,慢的provider收到较少的请求

ConsistentHash LoadBalance:一致性Hash,相同参数的请求总是发到同一提供者

我们可以在消费端引用时通过注解类配置负载策略:

@DubboReference(loadbalance=LoadbalanceRules.ROUND_ROBIN)

假如接口里面有多个方法,能否采用不同负载策略呢?当然是可以的。

我们可以打开HelloService,再定义一个方法:

publicdefaultStringqueryData(Stringcondition){
return"";
}

default是Java8提供的,用于定义默认实现的接口方法,这里主要是为了避免在更改接口之后,需要强制修改实现类的情况,保证了兼容性。此时我们可以在消费端单独指定该方法的负载策略:

@DubboReference(loadbalance=LoadbalanceRules.ROUND_ROBIN,methods= {
@Method(name="queryData",loadbalance=LoadbalanceRules.RANDOM )
})

此时queryData方法将采用随机负载,而该service的其他方法将采用轮询负载。


四、序列化对象传输

前面我们的示例中,服务方法的参数和返回值都是比较简单的字符串类型,但在实际项目中,几乎都是以更为复杂的对象形式传参,下面我们先演示一个例子。

首先我们在dubbo-demo-interface中新建用于传输的类型,这里给出两个:


/*** 请求实体对象*/publicclassRequestVo {
privateintid;
privateStringname;
/***getter setter略**/}
/*** 响应实体对象*/publicclassResponseVo {
privateintid;
privateStringname;
privateStringdata;
/***getter setter略**/}

然后在HelloService中新增服务方法:

publicdefaultResponseVosaveData(RequestVorequestVo){
returnnull;
}

在provider中实现该方法,此时我们重新启动provider并让consumer调用,会报出以下异常:

image.png



即传输对象未实现序列化接口,不能完成该调用,此时可以让RequestVo、ResponseVo实现java.io.Serializable,即可完成调用。

序列化是将对象转换成二进制流,反序列化是将二进制流转化成对象,在某种程度上,序列化/反序列化会直接影响通信效率。

目前Dubbo支持以下序列化方式:

1. hessian2:阿里基于hessian2改造之后的跨语言的序列化,目前是默认配置;

2. json序列化:目前有两种,即fastjson和dubbo json,但性能一般;

3. java序列化:JDK自带序列化,性能不好;

4. Kryo:目前在开源项目中运用广泛,性能较好;

5. FST: 比较新,性能较好,但缺乏更多成熟案例;


序列化生成字节大小比较


序列化实现

请求字节数

响应字节数

Kryo

272

90

FST

288

96

Dubbo Serialization

430

186

Hessian

546

329

FastJson

461

218

Json

657

409

Java Serialization

963

630


序列化响应时间和吞吐量对比 


远程调用方式

平均响应时间

平均TPS(每秒事务数)

Dubbo: FST

1.211

8244

Dubbo: kyro

1.182

8444

Dubbo: serialization

1.43

6982

Dubbo: hessian2

1.49

6701

Dubbo: fastjson

1.572

6352


五、容错及重试机制


在大规模服务中,总会出现各种异常情况,比如服务卡顿、网络抖动等等,都会导致客户端在调用时出现问题。通常来讲,我们要“面向失败编程”,即提前规划好调用异常时的解决方案。目前,dubbo提供了5中容错模式:

1. Failover 失败时自动切换到下一个服务,常用于读操作

2. Failfast 失败时快速报错,常用于写操作

3. Failsafe 失败时直接忽略,常用于非核心操作,比如日志等

4. Forking 并行调用多个服务器,只要一个成功返回,常用于实时性要求较高的读操作

5. Broadcast 广播调用所有服务,任意一个失败就整体失败,常用于通知所有服务更新数据

在@DubboReference中配置cluster/retries即可:


@DubboReference(
loadbalance=LoadbalanceRules.ROUND_ROBIN,
methods= {
@Method(name="queryData",loadbalance=LoadbalanceRules.RANDOM )}
            ,cluster=ClusterRules.FAIL_OVER,
retries=2)


六、请求过滤器/拦截器


过滤器在早期的Web开发中非常常见,它主要用于提供请求前后自定义处理逻辑,比如登录鉴权、权限路由、日志打印等。这个概念被延伸到常规的业务开发中,很多框架自身都提供了类似前后拦截处理的机制,所以我们也称之为拦截器。Dubbo中的过滤器在执行RPC调用的时候生效,很多核心功能是基于它扩展而来的。Dubbo本身内置了可以直接开箱可用的过滤器,我们可以在resources下的META-INF\dubbo\internal\org.apache.dubbo.rpc.Filter中找到:


echo=org.apache.dubbo.rpc.filter.EchoFilter
generic=org.apache.dubbo.rpc.filter.GenericFilter
genericimpl=org.apache.dubbo.rpc.filter.GenericImplFilter
token=org.apache.dubbo.rpc.filter.TokenFilter
accesslog=org.apache.dubbo.rpc.filter.AccessLogFilter
classloader=org.apache.dubbo.rpc.filter.ClassLoaderFilter
context=org.apache.dubbo.rpc.filter.ContextFilter
exception=org.apache.dubbo.rpc.filter.ExceptionFilter
executelimit=org.apache.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=org.apache.dubbo.rpc.filter.DeprecatedFilter
compatible=org.apache.dubbo.rpc.filter.CompatibleFilter
timeout=org.apache.dubbo.rpc.filter.TimeoutFilter
tps=org.apache.dubbo.rpc.filter.TpsLimitFilter

AccessLogFilter:请求日志过滤器

ExceptionFilter:异常处理过滤器

TpsLimitFilter:服务端限流过滤器

ExecuteLimitFilter:限制服务最大并行调用的过滤器


比如要配置AccessLogFilter生效,可以直接在@DubboService上设置:

@DubboService(accesslog="C:/opt/logs/provider.log")

当然,我也可以自定义过滤器,步骤分为三步:

1.  定义A类,实现com.learn.dubbo.provider.filter接口,并使用@Activate启用

2. 将定义好的A,配置在META-INF/dubbo/org.apache.dubbo.rpc.Filter中

3. provider或者consumer指定配置

下面我们定义一个简单的用于打印请求信息的过滤器测试一下。首先,新建过滤器类:


@Activate(group=PROVIDER)
publicclassPrintRequestInfoFilterimplementsFilter {
@OverridepublicResultinvoke(Invoker<?>invoker, Invocationinvocation) throwsRpcException {
StringmethodName=invocation.getMethodName();
Class[] parameterTypes=invocation.getParameterTypes();
Object[] arguments=invocation.getArguments();
URLurl=invoker.getUrl();
Classcls=invoker.getClass();
System.out.println("serviceName:"+serviceName);
System.out.println("methodName:"+methodName);
System.out.println("parameterTypes:"+Arrays.toString(parameterTypes));
System.out.println("arguments:"+Arrays.toString(arguments));
System.out.println("url: "+url);
System.out.println("cls: "+cls);
returninvoker.invoke(invocation);
    }
}

@Activate中的group指定该过滤器使用范围,这里指定了provider。

然后,在META-INF/dubbo/org.apache.dubbo.rpc.Filter(没有则新建)中配置:


printfilter=com.learn.dubbo.provider.filter.PrintRequestInfoFilter

最后,在使用的地方引用之:

@DubboService(accesslog="C:/opt/logs/provider.log",filter="printfilter")


七、Dubbo Admin 体验


 安装dubbo-admin控制台:


gitclonehttps://github.com/apache/dubbo-admin.gitcddubbo-adminmvncleanpackagecddubbo-admin-distribution/targetjava-Dserver.port=8081-jardubbo-admin-0.3.0.jar


默认账号root/root

image.png

相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
目录
相关文章
|
2月前
|
Dubbo 应用服务中间件 Apache
干翻Dubbo系列第一篇:Dubbo是什么?
干翻Dubbo系列第一篇:Dubbo是什么?
|
缓存 监控 负载均衡
将近2万字的Dubbo原理解析,彻底搞懂dubbo
市面上有很多基于RPC思想实现的框架,比如有Dubbo。今天就从Dubbo的SPI机制、服务注册与发现源码及网络通信过程去深入剖析下Dubbo。
23822 9
|
XML 负载均衡 监控
终于有人从入门到实战把Dubbo讲的这么清清楚楚了
很多时候,其实我们使用这个技术的时候,可能都是因为项目需要,所以,我们就用了,但是,至于为什么我们需要用到这个技术,可能自身并不是很了解的,但是,其实了解技术的来由及背景知识,对于理解一项技术还是有帮助的,那么,dubbo是怎么被提上日程的呢?
|
Dubbo 应用服务中间件
Dubbo 学习笔记 ——Dubbo 官网学习思维导图
Dubbo 学习笔记 ——Dubbo 官网学习思维导图
149 0
|
Dubbo Java 应用服务中间件
dubbo的入门学习(三)springboot整合dubbo
dubbo的入门学习(三)springboot整合dubbo
dubbo的入门学习(三)springboot整合dubbo
|
XML Dubbo Java
dubbo的入门学习(二)
dubbo的入门学习(二)
dubbo的入门学习(二)
|
监控 负载均衡 Dubbo
|
Dubbo Java 应用服务中间件
Dubbo源码学习一
首先,我们知道dubbo在以前都是基于zookeeper作为配置中心的,同时是建立在spring基础之上的。因此,就需要思考一些问题: 首先dubbo是怎样和spring集成的,也即dubbo集成在spring上需要具备什么条件?接着dubbo作为一个服务治理的微服务框架,那它的生产者和消费者与注册中心怎样进行交互的。 dubbo是基于spring的基础之上进行开发的RPC框架。需要和spring整合,必然就需要按照Spring解析默认标签和自定义标签的方式进行。而在Spring中,我们知道在Spring中是在ParseBeanDefintions(Elemen
136 0
Dubbo源码学习一
|
SpringCloudAlibaba 负载均衡 Dubbo
【Dubbo】高级特效以及SpringCloud整合Dubbo
【Dubbo】高级特效以及SpringCloud整合Dubbo
262 0
【Dubbo】高级特效以及SpringCloud整合Dubbo
|
XML Dubbo Java
DUBBO 项目开发|学习笔记
快速学习 DUBBO 项目开发
65 0