博文目录
Dubbo 2.7 用法示例
URL也就是Uniform Resource Locator,中文叫统一资源定位符。Dubbo中无论是服务消费方,或者服务提供方,或者注册中心。都是通过URL进行定位资源的。
标准的URL格式如下:protocol://username:password@host:port/path?key=value&key=value
举例
dubbo://192.168.1.6:20880/moe.cnkirito.sample.HelloService?timeout=3000
描述一个 dubbo 协议的服务
zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&interface=org.apache.dubbo.registry.RegistryService&pid=1214&qos.port=33333×tamp=1545721981946
描述一个 zookeeper 注册中心
consumer://30.5.120.217/org.apache.dubbo.demo.DemoService?application=demo-consumer&category=consumers&check=false&dubbo=2.0.2&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=1209&qos.port=33333&side=consumer×tamp=1545721827784
描述一个消费者
dubbo中用6个要素来区分不同的服务提供者, 协议/地址/端口/服务/分组/版本, 稍微有一点不同, 就是两个不同的服务
不同粒度配置的覆盖关系
以 timeout 为例,下面是配置的查找顺序,其它 retries, loadbalance, actives 等类似:
其中,服务提供方配置,通过 URL 经由注册中心传递给消费方。
(建议由服务提供方设置超时,因为一个方法需要执行多长时间,服务提供方更清楚,如果一个消费方同时引用多个服务,就不需要关心每个服务的超时设置)。
理论上 ReferenceConfig 中除了interface这一项,其他所有配置项都可以缺省不配置,框架会自动使用ConsumerConfig,ServiceConfig, ProviderConfig等提供的缺省配置。
Dubbo 中点对点的直连方式。在开发自测时,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表,A 接口配置点对点,不影响 B 接口从注册中心获取列表。
线上禁止使用这种方式
<dubbo:reference id="xxxService" interface="com.alibaba.xxx.XxxService" url="dubbo://localhost:20890" />
# 这样不知道可不可以?
dubbo.reference.com.foo.BarService.url=dubbo://localhost:20890
为方便开发测试,经常会在线下共用一个所有服务可用的注册中心,这时,如果一个正在开发中的服务提供者注册,可能会影响消费者不能正常运行。
可以让服务提供者开发方,只订阅服务(开发的服务可能依赖其它服务),而不注册正在开发的服务,通过直连测试正在开发的服务。
<dubbo:registry address="10.20.153.10:9090" register="false" />
不同服务在性能上适用不同协议进行传输,比如大数据用短连接协议,小数据大并发用长连接协议
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="world" />
<dubbo:registry id="registry" address="10.20.141.150:9090" username="admin" password="hello1234" />
<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />
<!-- 使用dubbo协议暴露服务 -->
<dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" protocol="dubbo" />
<!-- 使用rmi协议暴露服务 -->
<dubbo:service interface="com.alibaba.hello.api.DemoService" version="1.0.0" ref="demoService" protocol="rmi" />
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="world" />
<dubbo:registry id="registry" address="10.20.141.150:9090" username="admin" password="hello1234" />
<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="hessian" port="8080" />
<!-- 使用多个协议暴露服务 -->
<dubbo:service id="helloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" protocol="dubbo,hessian" />
</beans>
# Dubbo Registry
dubbo.registry.address=zookeeper://localhost:2181
# Dubbo Protocol
# 下面是默认一个协议的配置方法, 大多都是这种使用方式
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
# 下面是多个协议的配置方法, 启动时每个服务和每个协议做笛卡尔积组合, 即代码只有1份, 但服务在20881-20883端口上各启动1个共3个
# name可以是不同的协议, 比如1个http, 2个dubbo.
# 多协议配置下, 可以使用 protocol 指定其中的一个(如p1/dubbo1), 就不会每种协议都提供服务了
dubbo.protocols.p1.id=dubbo1
dubbo.protocols.p1.name=dubbo
dubbo.protocols.p1.port=20881
dubbo.protocols.p1.host=0.0.0.0
dubbo.protocols.p2.id=dubbo2
dubbo.protocols.p2.name=dubbo
dubbo.protocols.p2.port=20882
dubbo.protocols.p2.host=0.0.0.0
dubbo.protocols.p3.id=dubbo3
dubbo.protocols.p3.name=dubbo
dubbo.protocols.p3.port=20883
dubbo.protocols.p3.host=0.0.0.0
使用服务分组区分服务接口的不同实现。当一个接口有多种实现时,可以用 group 区分。
服务端提供
<dubbo:service group="feedback" interface="com.xxx.IndexService" />
<dubbo:service group="member" interface="com.xxx.IndexService" />
消费端引用
<dubbo:reference id="feedbackIndexService" group="feedback" interface="com.xxx.IndexService" />
<dubbo:reference id="memberIndexService" group="member" interface="com.xxx.IndexService" />
消费端引用任意组
# 总是只调一个可用组的实现, 可能是通过某种机制来调用某一个实现接口的服务
<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />
为同一个服务配置多个版本
服务提供者
<dubbo:service interface="com.foo.BarService" version="1.0.0" />
<dubbo:service interface="com.foo.BarService" version="2.0.0" />
服务消费者
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check=“true”。
可以通过 check=“false” 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
如果 check=“false”,总是会返回引用,当服务恢复时,能自动连上。
dubbo.reference.com.foo.BarService.check=false
# 强制改变所有 reference 的 check 值,就算配置中有声明,也会被覆盖
dubbo.reference.check=false
# 设置 check 的缺省值,如果配置中有显式的声明,如:<dubbo:reference check="true"/>,不会受影响
dubbo.consumer.check=false
# 前面两个都是指订阅成功,但提供者列表是否为空是否报错,如果注册订阅失败时,也允许启动,需使用此选项,将在后台定时重试
dubbo.registry.check=false
集群调用失败时,Dubbo 提供的容错方案。缺省为 failover 重试。
注意必须是同一个服务有多个提供者时,才会有集群容错。使用 cluster 来配置
loadbalance=random/roundrobin/leastactive/consistenthash
消费端做负载均衡, 即局部负载均衡, 这样不影响服务调用新能. 服务端做的话太难了, 需要由注册中心来做, 每次调用都要查询和更新, 压力太大
假如有两个服务提供者a和b, 应用c调用ab时启用了负载均衡, 应用d直连a调用a不调用b, 从局部来看, c调用ab是负载均衡的, 从全局来看, a的压力要比b大, cd互不影响
Random: 随机,按权重设置随机概率, 默认策略
权重大的, 随机到的概率就大一点, 假如 a权重9, b权重1, 则随机到a的概率是a的权重除以总权重即0.9, b的概率是0.1,
RoundRobin: 轮询,按公约后的权重设置轮询比率
轮询就是多个服务提供者, 一个一个来执行请求. 假如有abc3个节点提供同一个服务, 其性能比例a️c=2:1:1
LeastActive: 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差, 使慢的提供者收到更少请求
消费者缓存每个服务提供者的信息, 每个服务提供者有一个active字段来代表当前消费者正在调用的线程数, 调用前加1, 调用后减1, 调用时找一个active字段最小的, 说明该服务提供者正被调用中的线程少
我认为这个算法比较好, 一定程度上能够感知到服务端的压力, 动态调整选择的服务提供者
ConsistentHash: 一致性 Hash,相同参数的请求总是发到同一提供者. 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动
简单理解就是将参数的某部分通过某种hash算法算出一个数字, 然后用服务提供者的个数取余, 余数就是服务提供者的索引, 然后就按照余数去访问对应的服务提供者即可
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略
服务消费者在调用某个服务提供者时,如果该服务提供者报错了,所采取的措施。就是 Mock
集群容错和服务降级的区别在于:
消费端做服务超时限制, 服务端也可以配置, 但是并不会中断服务端线程的执行, 至多执行结束后如果超时则打印日志
超时时间配置优先级顺序:方法>接口>全局, 同级消费端配置优先
举例, 消费端配置方法3秒超时, 服务端配置方法5秒超时, 方法实际执行7秒, 则消费端3秒报错, 服务端7秒执行完毕并打印超时日志(warn)
Dubbo 本地存根和本地伪装
为了进一步的方便用户做 Dubbo 开发,框架提出了本地存根 Stub 和本地伪装 Mock 的概念。通过约定大于配置的理念,进一步的简化了配置,使用起来更加方便,并且不依赖额外的 AOP 框架就达到了 AOP的效果。
本地存根
消费端实现一个装饰器类, 实现服务接口并持有服务接口的引用, 代理远程接口, 强化接口功能, 相当于Aop的around, 可以在真正调用远程接口前做参数校验, 后做结果缓存等额外的操作
一般我们系统中集成二方三方远程接口时, 都会对接口做一层封装, 以满足我们当前系统的规范, 通常这里都会做参数校验, 异常处理等操作, 和本地存根一致
本地伪装
消费端实现服务接口, 当消费端调用服务最终失败时, 在没有对服务调用使用 try-catch 包裹的情况下, 也能够返回一个预设默认值
从 2.7.0 开始,Dubbo 的所有异步编程接口开始以 CompletableFuture 为基础
java.util.concurrent.CompletableFuture
Dubbo 同步调用太慢,也许你可以试试异步处理
异步执行
说的是服务提供方异步执行
通过 return CompletableFuture.supplyAsync() ,业务执行已从 Dubbo 线程切换到业务线程,避免了对 Dubbo 线程池的阻塞。
// 服务接口定义
public interface AsyncService {
CompletableFuture<String> sayHello(String name);
}
// 服务实现
public class AsyncServiceImpl implements AsyncService {
@Override
public CompletableFuture<String> sayHello(String name) {
RpcContext savedContext = RpcContext.getContext();
// 建议为supplyAsync提供自定义线程池,避免使用JDK公用线程池
return CompletableFuture.supplyAsync(() -> {
System.out.println(savedContext.getAttachment("consumer-key1"));
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "async response from provider.";
});
}
}
异步调用
说的是服务消费方异步调用
基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。
// 调用远程服务, 直接返回 CompletableFuture
CompletableFuture<String> future = asyncService.sayHello("async call request");
// 增加回调
future.whenComplete((v, t) -> {
if (t != null) {
t.printStackTrace();
} else {
System.out.println("Response: " + v);
}
});
// 早于结果输出
System.out.println("Executed before response return.");
参数回调
通过参数回调从服务器端调用客户端逻辑
参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理(dubbo协议是长连接),这样就可以从服务器端调用消费端逻辑。
假设有如下服务 FooService
public interface FooService {
public String bar(String name, String key, CallbackListener callback);
}
// 其中回调函数 CallbackListener 结构如下
public interface CallbackListener {
public void callback(String data);
}
FooService 的实现
// 指定了第三个参数是回调参数
// 服务端传了一个CallbackListener接口的实现类实例, 服务端这里收到的其实是Dubbo处理过的一个代理对象
// 其功能就是生成一个Invocation对象发送到消费端, invocation对象包含传给消费端的参数和指定要调用的方法, 然后用该参数调用实现类实例的对应方法
// callbacks = 3, 该服务最大同时支持回调数, 超过的会报错?
@Service(
method = {
@Method(name = "bar", arguments = {
@Argument(index = 2, callback = true)
}),
callbacks = 3
})
public class FooServiceImpl implements FooService {
@Override
public String bar(String name, String key, CallbackListener callback) {
// 执行业务
...
// 执行回调
callback.callback("server:" + name);
}
}
消费方调用服务
@Reference
private FooService fooService;
public String bar() {
String bar1 = fooService.bar("mrathena", "1", new CallbackListenerImpl());
String bar2 = fooService.bar("mrathena", "2", new CallbackListenerImpl2());
return bar1 + bar2;
}
使用泛化调用
说的是服务消费方泛化调用
使用 org.apache.dubbo.rpc.service.GenericService 可以替代所有接口引用
假如有一个服务提供者
public interface DemoService {
String foo(String bar);
String bar(User user);
}
常规调用方式
@Reference(version = "xxx")
private DemoService demoService;
public Object foo() {
return demoService.foo("parameter");
}
泛化调用方式
@Reference(id = "demoService", interfaceName = "com.xxx.DemoService", version = "xxx", generic = true)
private GenericService genericService;
public Object foo() {
return genericService.$invoke("foo", new String[]{"java.lang.String"}, new Object[]{"parameter"});
}
public Object bar() {
// 用Map表示POJO参数,如果返回值为POJO也将自动转成Map
Map<String, Object> user = new HashMap<>();
user.put("username", "foo");
user.put("password", "bar");
return genericService.$invoke("bar", new String[]{"com.xxx.User"}, new Object[]{user});
}
使用泛化调用方式时, 无需引入pom, 无需引入DemoService而是引入泛化服务GenericService, @Reference注解需要指定要代理的服务类interfaceName, 并指定接口为泛化调用 generic = true
如果是通过ApplicationContext.getBean()的方式, 如下
GenericService genericService = (GenericService) applicationContext.getBean("demoService");
实现泛化调用
说的是服务提供方泛化服务
一个服务, 通过实现 GenericService 接口处理所有服务请求, 然后把该服务暴露为指定的服务
// 把服务暴露为 DemoService, 消费方可通过泛化调用方式调用该服务
@Service(interfaceName = "com.xxx.DemoService", version = "xxx")
public class GenericDemoService implements GenericService {
@Override
public Object $invoke(String s, String[] strings, Object[] objects) throws GenericException {
if ("foo".equals(s)) {
return "foo";
}
return "bar";
}
}
REST 快速入门
dubbo 支持使用多种协议暴露一个服务, 如dubbo和rest等
当我们用Dubbo提供了一个服务后,如果消费者没有使用Dubbo也想调用服务,那么这个时候我们就可以让我们的服务支持REST协议,这样消费者就可以通过REST形式调用我们的服务了。
注意:如果某个服务只有REST协议可用,那么该服务必须用@Path注解定义访问路径
dubbo-admin
可以查看服务信息, 版本信息, 组信息, 服务提供者, 服务治理(条件路由/标签路由/黑白名单/动态配置/权重调整/负载均衡)等功能
README_ZH.md 里有详细的使用说明, 默认账号密码为 root/root
dubbo 注册中心和配置中心可以分开的, 默认配置中心使用注册中心的zookeeper
配置管理: 配置的是 application.properties 中 dubbo 相关的内容, 可以创建多个dubbo配置, 使用应用名或global来区分, 当dubbo应用指定了配置中心地址时, 将从配置中心中获取配置. 当应用中有配置且全局配置中也有配置, 则以全局配置为准
服务治理-动态配置(服务治理-指定服务-更多-动态配置): 配置的是服务上的参数(timeout等), 可以指定配置到服务端/消费端, 实时生效
服务治理-权重调整/负载均衡: 本质上还是动态配置, 相当于动态配置中权重调整和负载均衡的快捷方式, 因为经常要用, 为了使用更方便, 就单独放出来了
路由规则
消费端从m个提供者中选中n个, 然后再做负载均衡
标签路由通过将某一个或多个服务的提供者划分到同一个分组,约束流量只在指定分组中流转,从而实现流量隔离的目的,可以作为蓝绿发布、灰度发布等场景的能力基础。
判断消费者发送的请求中是否带有标签, 有的话找到该标签对应的服务提供者, 调用对应服务
消费者请求时添加标签: RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY,"tag1");
,
举例
什么是蓝绿部署、滚动发布和灰度发布?
综上所述,三种方式均可以做到平滑式升级,在升级过程中服务仍然保持服务的连续性,升级对外界是无感知的。那生产上选择哪种部署方法最合适呢?这取决于哪种方法最适合你的业务和技术需求。如果你们运维自动化能力储备不够,肯定是越简单越好,建议蓝绿发布,如果业务对用户依赖很强,建议灰度发布。如果是K8S平台,滚动更新是现成的方案,建议先直接使用。
ZooInspector: java -jar zookeeper-dev-ZooInspector.jar