Dubbo的基本使用

382 阅读4分钟

dubbo的定义

dubbo是一款 轻量级、高性能的开源框架。

dubbo的特性

  • 面向接口代理的高性能RPC调用
  • 服务注册与发现
  • 运行期流量管理
  • 智能负载均衡
  • 高度可扩展

dubbo架构图

image.png

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处理

image.png

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 "发生异常,伪装";
        }
    }
}