分布式RPC之Dubbo
1. 软件架构演变
- 一个项目的概念是复杂的,可以由多个架构构成,每个架构里有多种框架,涉及多个进程等。
- 补充知识点:垂直划分和水平划分的广泛意义,以日常生活中的成绩单为例
- 将原本一张表垂直划分为两张表,分别列出文理科成绩。
- 将原本一张年级成绩表水平划分为各个班级的表。
1.1 单体架构
- 从功能角度:全部集中在一个项目,同种语言开发
- 前期优势:低成本,短周期。
- 后期做大项目劣势:性能扩展只能增加集群节点,成本高。
1.2 垂直架构
- 功能上切割,大项目划分为多个单体项目,各自操作自己的数据
- 仍然存在功能、数据、冗余,互相之间重复调用。购物车系统和支付系统都需要订单系统的支持。但是由于代码不是共用的,在购物车系统和支付系统中都有着一套和订单系统进行交互的业务代码。
- 系统扩张也只能靠集群方式。
1.3 SOA架构
-
搞清概念从全称开始:Service-Oriented Architecture,面向服务的架构。
-
粗粒度应用组件(服务)进行分布式部署、组合和使用。
-
需要确认重复功能或模块。对功能的抽象程度更高,趋向于通用性业务服务。应用与应用之间相互冗余的部分也被抽取出来了,作为一个服务单独存在。比如上面讲到的购物车系统和支付系统中查询订单的业务代码,被提取出来作为一个订单查询服务而单独存在。
-
使用**ESB(企业服务总线)**的形式作为通信的桥梁
-
各系统之间业务不同,很难确认功能或模块是重复的。抽取服务的粒度大。
1.4 微服务架构
- 抽取为一个个微服务,粒度更细,遵循单一原则。
- 轻量级框架协议传输。
- 针对不同服务制定特定优化方案,利于迭代和技术革新。
- 微服务太多,治理难度大,因此需要优秀的治理框架。例如有的框架提供网关微服务用来对这些微服务中的API进行统一管理。而前端无感于各微服务之间的依赖关系,只需调用一个API即可。
2. Dubbo架构
2.1 引入目的
- 为了解决微服务架构中频繁的服务之间调用,带来的开销大问题,引入了RPC(remote procedure call,即远程过程调用),每种语言都有自己的RPC框架实现,JAVA1,2就提供了RMI功能。阿里巴巴也退出了Dubbo框架:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现三大核心能力。
2.2 交互过程
-
Dubbo 微服务组件与各个中心的交互过程:
2.3 服务注册中心
-
Dubbo推荐使用Zookeeper作为服务注册中心。
-
Zookeeper功能强大,这里我们主要使用的是其服务注册的功能,其管理结构如下:
2.4 服务提供方
2.4.1 导入依赖
-
主要是spring依赖和Dubbo依赖以及Zookeeper服务和客户端:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.7</version> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency>
2.4.2 配置web.xml
-
通过spring的
ContextLoaderListener
监听器达到在Tomcat容器启动后加载spring.xml这个配置文件,为项目提供了spring支持,初始化了spring的OC容器。<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> </web-app>
2.4.3 编写接口和实现
-
自定义一个需要注册到Zookeeper的服务接口:
public interface HelloService { public String sayHello(String name); }
-
使用dubbo提供的@Service注解声明该接口的实现类:
package cn.forwardxiang.service.impl @Service//注意:com.alibaba.dubbo.config.annotation.Service public class HelloServiceImpl implements HelloService { public String sayHello(String name) { return "hello " + name; } }
2.4.4 配置Dubbo
-
在classpath下新建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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--当前应用名称,用于注册中心计算应用间依赖关系,注意:消费者和提供者应用名不要一样--> <dubbo:application name="dubbodemo_provider" /> <!-- 连接服务注册中心zookeeper ip为zookeeper所在服务器的ip地址--> <dubbo:registry address="zookeeper://192.168.134.129:2181"/> <!-- 注册 协议和port --> <dubbo:protocol name="dubbo" port="20881"></dubbo:protocol> <!-- 扫描指定包,加入@Service注解的类会被发布为服务 --> <dubbo:annotation package="cn.forwardxiang.service.impl" /> </beans>
2.5 服务消费方
2.5.1 导入依赖
- 与服务提供方一样,但是Tomcat端口需要更改,避免两个服务占用同一个端口而出错。
2.5.2 拷贝Service接口
- 消费方虽然不用提供service接口实现类(由RPC实现),但是需要提供一样的接口。
- 可以将该接口独立出来,做成一个jar包,然后在pom文件里引入坐标即可。
2.5.3 编写Controller
-
主要是使用注解@Reference代替原有的@Resource或@Autowired注解来自动注入RPC服务:
@Controller @RequestMapping("/demo") public class HelloController { @Reference //com.alibaba.dubbo.config.annotation.Reference private HelloService helloService; @RequestMapping("/hello") @ResponseBody public String getName(String name){ //远程调用 String result = helloService.sayHello(name); System.out.println(result); return result; } }
2.5.4 配置Dubbo
-
因为消费方不需要注册服务,所以相较而言少了服务注册协议和端口指定,只需要表明Zookeeper的地址和端口即可:
<!--当前应用名称,用于注册中心计算应用间依赖关系,注意:消费者和提供者应用名不要一样--> <dubbo:application name="dubbodemo-consumer" /> <!-- 连接服务注册中心zookeeper ip为zookeeper所在服务器的ip地址--> <dubbo:registry address="zookeeper://192.168.134.129:2181"/> <!-- 扫描的方式暴露接口 --> <dubbo:annotation package="cn.forwardxiang.controller" />
2.6 Dubbo管理控制台
- 通过在Tomcat里安装一个web服务,提供web页面进行访问,以便于管理Dubbo项目。
- 资料包名称:dubbo-admin-2.6.0.war,放入webapps目录下并修改dubbo.properties配置文件中的dubbo.registry.address为走哦客人对应地址和端口即可。
- 管理界面登录时默认账户和密码为root
2.7 Dubbo支持的协议
-
Dubbo支持的协议有:dubbo、rmi、hessian、http、webservice、rest、redis等
-
而其中的dubbo 协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,
-
多协议配置:
<!-- 多协议配置 --> <dubbo:protocol name="dubbo" port="20880" /> <dubbo:protocol name="rmi" port="1099" /> <!-- 使用dubbo协议暴露服务 --> <dubbo:service interface="cn.forwardxiang.api.HelloService" ref="helloService" protocol="dubbo" /> <!-- 使用rmi协议暴露服务 --> <dubbo:service interface="cn.forwardxiang.api.DemoService" ref="demoService"protocol="rmi" /
2.8 负载均衡
- 在消费者一方使用:@Reference(check = false,loadbalance = “random”)开启。
- 或者在服务提供方使用:@Service(loadbalance = “random”)
2.9 Dubbo无法发布被事务代理的Service
2.9.1 问题复现
- 如果将dubbo提供的@Service注解的服务接口加上诸如@Transactional事务控制注解后,这个服务就无法发布了,在控制台里将无法找到该服务提供者。
2.9.2 问题原因
- 默认情况下Spring是基于JDK动态代理方式创建代理对象,最终生成的对象完全限定名:com.sun.proxy.$Proxy42(最后两位数字不是固定的),导致Dubbo在发布服务前进行包匹配时无法完成匹配,进而没有进行服务的发布。
2.9.3 解决方案
-
在
spring.xml
配置文件里将事务管理更改为采用CGLIB代理:<!--开启事务控制的注解支持--> <tx:annotation-driven transaction-manager="transactionManager" proxy-targetclass="true"/>
-
在dubbo提供的@Service注解里提供CGLIB代理后的接口名称:
@Service(interfaceClass = HelloService.class) @Transactional public class HelloServiceImpl implements HelloService { public String sayHello(String name) { return "hello " + name; } }