SpringBoot 系列之集成 Dubbo 示例教程

AI 乔治 · 05/26 16:27

分布式基本理论


分布式基本定义

《分布式系统原理与范型》定义:

“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”

分布式系统(distributed system)是建立在网络之上的软件系统。


架构发展演变

架构的发展是由最初的单一应用架构构建的,一般就是 ORM 框架方便数据库操作。


不过随着系统越来越复杂,单一应用架构会变得难以维护,所以架构逐渐演变出了垂直应用架构,所谓垂直应用架构其实就是安装业务模板进行拆分。


比如可以安装业务将一个电商系统分为订单模块,用户信息管理模块,商品管理模块等等,这时候 MVC 框架就派上用场,MVC 框架可以协助系统更好的按业务拆分,不过业务拆分后虽然是比单一应用架构更好维护了。


不过随着系统越来约复杂,发现很多共用的模块很难复用起来,这时候分布式服务架构登场了,分布式架构是将一些核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,当应用需要时,就去服务中心调服务就可以,而实现这种服务注册的肯定是 RPC 框架了。


当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率,这时候就需要流动计算架构(SOA)[ Service Oriented Architecture],用于提高机器利用率的资源调度,SOA 是一个治理中心。


综上所述,到目前,软件系统架构演变经历了:单一应用架构->垂直应用架构->分布式应用架构->流动计算架构,下面 Dubbo 官网的图片可以很好的描述


RPC 简介


RPC 概念

RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。

它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。


RPC 核心模块

RPC 有两个核心模块:通信和序列化


Dubbo 理论简介

Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

官网:

http://dubbo.apache.org/

Dubbo 的服务治理:

Dubbo 原理图片,图片来自 Dubbo 官网:


Dubbo 角色:

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


调用过程:

下面根据我的理解说明一下

  • 0:服务器容器负责启动、加载、运行服务提供者
  • 1:服务提供者在启动后就可以向注册中心暴露服务
  • 2:服务消费者在启动后就可以向注册中心订阅想要的服务
  • 3:注册中心向服务消费者返回服务调用列表
  • 4:服务消费者基于软负载均衡算法调用服务提供者的服务,这个服务提供者有可能是一个服务提供者列表,调用那个服务提供者就是根据负载均衡来调用了
  • 5:服务提供者和服务消费者定时将保存在内存中的服务调用次数和服务调用时间推送给监控中心


Dubbo 环境搭建

Zookeeper 搭建

搭建 Zookeeper,首先是搭建分布式架构的注册中心 Zookeeper,当然也可以用 Redis 等等来做服务注册中心,不过本博客只介绍 Zookeeper 的,因为没有 linux 服务器,所以只介绍 window 版的搭建

  • 1、下载 Zookeeper:
  • 网址 https://archive.apache.org/dist/zookeeper/zookeeper-3.4.13/
  • 2、解压 Zookeeper
  • 解压 Zookeeper 之后,运行 bin 目录里的 zkServer.cmd,发现报错了,提示找不到配置文件,所以需要继续步骤 3
  • 3、配置 Zookeeper
  • 因为 Zookeeper 的 conf 文件夹下面只提供 zoo_sample.cfg 文件,需要自己修改命名为 zoo.cfg


对于配置文件需要注意:

dataDir=./ 临时数据存储的目录(可写相对路径)

clientPort=2181 zookeeper 的端口号

  • 4、使用 zkCli.cmd 测试
  • 修改配置文件后,重新启动 zkServer.cmd,启动 bin 目录下面的 zkCli.cmd,很显然这是个客户端程序,注意 zkServer.cmd 是服务端程序,必须启动


ok,简单在 zkCli.cmd 敲几个命令测试一下:

ls /:列出 zookeeper 根下保存的所有节点

create –e /testNode 12345678:创建一个 testNode 节点,值为 12345678

get /testNode:获取/testNode 节点的值


Dubbo 管理页面搭建


搭建了服务注册中心后,就需要搭建 Dubbo-admin 了,最近看了一下,dubbo 的 Github 项目已经进行了更新,管理平台已经做了比较大的改动,而我学习的时候,平台是比较简单的,所以本 dubbo-admin 搭建是以旧版 master 的为准,不过以学习为目的的,只需要知道具体原理和操作技巧就可以

  • 下载 dubbo-admin
  • 去下载一下 dubbo-admin,可以找主干 master 分支的,找到 dubbo-admin,git clone 到本地
  • https://github.com/apache/incubator-dubbo-ops


因为我搭建时候(ps:不是博客写作时间),dubbo 还没做比较大改动,所以我以比较旧的版本为例子,现在新的具体参考 dubbo 官方的教程,本博客只是做记录

  • 修改 dubbo-admin


修改 src\main\resources\application.properties 指定 zookeeper 地址


  • Maven package dubbo-admin
mvn clean package -Dmaven.test.skip=true
  • 运行 dubbo-admin 的 jar

maven 打包之后,就去 target 里找到 jar,然后 cmd 运行

java -jar dubbo-admin-0.0.1-SNAPSHOT.jar

运行成功之后,访问: http://127.0.0.1:7001,输入默认的账号密码 root/root,登录成功


Dubbo 服务注册发现例子

经典例子(引用尚硅谷教程例子进行改写):


业务场景

某个电商系统,订单服务需要调用用户服务获取某个用户的所有地址;

我们现在 需要创建两个服务模块进行测试

  • 模块 功能 订单服务模块 创建订单等 用户服务模块 查询用户地址等 测试预期结果:
  • 订单服务 web 模块在 A 服务器,用户服务模块在 B 服务器,A 可以远程调用 B 的功能


api 工程创建

创建工程:

建议将服务接口,服务模型,服务异常等均放在 API 包中,因为服务模型及异常也是 API 的一部分,同时,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。

创建一个 API 工程,将实体类和接口都放在 api 工程

maven 新建一个 shop-api-common 工程:

用户地址 DTO 类:

package com.test.dubbo.bean;
import java.io.Serializable;
public class UserAddress implements Serializable {
	
	private Integer id;
    private String userAddress; //用户地址 private String userId; //用户 idprivate String consignee; //收货人 private String phoneNum; //电话号码 private String isDefault; //是否为默认地址 Y-是 N-否
    
    public UserAddress() {
		super();
	}
    
	public UserAddress(Integer id, String userAddress, String userId, String consignee, String phoneNum,
			String isDefault) {
		super();
		this.id = id;
		this.userAddress = userAddress;
		this.userId = userId;
		this.consignee = consignee;
		this.phoneNum = phoneNum;
		this.isDefault = isDefault;
	}
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getUserAddress() {
		return userAddress;
	}
	public void setUserAddress(String userAddress) {
		this.userAddress = userAddress;
	}
	public String getUserId() {
		return userId;
	}
	public void setUserId(String userId) {
		this.userId = userId;
	}
	public String getConsignee() {
		return consignee;
	}
	public void setConsignee(String consignee) {
		this.consignee = consignee;
	}
	public String getPhoneNum() {
		return phoneNum;
	}
	public void setPhoneNum(String phoneNum) {
		this.phoneNum = phoneNum;
	}
	public String getIsDefault() {
		return isDefault;
	}
	public void setIsDefault(String isDefault) {
		this.isDefault = isDefault;
	}
}

用户信息服务接口:

package com.test.dubbo.service;

import java.util.List;

import com.test.dubbo.bean.UserAddress;

/**
 * 用户服务
 */
public interface UserService {
	
	/**
 * 按照用户 id 返回所有的收货地址
 * @param userId
 * @return
 */
	public List<UserAddress> getUserAddressList(String userId);

}

订单信息服务接口:

package com.test.dubbo.service;

import java.util.List;

import com.test.dubbo.bean.UserAddress;

public interface OrderService {
	
	/**
 * 初始化订单
 * @param userId
 */
	public List<UserAddress> initOrder(String userId);

}


ok,创建好 api 工程


服务提供者工程

要实现服务提供,配置文件主要需要配置如下:


Dubbo 提供者加载过程(Dubbo 容器的启动):


Spring 加载 xml 配置之后暴露服务的过程:


Exporter 方法主要是打开 socket 的监听,接收客户的请求

ok,理解了上面的理论知识后,继续创建一个 user-service-provider 工程:

参考 Dubbo 官方例子

<properties><spring-boot.version>2.2.1.RELEASE</spring-boot.version><dubbo.version>2.7.5</dubbo.version><curator.version>2.12.0</curator.version></properties>


    <dependencies><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>${dubbo.version}</version></dependency>


        <!-- Zookeeper dependencies --><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-dependencies-zookeeper</artifactId><version>${dubbo.version}</version><type>pom</type><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></exclusion></exclusions></dependency>
</dependencies>

我用的 Springboot 版本是 2.2.1,dubbo starter 版本是 2.7.5,启动之后,发现报错

Caused by: java.lang.: org.apache.curClassNotFoundExceptionator.framework.CuratorFrame

找不到对应的类,看起来是缺少 jar 了?通过网上资料搜索再加上如下配置即可:

<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework --><dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>${curator.version}</version></dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes --><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>${curator.version}</version></dependency>

curator 是 Zookeeper 配置需要的

maven 配置好之后,就可以进行 dubbo 配置:

#server.port=7010
        
dubbo.application.name=user-service-provider
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper

dubbo.protocol.name=dubbo
dubbo.protocol.port=20882

dubbo.monitor.protocol=registry
#dubbo.scan.base-packages=com.example.springboot.dubbo

用户服务类:

package com.example.springboot.dubbo.service.impl;

import com.example.spring.dubbo.bean.UserAddress;
import com.example.spring.dubbo.service.UserService;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;


@Service//暴露服务
@Component
public class UserServiceImpl implements UserService {

    @Overridepublic List<UserAddress> getUserAddressList(String userId) {

        UserAddress address1 = new UserAddress(1, "北京市昌平区", "1", "李老师", "010-56253825", "Y");
        UserAddress address2 = new UserAddress(2, "深圳市宝安区", "1", "王老师", "010-56253825", "N");

        return Arrays.asList(address1,address2);
    }

}

Springboot 的启动类要加上@EnableDubbo 注解:

package com.example.springboot.dubbo;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * <pre>
 * Springboot 启动类
 * </pre>
 *
 * @author nicky
 * <pre>
 * 修改记录
 * 修改后版本: 修改人:修改日期: 2020 年 01 月 05 日 修改内容:
 * </pre>
 */
@EnableDubbo(scanBasePackages="com.example.springboot.dubbo.service.impl")
@SpringBootApplication
public class UserServiceProviderApplication {

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

启动 Springboot 类,然后在监控平台是可以看到服务注册成功的


查看服务接口的详细信息:



服务消费者工程

然后服务已经注册了,现在创建一个消费者工程 order-service-comsumer

  • maven 加上配置
 <properties><spring-boot.version>2.2.1.RELEASE</spring-boot.version><dubbo.version>2.7.5</dubbo.version><curator.version>2.12.0</curator.version></properties>


    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

        <dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>${dubbo.version}</version></dependency>


        <!-- Zookeeper dependencies --><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-dependencies-zookeeper</artifactId><version>${dubbo.version}</version><type>pom</type><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></exclusion></exclusions></dependency>


        <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework --><dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>${curator.version}</version></dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes --><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>${curator.version}</version></dependency>

        <dependency><groupId>com.example.springboot</groupId><artifactId>shop-api-common</artifactId><version>0.0.1-SNAPSHOT</version></dependency>

    </dependencies>

  • 消费者配置文件:
server.port=8081

dubbo.application.name=order-service-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.monitor.protocol=registry
  • 订单服务类:
package com.example.springboot.dubbo.service.impl;

import com.example.spring.dubbo.bean.UserAddress;
import com.example.spring.dubbo.service.OrderService;
import com.example.spring.dubbo.service.UserService;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.dubbo.config.annotation.Service;

import java.util.List;

/**
 * <pre>
 * 订单服务
 * </pre>
 *
 * @author nicky
 * <pre>
 * 修改记录
 * 修改后版本: 修改人:修改日期: 2020 年 01 月 05 日 修改内容:
 * </pre>
 */
@Service
public class OrderServiceImpl implements OrderService{
    @Reference(loadbalance="random",timeout=1000) //dubbo 直连
            UserService userService;

    @Overridepublic List<UserAddress> initOrder(String userId) {
        //1、查询用户的收货地址
        List<UserAddress> addressList = userService.getUserAddressList(userId);
        return addressList;
    }
}

  • Springboot 启动
package com.example.springboot.dubbo;


import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * <pre>
 * Springboot 启动类
 * </pre>
 *
 * @author nicky
 * <pre>
 * 修改记录
 * 修改后版本: 修改人:修改日期: 2020 年 01 月 05 日 修改内容:
 * </pre>
 */
@EnableDubbo(scanBasePackages="com.example.springboot.dubbo.service.impl")
@SpringBootApplication
public class OrderServiceProviderApplication {

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

写一个 Controller 类进行测试:

package com.example.springboot.dubbo.controller;

import com.example.spring.dubbo.bean.UserAddress;
import com.example.spring.dubbo.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;



@Controller
public class OrderController {

    @Autowired
    OrderService orderService;

    @ResponseBody
    @RequestMapping("/initOrder")
    public List<UserAddress> initOrder(@RequestParam("uid")String userId) {
        return orderService.initOrder(userId);
    }

}

在监控平台看,消费者启动也是可以的