Dubbo 是一个开源分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案以及 SOA 服务治理方案。Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需用 Spring 加载 Dubbo 的配置即可,Dubbo 基于 Spring 的 Schema 扩展进行加载。Dubbo 推荐的注册中心是 ZooKeeper。
调用关系说明:
- 服务容器负责启动、加载、运行服务提供者,服务提供者在启动时向注册中心注册自己提供的服务;
- 服务消费者在启动时,向注册中心订阅自己所需的服务;
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长链接推送变更数据给消费者;
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用;
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbo 架构具有以下几个特点:
Dubbo特点 | 说明 |
连通性 | 1)注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小; 2)监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示; 3)服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销; 4)服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销; 5)注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外; 6)注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者; 7)注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表; 8)注册中心和监控中心都是可选的,服务消费者可以直连服务提供者。 |
健状性 | 1)监控中心宕掉不影响使用,只是丢失部分采样数据; 2)数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务; 3)注册中心对等集群,任意一台宕掉后,将自动切换到另一台; 4)注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯; 5)服务提供者无状态,任意一台宕掉后,不影响使用; 6)服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复。 |
伸缩性 | 1)注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心; 2)服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者。 |
升级性 | 当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构: 节点角色说明: Deployer:自动部署服务的本地代理 Repository:仓库用于存储服务应用发布包 Scheduler:调度中心基于访问压力自动增减服务提供者 Admin:统一管理控制台 Registry:服务注册与发现的注册中心 Monitor:统计服务的调用次数和调用时间的监控中心 |
Dubbo 核心如下:
Dubbo核心 | 说明 |
远程通讯 | 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式 |
集群容错 | 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持 |
自动发现 | 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器 |
Dubbo 通讯协议推荐使用 Dubbo 协议(dubbo://)。Dubbo 协议采用单一长连接和 NIO 异步通讯,使用基于 mina 1.1.7 和 hessian 3.2.1 的 tbremoting 交互。适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。反之,Dubbo 协议不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。
dubbo:// | 说明 |
连接个数 | 单连接 |
连接方式 | 长连接 |
传输协议 | TCP |
传输方式 | NIO 异步传输 |
序列化 | Hessian 二进制序列化 |
适用范围 | 传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串。 |
适用场景 | 常规远程服务方法调用 |
关于 Dubbo 管理控制台的安装(Dubbo管理控制台的主要作用是服务治理,包含服务注册、服务降级、路由规则、访问控制、动态配置、权重调节、负载均衡、服务负责人、等管理功能。)请参考 dubbo-admin。
使用 Dubbo 服务提供者 (provider) 和消费者 (consumer) 需要添加 Maven 依赖:
<!-- dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
1.Dubbo的使用
1.服务接口(API)
api(服务接口,服务模型,服务异常)需单独打包,服务提供方和消费方依赖于 api。api 对服务消费方隐藏实现。下来定义统一服务接口 (如果返回的是 pojo,pojo 必须实现 Serializable 接口):
public interface DemoService {
String sayHello(String name);
}
2.服务提供者(Provider)
在服务提供方实现接口:
import org.springframework.stereotype.Service;
@Service
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
}
在 Provider 上尽量多配置 Consumer 端属性,在 Provider 配置后,Consumer 不配置则会使用 Provider 的配置值,即 Provider 配置可以作为 Consumer 的缺省值。否则,Consumer 会使用 Consumer 端的全局设置,这对于 Provider 不可控的,并且往往是不合理的。在 Provider 上可以配置的 Consumer 端属性有:
属性 | 说明 |
timeout | 方法调用超时。 |
retries | 失败重试次数,缺省是 2。 |
loadbalance | 负载均衡算法,缺省是随机 random,还可以有轮询 roundrobin、最不活跃优先 leastactive。 |
actives | 消费者端最大并发调用限制,缺省是 0,不限制。即当 Consumer 对一个服务的并发调用到上限后,新调用会等待,直到超时。 |
配置声明暴露服务 resources/spring-config-dubbo.xml:
<?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">
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="dubbo-demo-provider"/>
<!-- 使用zookeeper注册中心暴露服务地址,多个地址逗号隔开 -->
<dubbo:registry client="curator" protocol="zookeeper" address="127.0.0.1:2181"/>
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880"/>
<import resource="spring-config-dubbo-provider.xml"/>
</beans>
resources/spring-config-dubbo-provider.xml:
<?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">
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.example.server.soa.service.DemoService" group ="bj" version="1.0.0" ref="demoServiceImpl"
timeout="200" retries="2" loadbalance="random" actives="0"/>
</beans>
然后在项目启动类中导入配置即可:
@SpringBootApplication
@ImportResource(
"classpath:spring-config-dubbo.xml"
)
3.服务消费者(Consumer)
配置引用远程服务 resources/spring-config-rpc-dubbo.xml:
<?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">
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="dubbo-demo-consumer"/>
<!-- 使用zookeeper注册中心暴露发现服务地址,多个地址逗号隔开 -->
<dubbo:registry client="curator" protocol="zookeeper" address="127.0.0.1:2181"/>
<import resource="spring-config-rpc-dubbo-consumer.xml"/>
</beans>
resources/spring-config-rpc-dubbo-consumer.xml
<?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">
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference id="demoService" interface="com.example.server.soa.service.DemoService" group ="bj" version="1.0.0"/>
</beans>
然后在项目启动类中导入配置即可:
@SpringBootApplication
@ImportResource(
"classpath:spring-config-rpc-dubbo.xml"
)
接下来开始调用远程服务:
@RestController
public class DemoController {
@Resource
private DemoService demoService;
@RequestMapping("/sayHello")
public String sayHello(@RequestParam String name) {
return demoService.sayHello(name);
}
}
分别运行 Provider 和 Consumer 工程,浏览器访问 Consumer 对外提供的 RESTful API,即可成功调用远程服务。这里我们也可以看出,Dubbo 对应用没有任何 API 侵入。官方 schema 配置参考手册。
2.服务化最佳实践
- | 说明 |
粒度 | 1)服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持; 2)服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。 |
版本 | 1)每个接口都应定义版本号,为后续不兼容升级提供可能; 2)建议使用两位版本号,因为第三位版本号通常表示兼容升级,只有不兼容时才需要变更服务版本; 3)当不兼容时,先升级一半提供者为新版本,再将消费者全部升为新版本,然后将剩下的一半提供者升为新版本。 |
异常 | 1)建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,以及语义更友好。 2)如果担心性能问题,在必要时,可以通过 override 掉异常类的 fillInStackTrace() 方法为空方法,使其不拷贝栈信息。 3)查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 try…catch,并且不能进行有效处理。 4)服务提供方不应将 DAO 或 SQL 等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。 |
3.在Linux上部署Dubbo服务
部署目录规范(要避免应用迁移时的路径冲突):
/home/wwwroot/example/
|- /app //部署app
|- /app1
|- /app2
|- /service //部署分布式服务
|- /order //订单服务
|- /queue //消息队列服务
|- /user //用户服务
|- /timer //部署定时任务
|- /report //报表
|- /web //部署web工程
|- /gateway
|- /operation
|- /portal
手工维护 Dubbo 服务(有时会报映射错误,主机名ip映射可通过 vim /etc/hosts 配置):
# java -jar xxx.jar & //启动服务
# nohup java -jar xxx.jar & //加日志启动
# tail -f nohup.out //查看启动日志
# ps -ef | grep java //第一个数字就是PID
# kill PID //杀死服务
# kill -9 PID
自定义 Dubbo 服务维护的 Shell 脚本(命名规范:/home/wwwroot/example/xxx/service-xxx.sh):
#!/bin/sh
## java env
export JAVA_HOME=/usr/local/jdk
export JRE_HOME=$JAVA_HOME/jre
## service name
APP_NAME=user
SERVICE_DIR=/home/wwwroot/example/service/$APP_NAME
SERVICE_NAME=dubbo-service-$APP_NAME
JAR_NAME=$SERVICE_NAME\.jar
PID=$SERVICE_NAME\.pid
cd $SERVICE_DIR
case "" in
start)
## debug:-Xms256m-Xmx512m,formal:-Xms512m-Xmx2048m
nohup $JRE_HOME/bin/java -Xms256m -Xmx512m -jar $JAR_NAME >/dev/null 2>&1 &
echo $! > $SERVICE_DIR/$PID
echo "=== start $SERVICE_NAME"
;;
stop)
kill `cat $SERVICE_DIR/$PID`
rm -rf $SERVICE_DIR/$PID
echo "=== stop $SERVICE_NAME"
sleep 5
##
## dubbo-service-aa.jar
## dubbo-service-aa-bb.jar
P_ID=`ps -ef | grep -w "$SERVICE_NAME" | grep -v "grep" | awk '{print }'`
if [ "$P_ID" == "" ]; then
echo "=== $SERVICE_NAME process not exists or stop success"
else
echo "=== $SERVICE_NAME process pid is:$P_ID"
echo "=== begin kill $SERVICE_NAME process, pid is:$P_ID"
kill -9 $P_ID
fi
;;
restart)
# cd /home/wwwroot/example/service/user
# chmod 777 *.sh //切换到root用户,对*.sh赋可执行的权限
# ./service-user.sh start
# ./service-user.sh stop
# ./service-user.sh restart
# ps -ef | grep java //查看运行状态
stop
sleep 2
start
echo "=== restart $SERVICE_NAME"
;;
*)
## restart
stop
sleep 2
start
;;
esac
exit 0