Spring Boot 2.0 集成 Dubbo 示例
简单介绍下 Dubbo
Apache Dubbo™ (incubating) is a high-performance, java based, open source RPC framework.
通常情况 , Dubbo 应用有两种使用场景 , 其一为 Dubbo 服务提供方 , 另外一个是 Dubbo 服务消费方。
Apache Dubbo (incubating) offers three key functionalities:
- interface based remote call 基于接口的远程调用
- fault tolerance & load balancing 容错及负载均衡
- automatic service registration & discovery 自动化服务注册与发现
Dubbo 的主要的服务注册发现功能便是由 Zookeeper 来提供的。ZooKeeper 是一个高可用的分布式数据管理与系统协调框架。基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题。
Dubbo 架构Spring Boot 2.0 集成 Dubbo
通过 http://start.dubbo.io/ 构造基于 maven 的项目框架,包括服务提供端和服务消费端。
服务消费端 clientDemo
在 IntelliJ IDEA 中分别打开这两个项目。
由于在 http://start.dubbo.io/ 只能选择 1.X
版本的 Spring Boot,因此我们需要手动修改两个项目的 pom.xml
文件。参考 https://github.com/apache/incubator-dubbo-spring-boot-project/blob/master/README_CN.md
- 修改
spring-boot-starter-parent
的版本号为2.0.3.RELEASE
- 修改
dubbo-spring-boot-starter
的版本号为0.2.0
- 添加如下内容:
<repositories>
<repository>
<id>sonatype-nexus-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
分析服务提供端 serviceDemo
serviceDemo 项目结构application.properties
:Dubbo 的基本配置
dubbo.application.name = dubbo-demo-server
# Base packages to scan Dubbo Component: @com.alibaba.dubbo.config.annotation.Service
dubbo.scan.basePackages = com.example
## RegistryConfig Bean
dubbo.registry.id = my-registry
dubbo.registry.address = zookeeper://localhost:2181?client=curator
dubbo.application.qosEnable=false
HelloService.java
:接口,以后通过接口来进行服务调用
public interface HelloService {
String sayHello(String name);
}
HelloServiceImpl.java
:服务的具体实现
package com.example;
import java.util.Date;
import com.alibaba.dubbo.config.annotation.Service;
@Service(version = "1.0.0")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "Hello, " + name + ", " + new Date();
}
}
ServiceDemoApplication.java
:程序入口,修改为如下内容
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
@SpringBootApplication
public class ServiceDemoApplication {
public static void main(String[] args) {
// start embedded zookeeper server
new EmbeddedZooKeeper(2181, false).start();
new SpringApplicationBuilder(ServiceDemoApplication.class)
.web(false) // 非 Web 应用
.run(args);
}
}
EmbeddedZooKeeper.java
:内嵌的 ZooKeeper 实现。
分析服务消费端 clientDemo
clientDemo 项目结构application.properties
:Dubbo 的基本配置
# Dubbo Config properties
## ApplicationConfig Bean
dubbo.application.name= dubbo-demo-client
## RegistryConfig Bean
dubbo.registry.id = my-registry
dubbo.registry.address = zookeeper://localhost:2181?client=curator
dubbo.application.qosEnable=false
HelloService.java
:接口,以后通过接口来进行服务调用
public interface HelloService {
String sayHello(String name);
}
ClientDemoApplication.java
:程序入口,修改为如下内容
import com.alibaba.dubbo.config.annotation.Reference;
import com.example.HelloService;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import javax.annotation.PostConstruct;
@SpringBootApplication
public class ClientDemoApplication {
@Reference(version = "1.0.0")
private HelloService demoService;
public static void main(String[] args) {
new SpringApplicationBuilder(ClientDemoApplication.class)
.web(false) // 非 Web 应用
.run(args);
}
@PostConstruct
public void init() {
String sayHello = demoService.sayHello("world");
System.err.println(sayHello);
}
}
测试
分别通过 mvn spring-boot:run
启动两个项目,可以在服务消费端看到如下的日志内容,表明调用成功:
2018-06-27 14:24:42.828 INFO 36771 --- [ main] c.a.d.r.zookeeper.ZookeeperRegistry : [DUBBO] Register: consumer://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=consumers&check=false&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=36771&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530080682446&version=1.0.0, dubbo version: 2.6.2, current host: 10.93.67.135
2018-06-27 14:24:42.865 INFO 36771 --- [localhost:2181)] org.apache.zookeeper.ClientCnxn : Session establishment complete on server localhost/0:0:0:0:0:0:0:1:2181, sessionid = 0x1643fe4a4410001, negotiated timeout = 60000
2018-06-27 14:24:42.878 INFO 36771 --- [ain-EventThread] o.a.c.f.state.ConnectionStateManager : State change: CONNECTED
2018-06-27 14:24:42.916 INFO 36771 --- [ main] c.a.d.r.zookeeper.ZookeeperRegistry : [DUBBO] Subscribe: consumer://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=providers,configurators,routers&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=36771&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530080682446&version=1.0.0, dubbo version: 2.6.2, current host: 10.93.67.135
2018-06-27 14:24:42.948 INFO 36771 --- [ main] c.a.d.r.zookeeper.ZookeeperRegistry : [DUBBO] Notify urls for subscribe url consumer://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=providers,configurators,routers&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=36771&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530080682446&version=1.0.0, urls: [dubbo://10.93.67.135:20880/com.example.HelloService?anyhost=true&application=dubbo-demo-server&dubbo=2.6.2&generic=false&interface=com.example.HelloService&methods=sayHello&pid=36354&revision=1.0.0&side=provider×tamp=1530080308262&version=1.0.0, empty://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=configurators&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=36771&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530080682446&version=1.0.0, empty://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=routers&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=36771&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530080682446&version=1.0.0], dubbo version: 2.6.2, current host: 10.93.67.135
2018-06-27 14:24:43.152 INFO 36771 --- [ main] c.a.d.remoting.transport.AbstractClient : [DUBBO] Successed connect to server /10.93.67.135:20880 from NettyClient 10.93.67.135 using dubbo version 2.6.2, channel is NettyChannel [channel=[id: 0x580c5d4e, /10.93.67.135:52308 => /10.93.67.135:20880]], dubbo version: 2.6.2, current host: 10.93.67.135
2018-06-27 14:24:43.153 INFO 36771 --- [ main] c.a.d.remoting.transport.AbstractClient : [DUBBO] Start NettyClient acbc32941d79.ant.amazon.com/10.93.67.135 connect to the server /10.93.67.135:20880, dubbo version: 2.6.2, current host: 10.93.67.135
2018-06-27 14:24:43.227 INFO 36771 --- [ main] com.alibaba.dubbo.config.AbstractConfig : [DUBBO] Refer dubbo service com.example.HelloService from url zookeeper://localhost:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=dubbo-demo-client&check=false&dubbo=2.6.2&generic=false&interface=com.example.HelloService&methods=sayHello&pid=36771&qos.enable=false®ister.ip=10.93.67.135&remote.timestamp=1530080308262&revision=1.0.0&side=consumer×tamp=1530080682446&version=1.0.0, dubbo version: 2.6.2, current host: 10.93.67.135
2018-06-27 14:24:43.239 INFO 36771 --- [ main] c.a.d.c.s.b.f.a.ReferenceBeanBuilder : <dubbo:reference object="com.alibaba.dubbo.common.bytecode.proxy0@19da400d" singleton="true" interface="com.example.HelloService" uniqueServiceName="com.example.HelloService:1.0.0" generic="false" version="1.0.0" id="com.example.HelloService" /> has been built.
Hello, world, Wed Jun 27 14:24:43 CST 2018
将上述的服务注册到 Zookeeper
在上面的例子是,我们是使用了一个内嵌的 Zookeeper 来注册与发现服务,现在我们启动一个外部的 Zookeeper 服务来替代。
简单介绍下 Zookeeper,https://zookeeper.apache.org/
通过如下命令在 mac 中安装 Zookeeper:
brew info zookeeper
brew install zookeeper
安装完成后,默认配置文件在 /usr/local/etc/zookeeper/
中。
通过命令 zkServer
启动 Zookeeper,默认端口是2181。
xianch@acbc32941d79 ⮀ /usr/local/etc/zookeeper ⮀ zkServer
ZooKeeper JMX enabled by default
Using config: /usr/local/etc/zookeeper/zoo.cfg
Usage: ./zkServer.sh {start|start-foreground|stop|restart|status|upgrade|print-cmd}
客户端(服务提供者与消费者)与 Zookeeper 是 TCP 长连接,默认对外端口是 2181,通过这个连接,客户端保持和 Zookeeper 服务器的心跳以维护连接,也能向 Zookeeper 发送请求并响应,同时还可以接收到注册通知。
可以通过命令 zkServer status
和 zkServer stop
来分别查看和关闭 Zookeeper。
服务提供端 serviceDemo
注释掉 ServiceDemoApplication.java
中的 new EmbeddedZooKeeper(2181, false).start();
。
启动程序,日志中可以看出,注册了一个服务 dubbo://10.93.67.135:20880/com.example.HelloService
2018-06-27 14:52:28.053 INFO 41887 --- [ main] c.a.d.r.zookeeper.ZookeeperRegistry : [DUBBO] Register: dubbo://10.93.67.135:20880/com.example.HelloService?anyhost=true&application=dubbo-demo-server&dubbo=2.6.2&generic=false&interface=com.example.HelloService&methods=sayHello&pid=41887&revision=1.0.0&side=provider×tamp=1530082347373&version=1.0.0, dubbo version: 2.6.2, current host: 10.93.67.135
注意,每一个服务都需要一个未被使用的 dubbo 端口 ,默认是 20880,也可以通过 spring.dubbo.protocol.port=xxxxx
来配置。
做一个实验,在配置中修改端口为 spring.dubbo.protocol.port=20881
,将实现类 HelloServiceImpl
做一个小的修改:变成 New Hello。然后重新打开一个 Terminal,启动程序。
public String sayHello(String name) {
return "New Hello, " + name + ", " + new Date();
}
此时,通过 zkCli
可以查看 Zookeeper 的信息:可以看出,已经注册了一个服务 com.example.HelloService
,它有两个提供者 providers
,端口分别是 20880 与 20881。
xianch@acbc32941d79 ⮀ /usr/local/etc/zookeeper ⮀ zkCli
Connecting to localhost:2181
Welcome to ZooKeeper!
JLine support is enabled
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0] ls
[zk: localhost:2181(CONNECTED) 1] ls /
[dubbo, zookeeper]
[zk: localhost:2181(CONNECTED) 2] ls /dubbo
[com.example.HelloService]
[zk: localhost:2181(CONNECTED) 3] ls /dubbo/com.example.HelloService
[configurators, providers]
[zk: localhost:2181(CONNECTED) 4] ls /dubbo/com.example.HelloService/configurators
[]
[zk: localhost:2181(CONNECTED) 5] ls /dubbo/com.example.HelloService/providers
[
dubbo%3A%2F%2F10.93.67.135%3A20880%2Fcom.example.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-demo-server%26dubbo%3D2.6.2%26generic%3Dfalse%26interface%3Dcom.example.HelloService%26methods%3DsayHello%26pid%3D50337%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1530089150751%26version%3D1.0.0,
dubbo%3A%2F%2F10.93.67.135%3A20881%2Fcom.example.HelloService%3Fanyhost%3Dtrue%26application%3Ddubbo-demo-server%26dubbo%3D2.6.2%26generic%3Dfalse%26interface%3Dcom.example.HelloService%26methods%3DsayHello%26pid%3D50999%26revision%3D1.0.0%26side%3Dprovider%26timestamp%3D1530089806995%26version%3D1.0.0
]
- 服务提供者在启动的时候,向 Zookeeper 上的指定节点
/dubbo/${serviceName}/providers
目录下写入自己的 URL 地址,这个操作就完成了服务的发布。 - 服务消费者启动的时候,订阅
/dubbo/${serviceName}/providers
目录下的提供者 URL 地址, 并向/dubbo/${serviceName}/consumers
目录下写入自己的 URL 地址。 - 注意,所有向 Zookeeper 上注册的地址都是临时节点,这样就能够保证服务提供者和消费者能够自动感应资源的变化。临时节点的生命周期与会话一致,会话关闭则临时节点删除。这个特性经常用来做心跳,动态监控,负载等动作。
服务消费端 clientDemo
启动程序,日志中可以看出,订阅了一个服务 consumer://10.93.67.135/com.example.HelloService
,并且收到了一个 Notify,告诉它服务提供者的地址。
2018-06-27 15:32:19.338 INFO 44571 --- [ main] c.a.d.r.zookeeper.ZookeeperRegistry : [DUBBO] Subscribe: consumer://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=providers,configurators,routers&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=44571&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530084738835&version=1.0.0, dubbo version: 2.6.2, current host: 10.93.67.135
2018-06-27 15:32:19.367 INFO 44571 --- [ main] c.a.d.r.zookeeper.ZookeeperRegistry : [DUBBO] Notify urls for subscribe url consumer://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=providers,configurators,routers&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=44571&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530084738835&version=1.0.0, urls: [dubbo://10.93.67.135:20880/com.example.HelloService?anyhost=true&application=dubbo-demo-server&dubbo=2.6.2&generic=false&interface=com.example.HelloService&methods=sayHello&pid=41887&revision=1.0.0&side=provider×tamp=1530082347373&version=1.0.0, empty://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=configurators&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=44571&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530084738835&version=1.0.0, empty://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=routers&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=44571&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530084738835&version=1.0.0], dubbo version: 2.6.2, current host: 10.93.67.135
此时,通过 zkCli
可以查看 Zookeeper 的信息:可以看出,服务 com.example.HelloService
,它有一个消费者 consumers
。
[zk: localhost:2181(CONNECTED) 12] ls /dubbo/com.example.HelloService
[consumers, configurators, routers, providers]
[zk: localhost:2181(CONNECTED) 13] ls /dubbo/com.example.HelloService/consumers
[consumer%3A%2F%2F10.93.67.135%2Fcom.example.HelloService%3Fapplication%3Ddubbo-demo-client%26category%3Dconsumers%26check%3Dfalse%26dubbo%3D2.6.2%26interface%3Dcom.example.HelloService%26methods%3DsayHello%26pid%3D44571%26qos.enable%3Dfalse%26revision%3D1.0.0%26side%3Dconsumer%26timestamp%3D1530084738835%26version%3D1.0.0]
做一个实验,我们依次停止之前启动的两个 serviceDemo,从 clientDemo 的日志中,可以看出,它收到了通知 subscribe url
,并且依次断开了与两个服务的连接 disconnected from
:
2018-06-27 17:07:32.789 INFO 52762 --- [ain-EventThread] c.a.d.r.zookeeper.ZookeeperRegistry : [DUBBO] Notify urls for subscribe url consumer://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=providers,configurators,routers&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=52762&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530090434982&version=1.0.0, urls: [dubbo://10.93.67.135:20880/com.example.HelloService?anyhost=true&application=dubbo-demo-server&dubbo=2.6.2&generic=false&interface=com.example.HelloService&methods=sayHello&pid=50337&revision=1.0.0&side=provider×tamp=1530089150751&version=1.0.0], dubbo version: 2.6.2, current host: 10.93.67.135
2018-06-27 17:07:32.794 INFO 52762 --- [ain-EventThread] c.a.d.r.transport.netty.NettyChannel : [DUBBO] Close netty channel [id: 0x208cebb4, /10.93.67.135:55699 => /10.93.67.135:20881], dubbo version: 2.6.2, current host: 10.93.67.135
2018-06-27 17:07:32.799 INFO 52762 --- [andler-thread-1] c.a.d.rpc.protocol.dubbo.DubboProtocol : [DUBBO] disconnected from /10.93.67.135:20881,url:dubbo://10.93.67.135:20881/com.example.HelloService?anyhost=true&application=dubbo-demo-client&check=false&codec=dubbo&dubbo=2.6.2&generic=false&heartbeat=60000&interface=com.example.HelloService&methods=sayHello&pid=52762&qos.enable=false®ister.ip=10.93.67.135&remote.timestamp=1530089806995&revision=1.0.0&side=consumer×tamp=1530090434982&version=1.0.0, dubbo version: 2.6.2, current host: 10.93.67.135
2018-06-27 17:07:38.387 INFO 52762 --- [ain-EventThread] c.a.d.r.zookeeper.ZookeeperRegistry : [DUBBO] Notify urls for subscribe url consumer://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=providers,configurators,routers&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=52762&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530090434982&version=1.0.0, urls: [empty://10.93.67.135/com.example.HelloService?application=dubbo-demo-client&category=providers&dubbo=2.6.2&interface=com.example.HelloService&methods=sayHello&pid=52762&qos.enable=false&revision=1.0.0&side=consumer×tamp=1530090434982&version=1.0.0], dubbo version: 2.6.2, current host: 10.93.67.135
2018-06-27 17:07:38.389 INFO 52762 --- [ain-EventThread] c.a.d.r.transport.netty.NettyChannel : [DUBBO] Close netty channel [id: 0xee9aa5e5, /10.93.67.135:55698 => /10.93.67.135:20880], dubbo version: 2.6.2, current host: 10.93.67.135
关于 Zookeeper 的一些补充
参考 http://www.importnew.com/24411.html
对于一个服务框架,注册中心是其核心中的核心,虽然暂时挂掉并不会导致整个服务出问题,但是一旦挂掉,整体风险就很高。考虑一般情况,注册中心就是单台机器的时候,其实现很容易,所有机器起来都去注册服务给它,并且所有调用方都跟它保持长连接,一旦服务有变,即通过长连接来通知到调用方。但是当服务集群规模扩大时,这事情就不简单了,单机保持连接数有限,而且容易故障。
作为一个稳定的服务化框架,Dubbo 可以选择并推荐 Zookeeper 作为注册中心。其底层将 Zookeeper 常用的客户端 zkclient 和 curator 封装成为 ZookeeperClient。
- 当服务提供者服务启动时,向 Zookeeper 注册一个节点;
- 服务消费者则订阅其父节点的变化,诸如启动停止都能够通过节点创建删除得知,异常情况比如被调用方掉线也可以通过临时节点session 断开自动删除得知;
- 服务消费方同时也会将自己订阅的服务以节点创建的方式放到 Zookeeper;
- 于是可以得到映射关系,诸如谁提供了服务,谁订阅了谁提供的服务,基于这层关系再做监控,就能轻易得知整个系统情况。
Zookeeper 所有数据都在内存中,模型类似一颗文件树,ZNode Tree,每个 ZNode 节点都会保存自己的数据内容和一系列属性。
ZNode 分为持久节点和临时节点,后者和客户端会话绑定。
- 同一时刻多台机器创建同一个节点,只有一个会争抢成功。利用这个特性可以做分布式锁。
- 临时节点的生命周期与会话一致,会话关闭则临时节点删除。这个特性经常用来做心跳,动态监控,负载等动作。
- 顺序节点保证节点名全局唯一。这个特性可以用来生成分布式环境下的全局自增长 ID。
通过zookeeper提供的原语服务,可以对 Zookeeper 能做的事情有个精确和直观的认识:
- 创建节点
- 删除节点
- 更新节点
- 获取节点信息
- 权限控制
- 事件监听
关于 Zookeeper 的扩展
这里我们只有一台 Zookeeper 服务器,假如它挂了,那么整个系统就挂了。
为了防止这种情况,我们需要设置 Zookeeper 集群。
Zookeeper 不仅可以单机提供服务,同时也支持多机组成集群来提供服务,实际上 Zookeeper 还支持另外一种伪集群的方式,也就是可以在一台物理机上运行多个 Zookeeper 实例。
在 Zookeepe r中,有 Leader、Follower、Observer 三种角色。集群中所有机器通过一个 Leader 选举来决定一台机器作为 Leader,Leader 为客户端提供读和写服务。Follower 和 Observer 都提供读服务,区别在于 Observer 机器不参与选举。
Zookeeper 通过复制来实现高可用性,只要集合体中半数以上的机器处于可用状态,它就能够保证服务继续。
因为官网建议至少3个节点,3台机器只要有2台可用就可以选出leader并且对外提供服务(2n+1台机器,可以容n台机器挂掉)。