一、提出需求
某个电商系统,订单服务需要调用用户服务获取某个用户的所有地址列表,而订单服务和用户服务分别部署在不同的机器上,此时就需要通过RPC的方式。
我们需要创建两个服务模块:订单服务(服务消费者)和用户服务(服务提供者)
模块 | 功能 |
---|---|
订单服务web模块 | 创建订单等 |
用户服务service模块 | 查询用户地址等 |
测试预期结果:订单服务web模块在A服务器,用户服务模块在B服务器,A可以远程调用B的功能。 | |
关于服务拆分的粒度:服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持。服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。不建议使用过于抽象的通用接口,如:Map query(Map),这样的接口没有明确语义,会给后期维护带来不便。 |
二、搭建服务
1、创建服务提供者端(用户服务)项目
项目目录结构:
用户地址实体:
public class UserAddress implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String userAddress; // 用户地址
private String userId; // 用户id
private String consignee; // 收货人
private String phoneNum; // 电话号码
private String isDefault; // 是否为默认地址 Y-是 N-否
public UserAddress() {
super();
}
public UserAddress(Integer id, String userAddress, String userId, String consignee, String phoneNum,
String isDefault) {
super();
this.id = id;
this.userAddress = userAddress;
this.userId = userId;
this.consignee = consignee;
this.phoneNum = phoneNum;
this.isDefault = isDefault;
}
...
}
用户服务接口定义:
public interface UserService {
/**
* 据用户id查收货地址
* @param userId
* @return
*/
public List<UserAddress> getUserAddressList(String userId);
}
用户服务接口实现:
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
return Arrays.asList(address1, address2);
}
}
2、创建服务消费者端(订单服务)项目
由于需要调用用户模块的接口,因此需要在该项目中定义用户的相关实体(UserAddresss)和接口(UserService)
项目目录结构:UserAddress和UserService与服务提供者中完全相同
订单服务接口定义:
public interface OrderService {
public void initOrder(String userId);
}
订单服务接口实现:
public class OrderServiceImpl implements OrderService {
UserService userService;
/**
* 初始化订单
*/
public void initOrder(String userId) {
// 查询用户的收货地址,需要调用用户服务
List<UserAddress> userAddressList = userService.getUserAddressList(userId);
System.out.println(userAddressList);
}
}
这里有一个问题:在用户服务项目和订单服务项目中都有完全相同的UserAddress和UserService类,且这两个类很可能在其他的项目中也会被用到,因此可以把这些公共的实体定义和接口定义在另外一个单独的项目中完成,而与服务的实现和消费拆分开,然后采用maven依赖的方式引入到服务的提供者和消费者等项目中。在dubbo的官网中也有这样一段描述:建议将服务接口、服务模型、服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP)、共同重用原则(CRP)。
3、抽取出公共API项目
项目目录结构:
pom.xml:
<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.bdm.gmall</groupId>
<artifactId>gmall-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</project>
4、将原服务提供者和消费者中的实体定义和接口定义删除,只保留接口实现即可,然后在pom中引入公共API的依赖:
<dependency>
<groupId>com.bdm.gmall</groupId>
<artifactId>gmall-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
服务提供者项目结构:
服务消费者项目结构:
此时在订单项目中调用userService的方法肯定是失败的,因为在订单项目中并没有实现该方法,该方法的实现在用户项目中,这就需要通过RPC的方式来调用该方法,这就是dubbo的用处所在。
使用dubbo主要完成两个任务:
1、将服务提供者提供的服务注册到注册中心(暴露服务)
2、让服务消费者去注册中心订阅服务提供者的服务地址(订阅服务)
三、服务提供者配置
1、引入dubbo依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.4</version>
</dependency>
2、引入zookeeper的客户端依赖(dubbo2.6版本需引入curator,2.5及以下版本需引入zkclient),如果curator版本太高可能导致测试时报错
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
3、配置服务提供者
①创建dubbo的配置文件provider.xml,引入dubbo的命名空间并进行相关配置
<?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://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 1、指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名)-->
<dubbo:application name="user-service-provider"></dubbo:application>
<!-- 2、指定注册中心的位置:可使用下面的两种方式 -->
<!-- <dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry> -->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>
<!-- 3、指定通信规则(通信协议/通信端口) -->
<dubbo:protocol name="dubbo" port="20882"></dubbo:protocol>
<!-- 4、暴露服务:指向服务的实现类 -->
<dubbo:service interface="com.bdm.gmall.service.UserService" ref="userServiceImpl"></dubbo:service>
<bean id="userServiceImpl" class="com.bdm.gmall.service.impl.UserServiceImpl"></bean>
</beans>
②测试
public class MainApplication {
public static void main(String args[]) throws IOException {
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("provider.xml");
ioc.start();
System.in.read();// 这句代码是不让程序退出以便于我们在dubbo-admin中查看服务的情况
}
}
注
:在实际开发的时候只需要将dubbo的配置文件引入到Spring的Bean配置文件中:
<import resource="classpath*:provider.xml" />
查看管理控制台:可看到服务已经注册成功了
也可以看到服务提供者的信息:
四、服务消费者配置
1、引入dubbo和zookeeper依赖:
<!-- https://mvnrepository.com/artifact/com.alibaba/dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
2、配置服务消费者配置文件,创建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://dubbo.apache.org/schema/dubbo"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- Spring的包扫描:使该包下使用@Service注解的类被纳入Spring的ioc容器管理 -->
<context:component-scan base-package="com.bdm.gmall.service.impl"></context:component-scan>
<!-- 消费者应用名称 -->
<dubbo:application name="order-service-consumer"></dubbo:application>
<!-- 注册中心地址:向该注册中心订阅服务 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>
<!-- 订阅服务(声明该接口需调用远程接口,dubbo会为我们生成远程服务代理):这样就可以远程调用userService这个bean(实际上是服务提供者中的userServiceImpl) -->
<dubbo:reference interface="com.bdm.gmall.service.UserService" id="userService"></dubbo:reference>
</beans>
3、将订单服务的实现类稍作改动:加上@Autowired和@Service注解
@Service//是spring的@Service注解而非dubbo的
public class OrderServiceImpl implements OrderService {
@Autowired
UserService userService;
/**
* 初始化订单
*/
public void initOrder(String userId) {
// 查询用户的收货地址,需要调用用户服务
List<UserAddress> userAddressList = userService.getUserAddressList(userId);
System.out.println(userAddressList);
}
}
4、测试
public class MainApplication {
public static void main(String args[]) throws IOException {
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("consumer.xml");
OrderService orderService = ioc.getBean(OrderServiceImpl.class);
orderService.initOrder("1");
System.in.read();
}
}
注
:在实际开发的时候只需要将dubbo的配置文件引入到Spring的Bean配置文件中:
<import resource="classpath*:provider.xml" />
可以看到后台打印出:
[UserAddress [id=1, userAddress=北京市昌平区宏福科技园综合楼3层, userId=1, consignee=李老师, phoneNum=010-56253825, isDefault=Y], UserAddress [id=2, userAddress=深圳市宝安区西部硅谷大厦B座3层(深圳分校), userId=1, consignee=王老师, phoneNum=010-56253825, isDefault=N]]
在管理控制台也可以看到响应变化:
五、配置监控中心
在provider.xml和consumer.xml中都做如下配置:
<dubbo:monitor protocol="registry"></dubbo:monitor>
<!-- <dubbo:monitor address="127.0.0.1:7070"></dubbo:monitor> -->
上面是配置监控中心的两种方式,配置的时候可以任选一种:第一种通过protocal=“registry”(值固定为"registry")是让监控中心自己去注册中心发现;第二种则是监控中心直接与服务提供者或者服务消费者通信来完成监控
配置好之后就可以在监控中心看到通信的信息:
六、疑问
假如在同一个注册中心有多个服务提供者提供相同的服务,dubbo怎么区分该调用哪一个呢?
答案:相同的服务意味着服务类的包名和类名完全相同,此时dubbo是区分不出来的,也不需要区分,这种情况下dubbo会在这些相同的服务之间根据负载均衡的策略做负载均衡,因此相同的服务在不同的机器上实现的时候逻辑要保持一致;如果仅仅是类名相同,而包名不同,dubbo会根据包名区分服务,因为服务在消费者端是确定的;也就是说dubbo中的服务是以全类名区分的。