阶段七模块一 Dubbo
内容输出来源:拉钩教育Java就业训练营
1 doubbo
Dubbo是分布式服务框架,是阿里巴巴的开源项目,现交给apache进行维护
Dubbo致力于提高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案
1.1 RPC
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式
RPC基本的通信原理
- 在客户端将对象进行序列化
- 底层通信框架使用netty(基于tcp协议的socket),将序列化的对象发给服务方提供方
- 服务提供方通过socket得到数据文件之后,进行反序列化,获得要操作的对象
- 对象数据操作完毕,将新的对象序列化,再通过服务提供方的socket返回给客户端
- 客户端获得序列化数据,再反序列化,得到最新的数据对象,至此,完成一次请求
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V11FFUmu-1617956834742)(Dubbo.assets/Dubbo详解.jpg)]
1.2 节点
节点 | 角色 |
---|---|
Provider | 服务的提供方 |
Consumer | 服务的消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 监控服务的统计中心 |
Container | 服务运行容器 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VzX9cyWQ-1617956834743)(Dubbo.assets/Dubbo详解-1617848476648.jpg)]
1.3 调用关系
1.服务容器负责启动,加载,运行服务提供者;
2.服务提供者在启动时,向注册中心注册自己提供的服务;
3.服务消费者在启动时,向注册中心订阅自己所需的服务;
4.在注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者;
5.服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用;
6.服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心;
2 dubbo入门
官方推荐使用zookeeper注册中心;
注册中心负责服务地址的注册与查找,相当于目录服务;
服务提供者和消费者只在启动时与注册中心交互,注册中不转发请求,压力较小;
Zookeeper是apache hadoop的子项目,是一个树形的目录服务,支持变更推送,适合作为dubbo的服务注册中心,工业强度较高,可用于生产环境;
PS:dubbo即是求职的人,也是招聘单位,而zookeeper就是人才市场/招聘网站;
2.1 服务提供方
2.1.1 服务方的pom.xml
<packaging>war</packaging>
<properties>
<spring.version>5.0.6.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!--dubbo -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.7</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.11.0.GA</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven </groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<port>8001</port>
<path>/</path>
</configuration>
<executions>
<execution>
<!-- 打包完成后,运行服务 -->
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2.1.2 服务方接口及其实现
public interface HelloService {
String sayHello(String name);
}
@Service
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "Hello," + name + "!!!";
}
}
PS:实现中的@Service注解使用的是dubbo中的注解
2.1.3 服务方配置文件spring.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://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--1.服务提供方在zookeeper中的“别名”-->
<dubbo:application name="dubbo-server"/>
<!--2.注册中心的地址-->
<dubbo:registry address="zookeeper://192.168.163.128:2181"/>
<!--3.扫描类(将什么包下的类作为服务提供类)-->
<dubbo:annotation package="service.impl"/>
</beans>
2.1.4 服务方web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring.xml</param-value>
</context-param>
</web-app>
2.2 服务消费方
2.2.1 消费方pom.xml
与服务方一致,只修改tomcat端口为8002
2.2.2 消费方Controller
@RestController
public class HelloAction {
@com.alibaba.dubbo.config.annotation.Reference
private HelloService hs;
@RequestMapping("hello")
@ResponseBody
public String hello( String name){
return hs.sayHello(name);
}
}
2.2.3 消费方的接口
controller中要依赖HelloService,所以我们创建一个接口;
但消费方不需要实现该接口,我们会通过服务方去实现
注入时不能采用@Autowired,需要采用@Reference
public interface HelloService {
String sayHello(String name);
}
2.3.4 消费方的web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1">
<servlet>
<servlet-name>springmvc</servlet-name>
<servletclass>
org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/spring.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2.3.5 消费方springmvc.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://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--Dubbo的应用名称,通常使用项目名 -->
<dubbo:application name="dubbo-consumer" />
<!--配置Dubbo的注册中心地址 -->
<dubbo:registry address="zookeeper://192.168.204.141:2181" />
<!--配置Dubbo扫描类,将这个类作为服务进行发布 -->
<dubbo:annotation package="controller" />
</beans>
2.3 启动测试
启动服务方后,再启动消费方
再浏览器中访问http://localhost:8002/hello?name=james
3 dubbo综合应用
3.1 配置说明
3.1.1 启动时检查
启动时会在注册中心检查依赖的服务是否可用,不可用时会抛出异常
在消费方编写初始化容器的main方法启动(tomcat启动方式,必须访问一次action才能初始化spring)
public class Test {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("classpath:spring/spring.xml");
System.in.read();
}
}
<!--默认是true:抛异常;false:不抛异常-->
<dubbo:consumer check="false" />
一般情况下默认值即可
系统级别日志,需要配合log4j才输出,在resources下添加log4j.properties
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %m%n
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=dubbo.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %m%n
log4j.rootLogger=error, stdout,file
3.1.2 超时时间
由于网络或服务端不可靠,会导致调用过程中出现不确定的阻塞状态(超时)
为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间
在服务提供者添加如下配置
<!--设置超时时间为2秒,默认为1秒-->
<dubbo:provider timeout="2000"/>
@Service
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
try {
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
return "Hello,"+name+"!!!!!";
}
}
上述代码再超时时间为2s的情况下线程停了3秒。会报TimeOut的错误,即超时。
配置原则:
dubbo推荐在Provider上尽量多配置Consumer端属性:
- 作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数,等等
- 在Provider配置后,Consumer不配置则会使用Provider的配置值,即Provider配置可以作消费者的缺省值。
3.1.3 重试次数
当出现失败,自动切换并重试其它服务器,dubbo重试的缺省值是2次,我们可以自行设置
在provider提供方配置:
<!-- 消费方连接第1次不算,再来重试3次,总共重试4次 -->
<dubbo:provider timeout="2000" retries="3"/>
@Service
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
System.out.println("被调用 1 次");
try {
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
return "Hello,"+name+"!!!!!";
}
}
-
并不是所有的方法都适合设置重试次数
- 幂等方法:适合(当参数一样,无论执行多少次,结果是一样的,例如:查询,修改)
- 非幂等方法:不适合(当参数一样,执行结果不一样,例如:删除,添加)
-
单独设置某个方法
- 提供方接口添加sayNo()方法并实现
@Service
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
System.out.println("hello被调用一次");
try {
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
return "Hello,"+name+"!!!!!";
}
public String sayNo() {
System.out.println("no被调用一次");
return "no!";
}
}
- 消费方接口添加sayNo()方法声明
public interface HelloService {
String sayHello( String name );
String sayNo();
}
-
消费方controller
@Controller public class HelloAction { //@Reference 此注解已经在xml文件中被<dubbo:reference>顶替,所以自动注入即可 @Autowired private HelloService helloService; @GetMapping("hello") @ResponseBody public String sayHi(String name){ return helloService.sayHello(name); } @GetMapping("no") @ResponseBody public String no(){ return helloService.sayNo(); } }
- 消费方配置方法重试次数
<dubbo:reference interface="service.HelloService" id="helloService">
<dubbo:method name="sayHello" retries="3"/>
<dubbo:method name="sayNo" retries="0"/> <!-- 不重试 -->
</dubbo:reference>
3.1.4 多版本
一个接口,多个(版本的)实现类,可以使用定义版本的方式引入
为HelloService接口定义两个实现类,提供者修改配置:
<dubbo:service interface="service.HelloService"
class="service.impl.HelloServiceImpl01" version="1.0.0"/>
<dubbo:service interface="service.HelloService"
class="service.impl.HelloServiceImpl02" version="2.0.0"/>
消费者就可以根据version的版本,选择具体的服务版本
<dubbo:reference interface="service.HelloService" id="helloService"
version="2.0.0">
<dubbo:method name="sayHello" retries="3"/>
<dubbo:method name="sayNo" retries="0"/>
</dubbo:reference>
PS:消费者的控制层要改为自动注入,因为@Reference注解和 dubbo:reference在这里冲突
@Controller
public class HelloAction {
@Autowired
private HelloService helloService;
}
3.1.5 本地存根
目前我们的分布式架构搭建起来有一个严重的问题,就是所有的操作全都是 消费者发起,由服务提供者执行
先在消费者处理一些业务逻辑,再调用提供者的过程,就是“本地存根”
注意:必须使用构造方法的方式注入
public class HelloServiceStub implements HelloService {
private HelloService helloService;
// 注入HelloService
public HelloServiceStub(HelloService helloService) {
this.helloService = helloService;
}
public String sayHello(String name) {
System.out.println("本地存根数据验证。。。");
if(!StringUtils.isEmpty(name)){
return helloService.sayHello(name);
}
return "i am sorry!";
}
public String sayNo() {
return helloService.sayNo();
}
}
修改消费者配置:
<dubbo:reference interface="service.HelloService" id="helloService"
version="1.0.0" stub="service.impl.HelloServiceStub">
<dubbo:method name="sayHello" retries="3"/>
<dubbo:method name="sayNo" retries="0"/>
</dubbo:reference>
3.2 负载均衡策略
负载均衡(Load Balance), 其实就是将请求分摊到多个操作单元上进行执行,从而共同完成工作任务。
dubbo一共提供4种策略,缺省为 random 随机分配调用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZAJsNn6K-1617956834744)(Dubbo.assets/Dubbo详解-1617931957048.jpg)]
修改提供者配置并启动3个提供者,让消费者对其进行访问
tomcat端口8001,8002,8003
provider端口20881,20882,20883
<dubbo:provider timeout="2000" retries="3" port="20881"/>
HelloServiceImpl01类,服务器1,服务器2,服务器3
public String sayNo() {
System.out.println("----服务器1---1.0被调用一次-------");
return "no!";
}
此时,可启动consumer进行测试
<dubbo:reference loadbalance="roundrobin" interface="service.HelloService"
id="helloService" version="2.0.0" stub="stub.HelloServiceStub">
<dubbo:method name="sayHello" retries="3"/>
<dubbo:method name="sayNo" retries="0"/>
</dubbo:reference>
3.3 高可用
- zookeeper注册中心宕机,还可以消费dubbo暴露的服务
- 监控中心宕掉不影响使用,只是丢失部分采样数据
- 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
3.4 服务降级
服务降级,就是根据实际的情况和流量,对一些服务有策略的停止或换种简单的方式处理,从而释放服务器的资源来保证核心业务的正常运行
3.4.1 服务降级的原因
为什么要使用服务降级,这是防止分布式服务发生雪崩效应
蝴蝶效应,当一个请求发生超时,一直等待着服务响应,那么在高并发情况下,很多请求都是因为这样一直等着响应,直到服务资源耗尽产生宕机,而宕机之后会导致分布式其他服务调用该宕机的服务也会出现资源耗尽宕机,这样下去将导致整个分布式服务都瘫痪,这就是雪崩。
3.4.2 服务降级实现方式
-
在 管理控制台配置服务降级:屏蔽和容错
-
屏蔽:mock=force:return+null 表示消费方对该服务的方法调用都 直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
-
容错:mock=fail:return+null 表示消费方对该服务的方法调用在 失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
-
服务提供者无状态,任意一台宕掉后,不影响使用
-
服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
3.4 服务降级
服务降级,就是根据实际的情况和流量,对一些服务有策略的停止或换种简单的方式处理,从而释放服务器的资源来保证核心业务的正常运行
3.4.1 服务降级的原因
为什么要使用服务降级,这是防止分布式服务发生雪崩效应
蝴蝶效应,当一个请求发生超时,一直等待着服务响应,那么在高并发情况下,很多请求都是因为这样一直等着响应,直到服务资源耗尽产生宕机,而宕机之后会导致分布式其他服务调用该宕机的服务也会出现资源耗尽宕机,这样下去将导致整个分布式服务都瘫痪,这就是雪崩。
3.4.2 服务降级实现方式
- 在 管理控制台配置服务降级:屏蔽和容错
- 屏蔽:mock=force:return+null 表示消费方对该服务的方法调用都 直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
- 容错:mock=fail:return+null 表示消费方对该服务的方法调用在 失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。