Dubbo分布式框架学习(2)
学习链接
源自 - 千锋教育Dubbo教程,分布式服务框架dubbo源码级应用教程 - B站视频
dubbo-learn 所有代码在这里
dubbo官网文档
dubb官方使用示例代码 - github
文章目录
- 学习链接
 - ⼀、架构演进
 - 1.单体架构
 - 2.垂直应⽤架构
 - 3.分布式应⽤架构阶段
 - 4.微服务架构阶段
 
- ⼆、注册中⼼
 - 1.概述
 - 2.搭建zookeeper注册中⼼
 
- 三、RPC及Dubbo
 - 1.什么是RPC
 - 2.⼿写RPC项⽬
 - pom.xml
 - 服务提供者
 - Provider
 - HelloService
 - HelloServiceImpl
 - RemoteMpRegister
 - LocalRegister
 - Protocol
 - Url
 - ProtocolFactory
 - HttpProtocol
 - HttpServer(手动启动tomcat)
 - HttpClient
 
DubboProtocol- DispatcherServlet
 - HttpServerHandler
 
- 服务消费者
 - Consumer
 - ProxyFactory
 
- 总结
 
- 3.什么是dubbo
 - 4.dubbo怎么实现远程通信?
 - 5.dubbo初体验
 - my-dubbo-interfaces 公共接口定义
 - SiteService
 
- my-dubbo-service-provider 服务提供者
 - pom.xml
 - SiteServiceImpl
 - provider.xml
 - provider
 
- my-dubbo-service-consumer 服务消费者
 - pom.xml
 - consumer.xml
 - Consumer
 
- 启动zookeeper
 - 测试
 - 服务代理的过程
 
- 6.dubbo内部结构
 
- 四、Springboot中使⽤dubbo
 - 创建接口层
 - SiteService
 
- 创建服务提供者
 - pom.xml
 - application.yml
 - SiteServiceImpl
 - MyBootServiceProviderApplication
 
- 创建服务消费者
 - pom.xml
 - application.yml
 - SiteController
 - MyBootServiceConsumerApplication
 
- 服务启动源码的剖析
 
- 五、dubbo⽤法示例
 - 1.version版本号
 - 2.指定protocol协议
 - 3.使⽤rest访问dubbo的服务
 - 服务提供者
 - pom.xml
 - application.yml
 - SiteServiceImpl
 
- 服务消费方
 - 测试
 
- 4.消费者通过url直连指定的服务提供者
 - 5.服务超时
 - 6.集群容错
 - 7.服务降级
 - 8.本地存根
 - 9.参数回调
 - 公共接口
 - CallbackService
 - CallbackListener
 - CallbackListenerImpl
 
- 服务提供者
 - CallbackServiceImpl
 
- 服务消费者
 - CallbackController
 
- 10.异步调用
 - 公共接口
 - AsyncService
 
- 服务提供者
 - AsyncServiceImpl
 
- 服务消费者
 - AsyncController
 
- 六、dubbo的负载均衡策略
 - 1.负载均衡策略
 - 2.dubbo中如何配置负载均衡策略
 - 测试
 - 服务提供者
 - 服务消费者
 - 测试效果
 
- 3.⼀致性hash的实现
 - 4.最少活跃调⽤数的实现
 
- 七、安装Dubbo admin监管平台
 - docker构建
 - 1.使⽤docker安装
 - 2.访问
 
- 源码构建
 - Maven方式部署
 - 前后端分离部署
 - 测试
 
- 八、Dubbo的SPI可扩展机制
 - 1.Java的SPI(Service Provider Interface)机制
 - 测试java的SPI
 - Cat接口
 - BlackCat
 - WhiteCat
 - com.qf.cat
 - TestSPI
 
- 2.SPI机制的缺点
 - 3.dubbo的SPI机制
 - dubbo的spi机制简单实现
 - pom.xml
 - Cat接口
 - BlackCat
 - WhiteCat
 - CatWrapper
 - com.qf.cat
 - TestSPI
 - TestSPI2
 
- 九、Dubbo源码剖析
 - 1. Dubbo服务调⽤过程
 - 2.关于DubboInvoker的装饰
 - AsyncToSyncInvoker
 - ProtocolFilterWrapper
 - ListenerInvokerWrapper
 
- 3. 权重轮询算法
 
⼀、架构演进
为什么现在的系统不⽤单体架构,⽽⽤微服务架构。
1.单体架构
为了提⾼系统的吞吐量,单体架构中的垂直升级和⽔平扩展,存在以下⼏个问题
- 提升的性能是有限的
 - 成本过⾼,没有办法针对某⼀个具体的模块做性能的提升,因为单体,所有模块都是在⼀
起的。 - 更新、维护成本⾮常⾼,对于系统中要修改或增加的功能,整个发布的流程⾮常麻烦。
 - 某⼀个模块出现了bug,就会影响整个系统。
 
2.垂直应⽤架构
根据业务的边界,对单体应⽤进⾏垂直拆分,将⼀个系统拆分成多个服务。⽐如⼀个管理系
 统,可以拆分成权限系统、业务系统、数据系统等。
垂直应⽤存在的问题:
每个独⽴部署的服务之间,公共的部分需要部署多份。那么对公共部分的修改、部署、更新
 都需要重复的操作,带来⽐较⼤的成本
为了解决这⼀问题,接下来就进⼊到分布式应⽤架构阶段。
3.分布式应⽤架构阶段
在这个阶段⾥,将服务内部公⽤的模块抽取出来,部署成⼀个独⽴的服务,那么需要解决服
 务之间的⾼效通信问题。
但是分布式应⽤架构阶段会存在新的问题:
- 服务越来越多,这么多服务如何被发现?
 - 服务越来越多,服务如何被治理?
 - 服务之间如何实现⾼效通信?
 
4.微服务架构阶段
微服务架构阶段主要解决的⼏个问题:
- 服务的注册和发现:这么多服务如何注册。这么多服务如何被发现
 - 服务之间的⾼效调⽤:使⽤rpc或者http进⾏通信
 - 服务治理:服务权重、负载均衡、服务熔断、限流等等⼀些服务治理⽅⾯的问题
 

⼆、注册中⼼
1.概述
注册中⼼的选择有很多:
- ⾃研:redis
 - zookeeper(dubbo的推荐):zk是⼀个分布式服务组件中的⼀个⾮常重要的组件,⾥⾯ 涉及到很多优秀的分布式设计思想,堪称⿐祖地位。
 - nacos:nacos既可以作为注册中⼼使⽤,也可以作为分布式配置中⼼使⽤
 - eureka:eureka是spring cloud netflix框架中著名的注册中⼼,⾥⾯的服务的续约、⼼ 跳等等的设计⾮常的经典。
 
2.搭建zookeeper注册中⼼
- 克隆⼀个虚拟机
 - 安装jdk
 - 解压zk的压缩包
 - 进⼊到conf⽂件夹内,重命名:zoo_samples.cfg->zoo.cfg 进⼊到bin中,使⽤命令来操作zk
 
./zkServer.sh start   # 启动zk
./zkServer.sh status  # 查看zk状态,如果状态是:Mode: standalone 表示启动成功
./zkServer.sh stop    # 关闭zk
 
三、RPC及Dubbo
1.什么是RPC
dubbo是⼀款⾼性能的rpc框架。什么是rpc呢?
rpc是⼀种协议:是⼀种远程过程调⽤(remote procudure call)协议
rpc协议是在应⽤层之上的协议,规定了通信双⽅进⾏通信的数据格式是什么样的,及数据如 何传输:
- 指明调⽤的类或接⼝
 - 指明调⽤的⽅法及参数
 

2.⼿写RPC项⽬
完整代码:dubbo-learn / dubbo-init-demo
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.qf</groupId>
    <artifactId>dubbo-init-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>9.0.10</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.16.Final</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.51</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.3</version>
        </dependency>
    </dependencies>
</project>
 
服务提供者
Provider
public class Provider {
    public static void main(String[] args) {
        URL url = new URL("localhost",8080);
        //模拟远程注册中心
        RemoteMapRegister.regist(HelloService.class.getName(), url);
        //指明服务的实现类
        LocalRegister.regist(HelloService.class.getName(), HelloServiceImpl.class);
        Protocol protocol = ProtocolFactory.getProtocol();
        protocol.start(url);
    }
}
 
HelloService
public interface HelloService {
    String hello(String name);
}
 
HelloServiceImpl
public class HelloServiceImpl implements HelloService {
    @Override
    public String hello(String name) {
        return "hello:" + name;
    }
}
 
RemoteMpRegister
public class RemoteMapRegister {
    private static Map<String, List<URL>> REGISTER = new HashMap<>();
    public static void regist(String interfaceName, URL url){
        List<URL> list = REGISTER.get(interfaceName);
        if (list == null) {
            list = new ArrayList<>();
        }
        list.add(url);
        REGISTER.put(interfaceName, list);
        //saveFile实现REGISTER在provider和consumer之间共享
        saveFile();
    }
    public static List<URL> get(String interfaceName) {
        REGISTER = getFile();
        List<URL> list = REGISTER.get(interfaceName);
        return list;
    }
    private static void saveFile() {
        try {
            FileOutputStream fileOutputStream = new FileOutputStream("classpath:register.txt");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(REGISTER);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static Map<String, List<URL>> getFile() {
        try {
            FileInputStream fileInputStream = new FileInputStream("classpath:register.txt");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            return (Map<String, List<URL>>) objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}
 
LocalRegister
public class LocalRegister {
    private static Map<String, Class> map = new HashMap<>();
    public static void regist(String interfaceName, Class implClass) {
        map.put(interfaceName, implClass);
    }
    public static Class get(String interfaceName) {
        return map.get(interfaceName);
    }
}
 
Protocol
public interface Protocol {
    void start(URL url);
    String send(URL url, Invocation invocation);
}
 
Url
@Data
@AllArgsConstructor
public class URL implements Serializable {
    private String hostName;
    private int port;
}
 
ProtocolFactory
public class ProtocolFactory {
    public static Protocol getProtocol() {
        //vm options:-DprotocolName=dubbo
        String name = System.getProperty("protocolName");
        if (name == null || name.equals("")) name = "http";
        switch (name) {
            case "http":
                return new HttpProtocol();
            case "dubbo":
                return new DubboProtocol();
            default:
                break;
        }
        return new HttpProtocol();
    }
}
 
HttpProtocol
public class HttpProtocol implements Protocol {
    @Override
    public void start(URL url) {
        HttpServer httpServer = new HttpServer();
        httpServer.start(url.getHostName(), url.getPort());//tomcat-》DispatcherServlet接收请求
    }
    @Override
    public String send(URL url, Invocation invocation) {
        HttpClient httpClient = new HttpClient();
        return httpClient.send(url.getHostName(), url.getPort(),invocation);
    }
}
 
HttpServer(手动启动tomcat)
public class HttpServer {
    public void start(String hostname, Integer port) {
        Tomcat tomcat = new Tomcat();
        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");
        Connector connector = new Connector();
        connector.setPort(port);
        Engine engine = new StandardEngine();
        engine.setDefaultHost(hostname);
        Host host = new StandardHost();
        host.setName(hostname);
        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());
        host.addChild(context);
        engine.addChild(host);
        service.setContainer(engine);
        service.addConnector(connector);
        tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet());
        context.addServletMappingDecoded("/*", "dispatcher");
        try {
            tomcat.start();
            tomcat.getServer().await();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }
}
 
HttpClient
public class HttpClient {
    public String send(String hostname, Integer port, Invocation invocation) {
        try {
            URL url = new URL("http", hostname, port, "/");
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setRequestMethod("POST");
            httpURLConnection.setDoOutput(true);
            OutputStream outputStream = httpURLConnection.getOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(outputStream);
            oos.writeObject(invocation);
            oos.flush();
            oos.close();
            InputStream inputStream = httpURLConnection.getInputStream();
            String result = IOUtils.toString(inputStream);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
 
DubboProtocol 
 
public class DubboProtocol implements Protocol {
    @Override
    public void start(URL url) {
       /* NettyServer nettyServer = new NettyServer();
        nettyServer.start(url.getHostname(), url.getPort());*/
    }
    @Override
    public String send(URL url, Invocation invocation) {
       /* NettyClient nettyClient = new NettyClient();
        return nettyClient.send(url.getHostname(),url.getPort(), invocation);*/
        return "";
    }
}
 
DispatcherServlet
public class DispatcherServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        new HttpServerHandler().handler(req, resp);
    }
}
 
HttpServerHandler
public class HttpServerHandler {
    public void handler(HttpServletRequest req, HttpServletResponse resp) {
        try {
            ObjectInputStream ois = new ObjectInputStream(req.getInputStream());
            Invocation invocation = (Invocation)ois.readObject();
            String interfaceName = invocation.getInterfaceName();//HelloService
            Class implClass = LocalRegister.get(interfaceName);//HelloServiceImpl.hello(String name)
            Method method = implClass.getMethod(invocation.getMethodName(), invocation.getParamTypes());
            String result = (String) method.invoke(implClass.newInstance(), invocation.getParams());//HelloServiceImpl.hello(String name)
            System.out.println("tomcat:" + result);
            IOUtils.write(result.getBytes(), resp.getOutputStream());//写回给服务的消费者
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 
服务消费者
Consumer
public class Consumer {
    public static void main(String[] args) {
        HelloService helloService = ProxyFactory.getProxy(HelloService.class);
        String name = helloService.hello("qf");
        System.out.println(name);
    }
}
 
ProxyFactory
public class ProxyFactory<T> {
    @SuppressWarnings("unchecked")
    public static <T> T getProxy(final Class interfaceClass) {
        return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //Invocation封装的是这一次远程过程调用所需要的所有的参数
                Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(), method.getParameterTypes(), args);
                //去注册中心获得服务地址列表
                List<URL> urlList = RemoteMapRegister.get(interfaceClass.getName());
                //负载均衡
                URL url = LoadBalance.random(urlList);
                Protocol protocol = ProtocolFactory.getProtocol();
                String result = protocol.send(url, invocation);
                return result;
            }
        });
    }
}
 
总结
服务提供者将服务信息注册到Map中(注册中心),服务消费者从注册中心获取到服务提供者的信息,通过动态代理将请求参数封装为Invocation对象,发起Http协议(可以使用http协议,也可以使用其它协议)请求获取到数据之后返回
3.什么是dubbo
Apache Dubbo 是⼀款⾼性能、轻量级的开源服务框架。
Apache Dubbo |ˈdʌbəʊ| 提供了六⼤核⼼能⼒:⾯向接⼝代理的⾼性能RPC调⽤,智能容错 和负载均衡,服务⾃动注册和发现,⾼度可扩展能⼒,运⾏期流量调度,可视化的服务治理 与运维。
4.dubbo怎么实现远程通信?
服务消费者去注册中⼼订阅到服务提供者的信息。然后通过dubbo进⾏远程调⽤。
5.dubbo初体验
(注:以下代码在 dubbo官网都有: https://cn.dubbo.apache.org/zh-cn/docsv2.7/user/quick-start/)
my-dubbo-interfaces 公共接口定义
直接创建⼀个项⽬。项⽬⾥有⼀个接⼝。接⼝中定义了⼀个服务的内容。
SiteService
public interface SiteService {
    String getName(String name);
}
 
my-dubbo-service-provider 服务提供者
pom.xml
- 注意dubbo的jar包里面,传递依赖了spring的jar依赖。
 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>my-dubbo-demo</artifactId>
        <groupId>com.qf</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>my-dubbo-service-provider</artifactId>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>my-dubbo-interfaces</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.8.0-alpha2</version>
        </dependency>
        <!--dubbo-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.3</version>
        </dependency>
        <!--zk-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-client</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.13</version>
        </dependency>
    </dependencies>
</project>
 
SiteServiceImpl
public class SiteServiceImpl implements SiteService {
    @Override
    public String getName(String name) {
        return "hello:"+name;
    }
}
 
provider.xml
<?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="site-service-provider"/>
    
    <!--指明注册中心的地址-->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
   
    <!--指明使用的协议-->
    <dubbo:protocol name="dubbo" port="20881" />
    
    <!--指明要暴露的服务-->
    <dubbo:service interface="com.qf.api.SiteService" ref="siteService" />
    
    <!--服务具体的实现bean-->
    <bean id="siteService" class="com.qf.service.impl.SiteServiceImpl" />
</beans>
 
provider
public class provider {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"provider.xml"});
        context.start();
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 
my-dubbo-service-consumer 服务消费者
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>my-dubbo-demo</artifactId>
        <groupId>com.qf</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>my-dubbo-service-consumer</artifactId>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>my-dubbo-interfaces</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.8.0-alpha2</version>
        </dependency>
        <!--dubbo-->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.3</version>
        </dependency>
        <!--zk-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-client</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.13</version>
        </dependency>
    </dependencies>
</project>
 
consumer.xml
<?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="site-service-consumer"/>
    
    <!--定义注册中心的地址-->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />
    
    <!--生成一个当前SiteService的代理对象:当前这个SiteService的代理对象会注入到ioc容器中,要使用的话可以从ioc容器中拿出来-->
    <dubbo:reference id="siteService" interface="com.qf.api.SiteService"/>
</beans>
 
Consumer
public class Consumer {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"consumer.xml"});
        context.start();
        //从ioc容器中获得SiteService服务提供者的代理对象
        SiteService siteService = (SiteService) context.getBean("siteService");
        String qf = siteService.getName("qf");
        System.out.println(qf);
    }
}
 
启动zookeeper
例如,在windows上,在zk所在的bin目录下打开cmd窗口,输出zkServer.cmd,按enter 即可启动
测试
当启动服务提供者后,打开zkCli.cmd,可以查看到服务提供这的信息(这里创建的实际上是1个临时节点,当提供者关闭时,该节点为空)
 
 运行Consumer,得到服务提供者返回的信息
 
服务代理的过程

6.dubbo内部结构

- dubbo提供了⼀个容器⽤来存放服务提供者(初始化)
 - 服务提供者将服务名、及具体的服务地址、端⼝等信息注册到注册中⼼上(初始化) 服务消费者订阅需要的服务(初始化)
 - 注册中⼼异步通知服务的变更情况
 - 服务消费者同步的调⽤到服务提供者的服务
 - 监控中⼼实时监控和治理当前的服务
 
注意:
- 同步:好⽐打电话,双⽅必须在线,才能完成
 - 异步:好⽐发微信语⾳,上游发完就结束了,不需要等待对⽅执⾏完。
 
四、Springboot中使⽤dubbo
springboot中使⽤dubbo也是⼀样,需要建⽴接⼝层、服务提供者、服务消费者。
创建接口层
SiteService
public interface SiteService {
    String getName(String name);
}
 
创建服务提供者
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.qf</groupId>
    <artifactId>my-boot-service-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>my-boot-service-provider</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>my-boot-qf-interfaces</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-zookeeper</artifactId>
            <version>2.7.3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.qf.my.boot.service.provider.MyBootServiceProviderApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
 
application.yml
server:
  port: 9001
dubbo:
  application:
    name: site-service-boot-provider
  registry:
    address: zookeeper://127.0.0.1:2181
  protocol:
    name: dubbo
    port: 20882
 
SiteServiceImpl
在服务提供者的实现类上打上注解来⾃于dubbo的@Service
import com.qf.boot.api.SiteService;
import org.apache.dubbo.config.annotation.Service;
@Service
public class SiteServiceImpl implements SiteService {
    @Override
    public String getName(String name) {
        return "hello boot:" + name;
    }
}
 
MyBootServiceProviderApplication
在启动类上打上注解@EnableDubbo
@EnableDubbo
@SpringBootApplication
public class MyBootServiceProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBootServiceProviderApplication.class, args);
    }
}
 
创建服务消费者
引⼊依赖(与提供者相同)
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.qf</groupId>
    <artifactId>my-boot-service-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>my-boot-service-consumer</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>my-boot-qf-interfaces</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-zookeeper</artifactId>
            <version>2.7.3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.qf.my.boot.service.consumer.MyBootServiceConsumerApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
 
application.yml
server:
  port: 8001
dubbo:
  application:
    name: site-service-boot-consumer
  registry:
    address: zookeeper://127.0.0.1:2181
 
SiteController
使⽤@Reference注解订阅服务,注意这个注解来⾃于dubbo
@RestController
@RequestMapping("/site")
public class SiteController {
    // <dubbo:reference : 两件事情:
    /* 
    	1.服务启动时向注册中心订阅SiteService服务地址列表 
    	2.生成SiteService服务的代理对象(注意:是会生成1个代理对象,而非单独的注入!!!)
    */
    @Reference
    private SiteService siteService;
    @GetMapping("/name")
    public String getName(String name){
        return siteService.getName(name);
    }
}
 
MyBootServiceConsumerApplication
@EnableDubbo
@SpringBootApplication
public class MyBootServiceConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBootServiceConsumerApplication.class, args);
    }
}
 
启动服务,访问接⼝

服务启动源码的剖析
以@EnableDubbo注解为入口分析

五、dubbo⽤法示例
1.version版本号
版本号⽤处是对于同⼀个接⼝,具有不同的服务实现。
服务提供者1
@Service(version = "default")
public class DefaultSiteServiceImpl implements SiteService {
	@Override
	public String siteName(String name) {
		return "default:"+name;
    }
}
 
服务提供者2
@Service(version = "async")
public class AsyncSiteServiceImpl implements SiteService {  
	@Override
	public String siteName(String name) {
		return "async:" + name;
    }
}
 
服务消费者
@Controller
public class VersionDubboConsumer {
    @Reference(version = "async" /* "default" */)
    private SiteService siteService;
	@GetMapping("/name")
    public String getName(String name){
        return siteService.getName(name);
    }
}
 
(此处遇到导入dubbo-qf-demo中无法正确识别my-dubbo-interfaces这个模块,解决的办法是:File -> New -> Module frome Existing Sources…,建1个模块)
2.指定protocol协议
dubbo框架可以对协议进⾏扩展,⽐如使⽤:
- rest
 - http
 - dubbo
 - ⾃定义协议
 
在配置⽂件中配置协议:
# 应⽤名称
spring.application.name=my-dubbo-provider
# 应⽤服务 WEB 访问端⼝
server.port=8080
# Base packages to scan Dubbo Component: @org.apache.dubbo.config.annotation.Service==>@EnableDubbo 
dubbo.scan.base-packages=com.qf.my.dubbo.provider 
dubbo.application.name=${spring.application.name}
## Dubbo Registry
dubbo.registry.address=zookeeper://172.16.253.55:2181
# Dubbo Protocol
#dubbo.protocol.name=dubbo
#dubbo.protocol.name=dubbo
# @Path
#dubbo.protocol.name=rest
#dubbo.protocol.name=rest
# 特别注意:如果使用rest协议,那么服务提供者必须使用@Path注解,否则启动时,就会报错
dubbo.protocols.protocol1.id=rest
dubbo.protocols.protocol1.name=rest
dubbo.protocols.protocol1.port=8090
dubbo.protocols.protocol1.host=0.0.0.0
dubbo.protocols.protocol2.id=dubbo1
dubbo.protocols.protocol2.name=dubbo 
dubbo.protocols.protocol2.port=20882 
dubbo.protocols.protocol2.host=0.0.0.0
dubbo.protocols.protocol3.id=dubbo2
dubbo.protocols.protocol3.name=dubbo
dubbo.protocols.protocol3.port=20883
dubbo.protocols.protocol3.host=0.0.0.0
 
在暴露服务时指明要使⽤的协议:
 (注意:如果配置文件中(如上所示)配置了多个协议,但是下方的@Service注解,又未指定具体的协议,那么会针对每个协议都起1个服务)
// protocol2 表示使用protocol2所指定的协议提供服务,言外之意就是消费方要使用protocol2所指定的协议来调用该服务
@Service(version = "default",protocol = "protocol2")
public class DefaultSiteServiceImpl implements SiteService {
	@Override
	public String siteName(String name) {
		return "default:"+name;
    }
}
 
3.使⽤rest访问dubbo的服务
服务提供者
pom.xml
对比《springboot中使用dubbo》,需要添加一些依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.qf</groupId>
    <artifactId>my-boot-service-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>my-boot-service-provider</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>my-boot-qf-interfaces</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-zookeeper</artifactId>
            <version>2.7.3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-http</artifactId>
            <version>2.7.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-metadata-report-zookeeper</artifactId>
            <version>2.7.3</version>
        </dependency>
        <!-- Dubbo rest protocol -->
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jaxrs</artifactId>
            <version>3.0.19.Final</version>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>com.qf.my.boot.service.provider.MyBootServiceProviderApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
 
application.yml
指定1个protocol1的协议,它使用的是rest协议
dubbo:
  application:
    name: site-service-boot-provider
  registry:
    address: zookeeper://127.0.0.1:2181
  protocol:
    name: dubbo
    port: 20881
  protocols:
    protocol1:
      id: rest
      name: rest
      port: 8090
      host: 0.0.0.0
    protocol2:
      id: dubbo1
      name: dubbo
      port: 20882
      host: 0.0.0.0
    protocol3:
      id: dubbo2
      name: dubbo
      port: 20883
      host: 0.0.0.0
 
SiteServiceImpl
import com.qf.boot.api.SiteService;
import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.rpc.protocol.rest.support.ContentType;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
// 1、指定了protocol1所指定的协议,由于是rest协议,必须要使用@Path注解,否则启动时会报错
// 2、如果此处未指定具体的协议,那么也必须使用@Path注解,因为配置文件中只要配置了rest协议,所以也会起1个对应的rest服务
@Service(protocol = {"protocol1", "protocol3"})
@Path("site")
public class SiteServiceImpl implements SiteService {
    @Override
    @GET
    @Path("name")
    @Produces({ContentType.APPLICATION_JSON_UTF_8, ContentType.TEXT_PLAIN_UTF_8})
    public String getName(@QueryParam("name") String name) {
        return "hello boot:" + name;
    }
}
 
服务消费方
仍然使用《springboot中使用dubbo》的配置和代码
@RestController
@RequestMapping("/site")
public class SiteController {
    //<dubbo:reference : 两件事情:1.服务启动时向注册中心订阅SiteService服务地址列表 2.生成SiteService服务的代理对象
    @Reference
    private SiteService siteService;
    @GetMapping("/name")
    public String getName(String name){
        return siteService.getName(name);
    }
}
 
测试

 
4.消费者通过url直连指定的服务提供者
(代码使用《springboot中使用dubbo》中的,但作如下修改)
-  
服务提供者的配置⽂件中声明三个dubbo协议
server: port: 9001 dubbo: application: name: site-service-boot-provider registry: address: zookeeper://127.0.0.1:2181 protocols: protocol1: id: dubbo1 name: dubbo port: 20881 host: 0.0.0.0 protocol2: id: dubbo2 name: dubbo port: 20882 host: 0.0.0.0 protocol3: id: dubbo3 name: dubbo port: 20883 host: 0.0.0.0 -  
服务提供者暴露服务,
未指定协议,则会暴露三个服务,每个协议对应⼀个服务!@Service(version= "default") // 版本为default public class DefaultSiteServiceImpl implements SiteService { @Override public String siteName(String name) { URL url = RpcContext.getContext().getUrl(); // 方便确认是哪个协议对应的服务返回的 return String.format("%s:%s, Hello, %s", url.getProtocol(), url.getPort(), name); } } -  
服务消费者端通过url指定某⼀个服务
(当服务消费者切换不同的url时,会返回对应协议的服务的结果。这说明服务提供方不指定协议,则会每个协议都会启动1个服务)@RestController @RequestMapping("/site") public class SiteController { @Reference( id = "siteService", version = "default", // 协议:ip:端口/服务接口全限定名:版本号 // url = "dubbo://192.168.134.5:20881/com.qf.boot.api.SiteService:default" url = "dubbo://192.168.134.5:20882/com.qf.boot.api.SiteService:default" // url = "dubbo://192.168.134.5:20883/com.qf.boot.api.SiteService:default" ) private SiteService siteService; @GetMapping("/name") public String getName(String name){ return siteService.getName(name); } } 
5.服务超时
服务提供者和服务消费者都可以配置服务超时时间(默认时间为1秒):
-  
服务提供者的超时时间:执⾏该服务的超时时间。如果超时,则会打印超时⽇志 (warn),但服务会正常执⾏完。
@Service(version = "timeout", timeout = 4000) public class TimeoutSiteServiceImpl implements SiteService { @Override public String siteName(String name) { try { //如果服务执行时间超过了指定的超时时间则会打印warn日志 Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("serving..."); return "timeout site service:"+name; } } -  
服务消费者的超时时间:从发起服务调⽤到收到服务响应的整个过程的时间。如果超时, 则进⾏重试,重试失败抛异常
@RestController @RequestMapping("/site") public class SiteController { @Reference(version = "timeout", timeout = 3000) private SiteService siteService; @GetMapping("/name") public String getName(String name){ return siteService.getName(name); } }(服务者配置超时时间,对所有的消费者有效,消费者配置超时时间只对自己有效,二者同时存在时,按消费者自己的计算)
 
6.集群容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。

-  
failover:(默认,推荐)
- 当出现失败时,会进⾏重试其它服务器,默认重试2次,⼀共三次调⽤。但是会出现幂等性问题。 虽然会出现幂等性问题,但是依然推荐使⽤这种容错机制,在业务层⾯解决幂等性问题: 
    
- ⽅案⼀:把数据的业务id作为数据库的联合主键,此时业务id不能重复。
 - ⽅案⼆(推荐):使⽤分布式锁来解决重复消费问题
 
 
 - 当出现失败时,会进⾏重试其它服务器,默认重试2次,⼀共三次调⽤。但是会出现幂等性问题。 虽然会出现幂等性问题,但是依然推荐使⽤这种容错机制,在业务层⾯解决幂等性问题: 
    
 -  
failfast:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
 -  
failsafe:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
 -  
failback:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
 -  
forking:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。
 -  
broadcast:广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
 
结论:如果使⽤dubbo,不推荐把重试关掉,⽽是在⾮幂等性操作的场景下,服务提供者⽅ 要做幂等性的解决⽅案(保证)。
7.服务降级
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
服务消费者通过Mock指定服务超时后执⾏的策略:
@RestController
@RequestMapping("/site")
public class MockDubboConsumer {
	// 在调用失败后(包括超时失败)
    @Reference(version = "timeout", timeout = 1000, mock = "fail:return halo")
    private SiteService siteService;
	@GetMapping("/name")
    public String getName(String name){
        return siteService.getName(name);
    }
}
 
-  
mock=force:return null表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。 -  
还可以改为
mock=fail:return null表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。 
服务提供方代码示例
@Service(version = "timeout", timeout = 6000)
public class SiteServiceImpl implements SiteService {
    @Override
    public String getName(@QueryParam("name") String name) {
        System.out.println("收到请求");
        try {
            TimeUnit.SECONDS.sleep(4000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "timeout: " + name;
    }
}
 
8.本地存根
远程服务后,客户端通常只剩下接⼝,⽽实现全在服务器端,但提供⽅有些时候想在客户端 也执⾏部分逻辑,⽐如:做 ThreadLocal 缓存,提前验证参数,调⽤失败后伪造容错数据等 等,此时就需要在 API 中带上 Stub,客户端⽣成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给⽤户,Stub 可以决定要不要去调 Proxy。
 (这个比上面的服务降级更容易控制)

 1、在服务消费者的包里,定义SiteServiceStub
@Slf4j
public class SiteServiceStub implements SiteService {
    private SiteService siteService;
    public SiteServiceStub(SiteService siteService) {
        this.siteService = siteService;
    }
    @Override
    public String getName(String name) {
        // 参数校验
        if (StringUtils.isEmpty(name)) {
            return "";
        }
        try {
            return siteService.getName(name);
        } catch (Exception e) {
            // 异常处理
            log.info("getName发生错误, e:{}", e);
            return " stub handle error " + name;
        }
    }
}
 
2、服务消费者调⽤服务,开启本地存根
@Slf4j
@RestController
@RequestMapping("/site")
public class SiteController {
    // 默认是1s超时,而服务提供者需要4s才能返回,因此重试2次,一共3次后,由本地存根SiteServiceSub对异常进行处理
    // 注意,此处不要使用mock,否则效果演示不出来,因为异常被mock处理了
    // stub属性也可以写true,表示在SiteService所在的包下直接找SiteServiceStub实现类
    @Reference(version = "timeout", /* mock = "fail:return errorr~~",*/ stub = "com.qf.my.boot.service.consumer.controller.SiteServiceStub")
    private SiteService siteService;
    @GetMapping("/name")
    public String getName(String name){
        log.info("getName");
        return siteService.getName(name);
    }
}
 
3、服务提供者SiteServiceImpl
@Service(version = "timeout", timeout = 6000)
public class SiteServiceImpl implements SiteService {
    @Override
    public String getName(@QueryParam("name") String name) {
        System.out.println("收到请求");
        try {
            TimeUnit.SECONDS.sleep(4000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "timeout: " + name;
    }
}
 
4、测试:访问http://localhost:8001/site/name?name=zzhua666,由于客户端默认1s就要结果,而服务端处理需要4s,所以客户端会重试2次,一共3次,最后1次失败后,在SiteServiceStub本地存根(包装了对代理对象的远程调用)的异常处理处被处理
 
9.参数回调
参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑。
简⽽⾔之,就是服务端可以调⽤客户端的逻辑。
配置继续使用《Springboot中使⽤dubbo》
公共接口
CallbackService
public interface CallbackService {
    String sayHello(String name, String key, CallbackListener listener);
}
 
CallbackListener
public interface CallbackListener {
    void changed(String msg);
}
 
CallbackListenerImpl
public class CallbackListenerImpl implements CallbackListener, Serializable {
    @Override
    public void changed(String msg) {
        System.out.println("callback1:" + msg);
    }
}
 
服务提供者
CallbackServiceImpl
@Service(
        version = "callback_v1",
        callbacks = 3,
        connections = 1,
        methods = {
                @Method(
                        name = "sayHello",
                        arguments = {@Argument(index = 2, callback = true)}
                )
        }
)
public class CallbackServiceImpl implements CallbackService {
     
    public String sayHello(String name, String key, CallbackListener listener) {
        listener.changed(getChanged(key)); // 发送变更通知
        return "Hello: " + name;
    }
     
    private String getChanged(String key) {
        return "Changed: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
    }
}
 
服务消费者
CallbackController
@RestController
@RequestMapping("callback")
public class CallbackController {
    @Reference(version = "callback_v1")
    private CallbackService callbackService;
    @RequestMapping("callback1")
    public String test01(String name) {
        callbackService.sayHello(name, "foo.bar", new CallbackListenerImpl());
        return "ok";
    }
}
 
(就是消费者序列化了1个对象过去,然后服务提供者反序列化出这个对象,然后调用这个对象的方法。那个@Method注解即时不加,也不会报错。所以这只是服务消费者将参数传过去给服务提供者调用的概念)
10.异步调用
从 2.7.0 开始,Dubbo 的所有异步编程接口开始以 CompletableFuture 为基础
基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小。
 
 简⽽⾔之,消费者通过异步调⽤,不⽤等待服务提供者返回结果就⽴即完成任务,待有结果 后再执⾏之前设定好的监听逻辑。(类似netty的promise)
基础代码使用《Springboot中使⽤dubbo》
公共接口
AsyncService
import java.util.concurrent.CompletableFuture;
public interface AsyncService {
    String sayHello(String name);
    CompletableFuture<String> doAsync(String name);
}
 
服务提供者
AsyncServiceImpl
@Slf4j
@Service(version = "async")
public class AsyncServiceImpl implements AsyncService {
    @Override
    public String sayHello(String name) {
        return "hello: " + name;
    }
    @Override
    public CompletableFuture<String> doAsync(String name) {
        log.info("doAsync start");
        try {
            TimeUnit.MILLISECONDS.sleep(name.length() * 500L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info("doAsync end");
        return CompletableFuture.supplyAsync(() -> {
            return "async: " + name;
        });
    }
}
 
服务消费者
AsyncController
@Slf4j
@RestController
public class AsyncController {
    // 默认1s超时,这里改为2s
    @Reference(version = "async", timeout = 2000)
    private AsyncService asyncService;
    @RequestMapping("/async01")
    public String async01(String name) {
        return asyncService.sayHello(name);
    }
    @RequestMapping("/async02")
    public String async02(String name) {
        log.info("async02 start");
        CompletableFuture<String> future = asyncService.doAsync(name);
        future.whenComplete((result, throwable) -> {
            if (throwable == null) {
                log.info("异步处理完成..." + result);
            } else {
                log.error("异步处理出错..., e: {}", throwable);
            }
        });
        log.info("async02 end");
        return "ok";
    }
}
 
(可以看到消费者调用提供者的方法后,并不会阻塞当前处理请求的线程,当服务提供者返回了数据之后,执行异步掉哦那个)
六、dubbo的负载均衡策略
在上⼀章节中,已经涉及到dubbo的负载均衡概念:⼀个服务接⼝具有三个服务提供者。
1.负载均衡策略
dubbo的负载均衡是发⽣在服务提供者端,负载均衡策略⼀共有以下四种:
- 随机(默认的策略):random
 - 轮询: roundrobin
 - 最⼩活跃调⽤数:leastactive
 - ⼀致性hash: consistenthash
 
2.dubbo中如何配置负载均衡策略
-  
服务提供者
@Service(version = "default",loadbalance = "roundrobin") public class DefaultSiteServiceImpl implements SiteService { @Override public String siteName(String name) { return "default:"+name; } } -  
服务消费者:如果两边都配置了负载均衡策略,则以消费者端为准。
@EnableAutoConfiguration public class DefaultDubboConsumer { @Reference(version = "default", loadbalance = "roundrobin") private SiteService siteService; public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(DefaultDubboConsumer.class); SiteService siteService = (SiteService) context.getBean(SiteService.class); String name = siteService.siteName("q-face"); System.out.println(name); } } 
测试
使用《Springboot中使⽤dubbo》的配置,仅作如下修改
服务提供者
如果@Service不指定协议,那么针对每个协议都会生成对应的服务
@Service
public class SiteServiceImpl implements SiteService {
    @Override
    public String siteName(String name) {
        URL url = RpcContext.getContext().getUrl();
        // 方便确认是哪个协议对应的服务返回的
        return String.format("%s:%s, Hello, %s", url.getProtocol(), url.getPort(), name);
    }
}
 
dubbo:
  application:
    name: dubbo-provider
  registry:
    address: zookeeper://127.0.0.1:2181
  protocol:
    name: dubbo
    port: 20881
  protocols:
    protocol1:
      id: dubbo2
      name: dubbo
      port: 20882
    protocol2:
      id: dubbo3
      name: dubbo
      port: 20883
 
@SpringBootApplication
@EnableDubbo
public class Provider {
    public static void main(String[] args) {
        SpringApplication.run(Provider.class, args);
    }
}
 
服务消费者
@RestController
@RequestMapping("site")
public class SiteController {
    @Reference(loadbalance = "roundrobin")
    private SiteService siteService;
    @RequestMapping("name")
    public String siteName(String name) {
        return siteService.siteName(name);
    }
}
 
server:
  port: 8001
dubbo:
  application:
    name: dubbo-consumer
  consumer:
    registry: zookeeper://127.0.0.1:2181
 
@SpringBootApplication
@EnableDubbo
public class Consumer {
    public static void main(String[] args) {
        SpringApplication.run(Consumer.class, args);
    }
}
 
测试效果

3.⼀致性hash的实现
-  
服务端的实现
@Service(version = "default",loadbalance = "roundrobin") public class DefaultSiteServiceImpl implements SiteService { @Override public String siteName(String name) { URL url = RpcContext.getContext().getUrl(); return String.format("%s:%s, Hello, %s", url.getProtocol(), url.getPort(), name); } } -  
消费端的实现
@EnableAutoConfiguration public class LoadBalanceDubboConsumer { @Reference(version = "default", loadbalance = "consistenthash") private SiteService siteService; public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(DefaultDubboConsumer.class); SiteService siteService = (SiteService) context.getBean(SiteService.class); for (int i = 0; i < 100; i++) { String name = siteService.siteName("q-face" + i % 6); System.out.println(name); } } } 
4.最少活跃调⽤数的实现
最少活跃调⽤数:相同活跃数的随机,活跃数指调⽤前后计数差。使慢的提供者收到更少请 求,因为越慢的提供者的调⽤前后计数差会越⼤。
在服务消费者端记录当前服务器⽬前被调⽤的数量(消费者⾃⼰维护着这个数据)。具体的 执⾏过程如下:
-  
消费者在本地缓存所有服务提供者
 -  
消费者在调⽤某⼀个服务时,会选择本地的所有服务提供者中,属性active值最⼩的那个 服务提供者。
 -  
选定该服务提供者后,并对其active属性+1
 -  
开始调⽤该服务
 -  
完成调⽤后,对该服务提供者的active属性-1
 
整个过程,如果active的值越⼤,说明该服务提供者的响应性能越差,因此越少调⽤。
七、安装Dubbo admin监管平台
docker构建
1.使⽤docker安装
docker run -d \
-p 8080:8080 \
-e dubbo.registry.address=zookeeper://172.16.253.55:2181 \
-e dubbo.admin.root.password=root \
-e dubbo.admin.guest.password=guest \
chenchuxin/dubbo-admin
 
2.访问
http://127.0.0.1:8080
源码构建
可参考:https://cn.dubbo.apache.org/zh-cn/docsv2.7/admin/ops/introduction/
目前的管理控制台已经发布 0.1 版本,结构上采取了前后端分离的方式,前端使用 Vue 和 Vuetify 分别作为 Javascript 框架和UI框架,后端采用 Spring Boot 框架。既可以按照标准的 Maven 方式进行打包,部署,也可以采用前后端分离的部署方式,方便开发,功能上,目前具备了服务查询,服务治理(包括 Dubbo 2.7 中新增的治理规则)以及服务测试三部分内容。
Maven方式部署
安装
git clone https://github.com/apache/dubbo-admin.git
cd dubbo-admin
mvn clean package
cd dubbo-admin-distribution/target
java -jar dubbo-admin-0.1.jar
 
访问
http://localhost:8080
前后端分离部署
前端
cd dubbo-admin-ui 
npm install 
npm run dev 
 
后端
cd dubbo-admin-server
mvn clean package 
cd target
java -jar dubbo-admin-server-0.1.jar
 
访问
http://localhost:8081
配置文件为:dubbo-admin-server/src/main/resources/application.properties
admin.config-center=zookeeper://127.0.0.1:2181
admin.registry.address=zookeeper://127.0.0.1:2181
admin.metadata-report.address=zookeeper://127.0.0.1:2181
 
测试
1、下载源码:https://github.com/apache/dubbo-admin.git,导入idea
 2、在dubbo-admin-server的pom.xml中把dubbo-admin-ui给注释掉(跳过测试:-DskipTests=true)
 
 3、dubbo-admin-ui使用vscode打开,npm install安装依赖,注意,这里nvm use v16.17.0切换node版本为:v16.17.0,其它版本可能会报错。npm run serve启动
 
 
点击详情,查看到元数据可能无法查看
1、首先,dubbo-admin-server的application.properties中配置dubbo.metadata-report.address=${admin.registry.address}
2、生产者添加配置dubbo.metadata-report.address=${dubbo.registry.address}
3、正常显示
 
八、Dubbo的SPI可扩展机制
1.Java的SPI(Service Provider Interface)机制
Java中提供了DriverManager、Connection、Statement接⼝,来约定了JDBC规范。
但针对 于MySQL或Oracle数据库来说,需要指明具体的驱动包,⽐如MySQL:mysql-connector-java-5.7.25.jar 包中的META-INF/services下的java.sql.Driver⽂件,⽂件 中指明了具体的驱动类
com.mysql.cj.jdbc.Driver
 
这样Java会读取该jar包下的该⽂件,那java怎么找到该⽂件呢?因为java程序需要该类: java.sql.Driver,所以是到找⽂件名是java.sql.Driver的⽂件。——相当于是加载了Driver接⼝的 具体的实现类
测试java的SPI

Cat接口
public interface Cat {
    String getName();
}
 
BlackCat
public class BlackCat implements Cat{
    @Override
    public String getName() {
        return "black";
    }
}
 
WhiteCat
public class WhiteCat implements Cat{
    @Override
    public String getName() {
        return "white";
    }
}
 
com.qf.cat
1个文件可配置多个实现类
com.qf.BlackCat
com.qf.WhiteCat
 
TestSPI
public class TestSPI {
    public static void main(String[] args) {
        ServiceLoader<Cat> cats = ServiceLoader.load(Cat.class);
        for (Cat cat : cats) {
            System.out.println(cat.getName());
        }
    }
}
 
2.SPI机制的缺点
⽂件中的所有类都会被加载且被实例化。没有办法指定某⼀个类来加载和实⼒化。此时 dubbo的SPI可以解决
3.dubbo的SPI机制
dubbo⾃⼰实现了⼀套SPI机制来解决Java的SPI机制存在的问题。
dubbo源码中有很多的项⽬,每个项⽬被打成⼀个jar包。⽐如代码中通过@Service注解的属 性protocol="c1"找到application.properties的c1协议是rest,那么就会去rest项⽬中找该项 ⽬中的META-INF中对应的⽂件,再找到指定的类来加载。
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
 
通过这种机制,在项⽬中新增⼀个协议也⾮常⽅便:
- 项⽬中新增协议jar
 - 在application.properties中加⼊协议名称
 - 在新增的项⽬的META-INF/services⽂件中加⼊配置
 
dubbo的spi机制简单实现

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.qf</groupId>
    <artifactId>dubbo-spi</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-common</artifactId>
            <version>2.7.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-http</artifactId>
            <version>2.7.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-dubbo</artifactId>
            <version>2.7.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-api</artifactId>
            <version>2.7.3</version>
        </dependency>
    </dependencies>
</project>
 
Cat接口
public interface Cat {
    String getName();
}
 
BlackCat
public class BlackCat implements Cat{
    @Override
    public String getName() {
        return "black";
    }
}
 
WhiteCat
public class WhiteCat implements Cat{
    @Override
    public String getName() {
        return "white";
    }
}
 
CatWrapper
public class CatWrapper implements Cat {
    private Cat cat;
    public CatWrapper(Cat cat) {
        this.cat = cat;
    }
    @Override
    public String getName() {
        System.out.println("cat wrapper");
        return cat.getName();
    }
}
 
com.qf.cat
文件在resources/META-INFO/dubbo文件夹下,1个文件可配置多个实现类
black=com.qf.BlackCat
white=com.qf.WhiteCat
com.qf.CatWrapper
 
TestSPI
public class TestSPI {
    public static void main(String[] args) {
        //获得扩展点加载器
        ExtensionLoader<Cat> extensionLoader = ExtensionLoader.getExtensionLoader(Cat.class);
        Cat cat = extensionLoader.getExtension("white");
        System.out.println(cat.getName());
    }
}
 
TestSPI2
public class TestSPI2 {
    public static void main(String[] args) {
        //拿到的是ProtocolFilterWrapper包装类,实际被包装的是HttpProtocol
        ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
        Protocol protocol = extensionLoader.getExtension("http");
        System.out.println(protocol);
    }
}
 

九、Dubbo源码剖析
1. Dubbo服务调⽤过程

 服务提供者的集群集群⾥⾯有⼏个服务提供者,就有⼏个invoker,invoker理解成调⽤⼀个服 务提供者需要的完整的细节,封装成的对象
那集群是怎么知道有⼏个服务提供者——从注册中⼼获得,注册中⼼获得的数据封装在 RegistryDirectory对象中。那么RegistryDirectory怎么得到注册中⼼中的url地址呢?必须有 ⼀个zk客户端:ZookeeperRegistry
RegistryDirectory⾥包含了ZookeeperRegistry,RegistryDirectory维护了所有的invoker调 ⽤器,调⽤器通过RegsitryDirectory(ZookeeperRegistry)的⽅法获得的。
AbstractClusterInvoker⾥包含了RegistryDirectory,换句话说,RegistryDirectory被 AbstractClusterInvoker所使⽤。真正执⾏的是AbstractClusterInvoker中的invoker⽅法,负 载均衡也在⾥⾯。
proxy是由JavassistProxyFactory⽣成的,拼装代码来⽣成的。代理对象通过 JavassistProxyFactory中的InvokerInvocationHandler ⽣成⼀个代理对象,来发起对集群的 调⽤。
 InvokerInvocationHandler⾥封装了RpcInvocation,RpcInvocation⾥封装的是这⼀次请求 所需要的所有参数。
这个invoker如果⽤的是dubbo协议,那么就是DubboInvoker(还有http RMI等协议) 源码中的invoker.invoke()中的invoker,如果是dubbo协议,那么就是DubboInvoker。
2.关于DubboInvoker的装饰
AsyncToSyncInvoker
异步转同步:dubbo 2.7.3 引⼊了InvokeMode(1.SYNC同步, 2.ASYNC异步, 3.FUTURE调⽤ future.get()时会造成线程阻塞)
在消费者端进⾏调⽤时先判断是否是同步调⽤,如果是同步的话,通过asyncResult.get()获得 结果。
如果是异步的话,直接返回Result对象(CompetableFuture)。
ProtocolFilterWrapper
Dubbo内容提供了⼤量内部实现,⽤来实现调⽤过程额外功能, 如向监控中⼼发送调⽤数 据, Tps限流等等, 每个filer专注⼀块功能。⽤户同样可以通过Dubbo的SPI扩展机制现在⾃ ⼰的功能。
ProtocolFilterWrapper:在服务的暴露与引⽤的过程中构建调⽤过滤器链。
ListenerInvokerWrapper
dubbo在服务暴露(exporter)以及销毁暴露(unexporter)服务的过程中提供了回调窗⼝,供⽤ 户做业务处理。
ListenerInvokerWrapper装饰exporter, 在构造器中遍历listeners,构建export的监听链。
3. 权重轮询算法
假如⽬前有三台服务器A、B、C,它们的权重分别是6、2、2,那也就意味着在10次调⽤中,轮询的结果应该为:AAAAAABBCC
但如果把B和C穿插在A中,轮询的结果会更加的平滑,⽐如:ABACAABACA
此时可以通过如下设计来实现:
- 每台服务器确定两个权重变量:weight、currentWeight
 - weight固定不变:初始化指定了值
 - currentWeight每次调整,初始化为0:currentWeight = currentWeight+weight
 - 从集群中选择currentWeight最⼤的服务器作为选择结果。并将该最⼤服务器的 currentWeight减去各服务器的weight总数
 - 调整currentWeight = currentWeight+weight,开始新⼀轮的选择
 - 以此往复,经过10次⽐较厚currentWeight都为0
 

