官方文档http://dubbo.apache.org/zh-cn/docs/user/preface/background.html
github: https://github.com/apache/incubator-dubbo.git
本文章的特点是在官方文档内容的基础上加入了一些说明和一些代码示列的文章链接
dubbo进阶:https://www.jianshu.com/p/4e1c440a4ad2
dubbo
// 服务提供者暴露服务配置
ServiceConfig service = new ServiceConfig(); // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
service.setApplication(application);
service.setRegistry(registry); // 多个注册中心可以用setRegistries()
service.setProtocol(protocol); // 多个协议可以用setProtocols()
service.setInterface(XxxService.class);
service.setRef(xxxService);
service.setVersion("1.0.0");
// 暴露及注册服务
service.export();
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Provider {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"http://10.20.160.198/wiki/display/dubbo/provider.xml"});
context.start();
System.in.read(); // 按任意键退出
}
}
import com.alibaba.dubbo.rpc.config.ApplicationConfig;
import com.alibaba.dubbo.rpc.config.RegistryConfig;
import com.alibaba.dubbo.rpc.config.ConsumerConfig;
import com.alibaba.dubbo.rpc.config.ReferenceConfig;
import com.xxx.XxxService;
// 当前应用配置
ApplicationConfig application = new ApplicationConfig();
application.setName("yyy");
// 连接注册中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("10.20.130.230:9090");
registry.setUsername("aaa");
registry.setPassword("bbb");
// 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
// 引用远程服务
ReferenceConfig reference = new ReferenceConfig(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
reference.setApplication(application);
reference.setRegistry(registry); // 多个注册中心可以用setRegistries()
reference.setInterface(XxxService.class);
reference.setVersion("1.0.0");
// 和本地bean一样使用xxxService
XxxService xxxService = reference.get(); // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用
通过 Spring 配置引用远程服务 consumer.xml:
注册中心
或
或
会缺省配置
会缺省配置
注:注册中心不可用时dubbo会使用本地缓存服务列表。
dubbo服务本地缓存,链接https://www.jianshu.com/p/75931e545b36
服务提供方
import com.alibaba.dubbo.config.annotation.Service;
@Service(timeout = 5000)
public class AnnotateServiceImpl implements AnnotateService {
// ...
}
@Configuration
public class DubboConfiguration {
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("provider-test");
return applicationConfig;
}
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
registryConfig.setClient("curator");
return registryConfig;
}
}
@SpringBootApplication
@DubboComponentScan(basePackages = "com.alibaba.dubbo.test.service.impl")
public class ProviderTestApp {
// ...
}
服务消费方
public class AnnotationConsumeService {
@com.alibaba.dubbo.config.annotation.Reference
public AnnotateService annotateService;
// ...
}
@Configuration
public class DubboConfiguration {
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("consumer-test");
return applicationConfig;
}
@Bean
public ConsumerConfig consumerConfig() {
ConsumerConfig consumerConfig = new ConsumerConfig();
consumerConfig.setTimeout(3000);
return consumerConfig;
}
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");
registryConfig.setClient("curator");
return registryConfig;
}
}
@SpringBootApplication
@DubboComponentScan(basePackages = "com.alibaba.dubbo.test.service")
public class ConsumerTestApp {
// ...
}
启动时检查
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"
关闭所有服务的启动时检查 (没有提供者时报错):
关闭注册中心启动时检查 (注册订阅失败时报错):
dubbo.reference.com.foo.BarService.check=false
dubbo.reference.check=false
dubbo.consumer.check=false
dubbo.registry.check=false
java -Ddubbo.reference.com.foo.BarService.check=false
java -Ddubbo.reference.check=false
java -Ddubbo.consumer.check=false
java -Ddubbo.registry.check=false
负载均衡
随机、轮循、最少活跃调用数、一致性
配置
线程模型
```
```
直连提供者 仅用于测试
在 JVM 启动参数中加入-D参数映射服务地址 [,如:
java -Dcom.alibaba.xxx.XxxService=dubbo://localhost:20890
-Ddubbo.resolve.file
指定映射文件路径,此配置优先级高于
中的配置 ,如:java -Ddubbo.resolve.file=xxx.properties
然后在映射文件 xxx.properties
中加入配置,其中 key 为服务名,value 为服务提供者 URL:
com.alibaba.xxx.XxxService=dubbo://localhost:20890
只订阅
或
只注册
或
静态服务
或
多协议
dubbo://、rmi://、hessian://、http://、webservice://、thrift://、memcached://、redis://
各个协议性能对比,链接https://www.cnblogs.com/lengfo/p/4293399.html
不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议
多注册中心
注:竖号分隔表示同时连接多个不同注册中心
服务分组
可以用分组区分一个接口的多个实现
代码时示例,转自https://blog.csdn.net/u010317829/article/details/52150118
任意组
多版本
如果不需要区分版本,可以按照以下的方式配置
分组聚合
搜索所有分组
合并指定分组
指定方法合并结果,其它未指定的方法,将只调用一个 Group
某个方法不合并结果,其它都合并结果
指定合并策略,缺省根据返回值类型自动匹配,如果同一类型有两个合并器时,需指定合并器的名称
指定合并方法,将调用返回结果的指定方法进行合并,合并方法的参数类型必须是返回结果类型本身
参数验证
javax.validation
validation-api
1.0.0.GA
org.hibernate
hibernate-validator
4.2.0.Final
public class ValidationParameter implements Serializable {
private static final long serialVersionUID = 7158911668568000392L;
@NotNull // 不允许为空
@Size(min = 1, max = 20) // 长度或大小范围
private String name;
@NotNull(groups = ValidationService.Save.class) // 保存时不允许为空,更新时允许为空 ,表示不更新该字段
@Pattern(regexp = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$")
private String email;
...
}
public interface ValidationService { // 缺省可按服务接口区分验证场景,如:@NotNull(groups = ValidationService.class)
@interface Save{} // 与方法同名接口,首字母大写,用于区分验证场景,如:@NotNull(groups = ValidationService.Save.class),可选
void save(ValidationParameter parameter);
void update(ValidationParameter parameter);
}
import javax.validation.GroupSequence;
public interface ValidationService {
@GroupSequence(Update.class) // 同时验证Update组规则
@interface Save{}
void save(ValidationParameter parameter);
@interface Update{}
void update(ValidationParameter parameter);
}
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
public interface ValidationService {
void save(@NotNull ValidationParameter parameter); // 验证参数不为空
void delete(@Min(1) int id); // 直接对基本类型参数验证
}
try {
parameter = new ValidationParameter();
validationService.save(parameter);
System.out.println("Validation ERROR");
} catch (RpcException e) { // 抛出的是RpcException
ConstraintViolationException ve = (ConstraintViolationException) e.getCause(); // 里面嵌了一个ConstraintViolationException
Set> violations = ve.getConstraintViolations(); // 可以拿到一个验证错误详细信息的集合
System.out.println(violations);
}
结果缓存
缓存类型
lru
基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。threadlocal
当前线程缓存,比如一个页面渲染,用到很多 portal,每个 portal 都要去查用户信息,通过线程缓存,可以减少这种多余访问。jcache
与 JSR107 集成,可以桥接各种缓存实现。配置
使用泛化调用
GenericService barService = (GenericService) applicationContext.getBean("barService");
Object result = barService.$invoke("sayHello", new String[] { "java.lang.String" }, new Object[] { "World" });
// 声明为泛化接口
reference.setGeneric(true);
// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用
GenericService genericService = reference.get();
// 基本类型以及Date,List,Map等不需要转换,直接调用
Object result = genericService.$invoke("sayHello", new String[] {"java.lang.String"}, new Object[] {"world"});
// 用Map表示POJO参数,如果返回值为POJO也将自动转成Map
Map person = new HashMap();
person.put("name", "xxx");
person.put("password", "yyy");
// 如果返回POJO将自动转成Map
Object result = genericService.$invoke("findPerson", new String[]
{"com.xxx.Person"}, new Object[]{person});
实现泛化调用
...
// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口实现
GenericService xxxService = new XxxGenericService();
// 该实例很重量,里面封装了所有与注册中心及服务提供方连接,请缓存
ServiceConfig service = new ServiceConfig();
// 弱类型接口名
service.setInterface("com.xxx.XxxService");
service.setVersion("1.0.0");
// 指向一个通用服务实现
service.setRef(xxxService);
// 暴露及注册服务
service.export();
回声测试
所有服务自动实现 EchoService 接口,只需将任意服务引用强制转型为 EchoService,即可使用。
注:dubbo里的EchoFilter拦截到$echo方法后直接返回以一个参数
dubbo filter,链接https://zhuanlan.zhihu.com/p/98625894
filter实现链路追踪,链接https://blog.csdn.net/java_66666/article/details/82627773
// 远程服务引用
MemberService memberService = ctx.getBean("memberService");
EchoService echoService = (EchoService) memberService; // 强制转型为EchoService
// 回声测试可用性
String status = echoService.$echo("OK");
assert(status.equals("OK"));
上下文信息
// 远程调用
xxxService.xxx();
// 本端是否为消费端,这里会返回true
boolean isConsumerSide = RpcContext.getContext().isConsumerSide();
// 获取最后一次调用的提供方IP地址
String serverIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();
// 本端是否为提供端,这里会返回true
boolean isProviderSide = RpcContext.getContext().isProviderSide();
// 获取调用方IP地址
String clientIP = RpcContext.getContext().getRemoteHost();
// 获取当前服务配置信息,所有配置信息都将转换为URL的参数
String application = RpcContext.getContext().getUrl().getParameter("application");
// 注意:每发起RPC调用,上下文状态会变化
yyyService.yyy();
// 此时本端变成消费端,这里会返回false
boolean isProviderSide = RpcContext.getContext().isProviderSide();
隐式参数
RpcContext.getContext().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用
// 获取客户端隐式传入的参数,用于框架集成,不建议常规业务使用
String index = RpcContext.getContext().getAttachment("index");
异步调用
调用代码:
// 此调用会立即返回null
fooService.findFoo(fooId);
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
Future fooFuture = RpcContext.getContext().getFuture();
// 此调用会立即返回null
barService.findBar(barId);
// 拿到调用的Future引用,当结果返回后,会被通知和设置到此Future
Future barFuture = RpcContext.getContext().getFuture();
*=本地调用 本地调用使用了 injvm 协议,是一个伪协议,它不开启端口,不发起远程调用,只在 JVM 内直接关联,但执行 Dubbo 的 Filter 链。
优先使用 injvm
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:consumer.xml");
context.start();
CallbackService callbackService = (CallbackService) context.getBean("callbackService");
callbackService.addListener("http://10.20.160.198/wiki/display/dubbo/foo.bar", new CallbackListener(){
public void changed(String msg) {
System.out.println("callback1:" + msg);
}
});
事件通知 在调用之前、调用之后、出现异常时,会触发 oninvoke、onreturn、onthrow 三个事件,可以配置当事件发生时,通知哪个类的哪个方法
本地存根
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub ,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
配置
或
提供 Stub 的实现
package com.foo;
public class BarServiceStub implements BarService {
private final BarService barService;
// 构造函数传入真正的远程代理对象
public (BarService barService) {
this.barService = barService;
}
public String sayHello(String name) {
// 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
try {
return barService.sayHello(name);
} catch (Exception e) {
// 你可以容错,可以做任何AOP拦截事项
return "容错数据";
}
}
}
本地伪装
本地伪装通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
或
实现
package com.foo;
public class BarServiceMock implements BarService {
public String sayHello(String name) {
// 你可以伪造容错数据,此方法只在出现RpcException时被执行
return "容错数据";
}
}
简单返回null
服务降级,链接http://zhangjiaheng.cn/blog/20190412/dubbo%20%E6%9C%8D%E5%8A%A1%E9%99%8D%E7%BA%A7
延迟暴露
Spring 2.x 初始化死锁问题 applicationContext.getBean()
在spring为加载完成bean时调用可能导致死锁
规避办法
applicationContext.getBean()
的调用,全部采用 IoC 注入的方式使用 Spring的Bean。getBean()
,可以将 Dubbo 的配置放在 Spring 的最后加载。
,使 Dubbo 在 Spring 容器初始化完后,再暴露服务。getBean()
,相当于已经把 Spring 退化为工厂模式在用,可以将 Dubbo 的服务隔离单独的 Spring 容器。**并发控制**
- 限制 com.foo.BarService 的每个方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个:
```
```
- 限制 com.foo.BarService 的 sayHello 方法,服务器端并发执行(或占用线程池线程数)不能超过 10 个:
```
```
- 限制 com.foo.BarService 的每个方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个:
```
```
- 限制 com.foo.BarService 的 sayHello 方法,每客户端并发执行(或占用连接的请求数)不能超过 10 个:
```
```
```
```
- Load Balance
配置服务的客户端的 loadbalance 属性为 leastactive,此 Loadbalance 会调用并发数最小的 Provider(Consumer端并发数)
```
```
或
```
```
连接控制
延迟连接 延迟连接用于减少长连接数。当有调用发起时,再创建长连接
粘滞连接
令牌验证
通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者
可以全局设置开启令牌验证:
也可在服务级别设置:
或
还可在协议级别设置:
或
路由规则
路由规则决定一次 dubbo 服务调用的目标服务器,分为条件路由规则和脚本路由规则,并且可扩展
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("condition://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("host = 10.20.153.10 => host = 10.20.153.11") + "));
condition:// 表示路由规则的类型,支持条件路由规则和脚本路由规则,可扩展,必填。
0.0.0.0 表示对所有 IP 地址生效,如果只想对某个 IP 的生效,请填入具体 IP,必填。
com.foo.BarService 表示只对指定服务生效,必填。
group=foo 对指定服务的指定group生效,不填表示对未配置group的指定服务生效
version=1.0对指定服务的指定version生效,不填表示对未配置version的指定服务生效
category=routers 表示该数据为动态配置类型,必填。
dynamic=false 表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心,必填。
enabled=true 覆盖规则是否生效,可不填,缺省生效。
force=false 当路由结果为空时,是否强制执行,如果不强制执行,路由结果为空的路由规则将自动失效,可不填,缺省为 false。
runtime=false 是否在每次调用时执行路由规则,否则只在提供者地址列表变更时预先执行并缓存结果,调用时直接从缓存中获取路由结果。如果用了参数路由,必须设为 true,需要注意设置会影响调用的性能,可不填,缺省为 false。
priority=1 路由规则的优先级,用于排序,优先级越大越靠前执行,可不填,缺省为 0。
rule=URL.encode("host = 10.20.153.10 => host = 10.20.153.11") 表示路由规则的内容,必填。
脚本路由规则
"script://0.0.0.0/com.foo.BarService?category=routers&dynamic=false&rule=" + URL.encode("(function route(invokers) { ... } (invokers))")
(function route(invokers) {
var result = new java.util.ArrayList(invokers.size());
for (i = 0; i < invokers.size(); i ++) {
if ("10.20.153.10".equals(invokers.get(i).getUrl().getHost())) {
result.add(invokers.get(i));
}
}
return result;
} (invokers)); // 表示立即执行方法
dubbo路由规则,链接http://www.mamicode.com/info-detail-2011472.html
优雅停机
Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,通过 kill PID
停机
设置优雅停机超时时间,缺省超时时间是 10 秒,如果超时则强制关闭
# dubbo.properties
dubbo.service.shutdown.wait=15000
ProtocolConfig.destroyAll();
主机绑定
如果是 127.* 等 loopback 地址,则扫描各网卡,获取网卡 IP
注册的地址如果获取不正确,在 /etc/hosts 中加入:机器名 公网 IP 在 dubbo.xml 中加入主机地址的配置:
或dubbo.protocol.host=205.182.23.201
日志适配
自 2.2.1 开始,dubbo 开始内置 log4j、slf4j、jcl、jdk 这些日志框架的适配,也可以通过以下方式显示配置日志输出策略:
java -Ddubbo.application.logger=log4j
dubbo.application.logger=log4j
ReferenceConfig 缓存
ReferenceConfig 实例很重,封装了与注册中心的连接以及与提供者的连接,需要缓存。否则重复生成 ReferenceConfig 可能造成性能问题并且会有内存和连接泄漏。在 API 方式编程时,容易忽略此问题。
因此,自 2.4.0 版本开始, dubbo 提供了简单的工具类 ReferenceConfigCache用于缓存 ReferenceConfig 实例。
使用方式如下:
ReferenceConfig reference = new ReferenceConfig();
reference.setInterface(XxxService.class);
reference.setVersion("1.0.0");
......
ReferenceConfigCache cache = ReferenceConfigCache.getCache();
// cache.get方法中会缓存 Reference对象,并且调用ReferenceConfig.get方法启动ReferenceConfig
XxxService xxxService = cache.get(reference);
// 注意! Cache会持有ReferenceConfig,不要在外部再调用ReferenceConfig的destroy方法,导致Cache内的ReferenceConfig失效!
// 使用xxxService对象
xxxService.sayHello();
消除 Cache 中的 ReferenceConfig,将销毁 ReferenceConfig 并释放对应的资源
ReferenceConfigCache cache = ReferenceConfigCache.getCache();
cache.destroy(reference);
分布式事务
分布式事务基于 JTA/XA 规范实现
线程栈自动dump
当业务线程池满时,我们需要知道线程都在等待哪些资源、条件,以找到系统的瓶颈点或异常点。dubbo通过Jstack自动导出线程堆栈来保留现场,方便排查问题
默认策略:
# dubbo.properties
dubbo.application.dump.directory=/tmp
Netty4
dubbo 2.5.6版本新增了对netty4通信模块的支持,启用方式如下
provider端:
或
consumer端:
在Dubbo中使用高效的Java序列化(Kryo和FST)
要让Kryo和FST完全发挥出高性能,最好将那些需要被序列化的类注册到dubbo系统中,例如,我们可以实现如下回调接口:
public class SerializationOptimizerImpl implements SerializationOptimizer {
public Collection getSerializableClasses() {
List classes = new LinkedList();
classes.add(BidRequest.class);
classes.add(BidResponse.class);
classes.add(Device.class);
classes.add(Geo.class);
classes.add(Impression.class);
classes.add(SeatBid.class);
return classes;
}
}
然后在XML配置中添加:
默认注册了的类包括:
GregorianCalendar
InvocationHandler
BigDecimal
BigInteger
Pattern
BitSet
URI
UUID
HashMap
ArrayList
LinkedList
HashSet
TreeSet
Hashtable
Date
Calendar
ConcurrentHashMap
SimpleDateFormat
Vector
BitSet
StringBuffer
StringBuilder
Object
Object[]
String[]
byte[]
char[]
int[]
float[]
double[]
API
com.alibaba.dubbo.config.ServiceConfig
com.alibaba.dubbo.config.ReferenceConfig
com.alibaba.dubbo.config.ProtocolConfig
com.alibaba.dubbo.config.RegistryConfig
com.alibaba.dubbo.config.MonitorConfig
com.alibaba.dubbo.config.ApplicationConfig
com.alibaba.dubbo.config.ModuleConfig
com.alibaba.dubbo.config.ProviderConfig
com.alibaba.dubbo.config.ConsumerConfig
com.alibaba.dubbo.config.MethodConfig
com.alibaba.dubbo.config.ArgumentConfig
com.alibaba.dubbo.config.annotation.Service
com.alibaba.dubbo.config.annotation.Reference
com.alibaba.dubbo.common.URL
com.alibaba.dubbo.rpc.RpcException
com.alibaba.dubbo.rpc.RpcContext
com.alibaba.dubbo.rpc.service.GenericService
com.alibaba.dubbo.rpc.service.GenericException