分布式微服务应用开发应该是当前最为普遍的服务框架了吧,比如HSF、Dubbo以及spring cloud等。由于公司项目使用的就是HSF服务,为了雨露均沾,就闲暇时间,先研究了下Dubbo框架。
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
1.dubbo原理
在进行dubbo项目开发之前我们先来了解下dubbo的工作原理。下面这张图来自dubbo官方文档的首页:http://dubbo.apache.org/zh-cn/index.html
首先我们先介绍下dubbo的几个关键词:
Container:dubbo服务运行容器,整个dubbo服务都是在这个容器内进行的,你可以理解为web服务必须依赖于servlet容器一样;
Provider:服务提供者,暴露服务的一方称之为服务提供者;
Consumer:服务消费者,调用远程服务的一方称之为服务消费者;
Registry:dubbo注册中心,为服务提供者和消费者提供的一个平台;
Monitor:dubbo监控中心,统计服务的调用次数和调用时间的日志服务;
上面几个的关系其实在我们生活中随处可见的,举个我自认为很贴切的例子:我们整个网络环境相当于dubbo容器,商家相当于服务提供者,买家相当于服务消费者,淘宝这个平台相当于注册中心,商家将商品在淘宝展示相当于服务提供者向注册中心注册服务,买家将商品添加到购物车则可以比作为服务消费者订阅服务,结账付款则是服务消费者调用服务提供者提供的服务,其中监控中心可以看作是淘宝的我的订单功能,通过监控中心,你可以看到服务提供者和消费者的行为。
结合上面的图我们在分析一波dubbo工作原理:dubbo容器启动[0.start],服务提供者向dubbo的注册中心注册服务[1.register],服务消费者则从dubbo的注册中心订阅服务[2.subscribe],0/1/2这三步我们可以看到是用紫色虚线勾画的,表示的是dubbo服务初始化的过程;当服务提供者发生变化后注册中心会通知服务消费者[3.notify],这一步是用蓝色虚线勾画的,表示的是dubbo的异步过程;服务消费者订阅服务后就可以调用服务提供者提供的服务[4.invoke],这一步是用蓝色实线勾画的,表示的是dubbo的实时调用过程;服务提供者和消费者的行为都会被dubbo监控中心监控[5.count],这一步也是用蓝色虚线勾画的,同样表示的是异步进行的过程。
了解了dubbo整个工作原理后,那么我们就一起开始开发一个简单的dubbo工程吧!
2.安装dubbo注册中心
俗话说的好:工欲善其事必先利其器。在码代码之前我们需要安装下dubbo的注册中心并启动dubbo容器。
这里我们选择官方推荐的注册中心zookeeper:http://archive.apache.org/dist/zookeeper/
2.1第一步选择下载对应版本的压缩包:
2.2第二步减压到自定义目录即可:
2.3第三步编写配置文件
zookeeper的配置文件需要我们自己编写,当然它为我们提供了一个模板:即在conf目录下的zoo_sample.cfg文件。
我们只需要简单copy一份并命名为zoo.cfg即可。
2.4第四步修改配置文件
zoo.cfg配置文件中我们只需要关心zookeeper的端口为2181,并修改zookeeper的数据保存位置即可,这个目录在启动时会自动生成。
3.启动zookeeper
接下来我们就可以启动dubbo了,我们需要在bin目录下运行zkServer.cmd脚本。
进入doc窗口后输入zkServer.cmd后回车即可。
启动结果:
4.码代码
接下来就来到我们期待已久的环节了:编写代码!!!
4.1项目整体框架
上图就是我们整个项目的模块架构:
springBootDubboParent:项目的父工程,父工程主要是集成我们的子模块,没有编写其他业务代码,下面是它的pom文件:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springboot.dubbo</groupId>
<artifactId>dubbo.springBootDubboParent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springBootDubboParent</name>
<packaging>pom</packaging>
<modules>
<module>dubboServiceCommon</module>
<module>userServiceProvider</module>
<module>orderServiceConsumer</module>
</modules>
<properties>
<java.version>11</java.version>
</properties>
</project>
4.2公共模块
dubboServiceCommon:项目的公共模块,主要包含了我们自定义的bean对象和接口的定义,该模块的作用就是将我们的实体类和接口类统一定义封装然后以jar包的形式导入需要依赖的模块中,实现代码的复用。
pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.springboot.dubbo</groupId>
<artifactId>dubbo.springBootDubboParent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dubbo.dubboServiceCommon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>dubboServiceCommon</name>
<properties>
<java.version>11</java.version>
</properties>
</project>
UserAddress:
定义一个用户地址实体类,主要包括用户id和地址信息
package com.bean;
import java.io.Serializable;
public class UserAddress implements Serializable {
private Integer id;
private String userAddress; //用户地址
private String userId; //用户id
private String userName; //用户名字
private String phoneNum; //用户电话
private String isDefault; //是否默认收获地址 Y - 是 N - 否
public UserAddress(Integer id, String userAddress, String userId, String userName, String phoneNum, String isDefault) {
this.id = id;
this.userAddress = userAddress;
this.userId = userId;
this.userName = userName;
this.phoneNum = phoneNum;
this.isDefault = isDefault;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPhoneNum() {
return phoneNum;
}
public void setPhoneNum(String phoneNum) {
this.phoneNum = phoneNum;
}
public String getIsDefault() {
return isDefault;
}
public void setIsDefault(String isDefault) {
this.isDefault = isDefault;
}
@Override
public String toString() {
return "UserAddress{" +
"id=" + id +
", userAddress='" + userAddress + '\'' +
", userId='" + userId + '\'' +
", userName='" + userName + '\'' +
", phoneNum='" + phoneNum + '\'' +
", isDefault='" + isDefault + '\'' +
'}';
}
}
OrderService:
定义一个订单服务接口,根据用户id获取其所有的地址信息
package com.service.order;
import com.bean.UserAddress;
import java.util.List;
/**
* 订单服务
*/
public interface OrderService {
/**
* 根据用户id 初始化 订单
* @param userId
*/
public List<UserAddress> initOrder(String userId);
}
UserService:
定义一个用户服务接口,实现查询用户所有地址的功能
package com.service.user;
import com.bean.UserAddress;
import java.util.List;
/**
* 用户服务
*/
public interface UserService {
/**
* 根据用户id返回用户所有的收获地址
* @param userId
* @return
*/
public List<UserAddress> qryUserAddressList(String userId);
}
4.3服务提供者模块
在userServiceProvider这个模块中编写我们的服务提供者,并实现与dubbo的整合。
A.服务提供者想要在dubbo的注册中心注册服务,暴露服务给消费者,需要分以下几步进行:
1.指定dubbo注册中心的位置两种方式
dubbo.registry.address=zookeeper://10.20.153.10:2181
或者
dubbo.registry.address=127.0.0.1:2181 dubbo.registry.protocol=zookeeper
2.由于dubbo属于RPC的一种,所以需要指定通信协议
dubbo.protocol.name=dubbo //采用dubbo协议
dubbo.protocol.port=20880 //端口为20880
3.配置监控中心,从注册中心自动监听服务
dubbo.monitor.protocol=registry
4.指定当前服务(应用)的名字
dubbo.application.name=userServiceProvider
以上就是配置文件的所有配置[最小配置]
B.接下来需要使用dubbo的@Service
注解暴露我们的服务提供者
注意:是导入dubbo的service:
import com.alibaba.dubbo.config.annotation.Service
C.在启动类使用注解@EnableDubbo
开启dubbo功能
自此关于服务提供者的最小配置全部完毕,源码都有注释[注释是一个优秀程序员自我修养的标记]
pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.springboot.dubbo</groupId>
<artifactId>dubbo.springBootDubboParent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dubbo.userServiceProvider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>userServiceProvider</name>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.springboot.dubbo</groupId>
<artifactId>dubbo.dubboServiceCommon</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--引入spring-boot集成dubbo-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>RELEASE</version>
</dependency>
<!--NoClassDefFoundError: io/netty/bootstrap/ServerBootstrap-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<!--Caused by: java.lang.ClassNotFoundException: org.apache.curator.framework.CuratorFrameworkFactory-->
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
<!--日志-->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.4</version>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
UserServiceImpl
package com.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSON;
import com.bean.UserAddress;
import com.service.user.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 1.将服务提供者注册到注册中心(暴露服务)
* a.导入sringboot-dubbo依赖及其他dubbo依赖
* b.配置服务提供者
* 2.让服务消费者到注册中心订阅服务提供者的服务地址
*
*/
@Service //应该引入dubbo的service,暴露服务,通过该注解将该服务注册中注册中心
@Component //springboot 组件 因为要引入dubbo的service注解,所以spring的service不能用了,只能引入Component
public class UserServiceImpl implements UserService {
private final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
public List<UserAddress> qryUserAddressList(String userId) {
logger.info("Begin call UserServiceImpl qryUserAddressList method and request params{} " , JSON.toJSONString(userId));
UserAddress userAddress1 = new UserAddress(
1,
"北京大兴区经开大厦",
"10001",
"Tom",
"18402021042",
"N"
);
UserAddress userAddress2 = new UserAddress(
2,
"北京大兴区大雄郁金香",
"10001",
"Tom",
"18402021042",
"Y"
);
List<UserAddress> userAddresses = Arrays.asList(userAddress1, userAddress2);
System.out.println(userAddresses);
return userAddresses;
}
}
application.properties
#启动报错
spring.main.allow-bean-definition-overriding=true
#duboo配置
#指定当前服务(应用)的名字,区别于其他服务
dubbo.application.name=userServiceProvider
#指定注册中心的位置
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper
#指定通信规则:通信协议+通信端口
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#监控中心从注册中心自动监听服务
dubbo.monitor.protocol=registry
4.4服务消费者模块
在orderServiceConsumer这个模块中编写我们的服务消费者,并实现与dubbo的整合。
服务消费者想要实现订阅并消费服务,分以下几步进行:
1.dubbo配置文件[基本和提供者的配置一样]
a.指定当前服务(应用)的名字
dubbo.application.name=orderServiceConsumer
b.指定注册中心的位置
dubbo.registry.address=zookeeper://127.0.0.1:2181
c.监控中心从注册中心自动监听服务
dubbo.monitor.protocol=registry
申明调用哪个服务的提供者通过注解实现@Reference
,所以不需要在配置文件里配置
2.使用dubbo的@Reference
注解从注册中心发现并订阅指定的服务提供者
3.在启动类使用注解@EnableDubbo
开启dubbo功能
pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.springboot.dubbo</groupId>
<artifactId>dubbo.springBootDubboParent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>dubbo.orderServiceConsumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>orderServiceConsumer</name>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.springboot.dubbo</groupId>
<artifactId>dubbo.dubboServiceCommon</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--引入spring-boot集成dubbo-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.5</version>
<scope>compile</scope>
</dependency>
<!--Caused by: java.lang.ClassNotFoundException: org.apache.curator.framework.CuratorFrameworkFactory-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.13.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
OrderServiceImpl
package com.service.impl;
import com.alibaba.dubbo.config.annotation.Reference;
import com.bean.UserAddress;
import com.service.order.OrderService;
import com.service.user.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 1.将服务提供者注册到注册中心
* a.导入spring boot - dubbo依赖
* b。引入dubbo其他依赖
* 2.让服务消费者到注册中心订阅服务提供者的服务地址
*
*/
@Service //应该引入dubbo的service,如果不是作为服务提供者向注册中心注册服务,那么service注解可以使用spring的注解
@Component //springboot 组件
public class OrderServiceImpl implements OrderService {
//@Autowired
@Reference //通过dubbo的该注解从注册中心发现并订阅服务
private UserService userService;
public List<UserAddress> initOrder(String userId) {
List<UserAddress> userAddressList = userService.qryUserAddressList(userId);
return userAddressList;
}
}
OrderController
package com.controller;
import com.bean.UserAddress;
import com.service.order.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/initOrder")
public List<UserAddress> initOrder(@RequestParam(value = "userId",required = true) String userId) {
List<UserAddress> userAddresses = orderService.initOrder(userId);
return userAddresses;
}
}
application.properties
#由于该工程是war包,所以启动会使用tomcat的默认8080端口,而监控中心monitor的端口也是8080
#修改tomcat的端口为8081
server.port=8081
#duboo配置
#指定当前服务(应用)的名字,区别于其他服务
dubbo.application.name=orderServiceConsumer
#指定注册中心的位置,两种方式
dubbo.registry.address=zookeeper://127.0.0.1:2181
#监控中心从注册中心自动监听服务
dubbo.monitor.protocol=registry
#申明调用哪个服务的提供者通过注解@Reference实现,所以不需要在配置文件里配置
5.启动项目
5.1启动zookeeper注册中心
5.2启动服务提供者
5.3启动服务消费者
5.4测试
通过消费者暴露的端口访问我们的controller层,返回的数据是后台模拟的虚假数据。
http://127.0.0.1:8081/initOrder?userId=1
6.官方文档
http://dubbo.apache.org/zh-cn/docs/user/quick-start.html
上面只是简单的搭建了一个dubbo工程,dubbo还有很多知识点需要去探索,比如负载均衡,服务降级,服务超时以及dubbo的多种配置形式等等。大家可以通过上面的官方链接进行更深入的了解,里面每个知识点都有很详细的讲解以及示例。
我会把我的demo分享给大家,这样你们就可以基于这个demo进行扩展测试其他dubbo功能了,需要源码得小伙伴可以私信我。