Dubbo 分布式系统架构解决方案。

什么是分布式系统

  • 分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统
  • 分布式系统(distributed system)是建立在网络之上的软件系统
  • 简单来说:多个(不同职责)人共同完成一件事。多个相同职责的人那叫集群
  • 任何一台服务器都无法满足淘宝的双十一的数据吞吐量,一定是很多台服务器公共来完成的。

    单一应用架构

    当网站流程很小时,只需要一个应用,将所有功能部署到一起(所有业务都放在一个tomcat里),从而减少部署节点和成本。用 于简化 增删改查工作量的数据访问框架(ORM 对象关系映射)是关键。
    image.png
    优点:

  • 小项目开发快、成本低

  • 架构简单
  • 易于测试
  • 易于部署

缺点:

  • 大项目模块耦合严重 维护成本高
  • 新增业务困难
  • 核心业务与边缘业务混合在一起,出现问题互相影响

    垂直应用架构

    当访问了逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成几个互不相干的几个应用以提高效率。
    大模块按照MVC分层模式,进行拆分成多个互不相关的小模块,并且每个小模块都有独立服务器。
    用于加速前端页面开发的web框架(MVC)是关键:因为每个小应用都有独立的页面
    image.png
    缺点:模块之间不可能完全没有交集,公用模块无法重复利用,开发性的浪费
    1000的并发量支持活跃用户:3-5万的用户

    分布式服务架构

    当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的业务,逐渐形成稳健的服务中心,使前端应用能更快速的响应多变的市场需求。 此时,用户提高业务复用及整合的分布式服务框架(RPC)远程调用是关键。

image.png
RPC:独立的应用服务器之间,要依靠RPC(Romote Procedure Call)才能够调用
物流服务不忙,有100台服务器;商品服务特别忙,也是100台服务器。
如何做到资源优化调配?

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐呈现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率 此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

image.png
SOA:面向服务架构(Service-Oriented-Architecture) 简单理解就是“服务治理”,例如:公交车站的“调度员”
image.png

Dubbo 简介

Dubbo 是分布式服务框架,是阿里巴巴的开源项目,现在由Apache进行维护,Dubbo致力于提高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案;dubbo是个服务框架,如果没有分布式的需求,是不需要用的。

RPC

RPC(Remote Procedure Call)是指远程过程调用,是一种进程间的通信方式。

RPC基本的通信原理

  1. 在客户端将对象进行序列化
  2. 底层通信框架使用netty(基于TCP协议的socket),将序列化的对象发给服务方提供方
  3. 服务提供方通过socket得到数据文件之后,进行反序列化,获得要操作的对象
  4. 对象数据操作完毕,将新的对象序列化,再通过服务提供方的socket返回给客户端
  5. 客户端获得序列化数据,再反序列化,得到最新的数据对象,完成一次请求

image.png :::tips RPC两个核心模块:通信(socket)、序列化 :::

节点角色

节点 角色说明
Provider 暴露(提供)服务的服务方 (洗浴中心)
Consumer 调用远程服务的消费方 (客人)
Registry 服务注册与发现的注册中心 (便民服务中心:所有的娱乐场所都在本中心注册)
Monitor
统计服务的调用次数和调用时间的监控中心,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心 ()
Container 服务运行容器(洗浴一条街)

image.png
如上图的调用关系:

  1. 服务容器负责启动,加载,运行服务提供者
  2. 服务提供者在启动时,向注册中心注册自己提供的服务
  3. 服务消费者在启动时,向注册中心订阅自己所需要的服务
  4. 在注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
  5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

Dubbo快速入门

注册中心

需要Zookeeper作为注册中心,负责服务地址的注册与查找,相当于目录服务。服务提供者和消费者在启动时与注册中心交互,注册中不转发请求,压力较小。
dubbo即是求职的人,也是招聘单位。而Zookeeper就是人才市场/招聘网站

关于Zookeeper的安装和使用不再多介绍参考:
Zookeeper 快速入门
开启有Zookeeper的虚拟机,这里只开一个不使用集群环境。注意在zoo.cfg禁止掉集群

  1. #######################cluster##########################
  2. #server.1=172.16.150.130:2888:3888
  3. #server.2=172.16.150.131:2888:3888
  4. #server.3=172.16.150.132:2888:3888

开启Zookeeper服务

  1. [root@localhost bin]# ./zkServer.sh start
  2. ZooKeeper JMX enabled by default
  3. Using config: /opt/zookeeper/bin/../conf/zoo.cfg
  4. Starting zookeeper ... STARTED
  5. [root@localhost bin]# ./zkServer.sh status
  6. ZooKeeper JMX enabled by default
  7. Using config: /opt/zookeeper/bin/../conf/zoo.cfg
  8. Client port found: 2181. Client address: localhost.
  9. Mode: standalone

服务提供方

创建项目dubbo-server
dubbo和spring的5.0版本最为融洽,严格使用5.0.6的spring的版本和dubbo的2.5.7版本和Zookeeper融洽的版本3.4.6

service接口实现,注意@Service是dubbo提供的

/**
 * 服务方接口声明,具体实现远程调用dubbo-server的service的实现类
 */
public interface HelloService {
    String syHello(String name);
}
import com.alibaba.dubbo.config.annotation.Service;
import service.HelloService;

@Service
public class HelloServiceImpl implements HelloService {
    @Override
    public String syHello(String name) {
        return "Hello," + name;
    }
}

服务方的配置文件进行相关的配置:

<?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://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://code.alibabatech.com/schema/dubbo
      http://code.alibabatech.com/schema/dubbo/dubbo.xsd
">
    <!--1.服务提供方在Zookeeper中的别名-->
    <dubbo:application name="dubbo-server"/>
    <!--    2. 注册中心的地址-->
    <dubbo:registry address="zookeeper://172.16.150.130:2181"/>
    <!--    3. 扫描类:将什么包下的类作为服务提供类-->
    <dubbo:annotation package="service.impl"/>
</beans>

服务方的web.xml,初始化spring的环境

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
</web-app>

服务消费方

重新创建一个项目:dubbo-consumer
pom.xml和服务方一致。
controller的创建:
引入HelloService 使用服务提供方的service,使用import com.alibaba.dubbo.config.annotation.Reference; @Reference 注入进来

@Controller
public class HelloController {

    @Reference //远程去服务方将service注入进来
    private HelloService helloService;

    @GetMapping("hello")
    @ResponseBody
    public String syaHi(String name) {
        return helloService.syHello(name);
    }
}

同样需要创建和服务提供方一致的service接口类,注意必须一致,否则无法调用服务提供方的service

/**
 * 服务方接口声明,具体实现远程调用dubbo-server的service的实现类
 */
public interface HelloService {
    String syHello(String name);
}

核心配置文件配置:

<?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://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://code.alibabatech.com/schema/dubbo
      http://code.alibabatech.com/schema/dubbo/dubbo.xsd
">
    <!--1.服务提供方在Zookeeper中的别名-->
    <dubbo:application name="dubbo-client"/>
    <!--    2. 注册中心的地址-->
    <dubbo:registry address="zookeeper://172.16.150.130:2181"/>
    <!--    3. 扫描controller-->
    <dubbo:annotation package="controller"/>
</beans>

web.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext.xml</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

测试

测试消费方是否可以调用服务方:
http://localhost:8002/hello?name=jake
得到的结果:
image.png
服务方和消费方完全是两个项目:
服务方会将service.impl包下的类作为服务名地址注册到Zookeeper中,每个service的实现类相当于一个洗浴中心店。
消费方可以通过@Reference注解在Zookeeper查找具体的service的实现类,找到具体的洗浴中心店的位置。

监控中心

在开发时,我们需要知道注册中心都注册了哪些服务,以便我们开发和测试。
图形化的工具显示注册中心服务列表,可以通过部署一个web应用版的管理中心实现。
dubbo-admin-master.zip

解压上面的压缩包。

  1. 修改配置文件:

进入目录:
dubbo-admin-master/dubbo-admin/src/main/resources/application.properties 修改端口和Zookeeper的服务地址
image.png

  1. 进入:dubbo-admin目录使用命令:mvn clean package 打包
  2. 在target目录下找到jar文件:

image.png

  1. 运行jar文件:java -jar xxxx.jar
  2. 在浏览器打开:http://localhost:7001

用户和密码都是:root, 这样就进入到了管理端页面
image.png

启动上述的项目进入:可以看到服务名了。
image.png
在看一下我们的消费者:显示如下:
image.png
有了这个管理端,在开发的时候调试起来会非常方便,强烈推荐使用。

监控统计中心

Monitor 统计中心,记录服务被调用多少次等。dubbo-monitor-simple-2.5.3.zip
同样这个工具也在上述的资源中:dubbo-monitor-simple

  1. 同样修改配置文件,dubbo注册中心的地址改为Zookeeper的。

image.png

  1. 进入到bin目录,如下运行

image.png

  1. 分别修改dubbo-server和dubbo-consumer的配置,加入重新运行项目

     <!--    4. 让监控 去注册中心自动找服务-->
     <dubbo:monitor protocol="registry"/>
    

    访问:http://localhost:8080/ 就会显示如下界面
    image.png
    看一下消费次数:success显示6次,一般会有一定的时间间隔,不会立马显示。而是等一段时间之后才会显示统计的次数
    image.png

    综合实战

    配置说明

  2. 启动时检查

  • 启动时会在注册中心检查依赖的服务是否可用,不可用时会抛出异常
  • 在消费方编写初始化容器的main方法启动(而Tomcat启动方式,必须访问一次controller才能初始化)

    public class Test {
      public static void main(String[] args) throws IOException {
          //初始化spring
          ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
          System.in.read();
      }
    }
    

    运行main,会出现异常,因为服务方没有启动。
    image.png
    如果不想在启动时检查服务,那么可以在spring配置文件中,配置不检查服务方是否提供服务。默认是开启检查的

      <!--    5. 启动时不检查服务 默认是true-->
      <dubbo:consumer check="false"/>
    

    超时时间

    由于网络或服务不可靠,会导致调用过程中出现不确定的阻塞状态(超时),为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间,在服务提供者添加如下配置: 默认1S

      <!--    5. 设置服务调用超时时间 默认1s-->
      <dubbo:provider timeout="2000"/>
    

    测试超时,将服务实现HelloServiceImpl加入模拟的网络延迟进行测试,如下代码:

    @Service
    public class HelloServiceImpl implements HelloService {
      @Override
      public String syHello(String name) {
          try {
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
    
          return "Hello," + name;
      }
    }
    

    再次访问该服务就会出现错误
    image.png

配置原则:

  • dubbo推荐在provider上尽量多配置Consumer端属性:

    • 作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数,等等
    • 在provider配置后,Consumer不配置则会使用provider的配置值,及provider配置可以作为消费者的缺省值

      重试次数

      当出现失败,自动切换并重试其他服务器,dubbo重试的缺省值是2次,可以自行设置,在provider提供方配置:

      <!--    5. 设置服务调用超时时间 默认1s 请求失败一次,还会重试3次-->
      <dubbo:provider timeout="2000" retries="3"/>
      

      修改Service代码,进行测试一下,看看具体调用了几次

      @Service
      public class HelloServiceImpl implements HelloService {
      @Override
      public String syHello(String name) {
         System.out.println("===============调用1次==============");
         try {
             Thread.sleep(3000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
      
         return "Hello," + name;
      }
      }
      

      打印结果如下:重试了4次之后,抛出了异常
      image.png
      注意 并不是所有方法都适合设置重试次数

  • 幂等方法:适合 当参数一样,无论执行多少次,结果都是一样的。例如:查询、修改

  • 非幂等方法:不适合 当参数一样,执行结果不一样,例如:删除、添加操作

单独设置某个方法:
服务方添加两个方法

public interface HelloService {
    String syHello(String name);

    String sayNo();
}

消费方调用两个方法:

@Controller
public class HelloController {

    //    @Reference //远程去服务方将service注入进来
    @Autowired //自动注入即可,因为会在配置中 设置远程服务引用
    private HelloService helloService;

    @GetMapping("hello")
    @ResponseBody
    public String syaHi(String name) {
        return helloService.syHello(name);
    }

    @GetMapping("no")
    @ResponseBody
    public String syaNo() {
        return helloService.sayNo();
    }
}

在消费方进行设置,哪个方法重试几次:

    <!--    6. 设置重试的次数-->
    <dubbo:reference interface="service.HelloService" id="helloService">
        <!--        syaHi 方法重试3次-->
        <dubbo:method name="syaHi" retries="3"/>
        <!--       sayNo方法重试0次 也就是不重试 -->
        <dubbo:method name="sayNo" retries="0"/>
    </dubbo:reference>

多版本

一个接口,多个版本的实现类,可以使用定义版本的方式引入

为HelloService接口定义两个实现类,在服务提供者设置版本:

    <!--    6. 设置多版本的Service的实现类-->
    <dubbo:service interface="service.HelloService" class="service.impl.HelloServiceImplV1" version="1.0.0"/>
    <dubbo:service interface="service.HelloService" class="service.impl.HelloServiceImplV2" version="2.0.0"/>

在消费方,进行设置引用服务的版本:
version=”1.0.0” 也可以改成 * 随机调用版本。

    <!--    6. 设置重试的次数-->
    <dubbo:reference interface="service.HelloService" id="helloService" version="1.0.0">
        <!--        syaHi 方法重试3次-->
        <dubbo:method name="syHello" retries="3"/>
        <!--       sayNo方法重试0次 -->
        <dubbo:method name="sayNo" retries="0"/>
    </dubbo:reference>

如果我们业务既需要用到1.0版本又需要用到2.0的版本,可以这样配置

    <dubbo:reference interface="service.HelloService" id="helloService" version="1.0.0">
        <!--        syaHi 方法重试3次-->
        <dubbo:method name="syHello" retries="3"/>
        <!--       sayNo方法重试0次 -->
        <dubbo:method name="sayNo" retries="0"/>
    </dubbo:reference>
    <!--  helloService2 调用2.0.0的版本服务  -->
    <dubbo:reference interface="service.HelloService" id="helloService2" version="2.0.0">
        <!--        syaHi 方法重试3次-->
        <dubbo:method name="syHello" retries="3"/>
        <!--       sayNo方法重试0次 -->
        <dubbo:method name="sayNo" retries="0"/>
    </dubbo:reference>

根据bean的id进行调用

@Controller
public class HelloController {

    //    @Reference //远程去服务方将service注入进来
    @Autowired
    private HelloService helloService;

    @Autowired
    @Qualifier("helloService2")
    private HelloService helloService2;

    @GetMapping("hello")
    @ResponseBody
    public String syaHi(String name) {
        return helloService.syHello(name);
    }

    @GetMapping("no")
    @ResponseBody
    public String syaNo() {
        return helloService.sayNo();
    }

    @GetMapping("nov2")
    @ResponseBody
    public String syaNo2() {
        return helloService2.sayNo();
    }
}

本地存根

image.png
目前分布式架构搭建起来有一个严重问题,就是所有的操作都是消费者发起的,由服务提供者执行。先在消费者处理一些业务逻辑,再调用提供者的过程,就是本地存根。
代码肯定在消费者实现:

/**
 * HelloService 的本地存根
 * 本地存根必须以构造方法的形式注入
 */
public class HelloServiceStub implements HelloService {

    private HelloService helloService;

    //必须以构造方法的形式注入
    public HelloServiceStub(HelloService helloService) {
        this.helloService = helloService;
    }

    @Override
    public String syHello(String name) {
        System.out.println("本地存根验证:" + name);
        //验证name 不能为空
        if (StringUtils.isEmpty(name)) {
            return "I'm Sorry";
        }
        return helloService.syHello(name);
    }

    @Override
    public String sayNo() {
        return helloService.sayNo();
    }
}

spring的配置文件配置本地存根:

    <dubbo:reference interface="service.HelloService" id="helloService" version="1.0.0" stub="stub.HelloServiceStub">
        <!--        syaHi 方法重试3次-->
        <dubbo:method name="syHello" retries="3"/>
        <!--       sayNo方法重试0次 -->
        <dubbo:method name="sayNo" retries="0"/>
    </dubbo:reference>

运行消费方,查看执行结果:
name不传访问:http://localhost:8002/hello
image.png
image.png

负载均衡策略

负载均衡(Load Balance):其实就是将请求分摊到多个操作单元上进行执行,从而共同完成工作任务。简单来说,好多台服务器,不能总是让一台服务器干活
Dubbo提供了4种策略,缺省为random随机分配调用:

image.png
修改服务提供者,启动三个提供者:
Tomcat端口:8001、8003、8004
provider端口:20881、20882、20883

  <dubbo:provider timeout="2000" port="20881"/>

使用Dubbo 监控中心管理端查看启动的服务:
image.png
启动消费方,默认是随机策略:随机调用一台服务
image.png
image.png
image.png
修改消费方的配置,负载均衡策略改为”roundrobin 基于权重轮询的负载均衡机制:

    <dubbo:reference loadbalance="roundrobin" interface="service.HelloService" id="helloService" version="1.0.0" stub="stub.HelloServiceStub">
        <!--        syaHi 方法重试3次-->
        <dubbo:method name="syHello" retries="3"/>
        <!--       sayNo方法重试0次 -->
        <dubbo:method name="sayNo" retries="0"/>
    </dubbo:reference>

设置权重:去管理端操作权重
image.png
剩下的两种策略就不再测试了,不好测试。

高可用

  1. Zookeeper宕机

zookeeper注册中心宕机,还可以消费dubbo暴露的服务

  • 监控中心宕掉不影响使用,知识丢失部分采样数据
  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

我们测试一下Zookeeper宕机是否可以正常使用:
关闭Zookeeper:./zkServer.sh stop 查看消费者是否可以正常使用:
仍然可以正常使用
image.png

服务降级

服务降级,就是根据实际的情况和流量,对一些服务有策略的停止或换种简单的方式处理,从而释放服务器的资源来保证核心业务的正常运行。

服务降级可以防止分布式服务发生雪崩效应
什么是雪崩:就是蝴蝶效应,当一个请求发生超时,一直等待着服务响应,那么在高并发情况,很多请求都是因为这样一直等着响应,直到服务资源耗尽产生宕机,而宕机之后会导致分布式其他服务调用该宕机的服务也会出现资源耗尽宕机,这样下去将导致整个分布式服务都瘫痪,这就是雪崩。

服务降级实现方式:

  1. 第一种:在管理控制台配置服务降级:屏蔽和容错

屏蔽:mock=force:return+null 表示消费方对该服务的方法调用都直接返回null值,不发起远程调用,用来屏蔽不重要服务不可用时对调用方的影响。
容错:mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常,用来容忍不重要服务不稳定时对调用方的影响。
image.png

案例-实现用户注册

准备数据库:

CREATE DATABASE smd;
USE smd;
CREATE TABLE users (
uid INT(11) AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
PASSWORD VARCHAR(50) NOT NULL,
phone VARCHAR(50) NOT NULL,
createtime VARCHAR(50) NOT NULL
);

搭建聚合项目-项目模块化

创建一个空项目,添加Module

  • dubbo-parent 父工程,定义所有模块用的依赖版本

      <packaging>pom</packaging>
      <properties>
          <spring.version>5.0.6.RELEASE</spring.version>
      </properties>
      <dependencies>
          <!--JSP相关-->
          <dependency>
              <groupId>jstl</groupId>
              <artifactId>jstl</artifactId>
              <version>1.2</version>
          </dependency>
          <dependency>
              <groupId>javax.servlet</groupId>
              <artifactId>servlet-api</artifactId>
              <scope>provided</scope>
              <version>2.5</version>
          </dependency>
          <dependency>
              <groupId>javax.servlet</groupId>
              <artifactId>jsp-api</artifactId>
              <scope>provided</scope>
              <version>2.0</version>
          </dependency>
          <!--Spring-->
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-context</artifactId>
              <version>${spring.version}</version>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-beans</artifactId>
              <version>${spring.version}</version>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-webmvc</artifactId>
              <version>${spring.version}</version>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-jdbc</artifactId>
              <version>${spring.version}</version>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-aspects</artifactId>
              <version>${spring.version}</version>
          </dependency>
          <!--Mybatis-->
          <dependency>
              <groupId>org.mybatis</groupId>
              <artifactId>mybatis</artifactId>
              <version>3.2.8</version>
          </dependency>
          <dependency>
              <groupId>org.mybatis</groupId>
              <artifactId>mybatis-spring</artifactId>
              <version>1.2.2</version>
          </dependency>
          <!--连接池-->
          <dependency>
              <groupId>com.alibaba</groupId>
              <artifactId>druid</artifactId>
              <version>1.1.15</version>
          </dependency>
          <!--数据库-->
          <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>5.1.37</version>
          </dependency>
          <!--dubbo-->
          <dependency>
              <groupId>com.alibaba</groupId>
              <artifactId>dubbo</artifactId>
              <version>2.5.7</version>
          </dependency>
          <dependency>
              <groupId>org.apache.zookeeper</groupId>
              <artifactId>zookeeper</artifactId>
              <version>3.4.6</version>
          </dependency>
          <dependency>
              <groupId>com.github.sgroschupf</groupId>
              <artifactId>zkclient</artifactId>
              <version>0.1</version>
          </dependency>
          <dependency>
              <groupId>javassist</groupId>
              <artifactId>javassist</artifactId>
              <version>3.11.0.GA</version>
          </dependency>
          <!--fastjson-->
          <dependency>
              <groupId>com.alibaba</groupId>
              <artifactId>fastjson</artifactId>
              <version>1.2.47</version>
          </dependency>
          <!--junit-->
          <dependency>
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
              <version>4.12</version>
              <scope>test</scope>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-test</artifactId>
              <version>${spring.version}</version>
              <scope>test</scope>
          </dependency>
      </dependencies>
    
  • dubbo-entity(实体工程,jar项目)

    /**
    * 用户实体类
    */
    public class User implements Serializable {
      private Integer uid;
      private String username;
      private String password;
      private String phone;
      private String createtime;
    }
    
  • dubbo-dao(数据访问层工程,jar项目) ```xml

    mapper

    <?xml version=”1.0” encoding=”UTF-8” ?> <!DOCTYPE mapper

      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    insert into users values (null, #{username}, #{password}, #{phone}, #{createtime})

mybatis 配置

<?xml version=”1.0” encoding=”UTF-8” ?> <!DOCTYPE configuration PUBLIC “-//mybatis.org//DTD Config 3.0//EN” “http://mybatis.org/dtd/mybatis-3-config.dtd">

spring-dao 配置

<?xml version=”1.0” encoding=”UTF-8” ?>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<!-- 2. sqlSessionFactory -->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!-- 引入mybatis的其他配置 -->
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
</bean>

<!-- 3. mapper映射扫描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.sufulu.mapper"/>
</bean>


- dubbo-interface(服务接口定义工程,jar项目)只需要依赖`dubbo-entity` 即可,因为该工程只提供接口给消费者和提供者
```java
package com.sufulu.service;

import com.sufulu.entity.User;

public interface UserService {
    int register(User user);
}
  • dubbo-service(privoder服务提供者工程,war项目,注意创建webapp)

依赖dubbo-interface 和 dubbo-dao
服务的实现类 @Service 注解使用的是dubbo的

package com.sufulu.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.sufulu.entity.User;
import com.sufulu.mapper.UserMapper;
import com.sufulu.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;

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

    @Autowired
    private UserMapper userMapper;

    public int register(User user) {
        return userMapper.register(user);
    }
}

配置文件:

<?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://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://code.alibabatech.com/schema/dubbo
      http://code.alibabatech.com/schema/dubbo/dubbo.xsd
">
    <!-- 导入dao的配置文件 整合 -->
    <import resource="classpath:spring/spring-dao.xml"/>
    <!--1.服务提供方在Zookeeper中的别名-->
    <dubbo:application name="dubbo-case-server"/>
    <!--    2. 注册中心的地址-->
    <dubbo:registry address="zookeeper://172.16.150.130:2181"/>
    <!--    3. 扫描类:将什么包下的类作为服务提供类-->
    <dubbo:annotation package="com.sufulu.service.impl"/>
    <!--    4. 让监控 去注册中心自动找服务-->
    <dubbo:monitor protocol="registry"/>

</beans>

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
</web-app>
  • dubbo-web 服务的消费者,只需要依赖dubbo-interface 即可。

spring-mvc的配置:

<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://code.alibabatech.com/schema/dubbo
      http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <mvc:annotation-driven>
<!--        json转换器-->
        <mvc:message-converters register-defaults="true">
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes" value="application/json"/>
                <property name="features">
                    <array>
                        <value>WriteMapNullValue</value>
                        <value>WriteDateUseDateFormat</value>
                    </array>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <!--    1.消费方在Zookeeper中的别名-->
    <dubbo:application name="dubbo-case-client"/>
    <!--    2. 注册中心的地址-->
    <dubbo:registry address="zookeeper://172.16.150.130:2181"/>
    <!--    3. 扫描类:将什么包下的类作为服务提供类-->
    <dubbo:annotation package="com.sufulu.controller"/>

</beans>

web.xml的配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--解决post乱码-->
    <filter>
        <filter-name>charset</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>charset</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

controller层实现

@RestController
public class UserController {

    @Reference
    private UserService userService;

    @PostMapping("/register")
    public String register(@RequestBody User user) {
        String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        user.setCreatetime(format);
        int register = userService.register(user);
        if (register > 0) {
            return "注册成功";
        } else {
            return "注册失败";
        }
    }
}

启动服务提供者和消费者,进行测试
image.png
使用postman进行测试:返回注册成功则说明调用远程服务成功了。
image.png
image.png