关闭某个服务的启动时检查(没有提供者时报错):
dubbo.reference.com.foo.BarService.check=falsedubbo.consumer.check=falsedubbo.registry.check=false通过-D参数
java-Ddubbo.reference.com.foo.BarService.check=falsejava-Ddubbo.consumer.check=falsejava-Ddubbo.registry.check=false6.2直连提供者Dubbo中点对点的直连方式
在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A接口配置点对点,不影响B接口从注册中心获取列表。
配置:
在集群调用失败时,Dubbo提供了多种容错方案,缺省为failover重试。
各节点关系:
FailoverCluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过retries="2"来设置重试次数(不含第一次)。该配置为缺省配置
FailfastCluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
FailsafeCluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
FailbackCluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
ForkingCluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过forks="2"来设置最大并行数。
BroadcastCluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
AvailableCluster
调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常。通常用于不需要负载均衡的场景。
@reference(cluster="broadcast",parameters={"broadcast.fail.percent","20"})6.4负载均衡Dubbo提供的集群负载均衡策略
在集群负载均衡时,Dubbo提供了多种均衡策略,缺省为random随机调用。
具体实现上,Dubbo提供的是客户端负载均衡,即由Consumer通过负载均衡算法得出需要将请求提交到哪个Provider实例。
负载均衡策略
目前Dubbo内置了如下负载均衡算法,用户可直接配置使用:
算法
特性
备注
RandomLoadBalance
加权随机
默认算法,默认权重相同
RoundRobinLoadBalance
加权轮询
借鉴于Nginx的平滑加权轮询算法,默认权重相同
LeastActiveLoadBalance
最少活跃优先+加权随机
背后是能者多劳的思想,最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差;使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ShortestResponseLoadBalance
最短响应优先+加权随机
ConsistentHashLoadBalance
一致性Hash
一致性Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
当一个接口有多种实现时,可以用group区分。
服务端
@DubboService(group="groupImpl1")publicclassGroupImpl1implementsGroup{@OverridepublicStringdoSomething(Strings){System.out.println("===========GroupImpl1.doSomething");return"GroupImpl1.doSomething";}}代码语言:javascript复制@DubboService(group="groupImpl2")publicclassGroupImpl2implementsGroup{@OverridepublicStringdoSomething(Strings){System.out.println("===========GroupImpl2.doSomething");return"GroupImpl2.doSomething";}}消费端
@DubboReference(check=false,group="groupImpl1"/*,parameters={"merger","true"}*/)Groupgroup;6.6分组聚合通过分组对结果进行聚合并返回聚合后的结果
通过分组对结果进行聚合并返回聚合后的结果,比如菜单服务,用group区分同一接口的多种实现,现在消费方需从每种group中调用一次并返回结果,对结果进行合并之后返回,这样就可以实现聚合菜单项。
生产者配置:
@DubboService(group="groupImpl1")publicclassGroupImpl1implementsGroup{@OverridepublicStringdoSomething(Strings){System.out.println("===========GroupImpl1.doSomething");return"GroupImpl1.doSomething";}}@DubboService(group="groupImpl2")publicclassGroupImpl2implementsGroup{@OverridepublicStringdoSomething(Strings){System.out.println("===========GroupImpl2.doSomething");return"GroupImpl2.doSomething";}}消费者配置:
@DubboReference(check=false,group="*",parameters={"merger","true"})Groupgroup;SPI文件配置在resources下创建META-INF文件夹并在其下面创建dubbo文件夹,然后在dubbo文件夹下面创建org.apache.dubbo.rpc.cluster.Merger文件,在该文件下写好Merger的实现类,如:
publicclassStringMergerimplementsMerger
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
@DubboService(version="1.0.0")publicclassVersionServiceImplimplementsVersionService{@OverridepublicStringversion(Strings){System.out.println("========VersionServiceImpl.1.0.0");return"========VersionServiceImpl.1.0.0";}}@DubboService(version="1.0.1")publicclassVersionServiceImpl1implementsVersionService{@OverridepublicStringversion(Strings){System.out.println("========VersionServiceImpl1.1.0.1");return"========VersionServiceImpl1.1.0.1";}}消费者配置:
@DubboReference(check=false,version="1.0.0")VersionServiceversionService;6.8参数验证在Dubbo中进行参数验证
Maven依赖
publicclassValidationParamterimplementsSerializable{privatestaticfinallongserialVersionUID=32544321432L;@NotNull@Size(min=2,max=20)privateStringname;@Min(18L)@Max(100L)privateintage;@PastprivateDateloginDate;@FutureprivateDateexpiryDate;}生产者:
@DubboServicepublicclassValidationServiceImplimplementsValidationService{@Overridepublicvoidsave(ValidationParamtervalidationParamter){System.out.println("========ValidationServiceImpl.save");}@Overridepublicvoidupdate(ValidationParamtervalidationParamter){System.out.println("========ValidationServiceImpl.update");}@Overridepublicvoiddelete(longl,Strings){System.out.println("========ValidationServiceImpl.delete");}}消费者:
@DubboReference(check=false,validation="true")ValidationServicevalidationService;@Testpublicvoidvalidation(){ValidationParamterparamter=newValidationParamter();paramter.setName("Jack");paramter.setAge(98);paramter.setLoginDate(newDate(System.currentTimeMillis()-10000000));paramter.setExpiryDate(newDate(System.currentTimeMillis()+10000000));validationService.save(paramter);}6.9使用泛化调用选讲做测试的,没有使用场景
实现一个通用的服务测试框架,可通过GenericService调用所有服务实现
泛化接口调用方式主要用于客户端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过GenericService调用所有服务实现。
消费者:
@Testpublicvoidusegeneric(){ApplicationConfigapplicationConfig=newApplicationConfig();applicationConfig.setName("dubbo_consumer");RegistryConfigregistryConfig=newRegistryConfig();registryConfig.setAddress("zookeeper://192.168.67.139:2184");ReferenceConfig
服务接口示例:
publicinterfaceCallbackService{voidaddListener(Stringvar1,CallbackListenervar2);}CallbackListener.java
publicinterfaceCallbackListener{voidchanged(Stringmsg);}生产者:
@DubboService(methods={@Method(name="addListener",arguments={@Argument(index=1,callback=true)})})publicclassCallbackServiceImplimplementsCallbackService{@OverridepublicvoidaddListener(Strings,CallbackListenercallbackListener){//这里就是回调客户端的方法callbackListener.changed(getChanged(s));}privateStringgetChanged(Stringkey){return"Changed:"+newSimpleDateFormat("yyyy-MM-dd:mm:ss").format(newDate());}}消费者:
@DubboReference(check=false)CallbackServicecallbackService;callbackService.addListener("jack",newCallbackListener(){publicvoidchanged(Stringarg0){System.out.println("=====================callbackresult:"+arg0);}});6.11本地存根在Dubbo中利用本地存根在客户端执行部分逻辑
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做ThreadLocal缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在API中带上Stub,客户端生成Proxy实例,会把Proxy通过构造函数传给Stub1,然后把Stub暴露给用户,Stub可以决定要不要去调Proxy。
生产者:
@DubboServicepublicclassStubServiceImplimplementsStubService{@OverridepublicStringstub(Strings){System.out.println("==========本地存根业务逻辑=========");returns;}}消费者:
@DubboReference(check=false,stub="cn.enjoy.stub.LocalStubProxy")StubServicestubService;@Testpublicvoidstub(){System.out.println(stubService.stub("jingtian"));}代理层:
如何在Dubbo中利用本地伪装实现服务降级
生成者:
@DubboServicepublicclassMockServiceImplimplementsMockService{@OverridepublicStringmock(Strings){System.out.println("=======mockservice的业务处理=======");try{Thread.sleep(1000000);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("=======mockservice的业务处理完成=======");return"MockServiceImpl.mock";}@OverridepublicStringqueryArea(Strings){returns;}@OverridepublicStringqueryUser(Strings){returns;}}消费者:
//这两种方式会走rpc远程调用fail--会走远程服务@DubboReference(check=false,mock="true")//@DubboReference(check=false,mock="cn.enjoy.mock.LocalMockService")//不走服务直接降级force--是不会走远程服务的,强制降级..这种方式是用dubbo-admin去配置它,服务治理的方式//@DubboReference(check=false,mock="force:returnjack")MockServicemockService;@Testpublicvoidmock(){System.out.println(mockService.mock("wy"));}代码语言:javascript复制/**MockServiceMock**1、接口名+"Mock"*2、mock逻辑必须定义在接口的包下面*/publicclassMockServiceMockimplementsMockService{@OverridepublicStringmock(Strings){System.out.println(this.getClass().getName()+"--mock");returns;}@OverridepublicStringqueryArea(Strings){System.out.println(this.getClass().getName()+"--queryArea");returns;}@OverridepublicStringqueryUser(Strings){System.out.println(this.getClass().getName()+"--queryUser");returns;}}6.13异步调用在Dubbo中发起异步调用
从2.7.0开始,Dubbo的所有异步编程接口开始以CompletableFuture为基础
基于NIO的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。
代码案例
publicinterfaceAsyncService{StringasynctoDo(Stringvar1);}@DubboServicepublicclassAsyncServiceImplimplementsAsyncService{@OverridepublicStringasynctoDo(Strings){for(inti=0;i<10;i++){System.out.println("===============AsyncServiceImpl.asynctoDo");try{Thread.sleep(100);}catch(InterruptedExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}}return"hello,"+s;}}消费者:
@DubboReference(check=false,timeout=1000000000,methods={@Method(name="asynctoDo",async=true)})AsyncServiceasyncService;@Testpublicvoidasync()throwsInterruptedException{Stringaa=asyncService.asynctoDo("aa");System.out.println("main=="+aa);System.out.println("并行调用其他接口====");try{Thread.sleep(2000);}catch(InterruptedExceptione){e.printStackTrace();}//需要拿到异步调用的返回结果CompletableFuture
Provider端异步执行将阻塞的业务从Dubbo内部线程池切换到业务自定义线程,避免Dubbo线程池的过度占用,有助于避免不同服务间的互相影响。异步执行无异于节省资源或提升RPC响应性能,因为如果业务执行需要阻塞,则始终还是要有线程来负责执行。
注意:
Provider端异步执行和Consumer端异步调用是相互独立的,你可以任意正交组合两端配置
publicinterfaceAsyncService{CompletableFuture
@TestpublicvoidasyncDoone()throwsExecutionException,InterruptedException{CompletableFuture
本地调用使用了injvm协议,是一个伪协议,它不开启端口,不发起远程调用,只在JVM内直接关联,但执行Dubbo的Filter链。
本地调用,调用的就是本地工程的接口实例
示例:
@DubboReference(check=false,injvm=true)StudentServicestudentService;@TestpublicvoidinJvm()throwsInterruptedException{System.out.println(studentService.find("xx"));}6.16粘滞连接为有状态服务配置粘滞连接
粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。
粘滞连接将自动开启延迟连接,以减少长连接数。
sticky=true
@DubboReference(check=false,protocol="dubbo",retries=3,timeout=1000000000,cluster="failover",loadbalance="random",sticky=true,methods={@Method(name="doKill",isReturn=false)}/*,url="dubbo://localhost:20880"*/)UserServiceuserService;6.17Protobuf使用IDL定义服务
当前Dubbo的服务定义和具体的编程语言绑定,没有提供一种语言中立的服务描述格式,比如Java就是定义Interface接口,到了其他语言又得重新以另外的格式定义一遍。2.7.5版本通过支持ProtobufIDL实现了语言中立的服务定义。
1、maven插件支持
在dubbo-p工程的src/main下面创建proto文件夹在里面定义LoginService.proto文件,文件内容如下:
生成文件如下:
4、把生成的文件移到响应的service目录下
生产者代码:
@DubboService@Slf4jpublicclassLoginServiceImplimplementsLoginService{@OverridepublicLoginReplylogin(LoginRequestrequest){log.info("Hello"+request.getUsername()+",requestfromconsumer:"+RpcContext.getContext().getRemoteAddress());returnLoginReply.newBuilder().setMessage("Hello"+request.getUsername()+",responsefromprovider:"+RpcContext.getContext().getLocalAddress()).build();}@OverridepublicCompletableFuture
必须要加上序列化的配置属性,在dubbo-provider.properties中配置
dubbo.service.cn.enjoy.service.login.LoginService.serialization=protobuf消费者:
消费者的1,2,3,4步骤是相同的
消费者代码:
@Testpublicvoidprotobuf()throwsIOException{LoginRequestrequest=LoginRequest.newBuilder().setUsername("jingtian").setPassword("123").build();LoginReplyreply=loginService.login(request);System.out.println(reply.getMessage());}6.18主机绑定在Dubbo中绑定主机名
缺省主机IP查找顺序:
主机配置
注册的地址如果获取不正确,比如需要注册公网地址,可以:
1、可以在/etc/hosts中加入:机器名公网IP,比如:
test1205.182.23.2012、在dubbo.xml中加入主机地址的配置:
dubbo.protocol.host=205.182.23.201端口配置
协议
端口
dubbo
20880
rmi
1099
80
hessian
webservice
memcached
11211
redis
6379
可以按照下面的方式配置端口:
1、在dubbo.xml中加入主机地址的配置:
dubbo.protocol.dubbo.port=208806.19主机配置自定义Dubbo服务对外暴露的主机地址
背景
在Dubbo中,Provider启动时主要做两个事情,一是启动server,二是向注册中心注册服务。启动server时需要绑定socket,向注册中心注册服务时也需要发送socket唯一标识服务地址。
dubbo中不设置host时默认host是什么
一般的dubbo协议配置如下:
...
查看代码发现,在org.apache.dubbo.config.ServiceConfig#findConfigedHosts()中,通过InetAddress.getLocalHost().getHostAddress()获取默认host。其返回值如下:
那在dubbo中如何指定服务的socket
除此之外,可以通过dubbo.protocol或dubbo.provider的host属性对host进行配置,支持IP地址和域名,如下:
...
见dubbo通过环境变量设置host
有些部署场景需要动态指定服务注册的地址,如dockerbridge网络模式下要指定注册宿主机ip以实现外网通信。dubbo提供了两对启动阶段的系统属性,用于设置对外通信的ip、port地址。
以上四个配置项均为可选项,如不配置dubbo会自动获取ip与端口,请根据具体的部署场景灵活选择配置。dubbo支持多协议,如果一个应用同时暴露多个不同协议服务,且需要为每个服务单独指定ip或port,请分别在以上属性前加协议前缀。如:
PORT_TO_REGISTRY或IP_TO_REGISTRY不会用作默认PORT_TO_BIND或IP_TO_BIND,但是反过来是成立的如设置PORT_TO_REGISTRY=20881IP_TO_REGISTRY=30.5.97.6,则PORT_TO_BINDIP_TO_BIND不受影响如果设置PORT_TO_BIND=20881IP_TO_BIND=30.5.97.6,则默认PORT_TO_REGISTRY=20881IP_TO_REGISTRY=30.5.97.6
总结
减少注册中心上服务的注册数据
Dubboprovider中的服务配置项有接近30个配置项。排除注册中心服务治理需要之外,很大一部分配置项是provider自己使用,不需要透传给消费者。这部分数据不需要进入注册中心,而只需要以key-value形式持久化存储。
这些数据是以服务为维度注册进入注册中心,导致了数据量的膨胀,进而引发注册中心(如zookeeper)的网络开销增大,性能降低。
现有功能sample当前现状一个简单展示。通过这个展示,分析下为什么需要做简化配置。
参考sample子工程:dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-nosimple(跑sample前,先跑下ZKClean进行配置项清理)
dubbo-provider.xml配置
dubbo%3A%2F%2F30.5.124.158%3A20880%2Forg.apache.dubbo.samples.simplified.registry.nosimple.api.DemoService%3Fanyhost%3Dtrue%26application%3Dsimplified-registry-xml-provider%26async%3Dtrue%26dubbo%3D2.0.2%26**executes**%3D4500%26generic%3Dfalse%26group%3Ddubbo-simple%26interface%3Dorg.apache.dubbo.samples.simplified.registry.nosimple.api.DemoService%26methods%3DsayHello%26**owner**%3Dvict%26pid%3D2767%26**retries**%3D7%26revision%3D1.2.3%26side%3Dprovider%26**timeout**%3D5300%26timestamp%3D1542361152795%26valid%3Dtrue%26version%3D1.2.3从加粗字体中能看到有:executes,retries,owner,timeout。但是这些字段不是每个都需要传递给dubboops或者dubboconsumer。同样的,consumer也有这个问题,可以在例子中启动Consumer的main方法进行查看。
设计目标和宗旨
期望简化进入注册中心的provider和consumer配置数量。期望将部分配置项以其他形式存储。这些配置项需要满足:不在服务调用链路上,同时这些配置项不在注册中心的核心链路上(服务查询,服务列表)。
配置
简化注册中心的配置,只在2.7之后的版本中进行支持。开启provider或者consumer简化配置之后,默认保留的配置项如下:
ConstantKey
Key
remark
APPLICATION_KEY
application
CODEC_KEY
codec
EXCHANGER_KEY
exchanger
SERIALIZATION_KEY
serialization
CLUSTER_KEY
cluster
CONNECTIONS_KEY
connections
DEPRECATED_KEY
deprecated
GROUP_KEY
group
LOADBALANCE_KEY
loadbalance
MOCK_KEY
mock
PATH_KEY
path
TIMEOUT_KEY
timeout
TOKEN_KEY
token
VERSION_KEY
version
WARMUP_KEY
warmup
WEIGHT_KEY
weight
TIMESTAMP_KEY
timestamp
DUBBO_VERSION_KEY
SPECIFICATION_VERSION_KEY
specVersion
新增,用于表述dubbo版本,如2.7.0
consumer:
ConstantKey表示来自于类org.apache.dubbo.common.Constants的字段。
下面介绍几种常用的使用方式。所有的sample,都可以查看sample-2.7
方式1.配置dubbo.properties
sample在dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-xml工程下(跑sample前,先跑下ZKClean进行配置项清理)
dubbo.properties
dubbo.registry.simplified=truedubbo.registry.extra-keys=retries,owner怎么去验证呢?
provider端验证
provider端配置
dubbo%3A%2F%2F30.5.124.149%3A20880%2Forg.apache.dubbo.samples.simplified.registry.nosimple.api.DemoService%3Fapplication%3Dsimplified-registry-xml-provider%26dubbo%3D2.0.2%26group%3Ddubbo-simple%26**owner**%3Dvict%26**retries**%3D7%26**timeout**%3D5300%26timestamp%3D1542594503305%26version%3D1.2.3和上面的现有功能sample进行对比,上面的sample中,executes,retries,owner,timeout四个配置项都进入了注册中心。但是本实例不是:
总结:timeout,retries,owner进入了注册中心,而executes没有进入。
consumer端配置
sample在dubbo-samples-simplified-registry/dubbo-samples-simplified-registry-annotation工程下(跑sample前,先跑下ZKClean进行配置项清理)
Provider配置
privide端bean配置:
//等同于dubbo.properties配置,用@Bean形式进行配置@BeanpublicRegistryConfigregistryConfig(){RegistryConfigregistryConfig=newRegistryConfig();registryConfig.setAddress("zookeeper://127.0.0.1:2181");registryConfig.setSimplified(true);registryConfig.setExtraKeys("retries,owner");returnregistryConfig;}//暴露服务@Service(version="1.1.8",group="d-test",executes=4500,retries=7,owner="victanno",timeout=5300)publicclassAnnotationServiceImplimplementsAnnotationService{@OverridepublicStringsayHello(Stringname){System.out.println("asyncproviderreceived:"+name);return"annotation:hello,"+name;}}和上面sample中的dubbo.properties的效果是一致的。结果如下:
Consumer配置
consumer端bean配置:
@BeanpublicRegistryConfigregistryConfig(){RegistryConfigregistryConfig=newRegistryConfig();registryConfig.setAddress("zookeeper://127.0.0.1:2181");registryConfig.setSimplified(true);returnregistryConfig;}消费服务:
@Component("annotationAction")publicclassAnnotationAction{@Reference(version="1.1.8",group="d-test",owner="vvvanno",retries=4,actives=6,timeout=4500)privateAnnotationServiceannotationService;publicStringdoSayHello(Stringname){returnannotationService.sayHello(name);}}和上面sample中consumer端的配置是一样的。结果如下:
如果一个应用中既有provider又有consumer,那么配置需要合并成:
@BeanpublicRegistryConfigregistryConfig(){RegistryConfigregistryConfig=newRegistryConfig();registryConfig.setAddress("zookeeper://127.0.0.1:2181");registryConfig.setSimplified(true);//只对provider生效registryConfig.setExtraKeys("retries,owner");returnregistryConfig;}后续规划
本版本还保留了大量的配置项,接下来的版本中,会逐渐删除所有的配置项。
通过上下文存放当前调用过程中所需的环境信息
上下文中存放的是当前调用过程中所需的环境信息。所有配置信息都将转换为URL的参数,参见schema配置参考手册中的对应URL参数一列。
RpcContext是一个ThreadLocal的临时状态记录器,当接收到RPC请求,或发起RPC请求时,RpcContext的状态都会变化。比如:A调B,B再调C,则B机器上,在B调C之前,RpcContext记录的是A调B的信息,在B调C之后,RpcContext记录的是B调C的信息。
//远程调用xxxService.xxx();//本端是否为消费端,这里会返回truebooleanisConsumerSide=RpcContext.getContext().isConsumerSide();//获取最后一次调用的提供方IP地址StringserverIP=RpcContext.getContext().getRemoteHost();//获取当前服务配置信息,所有配置信息都将转换为URL的参数Stringapplication=RpcContext.getContext().getUrl().getParameter("application");//注意:每发起RPC调用,上下文状态会变化yyyService.yyy();生产者:
publicclassXxxServiceImplimplementsXxxService{publicvoidxxx(){//本端是否为提供端,这里会返回truebooleanisProviderSide=RpcContext.getContext().isProviderSide();//获取调用方IP地址StringclientIP=RpcContext.getContext().getRemoteHost();//获取当前服务配置信息,所有配置信息都将转换为URL的参数Stringapplication=RpcContext.getContext().getUrl().getParameter("application");//注意:每发起RPC调用,上下文状态会变化yyyService.yyy();//此时本端变成消费端,这里会返回falsebooleanisProviderSide=RpcContext.getContext().isProviderSide();}}6.22隐式参数通过Dubbo中的Attachment在服务消费方和提供方之间隐式传递参数
可以通过RpcContext上的setAttachment和getAttachment在服务消费方和提供方之间进行参数的隐式传递。
注意
path,group,version,dubbo,token,timeout几个key是保留字段,请使用其它值。