dubbo的定义
dubbo是一款 轻量级、高性能的开源框架。
dubbo的特性
- 面向接口代理的高性能RPC调用
- 服务注册与发现
- 运行期流量管理
- 智能负载均衡
- 高度可扩展
dubbo架构图
Dubbo的应用
Dubbo的三种使用: API、XML、注解
api方式的使用
provider 代码
public class Application {
public static void main(String[] args) throws Exception {
if (isClassic(args)) {
startWithExport();
} else {
startWithBootstrap();
}
}
private static boolean isClassic(String[] args) {
return args.length > 0 && "classic".equalsIgnoreCase(args[0]);
}
//bootstrap方式启动
private static void startWithBootstrap() {
//封装service
ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
//注册服务
bootstrap.application(new ApplicationConfig("dubbo-demo-api-provider"))
//配置注册中心
.registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
.service(service)
.start()
.await();
}
private static void startWithExport() throws InterruptedException {
ServiceConfig<DemoServiceImpl> service = new ServiceConfig<>();
service.setInterface(DemoService.class);
service.setRef(new DemoServiceImpl());
service.setApplication(new ApplicationConfig("dubbo-demo-api-provider"));
service.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
service.export();
System.out.println("dubbo service started");
new CountDownLatch(1).await();
}
}
Consumer 代码
public class Application {
public static void main(String[] args) {
if (isClassic(args)) {
runWithRefer();
} else {
runWithBootstrap();
}
}
private static boolean isClassic(String[] args) {
return args.length > 0 && "classic".equalsIgnoreCase(args[0]);
}
// bootstrap启动
private static void runWithBootstrap() {
//封装调用服务
ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
reference.setInterface(DemoService.class);
reference.setGeneric("true");
//配置注册中心
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
bootstrap.application(new ApplicationConfig("dubbo-demo-api-consumer"))
.registry(new RegistryConfig("zookeeper://127.0.0.1:2181"))
//引用
.reference(reference)
.start();
//RPC调用
DemoService demoService = ReferenceConfigCache.getCache().get(reference);
String message = demoService.sayHello("dubbo");
System.out.println(message);
GenericService genericService = (GenericService) demoService;
Object genericInvokeResult = genericService.$invoke("sayHello", new String[] { String.class.getName() },
new Object[] { "dubbo generic invoke" });
System.out.println("0000000"+genericInvokeResult);
}
private static void runWithRefer() {
ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
reference.setApplication(new ApplicationConfig("dubbo-demo-api-consumer"));
reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
reference.setInterface(DemoService.class);
DemoService service = reference.get();
String message = service.sayHello("dubbo");
System.out.println(message);
}
}
xml方式的使用
dubbo-provider.xml
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
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="demo-provider">
<dubbo:parameter key="mapping-type" value="metadata"/>
</dubbo:application>
<!-- 配置注册中心的标签 config-center或者 registry -->
<!-- <dubbo:config-center address="zookeeper://127.0.0.1:2181"/>-->
<dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>
<dubbo:registry id="registry1" address="zookeeper://127.0.0.1:2181"/>
<dubbo:protocol name="dubbo" port="-1"/>
<!--配置服务实例 -->
<bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>
<bean id="greetingService" class="org.apache.dubbo.demo.provider.GreetingServiceImpl"/>
<!--暴露服务 -->
<dubbo:service interface="org.apache.dubbo.demo.DemoService" timeout="3000" ref="demoService" registry="registry1"/>
<dubbo:service version="1.0.0" group="greeting" timeout="5000" interface="org.apache.dubbo.demo.GreetingService"
ref="greetingService"/>
</beans>
provider代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
context.start();
System.in.read();
服务实现
public class DemoServiceImpl implements DemoService {
private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);
@Override
public String sayHello(String name) {
logger.info("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
}
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
return "async result";
});
return cf;
}
}
dubbo-consumer.xml
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
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="demo-consumer">
<dubbo:parameter key="mapping-type" value="metadata"/>
<dubbo:parameter key="enable-auto-migration" value="true"/>
</dubbo:application>
<!-- 配置注册中心的标签 config-center或者 registry -->
<!-- <dubbo:metadata-report address="zookeeper://127.0.0.1:2181"/>-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!-- 配置引用的服务-->
<dubbo:reference provided-by="demo-provider" id="demoService" check="false"
interface="org.apache.dubbo.demo.DemoService"/>
<dubbo:reference provided-by="demo-provider" version="1.0.0" group="greeting" id="greetingService" check="false"
interface="org.apache.dubbo.demo.GreetingService"/>
</beans>
Consumer代码
public class Application {
/**
* In order to make sure multicast registry works, need to specify '-Djava.net.preferIPv4Stack=true' before
* launch the application
*/
public static void main(String[] args) throws Exception {
//解析xml
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");
context.start();
//获取代理对象
DemoService demoService = context.getBean("demoService", DemoService.class);
GreetingService greetingService = context.getBean("greetingService", GreetingService.class);
new Thread(() -> {
while (true) {
String greetings = greetingService.hello();
System.out.println(greetings + " from separated thread.");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
while (true) {
CompletableFuture<String> hello = demoService.sayHelloAsync("world");
System.out.println("result: " + hello.get());
String greetings = greetingService.hello();
System.out.println("result: " + greetings);
Thread.sleep(500);
}
}
}
注解方式的使用
dubbo-provider.properties
dubbo.application.name=dubbo-demo-annotation-provider
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
provider代码
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
context.start();
System.in.read();
}
/**
* @EnableDubbo 配置 dubbo 服务扫描包路径
* @PropertySource 配置文件路径
*/
@Configuration
@EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.provider")
@PropertySource("classpath:/spring/dubbo-provider.properties")
static class ProviderConfiguration {
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://127.0.0.1:2181");//设置配置中心地址
return registryConfig;
}
}
dubbo服务实现
/** 暴露出DemoServiceImpl */
@DubboService
public class DemoServiceImpl implements DemoService {
private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);
@Override
public String sayHello(String name) {
logger.info("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return "Hello " + name + ", response from provider: " + RpcContext.getContext().getLocalAddress();
}
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
return null;
}
}
dubbo-consumer.properties
dubbo.application.name=dubbo-demo-annotation-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
Consumer代码
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
context.start();
DemoService service = context.getBean("demoServiceComponent", DemoServiceComponent.class);
String hello = service.sayHello("world");
System.out.println("result :" + hello);
}
@Configuration
@EnableDubbo(scanBasePackages = "org.apache.dubbo.demo.consumer.comp")
@PropertySource("classpath:/spring/dubbo-consumer.properties")
@ComponentScan(value = {"org.apache.dubbo.demo.consumer.comp"})
static class ConsumerConfiguration {
}
远程调用实现
@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService {
@DubboReference
private DemoService demoService;
@Override
public String sayHello(String name) {
return demoService.sayHello(name);
}
@Override
public CompletableFuture<String> sayHelloAsync(String name) {
return null;
}
}
Dubbo的同步调用和异步调用: sync async
异步调用方式一:Consumer端 async return
<dubbo:application name="xml-demo-consumer"/>
//配置注册中心
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
//暴露服务 async = true 配置异步通信
<dubbo:reference interface="com.marx.async_demo.api.HelloService" id="helloService" async="true" timeout="3000" />
<dubbo:reference interface="com.marx.async_demo.api.WorldService" id="worldService" timeout="3000">
//方法级别配置
<!-- <dubbo:method name="sayHello" async="true" sent="true" return="true"/>-->
<!-- sent=true 等消息发出,如果消息发送失败则抛出异常。false 不等待消息发出,消息放到IO队列,马上返回 reture=false 忽略返回值,只异步调用-->
<dubbo:method name="sayHello" async="true" sent="true" return="false" />
</dubbo:reference>
异步调用方式二:Provider端 callback
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-provider.xml");
context.start();
System.in.read();
provider.xml配置
<dubbo:application name="xml-demo-provider"/>
<!--静态服务,非动态服务,服务提供者初次注册时为禁用状态,需人工启用。断线时,将不会被自动删除,需人工禁用。-->
<dubbo:registry address="zookeeper://127.0.0.1:2181" dynamic="true" />
<bean id="callbackService" class="com.nx.samuel.callback_demo.provider.impl.CallbackServiceImpl"/>
<dubbo:service interface="com.nx.samuel.callback_demo.api.CallbackService" ref="callbackService"
connections="1" callbacks="1000">
<dubbo:method name="addListener">
<!--index = 1表示第1个参数是回调参数 callback=true 表示开启回调-->
<dubbo:argument index="1" callback="true"/>
<!--也可以通过指定类型的方式-->
<!--<dubbo:argument type="com.demo.CallbackListener" callback="true" />-->
</dubbo:method>
</dubbo:service>
provider服务实现
public class CallbackServiceImpl implements CallbackService {
//缓存需要回调的服务
private final Map<String, CallbackListener> listenerMap;
public CallbackServiceImpl() {
listenerMap = new ConcurrentHashMap<>();
//线程维护缓存
Thread t = new Thread(() -> {
while (true) {
try {
for (Map.Entry<String, CallbackListener> entry : listenerMap.entrySet()) {
try {
entry.getValue().changed(getChanged(entry.getKey()));
} catch (Throwable t1) {
listenerMap.remove(entry.getKey());
}
}
Thread.sleep(5000); // timely trigger change event
} catch (Throwable t1) {
t1.printStackTrace();
}
}
});
t.setDaemon(true);
t.start();
}
@Override
public void addListener(String key, CallbackListener listener) throws InterruptedException {
//记录回调
listenerMap.put(key, listener);
System.out.println("执行正常的业务逻辑"+new Date());
TimeUnit.MILLISECONDS.sleep(500);
}
private String getChanged(String key) {
return "Changed: " + new Date();
}
}
Provider服务
public interface CallbackService {
/**
* provider提供的异步业务接口,执行完成后会通过listener回调consumer的回调实现
* 这个 索引为1的是callback类型。
* dubbo 将基于长连接生成反向代理,就可以在服务端调用客户端逻辑
* @param key
* @param listener
*/
void addListener(String key, CallbackListener listener) throws InterruptedException;
}
回调接口
public interface CallbackListener {
/**
* 理解为方法,就是一个方法
* @param msg
*/
void changed(String msg);
}
consumer代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-consumer.xml");
context.start();
CallbackService callbackService = context.getBean("callbackService", CallbackService.class);
// 增加listener;new CallbackListener时回调的具体实现,在消费端通过匿名内部类的方式实现的
callbackService.addListener("foo.bar", new CallbackListener() {
private Integer integer = 1;
@Override
public void changed(String msg) {
System.out.println("I am callback:" + msg);
}
});
System.in.read();
consumer远程调用provider的接口,provider通过RPC的通信回调consumer的接口实现
异步调用方式三:Consumer端 事件通知 onreturn onthrow oninvoke
<dubbo:application name="consumer"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<bean id="notifyServiceImpl" class="com.nx.samuel.notify_demo.consumer.impl.NotifyServiceImpl"/>
<dubbo:reference id="helloService" check="false" interface="com.nx.samuel.notify_demo.api.HelloService" version="1.0.0" >
<dubbo:method name="hello" async="false" onreturn="notifyServiceImpl.onReturn" onthrow="notifyServiceImpl.onThrow" />
</dubbo:reference>
代码实现
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-consumer.xml");
context.start();
HelloService helloService = context.getBean("helloService", HelloService.class);
NotifyServiceImpl notifyService = context.getBean("notifyServiceImpl", NotifyServiceImpl.class);
int id = 1;
String result = helloService.hello(id);
System.out.println("result is" + result);
for (int i = 0; i < 10; i++) {
if (!notifyService.retMap.containsKey(id)) {
Thread.sleep(200);
} else {
break;
}
}
System.out.println("result: " + notifyService.retMap.get(id));
}
Dubbo的延迟发布
延迟发布 delay,适用场景:实现平滑发布
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<bean id="helloService" class="com.nx.samuel.delay_demo.provider.impl.HelloServiceImpl"/>
<!--服务级别延迟发布-->
<dubbo:provider delay="10000" />
<!--接口级别延迟发布-->
<dubbo:service interface="com.nx.samuel.delay_demo.api.HelloService" ref="helloService" delay="10000"/>
Dubbo的Group和Merger
服务分组group 适用场景:版本更替的多种服务实现的分组(group)调用
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<bean id="aHelloService" class="com.nx.samuel.dubbo_vip_group_demo.provider.impl.HelloServiceImplA"/>
<bean id="bHelloService" class="com.nx.samuel.dubbo_vip_group_demo.provider.impl.HelloServiceImplB"/>
//对HelloService进行group
<dubbo:service interface="com.nx.samuel.dubbo_vip_group_demo.api.HelloService" group="a" ref="aHelloService"/>
<dubbo:service interface="com.nx.samuel.dubbo_vip_group_demo.api.HelloService" group="b" ref="bHelloService"/>
服务调用结果合并 merger 适用场景:merger进行调用结果合并
public class MyMerger implements Merger<String> {
@Override
public String merge(java.lang.String[] items) {
String result = "";
for (int i=0; i<items.length; i++) {
result += items[i];
}
return result;
}
}
配置merger 交由dubbo处理
myMerger=com.nx.samuel.dubbo_vip_group_demo.merger.MyMerger
Dubbo的Mock
mock的四种取值 false true force 类名 适用于联调时模拟数据
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!--false不调用mock服务;true 如果服务调用失败使用mock,默认值true;force 强制使用;基于Mock的类名-->
<!-- <dubbo:reference interface="com.nx.samuel.mock_demo.api.HelloService" id="helloService" mock="com.nx.samuel.mock_demo.api.HelloServiceMock"/>-->
<dubbo:reference interface="com.nx.samuel.mock_demo.api.HelloService" id="helloService" mock="return test"/>
<!-- <dubbo:reference interface="com.nx.samuel.mock_demo.api.HelloService" id="helloService" mock="com.nx.samuel.mock_demo.api.HelloServiceMock"/>-->
Dubbo的Stub
本地存根 实现一定的容错
<dubbo:application name="xml-demo-consumer"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
//配置调用服务的本地存根
<dubbo:reference interface="com.nx.samuel.stub_demo.api.HelloService" check="false" id="helloService"
stub="com.nx.samuel.stub_demo.consumer.stub.WorldServiceStub" />
通过构造函数方式注入服务代理对象
public class WorldServiceStub implements HelloService {
private HelloService helloService;
public WorldServiceStub(HelloService helloService) {
this.helloService = helloService;
}
/**
* 通过本地存根 对参数校验 或者根据条件判断是否进行远程调用
*/
@Override
public String sayHello(String username) {
try {
System.out.println("在消费端执行,可以进行参数验证");
String result = helloService.sayHello(username);
return result;
}catch (Exception e){
e.printStackTrace();
return "发生异常,伪装";
}
}
}