2022Dubbo服务框架入门级学习笔记
分布式系统的相关概念
大型互联网项目架构目标
高性能:提供快速的访问体验。
高可用:如果有一个机器挂了还有其他的替换
可伸缩:如果业务庞大了可以添加新的扩展
高可扩展:对于业务有更好的替换方法可以直接替换使用
集群与分布式的概念
集群:一个业务模块,部署在多台服务器上。
分布式:一个业务模块,部署在多台服务器上。
架构演进
-
单体架构:所有模块合在一起开发部署
优点:开发部署很方便、小型项目首选
缺点:
- 项目启动慢
- 可靠性差
- 可伸缩性差
- 扩展性和可维护性差
- 性能低
-
垂直架构
垂直架构是指将单体架构中的多个模块拆分为多个独立的项目。形成多个独立的单体架构。
单体架构存在的问题:
- 项目启动慢
- 可靠性差
- 可伸缩性差
- 扩展性和可维护性差
- 性能低
垂直架构存在的问题:重复功能太多,比如多个模块都使用查询用户
-
分布式架构
分布式架构是指在垂直架构的基础上,将公共业务模块抽取出来,作为独立的服务,供其他调用者消费,以实现服务的共享和重用
RPC: Remote Procedure Call 远程过程调用。有非常多的协议和技术来都实现了RPC的过程。比如:HTTP REST风格,Java RMI规范、WebService SOAP协议、Hession等等。
垂直架构存在的问题:重复功能太多
分布式架构存在的问题:服务提供方一旦产生变更,所有消费方都需要变更。
-
SOA架构
SOA:(Service-Oriented Architecture,面向服务的架构)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和契约联系起来。
ESB:(Enterparise Servce Bus) 企业服务总线,服务中介。主要是提供了一个服务于服务之间的交互。ESB 包含的功能如:负载均衡,流量控制,加密处理,服务的监控,异常处理,监控告急等等。
-
微服务架构
微服务架构是在 SOA 上做的升华,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。
微服务架构 = 80%的SOA服务架构思想 + 100%的组件化架构思想 + 80%的领域建模思想
特点:
- 服务实现组件化:开发者可以自由选择开发技术。也不需要协调其他团队
- 服务之间交互一般使用REST API
- 去中心化:每个微服务有自己私有的数据库持久化业务数据
- 自动化部署:把应用拆分成为一个一个独立的单个服务,方便自动化部署、测试、运维
Dubbo 是 SOA时代的产物,SpringCloud 是微服务时代的产物
1、Dubbo概念
- Dubbo是阿里巴巴公司开源的一个高性能、轻量级的 Java RPC 框架。
- 致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案。
- 官网:http://dubbo.apache.org
Dubbo架构
- init:初始化
- async:异步请求
- sync:同步请求
2、Dubbo快速入门
官方推荐使用
Zookeeper
作为Dubbo的注册中心Registry
创建入门项目,都使用maven来构建项目,将项目的业务模块和控制器模块拆分为两个子模块,目录结构如下
2.1、创建消费提供者项目dubbo-server
2.1.1、导入项目依赖
注意观察后面的dubbo
和curator-framework
及curator-recipes
这三个依赖
<properties>
<spring.version>5.1.9.RELEASE</spring.version>
<dubbo.version>2.7.4.1</dubbo.version>
<zookeeper.version>4.0.0</zookeeper.version>
</properties>
<dependencies>
<!--公共的接口-->
<dependency>
<groupId>com.zcl</groupId>
<artifactId>dubbo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- servlet3.0规范的坐标 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--spring的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--springmvc的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<!--Dubbo的起步依赖,版本2.7之后统一为rg.apache.dubb -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!--ZooKeeper客户端实现 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${zookeeper.version}</version>
</dependency>
<!--ZooKeeper客户端实现 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${zookeeper.version}</version>
</dependency>
</dependencies>
2.1.2、将pom文件的打包方式改为war
,并内置tomcat启动插件
因为我们需要分布式的开发,所以业务层也需要独立的启动
<build>
<plugins>
<!--tomcat插件-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>9000</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
创建web项目的配置文件web.xml
扫描applicationContext.xml
<?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">
<!-- spring -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
2.1.3、创建简单的业务层方法
package com.zcl.server.impl;
import com.zcl.server.UserServer;
import org.apache.dubbo.config.annotation.Service;
/**
* 项目名称:dubbo-pro
* 描述:
*
* @author zhong
* @date 2022-06-12 14:40
*/
/**
* 将这个类提供的方法(服务)对外公布,将访问地址 IP 端口的路径注册到zookeeper注册中心
* 与spring的Service不同注意区分导入的依赖
*/
@Service
public class UserServerImpl implements UserServer {
public String sayHello() {
return "hello Dubbo";
}
}
2.1.4、创建核心的applicationContext
的文件,完成dubbo的相关配置
在配置文件里面需要注意的是引入
dubbo
的命名空间,才能对dubbo进行一个配置bug遇见:如果是idea引入的命名空间一定要检查是否修改了空间的名称,如果没有修改启动将会报错
<?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://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--扫描-->
<!--<context:component-scan base-package="com.zcl.server"/>-->
<!--配置dubbo的信息-->
<!--1.配置项目的名称,唯一-->
<dubbo:application name="dubbo-server"/>
<!--2.配置注册中心的地址-->
<dubbo:registry address="zookeeper://192.168.26.131:2181"/>
<!--3.配置dubbo包扫描-->
<dubbo:annotation package="com.zcl.server.impl" />
</beans>
2.1.5、项目启动后的日志打印
2.2、创建共同的接口dubbo-interface
该模块只是一个jar类型的,不需要对外提供什么服务,只需要定义接口被其他模块引用即可
上下两个模块通过pom文件引入即可
2.3、创建消费者项目dubbo-web
该文件的
pom.xml
与服务提供者的一样,同时也需要调用公共的接口模块dubbo-interface
,如果不调用那么UserServer
类就会报错
2.3.1、创建控制器
使用了@Reference
注解替换了jdk的@Autowired
,该注解是dubbo下的实现类
package com.zcl.controller;
import com.zcl.service.UserServer;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 项目名称:dubbo-pro
* 描述:用户控制器
*
* @author zhong
* @date 2022-06-12 14:50
*/
@RestController
@RequestMapping("/user")
public class UserController {
// @Autowired
/**
* 远程注入(注意引入的包是dubbo的而不是jdk的)
* 1、从zookeeper注册中心获取userServer获取到访问的url
* 2、进行远程调用RPC
* 3、将结果封装为一个代理对象,给变量赋值
*/
@Reference
private UserServer userServer;
@RequestMapping("/sayHello")
public String sayHello(){
System.out.println("接收到页面请求");
return userServer.sayHello();
}
}
2.3.2、核心配置文件
<?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:mvc="http://www.springframework.org/schema/mvc"
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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- <mvc:annotation-driven/>
<context:component-scan base-package="com.zcl.controller"/>-->
<!--配置dubbo的信息-->
<!--1.配置项目的名称,唯一-->
<dubbo:application name="dubbo-web"/>
<!--2.配置注册中心的地址-->
<dubbo:registry address="zookeeper://192.168.26.131:2181"/>
<!--3.配置dubbo包扫描-->
<dubbo:annotation package="com.zcl.server.impl" />
</beans>
2.4.1、web.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:mvc="http://www.springframework.org/schema/mvc"
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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- <mvc:annotation-driven/>
<context:component-scan base-package="com.zcl.controller"/>-->
<!--配置dubbo的信息-->
<!--1.配置项目的名称,唯一-->
<dubbo:application name="dubbo-web"/>
<!--2.配置注册中心的地址-->
<dubbo:registry address="zookeeper://192.168.26.131:2181"/>
<!--3.配置dubbo包扫描-->
<dubbo:annotation package="com.zcl.server.impl" />
</beans>
2.4.2、项目启动
项目的启动,先启动消费提供者再启动消费者
一定要保证虚拟机的zookeeper服务是处于启动状态的
后面会结合
dubbo-admin
可视化界面进行使用,详细的安装过程可以查看往期文章
3、dubbo常用的高级配置
3.1、序列化
两个机器传输数据,如何传输Java对象?
java不进行序列化的效果样式和搭建
3.1.1、创建模块dubbo-pojo
模块
里面只需要创建一个
User
的实体类
/**
* 项目名称:dubbo-pro
* 描述:用户实体类
*
* @author zhong
* @date 2022-06-13 8:03
*/
public class User {
private Integer id;
private String username;
private String password;
// 省略创建get、set和有参、无参的构造方法
}
3.1.2、该模块需要被dubbo-interface
模块所依赖才能被调用
在
UserServer
接口上写上一个根据id查询用户的方法
public User findUserById(int id);
3.1.3、在dubbo-server
模块中实现接口的findUserById
方法
public User findUserById(int id) {
return new User(1, "zhangsan", "1234");
}
3.1.4、在dubbo-web
模块中创建一个访问的控制器,通过id接收参数
/**
* 根据id查询用户信息
* @param id
* @return
*/
@RequestMapping("/find")
public User find(Integer id){
return userServer.findUserById(id);
}
3.1.5、启动项目测试
- 必须在虚拟机中启动
zookeeper
- 将
dubbo-pojo
与dubbo-interface两个模块进行
install`打包 - 启动消费提供者再启动消费者
3.1.6、访问查看报错信息
http://localhost:8000/user/find.do?id=1
控制台报错信息
- dubbo 内部已经将序列化和反序列化的过程内部封装了
- 我们只需要在定义pojo类时实现Serializable接口即可
- 一般会定义一个公共的pojo模块,让生产者和消费者都依赖该模块。
在User
类上实现一个Serializable
序列化即可
3.2、地址缓存
注册中心挂了,服务是否可以正常访问?
- 可以,因为dubbo服务消费者在第一次调用时,会将服务提供方地址缓存到本地,以后在调用则不会访问注册中心。
- 当服务提供者地址发生变化时,注册中心会通知服务消费者。
3.2.1、怎么进行测试?
-
到虚拟机关闭
zookeeper
服务端./zkServer.sh stop
-
浏览器再次请求服务是否可以正常访问
3.3、超时与重试
超时问题:
- 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
- 在某个峰值时刻,大量的请求都在同时请求服务消费者,会造成线程的大量堆积,势必会造成雪崩。
- 设置了超时时间,在这个时间段内,无法完成服务访问,则自动断开连接
- 如果出现网络抖动,则这一次请求就会失败。
- Dubbo 提供重试机制来避免类似问题的发生。
- 通过 retries 属性来设置重试次数。默认为 2 次。
3.3.1、解决方式
-
在服务提供者上的
@Service
注解上可以设置时间和重试次数// 当前服务超时三秒,重试2次,一共三次 @Service(timeout = 300,retries = 2)
建议使用
@Service
注解上的超时时间设置,因为提供者可以知道查询的大概时间是多少 -
在消费者的
@Reference
注解上也可以设置优先级比
@Service
要高,同时设置会被覆盖@Reference(timeout = 1000)
3.4、多版本
- 灰度发布:当出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能。
- dubbo 中使用version 属性来设置和调用同一个接口的不同版本
3.4.1、实现方案
在服务提供者上编写version版本号
@Service(version = "v1.0")
在消费者上远程注入指定版本
@Reference(version = "v1.0")
3.6、负载均衡
负载均衡策略(4种):
- Random :按权重随机,默认值。按权重设置随机概率。
- RoundRobin :按权重轮询。
- LeastActive:最少活跃调用数,相同活跃数的随机。
- ConsistentHash:一致性 Hash,相同参数的请求总是发到同一提供者。
负载均衡演示
-
通过访问提供者的
@Service
注解可以设置权重@Service(weight = 100)
同时修改如下信息
==下面没修改启动一个提供者都该一下数值,访问的时候就可以查看了
public String sayHello() { return "1"; }
-
启动第一个访问提供者
-
修改访问提供者的端口号并修改权重再次启动tomcat
注意:不是重启上一个已运行的,而是重新运行一个
修改权重
@Service(weight = 200)
修改
applicationContext
配置文件主要是修改端口
<!--配置端口信息--> <dubbo:protocol port="20870"/> <!--1.配置项目的名称,唯一--> <dubbo:application name="dubbo-server2"> <dubbo:parameter key="qos.port" value="2211"/> </dubbo:application>
重复多个步骤
-
启动
dubbo-admin
可视化页面可以查看到具体的端口信息和权重 -
在消费者的
@Reference(loadbalance = "random")
中可以设置负载均衡的4中策略 -
浏览器启动测试访问
/user/sayHello.do
多刷新几回查看返回的结果数据就可以知道具体的请求到的提供者了
3.7、集群容错
集群容错模式:
- Failover Cluster:失败重试。默认值。当出现失败,重试其它服务器 ,默认重试2次,使用 retries 配置。一般用于读操作
- Failfast Cluster :快速失败,只发起一次调用,失败立即报错。通常用于写操作。
- Failsafe Cluster :失败安全,出现异常时,直接忽略。返回一个空结果。
- Failback Cluster :失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
- Forking Cluster :并行调用多个服务器,只要一个成功即返回。
- Broadcast Cluster :广播调用所有提供者,逐个调用,任意一台报错则报错。
演示过程
默认的超时时间是1秒中,下面的程序中我们设置了3秒睡眠,所以肯定会报错的,但是第三个程序我们是注释掉了睡眠的,所以还是会有一个被访问到数据
-
在消费提供者中给
findUserById
查询用户的方法添加如下内容,让提供者执行的时候睡眠public User findUserById(int id) { System.out.println(1); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return new User(1, "zhangsan", "1234"); }
每启动一台消费提供者就修改里面打印的数值,启动三台服务提供者,然后在第三台的时候将线程睡眠注释掉进行演示
-
每启动一台服务提供者都需要修改对应的端口号避免冲突
-
在消费者中的
@Reference(cluster = "failover")
进行容错策略的选择 -
启动消费浏览器访问,刷新多次查看提供者的输出结果
3.8、服务降级
mock=force:return null
表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。- 还可以改为
mock=fail:return null
表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
实现方式
在消费者的
@Reference()
注解上通过下面的参数进行设置,降级的策略
@Reference(mock = "force:return+null")