当前位置: 首页 > news >正文

Dubbo分布式框架学习(2)

学习链接

源自 - 千锋教育Dubbo教程,分布式服务框架dubbo源码级应用教程 - B站视频

dubbo-learn 所有代码在这里

dubbo官网文档

dubb官方使用示例代码 - github

文章目录

⼀、架构演进

为什么现在的系统不⽤单体架构,⽽⽤微服务架构。

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不能重复。
      • ⽅案⼆(推荐):使⽤分布式锁来解决重复消费问题
  • 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

在这里插入图片描述

相关文章:

  • Cursor:AI 驱动的编程变革者
  • JavaScript性能优化实践:从微观加速到系统级策略
  • 【数据分享】中国3254座水库集水区特征数据集(免费获取)
  • C++11QT复习 (七)
  • MySQL中的函数(字符串,数值,日期,流程)以及部分案例
  • A股复权计算_权息数据整理代码
  • 自适应二值化与形态学变换在图像颜色识别与替换中的应用解析
  • .Net中的流处理类总结 Stream/FileStream/MemoryStream/NetworkStream/StreamReader
  • 04-深入解析 Spring 事务管理原理及源码
  • YOLOv5配置训练以及华为昇腾910B推理
  • JavaScript数组Array的使用:添加、删除、排序、遍历、互转
  • 【python中级】使用 distutils 和wheel生成whl 轮子文件
  • 蓝桥杯 切割
  • 深入探究Spring MVC
  • C++学习day4
  • 基于大数据分析的门户信息推荐系统
  • 《STL 六大组件之容器篇:简单了解 list》
  • 国家天文台携手阿里云,发布国际首个太阳大模型“金乌”
  • 用Python实现TCP代理
  • Java数据类型与数据库类型映射技术文档
  • 西安市网站建设/设计公司网站
  • 怎么做网站优化推广/html网页制作模板代码
  • 北京怎样做企业网站/百度免费
  • 做网站找云无限/网络营销课程个人总结
  • 上饶网站建设srsem/关键字排名优化公司
  • wordpress做购物网站/学新媒体运营最好的培训学校