本文章中的Demo案例已经同步至gitee,供各位参考。

Demo地址:https://gitee.com/lemon_ant/dubbo-demo.git

zookeeper&dubbo简介

架构

节点角色说明

节点角色说明
Provider暴露服务的服务提供方
Consumer调用远程服务的服务消费方
Registry服务注册与发现的注册中心
Monitor统计服务的调用次数和调用时间的监控中心
Container服务运行容器

调用关系说明

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

Dubbo 架构具有以下几个特点,分别是连通性、健壮性、伸缩性、以及向未来架构的升级性。

zookeeper安装

下载zookeeper.tar.gz

https://www.apache.org/dyn/closer.lua/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz

上传到centos上、

解压使用命令解压在/usr/local目录下

1
tar -zxvf zookeeper.tar.gz  -C /usr/local   

zookeeper目录下创建data文件夹存放数据,修改配置conf下的zoo_sample.cfg名为zoo.cfg(默认加载zoo.cfg)。

修改zoo.cfgdataDir参数为刚才创建的目录,保存退出。

zookeeper启动

启动的前提是需要安装jdk

作者:疯子加天才

标题:centos安装jdk1.8的三种方法

文章链接:https://www.cnblogs.com/telwanggs/p/11546751.html

在zookeeper目录下使用 ./zkServer.sh start 启动

./zkServer.sh status 查看运行状态 standalone 代表单机版

./zkServer.sh stop 停止

dubbo-admin可视化界面

dubbo-admin:https://github.com/apache/incubator-dubbo/releases/tag/dubbo-2.6.0

安装dubbo-admin.war放在本机tomcat中的wabapps目录,启动tomcat,待dubbo-admin.war解压后,进入目录dubbo-admin-2.6.0\WEB-INF\找到dubbo.properties配置文件,修改配置为zookeeper的IP地址,重新启动tomcat。

浏览器访问地址:http://127.0.0.1:8080/dubbo-admin-2.6.0 账号信息:root root

里面就是所有信息home > governance > applications能够看到注册上去的接口信息。

Dubbo服务提供方

创建一个空的maven项目,新建modul作为服务提供方。dubbo_provider。

导入pom文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?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.hkrt</groupId>
<artifactId>dubbo_provider</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>dubbo Maven Webapp</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.0.5.RELEASE</spring.version>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.8.0</version>
</dependency>
<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>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>

<build>
<finalName>dubbo</finalName>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8081</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>

创建service层接口和实现类

1
2
3
4
5
6
7
8
9
import com.alibaba.dubbo.config.annotation.Service;
@Service //发布服务必须使用Dubbo提供的service注解
public class HelloServiceImpl implements HelloService {

@Override
public String sayHello(String name) {
return "hello";
}
}

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
<web-app>
<display-name>Archetype Created Web Application</display-name>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>

applicationContext-service.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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" xmlns:tx="http://www.springframework.org/schema/tx"
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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--每个Dubbo应用(服务提供方和消费方)都需要指定一个唯一的名称-->
<dubbo:application name="dubbo_provider"></dubbo:application>

<!--指定Dubbo的注册中心,部署zookeeper的地方-->
<dubbo:registry address="zookeeper://IP:2181"></dubbo:registry>

<!--指定协议和端口,只在提供方配置-->
<dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>
<!--<dubbo:service interface="com.hkrt.service.HelloService" ref="helloService" protocol="dubbo" />-->

<!--1、注解形式:指定包扫描路径,用于发布dubbo服务-->
<dubbo:annotation package="com.hkrt.service"></dubbo:annotation>
</beans>

通过maven插件启动服务provider,查看dubbo-admin发现已经存在HelloService的相关信息。

Dubbo服务消费方

新建modul作为服务提供方。dubbo_consumer

同样和provider相同的pom文件依赖。

创建一个和服务提供方相同的包package,创建controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import com.alibaba.dubbo.config.annotation.Reference;

@RestController
@RequestMapping("/hello")
public class HelloController {

@Reference //订阅zookeeper,查找相关服务
private HelloService helloService;

@ResponseBody
@RequestMapping("/say")
public String sayHello(String name){
System.out.println("响应成功");
return helloService.sayHello(name);
}
}

HelloService.class

1
2
3
public interface HelloService {
public String sayHello(String name);
}

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<web-app>
<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*:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

springmvc-servlet.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
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">

<!-- 自动扫描包,实现支持注解的IOC -->
<context:component-scan base-package="com" />

<!-- Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler />

<!-- 支持mvc注解驱动 -->
<mvc:annotation-driven />

<!--每个Dubbo应用(服务提供方和消费方)都需要指定一个唯一的名称-->
<dubbo:application name="dubbo_customer" />

<!--指定Dubbo的注册中心,部署zookeeper的地方-->
<dubbo:registry address="zookeeper://IP:2181" />

<!--1、注解形式:指定包扫描路径,用于发布dubbo服务-->
<dubbo:annotation package="com.hkrt.controller" />
</beans>

通过maven插件启动服务consumer,查看dubbo-admin发现已经存在HelloService的相关信息。

案例回顾

需要在impl类上添加注解@service

注意这里的service已经是 spring提供的 现在要使用Dubbo提供的service注解

1
import com.alibaba.dubbo.config.annotation.Service

问题:案例中服务提供方和服务消费方里面都会存在HelloService接口,这样做是否合理?有没有更好的方法?

:这种做法不好,同一个接口复制了两份,放于两个服务中,不利于维护,更好的方式是单独创建一个maven工程,将接口全部放到这个maven工程中,需要依赖此接口的工程只需要在自己的pom.xml中引入坐标即可。

问题:在服务的消费方里面只是引用了HelloService接口,并没有实现类,那么Dubbo是如何实现远程调用的?

:Dubbo底层是基于代理技术为HelloService接口创建代理对象,远程调用时通过此代理对象完成的,可以通过开发工具的Debug功能查看此代理对象的内部结构,另外,Dubbo实现网络传输是基于Netty框架完成的。

问题:使用zookeeper作为服务的注册中心,默认单机版运行,如何防止zookeeper单点故障呢?

:zookeeper支持集群模式,可以配置zookeeper集群来达到zookeeper服务的高可用,防止单点故障的发生。

加入log4j日志

创建log4j.properties文件放于resources下,spring自动加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
### 设置###
log4j.rootLogger = debug, stdout

### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Threshold = DEBUG
log4j.appender.stdout.Target = System.err
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = C:/Users/Surpass/Desktop/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n

### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n

Dubbo相关配置

包扫描

1
<dubbo:annotation package="com.hkrt.service"></dubbo:annotation>

表示包扫描,作用是扫描改包下的所有了类,存在相关的注解。

如果不使用包扫描,可以通过配置的方式进行注册发布服务。

服务提供方:

1
2
3
4
5
6
7
<!--推荐注解开发,二者选其一-->
<!--1、注解形式:指定包扫描路径,用于发布dubbo服务-->
<dubbo:annotation package="com.hkrt.service"></dubbo:annotation>

<!--2、配置形式:通过bean的方式进行服务的注册(这是服务提供方)-->
<bean class="com.hkrt.service.impl.HelloServiceImpl" id="helloService"></bean>
<dubbo:service interface="com.hkrt.service.HelloService" ref="helloService"/>

服务消费方:

1
2
3
4
5
6
<!--推荐注解开发,两者只存在其一-->
<!--1、注解形式:指定包扫描路径,用于发布dubbo服务-->
<dubbo:annotation package="com.hkrt.controller" />

<!--2、配置形式:配置reference进行实例,相当于<bean/>-->
<dubbo:reference interface="com.hkrt.service.HelloService" id="helloService"/>

配置的方式只能进行单一的接口配置,如果有多个服务,那么就很繁琐了。

协议

1
<dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>

相关协议的介绍请移步博客

作者:xiaojin21cen

名称:dubbo 支持的9种协议

文章地址:https://blog.csdn.net/xiaojin21cen/article/details/79834222

启动时检查

1
<dubbo:consumer check="false"/>

这个配置主要是用在服务的消费端,如果不配置则默认为true,Dubbo缺省会在启动的时候检查依赖的服务是否可用,不可用是会抛出异常,阻止Spring进行初始化完成,以便上线时,及时发现问题,可以通过将check的值修改为false关闭检查。

待上线时设置为true

负载均衡

Dubbo内置了4种负载均衡策略:

  1. RandomLoadBalance:随机负载均衡。随机的选择一个。是Dubbo的默认负载均衡策略。
  2. RoundRobinLoadBalance:轮询负载均衡。轮询选择一个。
  3. LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机。活跃数指调用前后计数差。使慢的 Provider 收到更少请求,因为越慢的 Provider 的调用前后计数差会越大。
  4. ConsistentHashLoadBalance:一致性哈希负载均衡。相同参数的请求总是落在同一台机器上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//在服务消费方配置负载均衡
@Reference(check=false,loadbalance="random")
private HelloService helloService;

@ResponseBody
@RequestMapping("/say")
public String sayHello(String name){
System.out.println("响应成功");
return helloService.sayHello(name);
}

//在服务提供者配置负载均衡
@Service(loadbalance = "random") //发布服务必须使用Dubbo提供的service注解
public class HelloServiceImpl implements HelloService {

@Override
public String sayHello(String name) {
return "hello"+name;
}
}

更多负载均衡,请移步Dubbo官方文档:

http://dubbo.apache.org/zh-cn/blog/dubbo-loadbalance.html

这个案例里面,修改端口号来模拟多台并行的服务提供方,启动服务provider和consumer,访问consumer。

解决Dubbo无法发布被事务代理的service问题

如果在服务提供者注册的类上面添加事务注解@Transactinal事务控制后,发现服务就发布不成功了,原因是因为事务控制的底层原理是为服务提供者类创建代理对象,而默认情况下Spring是基于JDK动态代理方式创建代理对象,而代理对象的完成类名为com.sunproxy.$Proxy–(后面是数字),导致Dubbo在发布服务的时候进行包匹配匹配不到。

问题展示

使用dubbo坐标版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>

在applicationContext-service.xml中添加事务管理器和连接池相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--连接数据库-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/dow_jones"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>

<!--事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务管理器-->
<tx:annotation-driven transaction-manager="transactionManager"/>

然后在HelloServiceImpl类添加@Transactinal,完成后启动provider,在dubbo-admin中发现并没有服务提供者进行注册,只有一个服务消费者。

如果修改成cglib动态代理,回事什么样子?

修改配置applicationContext-service.xml

1
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

这样子就使用的是cglib代理。那么启动provider看到有注册信息。

但是它的注册信息是这样的。

1
org.springframework.aop.SpringProxy

这样子还是访问不到的。

解决方案

在之前的基础上,需要在注册服务类上添加注解参数。

1
2
3
4
5
6
7
8
9
@Service(interfaceClass = HelloService.class)   //发布服务必须使用Dubbo提供的service注解
@Transactional
public class HelloServiceImpl implements HelloService {

@Override
public String sayHello(String name) {
return "hello"+name;
}
}

明确该服务实现的是HelloService.class的这个类,然后启动,查看dubbo-admin看结果,这时候发现provider注册的就是正确的接口服务了。

1
2
3
4
5
//服务
com.hkrt.service.HelloService

//服务地址
dubbo://ip:20880/com.hkrt.service.HelloService?anyhost=true&application=dubbo_provider&dubbo=2.6.0&generic=false&interface=com.hkrt.service.HelloService&methods=sayHello&pid=24892&side=provider×tamp=1603959532358

另述

前面到在问题展示那里,我指定了dubbo坐标的版本号是2.6.0,博主在这个案例中发现,2.6.0以上的版本,即使使用jdk动态代理,也是能够将被事务管理的服务接口注册到zookeeper上去,并且能够成功访问。

尾言

您的支持和鼓励是我最大的动力,麻烦动动您高贵的小手,点赞,评论和收藏,感谢!