Dubbo 2.5.3+
1. 关于Dubbo、原理、负载均衡
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。 官网地址 http://dubbo.io GitHub地址 https://github.com/alibaba/dubbo Dubbo 是阿里巴巴的一个开源产品,淘宝内部使用的HSF并未开源。 关于Dubbo的架构可以参考官网的示例图

当整个系统越来越庞大的时候,基础的分布式服务越来越多,这些基础服务的负载均衡,服务URL配置管理,服务的协调都显得复杂,dubbo就是这样一个分布式服务的协调和治理框架。
与Hessian比较,dubbo具有如下优势:
- 服务URL地址的管理,服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者
- Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。
- Dubbo提供了多种均衡策略,缺省为random随机调用。
Random LoadBalance随机,按权重设置随机概率。 RoundRobin LoadBalance轮循,按公约后的权重设置轮循比率。。 LeastActive LoadBalance最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 ConsistentHash LoadBalance 一致性Hash,相同参数的请求总是发到同一提供者。
2. Dubbo的几个概念:注册中心、服务提供者、服务消费者、协议(dubbo、http、hessian)
在架构图中,可以看到有以下几个节点:
- Provider: 暴露服务的服务提供方。
- Consumer: 调用远程服务的服务消费方。
- Registry: 服务注册与发现的注册中心。
- Monitor: 统计服务的调用次调和调用时间的监控中心。
- Container: 服务运行容器。
-
注册中心 注册中心负责服务地址的注册与查找,相当于目录服务。服务提供者向注册中心注册自己的服务,服务消费者向注册中心发现自己的服务,当服务有变更时,注册中心通知到消费者。消费者发现所需服务列表后,将根据软负载均衡,选择一台提供者进行调用。注册中心,服务提供者,服务消费者三者之间均为长连接。 注册中心推荐使用Zookeeper,还可以使用Redis、数据库等客户端。如
<dubbo:registry address="zookeeper://127.0.0.1:7181" /> -
服务提供者 暴露服务一方,每个接口都应定义版本号,为后续不兼容升级提供可能。由于默认使用hessian二进制序列化,所以接口的参数和返回值必须实现序列化接口。如
<dubbo:service interface="com.deepoove.XxxService" version="1.0" /> -
服务消费者 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者。
<dubbo:reference id="xxxService" interface="com.deepoove.XxxService" /> -
协议
服务调用涉及到网络通讯,缺省协议为dubbo://,dubbo协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。对于大数据量不建议采用dubbo协议,可以考虑短连接协议,比如rmi、http。dubbo协议默认序列化方式为hessian2,对性能有更高要求可以使用dubbo序列化。
<dubbo:protocol name=“dubbo” port=“9090” server=“netty” client=“netty” codec=“dubbo” serialization=“hessian2” charset=“UTF-8” threadpool=“fixed” threads=“100” queues=“0” iothreads=“9” buffer=“8192” accepts=“1000” payload=“8388608” />
- Serialization枚举值:dubbo, hessian2, java, json dubbo协议说明:
- 连接个数:单连接
- 连接方式:长连接
- 传输协议:TCP
- 传输方式:NIO异步传输
- 序列化:Hessian二进制序列化
- 适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用dubbo协议传输大文件或超大字符串。
- 适用场景:常规远程服务方法调用
除了默认dubbo协议,还有rmi、http、hessian(可以与原生hessian服务互操作)等协议。
3. 注册中心Zookeeper
Zookeeper是一个针对分布式应用程序的高性能协调服务,提供的功能有:命名、配置管理、同步、组服务等。Because coordinating distributed systems is a Zoo。 dubbo默认为zkclient,推荐使用Java客户端Curator,中文意思为馆长。
单机安装Zookeeper,下载tar包:
~/sayi/zookeeper/zookeeper-3.5.2-alpha$ cd conf/
~/sayi/zookeeper/zookeeper-3.5.2-alpha/conf$ cp zoo_sample.cfg zoo.cfg
~/sayi/zookeeper/zookeeper-3.5.2-alpha/conf$ vi zoo.cfg
~/sayi/zookeeper/zookeeper-3.5.2-alpha/conf$ cd ../bin/
~/sayi/zookeeper/zookeeper-3.5.2-alpha/bin$ ./zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /home/sayi/zookeeper/zookeeper-3.5.2-alpha/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
至此,Zookeeper已经启动完毕,其中zoo.cfg内容如下:
# 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=~/sayi/zookeeper/data/zookeeper
# the port at which the clients will connect
clientPort=7181
客户端连接Zookeeper,连接成功后可以通过help查看可用命令:
./zkCli.sh -server 127.0.0.1:7181
Dubbo使用Zookeeper作为注册中心,目录结构如下:
---dubbo
-----|---com.deepoove.XxxService
------------|-----configurators
------------|-----routers
------------|-----consumers
------------|-----providers
----------------------|--dubbo://172.16.11.18:20880/com.deepoove.XxxService?XXX
流程说明:
1.服务提供者启动时
向/dubbo/com.deepoove.XxxService/providers目录下写入自己的URL地址。
2.服务消费者启动时
订阅/dubbo/com.deepoove.XxxService/providers目录下的提供者URL地址。
并向/dubbo/com.deepoove.XxxService/consumers目录下写入自己的URL地址。
3.监控中心启动时
订阅/dubbo/com.deepoove.XxxService目录下的所有提供者和消费者URL地址。
4. Dubbo Example 一个示例
Dubbo版本为2.5.3,Zookeeper版本3.5.2。服务提供方[项目dubbo_example]采用jetty(tomcat)作为服务容器,服务消费端[项目dubbo_example_consumer]只是一个简单的Main方法,并加载一个简单的Spring容器,用于消费服务,服务接口[项目dubbo_example_api](该接口需单独打包,在服务提供方和消费方共享)不依赖任何jar包。项目基于Gradle进行构建。
1. 服务接口dubbo_example_api
服务接口需要单独打包,供服务提供方和消费方共享。定义一个简单的接口如下:
package com.deepoove.dubboexample.api.service;
import com.deepoove.dubboexample.api.pojo.User;
public interface UserService {
User getUser(String id);
}
定义简单的类User:
package com.deepoove.dubboexample.api.pojo;
import java.io.Serializable;
public class User implements Serializable{
private static final long serialVersionUID = -1169812613737118557L;
private String id;
private String name;
private String site;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSite() {
return site;
}
public void setSite(String site) {
this.site = site;
}
}
2. 服务提供方dubbo_example
项目除了依赖dubbo、spring、Zookeeper、zkclient,还需要加上服务接口的依赖,在settings.gradle文件中,包含服务接口项目:
rootProject.name = 'dubbo_example'
include ':dubbo_example_api'
project(':dubbo_example_api').projectDir = new File(settingsDir, '../dubbo_example_api')
由于采用logback打印日志,同时使用jetty作为容器,所以我也加上了logback的依赖和jetty gradle插件,最终build.gradle文件内容如下:
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'jetty'
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
dependencies {
compile project(":dubbo_example_api")
compile group: 'org.springframework', name: 'spring-webmvc', version: '4.1.5.RELEASE'
compile group: 'org.springframework', name: 'spring-test', version: '4.0.5.RELEASE'
compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1'
compile (group: 'com.alibaba', name: 'dubbo', version: '2.5.3'){
exclude group: 'org.springframework'
}
compile group: 'org.apache.zookeeper', name: 'zookeeper', version: '3.5.2-alpha'
compile group: 'com.github.sgroschupf', name: 'zkclient', version: '0.1'
compile group: 'ch.qos.logback', name: 'logback-core', version: '1.1.7'
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.7'
testCompile 'junit:junit:4.12'
}
jettyRun{
contextPath = "demo"
httpPort = 8077
}
定义服务接口的实现,实现类如下:
package com.deepoove.dubboexample.provider;
import com.deepoove.dubboexample.api.pojo.User;
import com.deepoove.dubboexample.api.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public User getUser(String id) {
User user = new User();
user.setId("sayi");
user.setName("sayi");
user.setSite("http://www.deepoove.com");
return user;
}
}
服务接口实现好之后,接下来就可以配置dubbo,在src/main/resources下新增logback.xml日志配置文件,在src/main/resources/application下新增配置文件remote-provider.xml,同时在web.xml中配置路径。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:application/*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
remote-provider.xml中的典型配置如下,我们通过dubbo:application配置提供方应用信息名称,通过dubbo:registry向注册中心暴露服务地址,通过dubbo:protocol配置暴露服务的协议和端口,通过dubbo:service申明要暴露的服务接口。
<beans xmlns="http://www.springframework.org/schema/beans"
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://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="dubbo-example-app" />
<dubbo:registry address="zookeeper://127.0.0.1:7181" />
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:service interface="com.deepoove.dubboexample.api.service.UserService"
ref="userRemoteService" />
<bean id="userRemoteService" class="com.deepoove.dubboexample.provider.UserServiceImpl" />
</beans>
运行grade jettyrun后,可以通过查看Zookeeper的目录或者lsof -i:20880查看服务提供的接口是否使用来确认服务是否提供成功。
3. 服务消费端dubbo_example_consumer
消费方式一个Main方法,加载了spring容器,同时依赖服务接口项目。定义文件remote-consumer.xml,通过dubbo:application配置消费方应用名,通过dubbo:reference生成远程服务代理,可以和本地bean一样使用,内容如下:
<dubbo:application name="consumer-of-dubbo-example-app" />
<dubbo:registry address="zookeeper://115.29.10.121:7181" />
<dubbo:reference id="userService"
interface="com.deepoove.dubboexample.api.service.UserService" />
Main方法中加载此xml文件,远程调用服务方提供的接口:
package com.deepoove.dubbo_example_consumer;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.deepoove.dubboexample.api.pojo.User;
import com.deepoove.dubboexample.api.service.UserService;
public class Consumer {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "remote-consumer.xml" });
context.start();
UserService userService = (UserService) context.getBean("userService"); // 获取远程服务代理
User sayi = userService.getUser("sayi"); // 执行远程方法
System.out.println(sayi); // 显示调用结果
System.in.read(); // 按任意键退出
context.close();
}
}
5. Dubbo:检测服务是否提供成功、验证是否可用
在实际使用中,在服务提供者启动完毕后,通常会检测服务是否发布成功,或者对服务的某个接口进行调用测试。大体上有以下三个方法:
- 检测服务提供者监听端口号
lsof -i:20880
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 4675 Sai 217u IPv6 0x1ca3042d017b5851 0t0 TCP *:20880 (LISTEN)
linux上也可以用命令netstat -apn | grep 20880
- 查看注册中心是否注册服务 以Zookeeper为例,可以查看在providers目录下是否暴露对应服务,同时,我们也可以提供一些连接Zookeeper的dubbo工具,如dubbo-admin、dubbo-monitor。
[zk: 127.0.0.1:7181(CONNECTED) 0] ls /
[dubbo, zookeeper]
[zk: 127.0.0.1:7181(CONNECTED) 1] ls /dubbo
[com.alibaba.dubbo.monitor.MonitorService, com.deepoove.dubboexample.api.service.UserService]
[zk: 127.0.0.1:7181(CONNECTED) 2] ls /dubbo/com.
com.alibaba.dubbo.monitor.MonitorService com.deepoove.dubboexample.api.service.UserService
[zk: 127.0.0.1:7181(CONNECTED) 2] ls /dubbo/com.deepoove.dubboexample.api.service.UserService
[configurators, consumers, providers, routers]
[zk: 127.0.0.1:7181(CONNECTED) 3] ls /dubbo/com.deepoove.dubboexample.api.service.UserService/providers
[dubbo%3A%2F%2F172.16.11.18%3A20880%2Fcom.deepoove.dubboexample.api.service.UserService%3Fanyhost%3Dtrue%26application%3Ddubbo-example-app%26dubbo%3D2.5.3%26interface%3Dcom.deepoove.dubboexample.api.service.UserService%26methods%3DgetUser%26pid%3D4675%26revision%3Ddubbo_example_api%26side%3Dprovider%26timestamp%3D1478840366117]
- 模拟服务消费者调用服务 对于http协议可以通过浏览器进行模拟调用,但是由于缺省采用的是dubbo协议,所以我们需要写个简单的Main方法,参加服务消费端dubbo_example_consumer,如果不考虑具体方法的调用,可以采用回声测试:所有服务自动实现EchoService接口,只需将任意服务引用强制转型为EchoService。
UserService demoService = (UserService) context.getBean("userService"); // 获取远程服务代理
EchoService echoService = (EchoService) demoService; // 强制转型为EchoService
Object status = echoService.$echo("OK"); // 回声测试可用性
assert (status.equals("OK"));
6. Dubbo admin 和 Dubbo monitor
- Dubbo admin 获取dubbo-admin.war,修改文件WEB-INF/dubbo.properties文件,配置Zookeeper地址:
dubbo.registry.address=zookeeper://127.0.0.1:7181
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest
服务启动完毕后,浏览器访问控制台:127.0.0.1:8080/,输入密码进入管理界面。
- Dubbo monitor 监控中心也是一个标准的dubbo服务,所以也需要配置dubbo.protocol.port端口。
dubbo.container=log4j,spring,registry,jetty
dubbo.application.name=simple-monitor
dubbo.application.owner=
dubbo.registry.address=zookeeper://127.0.0.1:7181
dubbo.protocol.port=7077
dubbo.jetty.port=8099
dubbo.jetty.directory=/Users/Sai/Sayi/source/dubbo/monitordirectory/monitor
dubbo.charts.directory=${dubbo.jetty.directory}/charts
dubbo.statistics.directory=/Users/Sai/Sayi/source/dubbo/monitordirectory/monitor/statistics
dubbo.log4j.file=/Users/Sai/Sayi/source/dubbo/monitordirectory/logs/dubbo-monitor-simple.log
dubbo.log4j.level=WARN
通过命令bin/start.sh启动监控中心服务,浏览器访问127.0.0.1:8099/,进入监控界面。
7. 安全:关于Zookeeper的ACL
Zookeeper使用ACL控制节点的访问,ACL不具有继承性,即对父节点设置的Access Control不应用到子节点上。ACL可以理解成为scheme : id : permission的组合,其中permission为:CREATE、READ、WRITE、DELETE、ADMIN。Zookeeper支持pluggable authentication schemes.
- world has a single id, anyone, that represents anyone.
- auth doesn't use any id, represents any authenticated user.
- digest uses a username:password string to generate MD5 hash which is then used as an ACL ID identity. Authentication is done by sending the username:password in clear text. When used in the ACL the expression will be the username: base64 encoded SHA1 password digest(sha1加密后base64编码) .
- host uses the client host name as an ACL ID identity. The ACL expression is a hostname suffix. For example, the ACL expression host:corp.com matches the ids host:host1.corp.com and host:host2.corp.com, but not host:host1.store.com.
- ip uses the client host IP as an ACL ID identity. The ACL expression is of the form addr/bits where the most significant bits of addr are matched against the most significant bits of the client host IP.
下面的示例演示通过zkcli操作ACL,setAcl可以设置权限,getAcl获取权限列表:
[zk: 127.0.0.1:7181(CONNECTED) 14] ls /
[dubbo, zookeeper]
[zk: 127.0.0.1:7181(CONNECTED) 15] create /sayi "hello,data"
Created /sayi
[zk: 127.0.0.1:7181(CONNECTED) 16] get /sayi
hello,data
[zk: 127.0.0.1:7181(CONNECTED) 17] getAcl /sayi
'world,'anyone
: cdrwa
[zk: 127.0.0.1:7181(CONNECTED) 18] addauth digest sayi:123456
[zk: 127.0.0.1:7181(CONNECTED) 19] setAcl /sayi auth:sayi:123456:cdrwa
[zk: 127.0.0.1:7181(CONNECTED) 20] getAcl /sayi
'digest,'sayi:8n7aQg/S4IP6WJGOwf3DGTLg4iY=
: cdrwa
[zk: 127.0.0.1:7181(CONNECTED) 21]