使用Dubbo&zk进行远程调用


Tips: 关于Dubbo的学习,Dubbo官网写的简单易懂 Dubbo官网,如果有时间没必要网上找了,基本也是官网一大抄。如果赶时间了解下可以参考下面的内容。

Zookeeper和Dubbo-Admin的安装

linux下安装,linux下安装zookeeper

windows下安装Dubbo:

下载zookeeper-3.4.11.tar.gz,解压后进入bin目录,命令行运行zkServer.cmd,会报错,提示找不到zoo.cfg配置文件,如下:

 ERROR [main:QuorumPeerMain@88] - Invalid config, exiting abnormally
org.apache.zookeeper.server.quorum.QuorumPeerConfig$ConfigException: Error processing C:\Users\lenovo\Desktop\dubbo\zookeeper-3.4.11\bin\..\conf\zoo.cfg
        at org.apache.zookeeper.server.quorum.QuorumPeerConfig.parse(QuorumPeerConfig.java:156)
        at org.apache.zookeeper.server.quorum.QuorumPeerMain.initializeAndRun(QuorumPeerMain.java:104)
        at org.apache.zookeeper.server.quorum.QuorumPeerMain.main(QuorumPeerMain.java:81)
Caused by: java.lang.IllegalArgumentException: C:\Users\lenovo\Desktop\dubbo\zookeeper-3.4.11\bin\..\conf\zoo.cfg file is missing

解决: 进入zookeeper的conf目录下,找到zoo_xample.cfg文件,复制一份,重命名为zookeeper即可。可以进入到这个配置文件瞄一眼,看到快照存储位置/tmp/zookeeper,监听的端口2181(可用于客户端连接)

# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=/tmp/zookeeper
# the port at which the clients will connect
clientPort=2181 

既然让不要用/tmp/zookeeper来存储,那么我们就修改以下,比如修改到conf同级data文件

dataDir=../data

现在再来启动,看到下面的日志说明成功了

在这里插入图片描述

也可以使用客户端来访问一下,同样还是进入到bin目录,命令行执行./zkCli.cmd ,并且查看根节点有哪些目录

在这里插入图片描述

更多关于zookeeper的命令行操作,查看zookeeper数据结构和监听功能

tips:至于Dubbo则是不需要安装的,因为dubbo本身并不是一个服务软件。它其实就是一个jar包能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序,不过这个监控即使不装也不影响使用(下面装的是管理控制台)。

下载dubbo-admin,进入目录,打jar包mvn clean pagkage,接下来在target目录下就能看到dubbo-admin-0.0.1-SNAPSHOT.jar,该目录下命令行运行java -jar dubbo-admin-0.0.1-SNAPSHOT.jar程序。启动起来后浏览器访问localhost:7001,要求输入用户名和密码,均为root

在这里插入图片描述

这个时候服务数,应用数都为0。接下来编写服务提供者和服务消费者程序,之后再来看这个管理控制台界面。

需要注意的是在application.properties文件里面是配置了zookeeper的地址和监听的端口号。

使用Dubbo进行原远程过程调用

服务提供方:提供商品查询的服务;

服务消费方:传入参数,调用服务提供方的服务。

通用服务:存放服务接口,服务模型,服务异常等均放在 API。

所以需要创建三个工程,使用idea的话,那么就先创建一个空的project,再创建三个moduel。

通用服务service-interface

实体类:

public class Goods implements Serializable {
    private Integer id;
    private String name;
    private Double price;
    private String desc;
    toString,全参和无参构造器,getter和setter方法
}

服务接口:

public interface GoodsService {
    Goods getGoods(int id);
}

在这里插入图片描述

最后将该模块安装到本地仓库,mvn install

服务提供方: goods-service-provider

pom.xml引入依赖 分别为dubbo,zk, 通用服务

<?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.scu</groupId>
    <artifactId>goods-service-provider</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.scu</groupId>
            <artifactId>service-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- 引入dubbo -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.2</version>
        </dependency>
        <!-- 由于我们使用zookeeper作为注册中心,所以需要操作zookeeper
            dubbo 2.6以前的版本引入zkclient操作zookeeper
            dubbo 2.6及以后的版本引入curator操作zookeeper
            下面两个zk客户端根据dubbo版本2选1即可
            -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>
    </dependencies>
</project>

服务配置文件(Spring配置文件)applicatonContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <!--当前服务名称-->
    <dubbo:application name="goods-service-provider"/>
    <!--使用zookeeper广播注册中心暴露服务地址-->
    <dubbo:registry address="zookeeper://localhost:2181"/>
    <!--使用dubbo协议在20880端口暴露服务-->
    <dubbo:protocol name="dubbo" port="20880"/>
    <!--声明需要暴露的服务的接口-->
    <dubbo:service interface="com.scu.service.GoodsService" ref="goodsService"/>
    <bean id="goodsService" class="com.scu.dubbo.service.GoodsServiceImpl"/>
</beans>

服务实现类:

public class GoodsServiceImpl implements GoodsService {
    @Override
    public Goods getGoods(int id) {
        System.out.println(id);
        return new Goods(id,"mi 9 pro",3600.0,"小米首款5G手机");
    }
}

启动类:读取配置文件,注册服务

public class DubboMain {
    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.in.read();
    }
}

目录结构如下

在这里插入图片描述

运行main方法启动服务(先得把zookeeper服务启动起来),查看dubbo admin

在这里插入图片描述

在这里插入图片描述

服务消费者:goods-service-consumer

其中pom.xml同服务提供者

applicationContext.xml内容:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <!--当前服务名称-->
    <dubbo:application name="goods-service-provider"/>
    <!--使用zookeeper广播注册中心暴露服务地址-->
    <dubbo:registry address="zookeeper://localhost:2181"/>

    <!--生成远程代理服务,可以和本地bean一样使用GoodsService-->
    <dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" />
</beans>

主程序,启动Spring容器调用服务

public class ConsumerMain {
    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        GoodsService goodsService = applicationContext.getBean("goodsService",GoodsService.class);
        Goods goods = goodsService.getGoods(1);
        System.out.println("id为1的商品信息为:" + goods);
        System.in.read();
    }
}

目录结构如下:

在这里插入图片描述

启动服务,查看两个控制台的输出分别为:

1
id为1的商品信息为:Goods{id=1, name='mi 9 pro', price=3600.0, desc='小米首款5G手机'}

到这一个简单的使用dubbo的RPC调用demo就完成了。

tips:maven如果使用3.6.2会报绑定错误。换回3.6.1

Dubbo整合spring-boot

三个点:

1.导包不同

2.配置文件使用application.yml(或者application.properties),照着applicationContext.xml填即可

3.添加注解的时候注意导入的是哪个类(@Service,@Reference)。

服务提供者依赖如下

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>com.scu</groupId>
            <artifactId>service-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
    </dependencies>

编写配置文件application.yml:

dubbo:
  application:
    name: goods-service-provider
  registry:
    protocol: zookeeper
    address: localhost:2181
  protocol:
    name: dubbo
    port: 20880

application.name就是服务名,不能跟别的dubbo提供端重复

registry.protocol 是指定注册中心协议

registry.address 是注册中心的地址加端口号

protocol.name 是分布式固定是dubbo,不要改。

base-package 注解方式要扫描的包,可以在启动类上使用注解代替

服务实现:

package com.scu.dubbo.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.scu.bean.Goods;
import com.scu.service.GoodsService;

@Service // 注意导入的是alibaba的Service而不是Spring的
public class GoodsServiceImpl implements GoodsService {
    @Override
    public Goods getGoods(int id) {
        System.out.println(id);
        return new Goods(id,"mi 9 pro",3600.0,"小米首款5G手机");
    }
}

启动类:

package com.scu.dubbo;

import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@DubboComponentScan(basePackages = "com.scu.dubbo.service") // 扫描的包
public class ServiceStarter {
    public static void main(String[] args) {
        SpringApplication.run(ServiceStarter.class,args);
    }
}

再写消费者

导入依赖:

	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>com.scu</groupId>
            <artifactId>service-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
    </dependencies>

编写配置文件application.yml:

server:
  port: 8081 #使用了81端口 前面用了80端口
dubbo:
  application:
    name: goods-service-consumer
  registry:
    protocol: zookeeper
    address: localhost:2181

controller类:

package com.scu.dubbo.controller;

import com.alibaba.dubbo.config.annotation.Reference;
import com.scu.bean.Goods;
import com.scu.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GoodsController {

    @Reference
    private GoodsService goodsService;

    @GetMapping("/goods")
    public Goods getGoodsById(int id){
        System.out.println(123);
        return goodsService.getGoods(id);
    }
}

启动类:

@SpringBootApplication
public class ConsumerStarter {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerStarter.class,args);
    }
}

整个的目录结构如下

在这里插入图片描述
先启动生产者,再启动消费者

页面访问 http://localhost:8081/goods?id=1,返回如下结果说明调用成功。

{"id":1,"name":"mi 9 pro","price":3600.0,"desc":"小米首款5G手机"}

配置解释

启动时检查

之前的demo要求先启动服务提供方,后启动服务消费方。如果先启动服务消费方,那么会报错,找不到服务提供方。这是因为启动的时候会检查容器,找不到服务提供方,抛出异常服务就没有启动起来了。必要的时候我们需要关闭这个启动时检查。使用配置check=false即可。

public class ConsumerMain {
    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//        GoodsService goodsService = applicationContext.getBean("goodsService", GoodsService.class);
//        Goods goods = goodsService.getGoods(10086);
//        System.out.println(goods);
        System.in.read();
    }
}
<dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" check="false"/>

或者使用全局配置

<dubbo:consumer check="false" timeout="4000"/>

超时设置

默认情况下,服务消费方调用服务提供方提供的服务,如果1000ms内没有收到响应,抛出超时异常。比如我在服务消费方,设置服务超时时间为3秒,服务提供方提供的服务在4秒后才会返回结果。

服务消费方设置超时时间:timeout=3000

<dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" check="false"
                     timeout="3000"/>

服务提供方: 4秒后返回结果

public class GoodsServiceImpl implements GoodsService {
    @Override
    public Goods getGoods(int id) {
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("服务被调用:" + id);
        return new Goods(id,"xiaomi 9 pro",3699.0,"小米的第一款5G手机");
    }
}

所以最后会抛出超时异常

Caused by: com.alibaba.dubbo.remoting.TimeoutException: Waiting server-side response timeout.

此外,超时设置还可以加到方法上,全局配置上。优先级是方法>接口>全局配置

<dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" check="false"
                     timeout="3000">
        <dubbo:method name="getGoods" timeout="1000"/>
</dubbo:reference>

<!--消费者全局配置:所有的服务都不检查-->
<dubbo:consumer check="false" timeout="4000"/>

甚至可以配置到服务提供的那一方,而且如果级别是一样的话那么就是消费者优先。但是,如果服务提供者的级别更高,那么仍然是服务提供者邮箱。具体的顺序,参考官网。

重试次数

由于网络问题可能导致超时,所以有时需要设置超时重试,使用retries=2即可,2表示重试的次数,不包括最初的调用,所以最终会调用三次,但是只要有一次返回则不会继续重新调用。

服务消费方,设置重试次数 2次。

<dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" check="false"
                     timeout="3000" retries="2">
        <dubbo:method name="getGoods" timeout="1000"/>
</dubbo:reference>

服务提供方代码如下: 每次调用会答应id,同时休眠2秒,调用方超时时间设置为1秒,所以肯定会超时。

public class GoodsServiceImpl implements GoodsService {
    @Override
    public Goods getGoods(int id) {
        System.out.println("服务被调用:" + id);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return new Goods(id,"xiaomi 9 pro",3699.0,"小米的第一款5G手机");
    }
}

想起的服务提供方再启动服务消费方,服务方控制台输出三次:服务被调用:10086

服务被调用:10086
服务被调用:10086
服务被调用:10086

此外,如果该服务在多台机器上都有,比如三台机器都有该服务,那么默认每次重试会调用不同的服务。为了做个试验,启动三次服务提供方(三个进程当作三台机器),每次都需要修改服务暴露的端口(20880,20881,20882),同时修改getGoods方法,比如启动三次,3次只修改如下

System.out.println("服务1被调用:" + id);
System.out.println("服务2被调用:" + id);
System.out.println("服务3被调用:" + id);
public class ProviderMain {

    @Test // 端口20880    System.out.println("服务1被调用:" + id);
    public void test1() throws IOException {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.in.read();
    }
    @Test // 端口20881    System.out.println("服务2被调用:" + id);
    public void test2() throws IOException {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.in.read();
    }
    @Test // 端口20882    System.out.println("服务3被调用:" + id);
    public void test3() throws IOException {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.in.read();
    }
}

启动服务消费方后,对应的test1-test3控制台分别打印

服务1被调用:10086
服务2被调用:10086
服务3被调用:10086

tips: 如果是修改/删除/查询等操作需要设计成幂等操作(多次相同的操作不会带来额外的影响,比如多次相同的查询得到的会是同一个结果,多次相同的删除操作,第一次成功,后面虽然不能删除带上不会带来额外的影响),同时设置重试次数;新增操作则设计为非幂等操作,并且不要加重试次数。

多版本

有时候会遇到接口的升级,那么就有新版本和旧版本,我们先让服务消费方调用部分的新版本,等新版本稳定会再全部换成新版本。比如GoodsService接口,现在又了新版本实现如下

旧版本:

public class GoodsServiceImpl implements GoodsService {
    @Override
    public Goods getGoods(int id) {
        System.out.println("服务1被调用:" + id);// 新旧版本区别在这
        return new Goods(id,"xiaomi 9 pro",3699.0,"小米的第一款5G手机");
    }
}

新版本:

public class GoodsServiceImpl2 implements GoodsService {
    @Override
    public Goods getGoods(int id) {
        System.out.println("新的服务被调用:" + id);
        return new Goods(id,"xiaomi 9 pro",3699.0,"小米的第一款5G手机");
    }
}

提供方修改的配置文件

<dubbo:service interface="com.scu.service.GoodsService" ref="goodsService" version="0.0.1"/>
<bean id="goodsService" class="com.scu.dubbo.service.GoodsServiceImpl"/>

<dubbo:service interface="com.scu.service.GoodsService" ref="goodsService2" version="0.0.2"/>
<bean id="goodsService2" class="com.scu.dubbo.service.GoodsServiceImpl2"/>

消费方的配置文件: 使用version="0.0.1" 使用旧版本

<dubbo:reference id="goodsService" interface="com.scu.service.GoodsService" check="false"
                     timeout="3000" retries="2" version="0.0.1">
     <dubbo:method name="getGoods" timeout="1000"/>
</dubbo:reference>

输出为: 服务1被调用:10086

假设新服务稳定了,此时修改调用方配置文件,设置version="0.0.2"

那么这次输出的就是:新的服务被调用:10086

Zookeeper宕机与Dubbo直连

现象:注册中心Zookeeper宕机后,还可以调用Dubbo暴露的服务,这是因为注册中心虽然全部宕机,但是服务提供者和服务消费者可以通过本地缓存进行通讯,即服务消费者,如果调用过服务,那么会本地记录服务地址,但Zookeeper宕机后,可以绕过Zookeeper直接和Dubbo提供的服务连接。此外,还有种方案,即Dubbo直连可以使用url=127.0.0.1:20880标签(如果是使用@Reference注解就是``@Reference(url=xxx))与Dubbo直接进行直连,即告诉消费者服务提供者地址。

负载均衡

假设将GoodsService服务部署到了3台机器,那么则需要使用负载均衡策略来决定每次服务调用者调用的是哪台机器。

下面的介绍来自Dubbo官网,因为确实没啥好说的了。

Random LoadBalance

  • 随机,按权重设置随机概率。
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin LoadBalance

  • 轮询,按公约后的权重设置轮询比率。
  • 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

LeastActive LoadBalance

  • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHash LoadBalance

  • 一致性 Hash,相同参数的请求总是发到同一提供者。
  • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

服务端服务级别

<dubbo:service interface="..." loadbalance="roundrobin" />

客户端服务级别

<dubbo:reference interface="..." loadbalance="roundrobin" />

服务端方法级别

<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>

客户端方法级别

<dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值