SpringBoot整合Dubbo实现RPC+SOA
本文主要讲解如何使用SpringBoot框架配合Spring-Boot-starter-dubbo实现hlRPC调用。Springboot框架大家基本都有所认知,在此就不多做讲述,文章开始简单介绍一下Dubbo框架。
Dubbo是阿里巴巴出品的一款分布式的SOA服务治理框架,致力于提供高性能和透明化的RPC(Remote Procedure Call Protocol)远程调用的解决方案,以及SOA服务治理方案。
Dubbo框架基本的架构如下
可见,其由四部分组成,分别是
Registry: 注册中心
Consumer: 服务消费方
Provider: 服务提供方
Monitor: 服务监控方
其中前三者是必需提供的,在生产环境中,服务注册中心一般使用zookeeper。
更多的Dubbo框架的信息可以参考官方文档 https://dubbo.gitbooks.io/dubbo-user-book/,该框架在停滞一段时间之后又开始了更新,加上国内使用该框架的公司数目也很客观,因此不必担心社区的热度会下降。
工程介绍
本文的业务基于之前的微信凭证查询业务进行,分为服务提供方和服务消费方两部分,基于springboot1.4.3。并在文章最后介绍一下利用Dubbo管控台进行服务的治理。
服务提供方wechat-service-consumer-dubbo
1. Pom.xml
主要引入spring-boot-dubbo-starter,实现的很巧妙只用了四个Java配置类就将Dubbo与springboot整合到一起。
<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.hispeed.microservice</groupId>
<artifactId>wechat-pz-service-provider-dubbo</artifactId>
<version>1.0.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
</parent>
<properties>
<dubbo-spring-boot>1.0.0</dubbo-spring-boot>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.7</java.version>
</properties>
<dependencies>
<!-- wechat-pz-common-core -->
<dependency>
<groupId>com.hispeed.microservice</groupId>
<artifactId>wechat-pz-common-core</artifactId>
<version>1.0.0</version>
</dependency>
<!-- spring-boot-start-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-boot-actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- spring-cloud-sleuth-zipkin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<!-- spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- spring-boot-devtools
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>-->
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- oracle-driver -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>14.0.0</version>
</dependency>
<!-- spring-boot-starter-dubbo -->
<dependency>
<groupId>io.dubbo.springboot</groupId>
<artifactId>spring-boot-starter-dubbo</artifactId>
<version>${dubbo-spring-boot}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Camden.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 服务接口定义,可以单独发布到common包,并由服务提供方和调用方都依赖
定义如下
public interface WeChatPzService {
/**根据订单号和凭证类型微信凭证实体*/
public List<WeChatPzInfoVobj> getPzInfoVobj(String order_id, String pz_type) throws NullPointerException;
/**新增微信凭证实体*/
public boolean insertPzInfo(WeChatPzInfoVobj weChatPzInfoVobj);
/**检测是否重复插入/
public boolean isPzInfoExist(String hf_id);
}
3. 服务接口实现类,通过@Service注解配置其为dubbo服务
@Service(version = "1.0.0")
public class WeChatPzServiceImpl implements WeChatPzService {
private static final Logger LOGGER = LoggerFactory.getLogger(WeChatPzServiceImpl.class);
@Autowired
private WeChatPzMapper weChatPzMapper;
@Override
public List<WeChatPzInfoVobj> getPzInfoVobj(String order_id, String pz_type) throws NullPointerException {
LOGGER.info("业务层进行处理,将[查询参数]传递给服务持久层");
List<WeChatPzInfoVobj> weChatPzInfoVobjs = this.weChatPzMapper.getPzInfoVobj(order_id, pz_type);
if (weChatPzInfoVobjs.size() == 0 || weChatPzInfoVobjs == null) {
LOGGER.info("业务层得到的查询实体信息为空");
}
LOGGER.info("业务层得到的查询实体信息为:List<WeChatPzInfoVobj>=" + weChatPzInfoVobjs.toString());
return weChatPzInfoVobjs;
}
@Override
public boolean insertPzInfo(WeChatPzInfoVobj weChatPzInfoVobj) {
LOGGER.info("业务层进行处理,将[插入数据 ]传递给服务持久层");
int count = this.weChatPzMapper.insertPzInfo(weChatPzInfoVobj);
if (count > 0) {
LOGGER.info("数据持久层处理完毕,hf_id=" + weChatPzInfoVobj.getHfId() + "添加成功");
return true;
}
LOGGER.info("数据持久层处理完毕,hf_id=" + weChatPzInfoVobj.getHfId() + "添加失败");
return false;
}
@Override
public boolean isPzInfoExist(String hf_id) {
WeChatPzInfoVobj weChatPzInfoVobj = this.weChatPzMapper.getWeChatPzByHfId(hf_id);
// 通过话费流水号能够查到对应的条目,说明已经添加,返回true
// 否则返回false
if (weChatPzInfoVobj != null) {
LOGGER.info("数据持久层处理完毕,hf_id=" + weChatPzInfoVobj.getHfId() + "的凭证信息存在,具体的信息为:" + weChatPzInfoVobj.toString());
return true;
}
return false;
}
}
核心就是@Service 注解标识为 Dubbo 服务,并通过 version 指定了版本号。
com.hispeed.wechat.pz.mapper.WeChatPzMapper是通过mybatis的注解方式进行数据库的访问。具体的代码感兴趣可以自行查看。
4. 配置文件
server.port=8082
/#Dubbo
spring.dubbo.application.name=wechat_pz_service_provider_dubbo应用名称
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181 注册中心地址
spring.dubbo.protocol.name=dubbo 协议名称
spring.dubbo.protocol.port=20880 协议端口
spring.dubbo.scan=com.hispeed.wechat.pz.service 服务类包目录
/#datasource
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@192.168.63.90:1521:testdb
spring.datasource.username=esales26
spring.datasource.password=esales26
/#eureka-client
spring.application.name=wechat-pz-service-provider-dubbo
eureka.client.serviceUrl.defaultZone=http://192.168.66.40:8761/eureka
eureka.instance.prefer-ip-address=true
/#interface version
wechat.pz.inter.version=v1.0
这里就解释一个地方,spring.dubbo.scan表示要扫描作为服务的类路径,为了方便就将service包都扫描进去了。
服务消费方wechat-service-consumer-dubbo
1. pom.xml
2. 服务接口定义,同服务提供方
为了避免将代码到处复制粘贴,引起代码的不一致,再次建议将公共代码封装为一个公共依赖。
3. 服务消费方配置
通过@Reference引用远程服务提供者,发起服务的调用
@Component
public class WeChatPzDubboServiceImpl {
@Reference(version = "1.0.0")
WeChatPzService weChatPzService;
public List<WeChatPzInfoVobj> getPzInfoVobj(String order_id, String pz_type)
throws NullPointerException {
return weChatPzService.getPzInfoVobj(order_id, pz_type);
}
public boolean insertPzInfo(WeChatPzInfoVobj weChatPzInfoVobj) {
return weChatPzService.insertPzInfo(weChatPzInfoVobj);
}
public boolean isPzInfoExist(String hf_id) {
return weChatPzService.isPzInfoExist(hf_id);
}
}
这里我们定义了一个service 用于调用远程的接口,通过@Reference(version = “1.0.0”) 引用远程服务。通过该注解,订阅该接口版本为 1.0.0 的 Dubbo 服务。
同时我们把WeChatPzDubboServiceImpl这个bean添加到spring的bean容器中,便于后续的调用。
4. 业务逻辑开发,可以是任意形式,这里用一个controller进行调用,其效果就如同进行本地的服务调用
这里我就截取一部分代码,更详细的代码请参考wechat-pz-service-consumer-dubbo工程下的com.hispeed.wechat.pz.controller.WeChatDubboController类。
@RestController
public class WeChatDubboController {
private static final Logger LOGGER = LoggerFactory.getLogger(WeChatDubboController.class);
@Autowired
WeChatPzDubboServiceImpl iWeChatPzService;
@Value("${wechat.pz.inter.version}")
private String interVersion; // 接口版本号
private ResponseParam<WeChatPzInfoVobj> responseParam; // 封装非正常情况返回信息
/**
* 根据查询条件或许微信凭证
* @param <T>
* @return ResponseParam
*/
@GetMapping("pzinfo/select")
public ResponseParam<WeChatPzInfoVobj> getDataByParams(
@RequestParam("order_id") String order_id,
@RequestParam("pz_type") String pz_type,
@RequestParam("version") String version) {
LOGGER.info("该接口版本号为:version=" + interVersion);
LOGGER.info("接收到的参数列表为:" + " order_id=" + order_id + ",pz_type=[" + pz_type + "],version=" + version);
responseParam = new ResponseParam<WeChatPzInfoVobj>();
// 请求参数不完整或存在空值
if (StringUtils.isEmpty(order_id) || StringUtils.isEmpty(pz_type) || StringUtils.isEmpty(version)) {
responseParam.setData(null);
responseParam.setReturnCode(SelectCodeEnum.INVALID_PARAM.getCode());
responseParam.setDetail(SelectCodeEnum.INVALID_PARAM.getDetail());
return responseParam;
}
5. 服务消费方的配置文件
server.port=8083
/# name of consumer
spring.application.name=wechat-pz-service-consumer-dubbo
/# eureka url
eureka.client.serviceUrl.defaultZone=http://192.168.66.40:8761/eureka
eureka.instance.prefer-ip-address=true
/# interface version
wechat.pz.inter.version=v1.0
/# dubbo
spring.dubbo.application.name=wechat_pz_service_consumer_dubbo
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.scan=com.hispeed.wechat.pz.service
spring.dubbo.scan扫描本消费者工程中引用了远程服务的包。
服务注册中心的搭建
服务注册中心使用zookeeper,这也是官方推荐的注册中心组件,还可以使用redis,不过生产环境还是zookeeper更可靠。
本文使用的是zookeeper-3.4.9,下载一份并解压,进入zookeeper-3.4.9\conf下,重命名zoo_sample.cfg为zoo.cfg,打开进行编辑。
核心配置项如下:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=d:/data/zookeeper
clientPort=2181
server.1=127.0.0.1:2888:3888
dataDir下的路径需要自己创建,创建之后,在该路径下创建一个无扩展名的文本文件myid,在其中写入写入一个编号,如1。在zk集群部署时生效。
由于本文是测试,只需要启动一台zk即可。
在zookeeper-3.4.9\bin启动zkServer.cmd,linux下为zkCli.sh,别忘记授予可执行权限。
更多的zk相关的资料,可以参考阅读《从Paxos到zookeeper分布式一致性原理与实践》
进行测试
- 启动zookeeper服务端,在zookeeper-3.4.9\bin启动zkServer.cmd,双击运行即可
- 启动服务提供方,运行wechat-pz-service-provider-dubbo下的启动类com.hispeed.wechat.pz.WeChatServiceDubboProviderBootstrap
可以在zk控制台看到如下日志输出
2017-09-14 15:53:40,850 [myid:] - INFO [ProcessThread(sid:0 cport:2181)::PrepRequestProcessor@649] - Got user-level KeeperException when processing sessionid:0x15e79be352d000f type:create cxid:0x7 zx id:0xe3b txntype:-1 reqpath:n/a Error Path:/dubbo/com.hispeed.wechat.pz.service.WeChatPzService/conf igurators Error:KeeperErrorCode = NodeExists for /dubbo/com.hispeed.wechat.pz.service.WeChatPzServic e/configurators
表明服务已注册到zk
- 启动服务消费方,调用消费方暴露的http接口,通过RPC方式调用服务提供方的服务。
REST测试工具中访问接口 - 服务消费方日志
- 服务提供方日志:
可以看到,两个结合在一起完成了一次完整的服务调用,服务消费方只对外暴露接口,对内通过Dubbo框架的RPC方式进行服务调用。而springCloud则是另外的一种方式,每个服务都暴露了Http接口,服务间通过Httpclient方式进行服务的调用。只能说各有千秋吧。
到这里,我们就开发并测试完成基于springboot 的dubbo服务的发布和调用。
附录:dubbo管控台的使用
Dubbo-admin管控台是官方提供的用于进行服务管理和控制的单独的一个web应用,有两种方式获取
- 直接在网络上下载编译好的二进制war包,但不是最好的方式,可能会由于jdk版本的问题造成启动失败。链接:http://www.pc0359.cn/downinfo/58773.html
- 更推荐的方式还是下载源码编译安装
- 从官方github下载最新的源码,我下载的是2.5.3,目前官方更新到了2.5.4,在管控台上功能变化不大。安装方法是一样的。
- 源码下载地址:https://github.com/alibaba/dubbo
- 解压源码包,在项目根路径下执行 mvn –clean –DskipTests install 跳过测试并安装
等待片刻即可安装成功,jdk版本至少在1.6以上
解压一份儿和jdk版本匹配的tomcat,我用的是tomcat7,jdk版本jdk7,从dubbo-admin\target下获取dubbo-admin-2.8.4.war,放置在tomcat的webapp/ROOT下,删除原有的文件。
启动tomcat,这里可能需要设置server.xml中的端口防止端口冲突
启动完成,在浏览器访问,我的链接是http://localhost:8088/
这里会提示输入密码,默认为root/root
登录成功可以看到如下界面- Dubbo-admin提供了基本的服务治理功能,点击服务治理-服务,可以看到我们发布的服务提供者
点击进入可以对其做一些动态的设置,包括权重、是否启用、对其消费者进行动态设置、路由配置、访问控制、负载均衡等。
基本提供了一个服务治理的控制台,需要更多的其他功能需要自行扩展。
感兴趣可以搭建一套进行测试。
注意
- 这里还需要注意的一点是,凡是调用过程中涉及到的实体都需要实现Serializable 接口,并生成serialVersionUID。否则dubbo会报remoteException
- 开发工具中的devtools不要使用,否则会提示java.lang.ClassCastException错误