Dubbo学习记录

本篇是学习了Dubbo 视频教程全集(30P)| 4 小时从入门到精通后,总结的记录。

Dubbo是什么?

Dubbo

Dubbo作为阿里巴巴提供的分布式RPC和微服务框架,提供了基于接口的远程调用,容错和负载均衡,服务注册与发现功能。

什么是RPC

指远程过程调用,是一种进程间的通信方式。这是一种思想,而不是规范。

Dubbo的三大核心能力

  • 面向接口的远程方法调用
  • 智能容错和负载均衡
  • 服务的自主注册和发现

Dubbo的架构

流程图
  • Provider:暴露服务的服务者
  • Consumer:调用远程服务的服务消费者
  • Registry:服务注册与发现的注册中心
  • Monitor:统计服务的调用次数和调用时间的监控中心
  • Container:服务运行容器

Dubbo的服务注册与发现流程

  • 服务提供者(Provider):服务者,负责暴露服务,启动时,向注册中心注册服务。
  • 服务消费者(Consumer):调用远程服务的服务消费者,启动时,向注册中心订阅服务,获取服务者的地址列表,基于软负载均衡算法,选取一个服务进行调用,如果调用失败,选取下一台。
  • 注册中心(Registry):注册中心维护服务提供者的地址列表,如果有变更,通过长连接推送给服务消费者。
  • 监控中心(Monitor):服务消费者和提供者之间的调用次数、调用时间等,定时发送给监控中心。

调用关系

  • 服务容器负责启动、加载、运行服务提供者。
  • 服务提供者启动时,向注册中心注册自己提供的服务。
  • 服务消费者启动时,向注册中心订阅自己所需的服务。
  • 注册中心返回服务提供者的地址列表给消费者,如果有变更,会通过长连接推送变更数据给消费者。
  • 服务消费者,从服务提供者信息列表中,通过软负载均衡算法,选取一个提供者进行调用,如果调用失败,再选取另外一个进行调用。
  • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时发送一次到监控中心。

Dubbo环境搭建

JDK环境

必须有,大部分都已经有了,如果没有的话,自行配置一下

注册中心

Dubbo支持4种,分别为MulticastZookeeperRedisSimple,推荐使用Zookeeper,这里也只用Zookeeper

Zookeeper官方

Zookeeper下载地址,我选择3.4.14版本

配置环境变量

解压压缩包到指定目录,我选择放在User目录下

export ZOOKEEPER_HOME=/Users/wally/zookeeper-3.4.14
export PATH=$PATH:$ZOOKEEPER_HOME/bin
  • 更新环境变量
source .bash_profile

配置Zookeeper

  • 配置Zookeeper的配置文件

进入Zookeeper的目录,找到conf子目录,默认会有一个配置文件模板zoo_sample.cfg,复制一份,重命名为zoo.cfg。

  1. 编辑zoo.cfg,将dataDir属性的值改为../data,就是将数据目录配置到了Zookeeper根目录下的data目录。
  2. 在Zookeeper根目录下新建一个data目录,作为Zookeeper的数据存放目录。
  • clientPort,为Zookeeper要使用的端口号
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial 
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between 
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=../data
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the 
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

Zookeeper常用命令

去到bin目录,终端下使用zkServer.sh

//启动
zkServer.sh start
//停止
zkServer.sh stop
//查看状态
zkServer.sh status

使用Zookeeper的客户端cli连接Zookeeper

zkCli.sh  -server localhost

admin管理控制台

为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序,不是必须,其实不装,也不影响Dubbo的使用。

  • 下载dubbo-admin
  • 进入src\main\resources\目录,修改application.properties,指定zookeeper地址:dubbo.registry.address=zookeeper://127.0.0.1:2181
  • 打包dubbo-admin:mvn clean package -Dmaven.test.skip=true
  • 运行dubbo-admin:java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
  • 登录admin,用户名和密码都为root

使用案例

有2个服务,用户服务订单服务,订单服务依赖用户服务。所以订单服务为服务消费方,而用户服务为提供方
订单服务初始化订单时,会传入userId,订单服务调用用户服务,获取用户的收货地址列表,再返回。

公共模块

用户地址-实体类

public class UserAddress implements Serializable {
    private Integer id;
    /**
     * 用户地址
     */
    private String userAddress;
    /**
     * 用户Id
     */
    private String userId;
    /**
     * 收货人
     */
    private String consignee;
    /**
     * 电话号码
     */
    private String phoneNum;
    /**
     * 是否为默认地址,Y-是 N-否
     */
    private String idDefault;

    public UserAddress(Integer id, String userAddress, String userId, String consignee, String phoneNum, String idDefault) {
        this.id = id;
        this.userAddress = userAddress;
        this.userId = userId;
        this.consignee = consignee;
        this.phoneNum = phoneNum;
        this.idDefault = idDefault;
    }

    //省略get、set方法

    @Override
    public String toString() {
        return "UserAddress{" +
                "id=" + id +
                ", userAddress='" + userAddress + '\'' +
                ", userId='" + userId + '\'' +
                ", consignee='" + consignee + '\'' +
                ", phoneNum='" + phoneNum + '\'' +
                ", idDefault='" + idDefault + '\'' +
                '}';
    }
}

Service层接口

  • 订单服务
public interface OrderService {
    /**
     * 初始化订单
     *
     * @param userId 用户Id
     */
    List<UserAddress> initOrder(String userId);
}
  • 用户服务
public interface UserService {
    /**
     * 按照用户Id,获取用户的所有收货地址
     *
     * @param userId 用户Id
     */
    List<UserAddress> getUserAddressList(String userId);
}

建立服务提供者Provider

服务者模块名:user-service-provider

  • pom文件

配置Dubbo依赖,Zookeeper依赖、日志等

<?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.atguigu.gmail</groupId>
    <artifactId>user-service-provider</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- 公共API接口 -->
        <dependency>
            <groupId>com.atguigu.gmail</groupId>
            <artifactId>gmall-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- 日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <!-- dubbo依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.2</version>
        </dependency>
        <!-- 注册中心zookeeper客户端 -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>
    </dependencies>
</project>
  • log4j日志配置
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE            debug   info   warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE

# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
  • 编写用户服务实现

dubbo支持多个实现类来做多版本,一般用来做灰度发布使用。

注意,这个@Service注解是Spring的,不是Dubbo的,不需要导错包了。

//1.0.0版本,用户接口实现类
@Service
public class UserServiceImpl implements UserService {
    public List<UserAddress> getUserAddressList(String userId) {
        System.out.println("---------- 线上版本订单服务,版本1.0.0 ----------");

        UserAddress userAddress1 = new UserAddress(1,
                "北京市昌平区xx科技园综合楼3层",
                userId,
                "李老师",
                "010-yyyyy",
                "Y");
        UserAddress userAddress2 = new UserAddress(2,
                "深圳市宝安区西部硅谷大厦B座3层",
                userId,
                "王老师",
                "010-xxxxx",
                "N"
        );
        return Arrays.asList(userAddress1, userAddress2);
    }
}
//2.0.0版本的实现
@Service
public class UserServiceImpl2 implements UserService {
    public List<UserAddress> getUserAddressList(String userId) {
        System.out.println("---------- 灰度订单服务,版本2.0.0 ----------");

        UserAddress userAddress1 = new UserAddress(1,
                "北京市昌平区xx科技园综合楼3层",
                userId,
                "李老师",
                "010-yyyyy",
                "Y");
        UserAddress userAddress2 = new UserAddress(2,
                "深圳市宝安区西部硅谷大厦B座3层",
                userId,
                "王老师",
                "010-xxxxx",
                "N"
        );
        return Arrays.asList(userAddress1, userAddress2);
    }
}
  • provider配置文件

配置一个服务提供者,需要6步,最后一步不是必须的,可以不配置监控中心。

  1. 配置当前服务/应用的名字
  2. 指定注册中心的位置,有2种写法,第一种是协议名和地址合起来一起写,第二种是分开属性写
  3. 指定通信的规则,就是协议和端口
  4. 暴露服务,就是暴露刚才上面写的接口实现,并且标签里面指定暴露的服务方法
  5. dubbo支持多版本发布,常用来做灰度发布,和第四步一样,就是version属性指定不同的版本
  6. 指定配置中心的地址和端口,可以直接使用一个registry关键字,让dubbo自己查找
<?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">

    <!-- 1.配置当前服务/应用的名字 -->
    <dubbo:application name="user-service-provider"/>

    <!-- 2.指定注册中心的位置 -->
    <!--    <dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>-->
    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>

    <!-- 3.指定通信规则(通信协议、通信端口) -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- 4.暴露服务,interface属性指定服务接口,ref指定服务具体实现 -->
    <dubbo:service interface="com.atguigu.gmail.service.UserService" ref="userServiceImpl1"
                   version="1.0.0">
        <dubbo:method name="getUserAddressList" timeout="1000"/>
    </dubbo:service>
    <!-- 服务实现 -->
    <bean id="userServiceImpl1" class="com.atguigu.gmail.service.impl.UserServiceImpl"/>

    <!-- 5.配置一个灰度服务实现 -->
    <dubbo:service interface="com.atguigu.gmail.service.UserService" ref="userServiceImpl2"
                   version="2.0.0"/>
    <bean id="userServiceImpl2" class="com.atguigu.gmail.service.impl.UserServiceImpl2"/>

    <!-- 6.配置监控中心,2种方式,protocol属性写协议,如果写registry,则代表自动从注册中心查找监控中心的位置 -->
    <dubbo:monitor protocol="registry"/>
    <!-- 或者手动指定位置 -->
    <!--    <dubbo:monitor address="127.0.0.1:7070"/>-->
</beans>
  • 启动类

提供者启动,为了后续的消费方能调用到,先用System.in.read()阻塞,不马上结束程序。

public class ProviderApplication {
    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
        context.start();
        //阻塞
        System.in.read();
    }
}

建立服务消费者Consumer

  • pom文件
<?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.atguigu.gmail</groupId>
    <artifactId>order-service-consumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- 公共API -->
        <dependency>
            <groupId>com.atguigu.gmail</groupId>
            <artifactId>gmall-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- 日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <!-- dubbo依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.2</version>
        </dependency>
        <!-- 注册中心zookeeper客户端 -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>2.12.0</version>
        </dependency>
    </dependencies>
</project>
  • log4j配置

和上面服务提供者的一样,拷贝一份即可,不再赘述

  • 编写订单服务实现
@Service
public class OrderServiceImpl implements OrderService {
    /**
     * 注入用户服务
     */
    @Autowired
    private UserService userService;

    public List<UserAddress> initOrder(String userId) {
        //1.查询用户的搜索地址
        return userService.getUserAddressList(userId);
    }
}
  • 本地存根

本地存根,用于调用远程服务时,先走一遍自己的本地实现,可以在这里做参数验证、拦截、容错以及结果缓存等操作。
类似于一个静态代理的拦截器,或者说AOP的效果。

public class UserServiceStub implements UserService {
    private final UserService userService;

    /**
     * 必须提供一个有参构造器,dubbo会传入远程服务的代理对象,通过这个对象,来进行调用远程方法
     *
     * @param userService 远程服务的代理对象
     */
    public UserServiceStub(UserService userService) {
        this.userService = userService;
    }

    public List<UserAddress> getUserAddressList(String userId) {
        System.out.println("UserServiceStub.getUserAddressList() => 本地存根被调用...");
        try {
            if (StringUtils.isEmpty(userId)) {
                throw new RuntimeException("传入的用户Id不能为空!");
            }
            return userService.getUserAddressList(userId);
        } catch (Exception e) {
            e.printStackTrace();
            //容错处理,如果调用出错,返回空
            return new ArrayList<UserAddress>();
        }
    }
}
  • consumer配置文件

配置一个服务消费者,需要5步,最后一步监控中心可省略。

  1. 开启包扫描,这个是Spring的配置
  2. 配置服务消费者的应用名称
  3. 指定注册中心的地址。有一个check属性,配置注册中心不存在时,是否报错,默认为true,就是会报错
  4. 声明需要远程调用的远程服务接口,生成远程服务代理
  5. 配置监控中心的地址和端口,也可以指定为registry,代表dubbo自动搜索监控中心
<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns:context="http://www.springframework.org/schema/context"
        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.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        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">

    <!-- 1.配置包扫描 -->
    <context:component-scan base-package="com.atguigu.gmail.service.impl"/>

    <!-- 2.配置服务消费者的应用名称 -->
    <dubbo:application name="order-service-consumer"/>

    <!-- 3.指定注册中心的地址 -->
    <!-- 3.1check属性,配置注册中心不存在时,是否报错,默认为true,就是会报错 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181" check="false"/>

    <!-- 4.声明需要远程调用的远程服务接口,生成远程服务代理 -->
    <!-- 4.1 check属性,启动时是否检查依赖的服务是否启动,默认为true,启动会检查,会导致如果依赖的服务没有启动,就会报错,一般我们设置为false,调用的时候,才去检查对方服务状态 -->
    <!-- 4.2 timeout属性,配置消费者调用服务提供者的方法超过一定时间没响应,就判定为超时,避免大量的阻塞,单位为毫秒,默认值为1000,就是一秒 -->
    <!-- 4.3 retries属性,重试次数,调用远程方法超时时,进行重试,不包含第一次调用(如果服务提供方有多个服务,重试请求会分散到不同的服务中) -->
    <!-- 幂等方法(设置重试次数,来提升系统的性能),非幂等方法(不能设置重试次数) -->
    <!-- 幂等:就是方法无论运行多少次,都是一个效果,例如数据库的查询、删除、修改 -->
    <!-- 非幂等:每次方法运行都是产生一个新的效果,例如数据库新增 -->
    <!-- 如不需要重试,设置值为0即可,代表不重试 -->
    <!-- 4.4 dubbo还支持多版本,就是灰度,version属性指定使用调用哪个版本的实现,还支持*通配符来表示随机选一个来调用,来达到灰度 -->
    <!-- 4.5 配置本地存根,使用stub属性配置本地存根的全路径类名 -->
    <dubbo:reference id="userService" interface="com.atguigu.gmail.service.UserService"
                     check="false" timeout="3000" retries="3"
                     version="*" stub="com.atguigu.gmail.service.stub.UserServiceStub"/>

    <!-- 4.2每个去配置check属性是很麻烦的,我们可以统一配置所有消费者的缺省配置,我们可以默认所有服务都不检查,如果有某一项服务不一样,则单独配置即可 -->
    <!-- 4.3 timeout属性,为所有的消费者提供缺省设置,而不需要去dubbo:reference下每个都配置 -->
    <dubbo:consumer check="false" timeout="1000"/>

    <!-- 5.配置监控中心,2种方式,protocol属性写协议,如果写registry,则代表自动从注册中心查找监控中心的位置 -->
    <dubbo:monitor protocol="registry"/>
    <!-- 或者手动指定位置 -->
    <!--    <dubbo:monitor address="127.0.0.1:7070"/>-->
</beans>
  • 启动类

我们为了演示方便,直接在启动时,获取服务实例,调用远程服务。

public class ConsumerApplication {
    public static void main(String[] args) throws IOException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
        OrderService orderService = context.getBean(OrderService.class);
        orderService.initOrder("1");
        System.out.println("调用完成");
        //阻塞
        System.in.read();
    }
}

整合SpringBoot的改造

上面的Dubbo使用比较原始,一般在SpringBoot出现前使用,现在基本都用SpringBoot,所以Dubbo也提供了和SpringBoot整合的方式。

建立服务提供者Provider

建立模块:boot-user-service-provider

  • pom文件
<?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.atguigu.gmail</groupId>
    <artifactId>boot-user-service-provider</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- 依赖基础接口 -->
        <dependency>
            <groupId>com.atguigu.gmail</groupId>
            <artifactId>gmall-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- spring boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--热启动-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!-- Dubbo Spring Boot 的 Starter -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
        <!-- 服务容错 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.0.4.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • application.properties

相当于将之前的provider.xml的内容挪到这里

#配置应用名称
dubbo.application.name=boot-user-service-provider

#配置注册中心的位置和注册中心协议
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper

#配置通信规则,协议名以及端口
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881

#配置监控中心
dubbo.monitor.protocol=registry

#配置包扫描,@EnableDubbo注解是配置这个包扫描功能
#dubbo.scan.base-packages=com.atguigu.gmail
  • dubbo.properties

dubbo还提供了全局配置文件,该文件一般是配置公共配置,application.xx的配置可以覆盖该配置。

#配置公共端口号,当在虚拟机参数、xml或application.properties覆盖相同属性时,会进行覆盖
#虚拟机参数:-Ddubbo.protocol.port,值为20880
#优先级:虚拟机参数 > xml或application.properties > dubbo.properties
#一般dubbo.properties文件配置的是公共配置
dubbo.protocol.port=20882
  • 编写用户服务实现类

SpringBoot抛弃了XML,推荐注解和JavaConfig的方式配置,所以Dubbo也有对应的注解进行配置。

这里的@Service注解,是dubbo的注解,用来暴露服务的,同时我们也要Spring管理该Bean,为了和Spring的@Service不重名,我们使用@Component注解。

//1.0.0版本

//注解@Service,标识暴露给dubbo的服务,timeout属性配置远程服务调用超时时间,version属性配置多版本功能,可实现灰度
@Service(timeout = 3000, version = "*")
//因为和spring的@Service注解同名,这里用@Component注解也是一样的
@Component("userService1")
public class BootUserServiceImpl implements UserService {
    /**
     * Hystrix容错处理,该方法会被Hystrix代理,来处理容错异常
     *
     * @param userId 用户Id
     */
    @HystrixCommand
    public List<UserAddress> getUserAddressList(String userId) {
        System.out.println("---------- 线上版本订单服务,版本1.0.0 ----------");

        UserAddress userAddress1 = new UserAddress(1,
                "北京市昌平区xx科技园综合楼3层",
                userId,
                "李老师",
                "010-yyyyy",
                "Y");
        UserAddress userAddress2 = new UserAddress(2,
                "深圳市宝安区西部硅谷大厦B座3层",
                userId,
                "王老师",
                "010-xxxxx",
                "N"
        );
        return Arrays.asList(userAddress1, userAddress2);
    }
}

//2.0.0版本
//注解@Service,标识暴露给dubbo的服务,timeout属性配置远程服务调用超时时间,version属性配置多版本功能,可实现灰度
@Service(timeout = 3000, version = "*")
//因为和spring的@Service注解同名,这里用@Component注解也是一样的
@Component("userService2")
public class BootUserServiceImpl2 implements UserService {
    public List<UserAddress> getUserAddressList(String userId) {
        System.out.println("---------- 灰度订单服务,版本2.0.0 ----------");

        UserAddress userAddress1 = new UserAddress(1,
                "北京市昌平区xx科技园综合楼3层",
                userId,
                "李老师",
                "010-yyyyy",
                "Y");
        UserAddress userAddress2 = new UserAddress(2,
                "深圳市宝安区西部硅谷大厦B座3层",
                userId,
                "王老师",
                "010-xxxxx",
                "N"
        );
        return Arrays.asList(userAddress1, userAddress2);
    }
}
  • 启动类
  1. dubbo整合springboot的三种方式

    • 导入dubbo的starter,在application.properties配置属性,使用@Service暴露服务,使用@Reference引用服务,并且Application入口类必须加上@EnableDubbo注解来开启注解功能
    • 导入dubbo的starter,保留dubbo的xml配置文件,再在Application入口类上使用@ImportResource注解,引入xml配置文件,使用原始方式,就不需要用注解了
    • 使用注解API的方式,就是SpringJavaConfig的方式提供类对象,将每个组件手动创建放到Spring容器中,让dubbo扫描出组件
  2. 注意!!!上面3种配置方式,不能共存,只能选一种,否则会出现重复定义的异常,例如注解和JavaConfig的方式都做了,而@EnableDubbo内部又使用@DubboComponentScan注解,导致2种配置方式都开启了,出现了重复定义
    服务容错,使用Hystrix,使用@EnableHystrix注解开启Hystrix,在需要保护的方法上使用@HystrixCommand注解,在调用方,也依赖Hystrix,同样使用@EnableHystrix注解开启Hystrix,在调用的方法上,也加上@HystrixCommand注解

推荐使用第一种和第三种方式,就是注解方式或者JavaConfig的方式,我更倾向于第一种,配置文件方式会比JavaConfig简洁一些。

//1)注解方式,使用@EnableDubbo注解,开启基于注解的dubbo功能,例如@Service注解才能生效
@EnableDubbo
//2)xml方式,使用@ImportResource注解导入配置
//@ImportResource(locations = "classpath:provider.xml")
//3)JavaConfig配置方式,需要使用@DubboComponentScan注解配置扫描包的包名,用来扫描到Dubbo的Java配置类,用@EnableDubbo也是可以的,@EnableDubbo注解就已经引用了@DubboComponentScan注解
//@DubboComponentScan(basePackages = "com.atguigu.gmail")
//开启服务容错
@EnableHystrix
@SpringBootApplication
public class BootProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootProviderApplication.class, args);
    }
}
  • JavaConfig方式配置Dubbo

注意:不能和其他2种同时存在!!

//SpringJavaConfig的方式,配置dubbo的组件
@Configuration
public class MyDubboConfig {
    /**
     * 提供Application标签的功能
     * 相当于:dubbo.application.name=boot-user-service-provider
     */
    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig config = new ApplicationConfig();
        config.setName("boot-user-service-provider");
        return config;
    }

    /**
     * 提供注册中心配置
     * 相当于:
     * dubbo.registry.address=127.0.0.1:2181
     * dubbo.registry.protocol=zookeeper
     */
    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig config = new RegistryConfig();
        config.setAddress("127.0.0.1:2181");
        config.setProtocol("zookeeper");
        return config;
    }

    /**
     * 提供通信协议的配置
     * 相当于:
     * dubbo.protocol.name=dubbo
     * dubbo.protocol.port=20881
     */
    @Bean
    public ProtocolConfig protocolConfig() {
        ProtocolConfig config = new ProtocolConfig();
        config.setName("dubbo");
        config.setPort(20881);
        return config;
    }

    /**
     * 提供暴露服务的配置
     * 相当于
     * <dubbo:service interface="com.atguigu.gmail.service.UserService" ref="userServiceImpl1"
     * version="1.0.0">
     * <dubbo:method name="getUserAddressList" timeout="1000"/>
     * </dubbo:service>
     */
    @Bean
    public ServiceConfig<UserService> userServiceConfig(@Qualifier("userService1") UserService userService) {
        ServiceConfig<UserService> serviceConfig = new ServiceConfig<>();
        //配置暴露的服务的接口
        serviceConfig.setInterface(UserService.class);
        //配置暴露的服务的具体实现,spring会自动注入到参数上
        serviceConfig.setRef(userService);
        //设置版本
        serviceConfig.setVersion("1.0.0");
        //配置每一个方法的信息
        MethodConfig methodConfig = new MethodConfig();
        //设置方法名
        methodConfig.setName("getUserAddressList");
        //设置超时时间
        methodConfig.setTimeout(1000);
        //将MethodConfig关联到Service的配置中
        serviceConfig.setMethods(Arrays.asList(methodConfig));
        return serviceConfig;
    }

    /**
     * 提供监控中心的配置
     */
    @Bean
    public MonitorConfig monitorConfig() {
        MonitorConfig config = new MonitorConfig();
        config.setProtocol("registry");
        return config;
    }
}
  • provider.xml

和普通方式配置的provider.xml文件一致,所以不在赘述了。

建立服务消费者Consumer

建立模块:boot-order-service-consumer

  • pom文件
<?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.atguigu.gmail</groupId>
    <artifactId>boot-order-service-consumer</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- 公共API -->
        <dependency>
            <groupId>com.atguigu.gmail</groupId>
            <artifactId>gmall-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- web项目 -->
        <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>
        </dependency>
        <!--热启动-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!-- Dubbo Spring Boot 的 Starter -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>
        <!-- 服务容错 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.0.4.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • application.properties
#配置端口号
server.port=8081
#配置应用名称
dubbo.application.name=boot-order-service-consumer
#配置注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#配置监控中心
dubbo.monitor.protocol=registry
  • 编写订单服务实现
  1. 和服务提供者注册的服务类似,也用@Service注解进行服务暴露
  2. 使用@Reference注解,注入依赖的服务,例如这里是用户服务
@Service
public class BootOrderServiceImpl implements OrderService {
    /**
     * 依赖注入dubbo的远程服务,因为服务是远程服务,不能用spring的@Autowired注解了
     * 1.url属性,可以实现dubbo直连,直接配置远程服务的ip和端口,不经过注册中心
     * <p>
     * 2.loadbalance属性,负载均衡配置,负载均衡实现有4种,分别是
     * 1)roundrobin:轮训,挨个按顺序分配,第一轮:A-B-C 第二轮:A-B-C
     * 2)leastactive:最小活跃调用数,选中处理速度最快的服务。活跃数指调用前后计数差,优先调用高的,相同活跃数的随机。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
     * 3)random:随机抽取,dubbo模式使用这种
     * 4)一致性哈希,只要请求过1次,后面的请求都会找到上次的机器进行处理
     * <p>
     * 3.check属性,是否启动时检查服务提供方是否存在,默认为true,代表检查,如果发现没启动,会报错
     */
//    @Autowired
//    @Reference(url = "127.0.0.1:20881")
//    @Reference(loadbalance = "roundrobin")
    @Reference(check = false)
    private UserService userService;

    /**
     * 使用@HystrixCommand注解,保护调用失败时,调用容错方法进行返回
     */
    @HystrixCommand(fallbackMethod = "fallback")
    public List<UserAddress> initOrder(String userId) {
        //1.查询用户的搜索地址
        return userService.getUserAddressList(userId);
    }

    /**
     * 容错方法,注意方法参数必须和容错方法一致,否则会抛出找不到方法的异常
     */
    private List<UserAddress> fallback(String userId) {
        UserAddress address = new UserAddress(
                0,
                "测试",
                userId,
                "测试",
                "xxxx",
                "N"
        );
        return Arrays.asList(address);
    }
}
  • Controller层

使用@Autowired注解,可以注入暴露的服务,提供initOrder接口,进行调用

@Controller
public class OrderController {
    @Autowired
    private OrderService orderService;

    /**
     * 初始化订单
     */
    @ResponseBody
    @RequestMapping("/initOrder")
    public List<UserAddress> initOrder(@RequestParam("uid") String userId) {
        return orderService.initOrder(userId);
    }
}
  • 启动类

重点:使用注解@EnableDubbo,开启dubbo的注解功能。

//注解@EnableDubbo,开启dubbo的注解功能
@EnableDubbo
//开启服务容错
@EnableHystrix
@SpringBootApplication
public class BootConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootConsumerApplication.class, args);
    }
}

总结

代码我上传到了Github,有兴趣的同学可以clone。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351