1.背景知识
-
单一应用架构
简单的 ORM 单机的 ssm
-
垂直架构
将应用的功能独立拆分, 使用nginx 负载均衡代理多台tomcat
-
分布式架构
分布式架构,就是将应用按照功能模块进行拆分,模块块之间通过http,tcp 请求完成远程的访问和业务调用
RPC: 远程过程调用 ,底层通过 tcp (nio 非阻塞io 实现(netty) )
具体框架dubbo
soa: 面向服务的框架
springcloud : springcloud -netflix ,springcloud -alibaba
dubbo 相当于springcloud 中的feign
2.Dubbo
高性能Java RPC框架
Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:
面向接口的远程方法调用,----》feign
智能容错和负载均衡,----》sentinel,ribbon
以及服务自动注册和发现(使用zk,redis)。----->nacos
dubbo 架构体系
节点角色说明
节点 | 角色说明 |
---|---|
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Container | 服务运行容器 |
调用关系说明
-
服务容器负责启动,加载,运行服务提供者。
-
服务提供者在启动时,向注册中心注册自己提供的服务。
-
服务消费者在启动时,向注册中心订阅自己所需的服务。
-
注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
-
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
-
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
连通性
注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中
心交互,注册中心不转发请求,压力较小监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心
服务器,并以报表展示
服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用
时间到监控中心,此时间包含网络开销
健壮性
监控中心宕掉不影响使用,只是丢失部分采样数据
数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
注册中心对等集群,任意一台宕掉后,将自动切换到另一台
注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
服务提供者无状态,任意一台宕掉后,不影响使用
服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
升级性
当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分
布式服务架构不会带来阻力。
3.dubbo 例子
1.provider 服务提供者 提供查询 学生的服务
2.consumer 服务消费者 需要远程调用 查询学生的服务
1.创建一个父子工程
<!-- 父工程-->
<packaging>pom</packaging>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
2.创建common子工程
不用导入依赖。创建实体类Student
import java.io.Serializable;
/**
* 序列化
* 1.将对象保存在磁盘
* 2.便于对象 网络传输
*/
public class Student implements Serializable {
private Integer id;
private String name;
private Integer age;
private String school;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", school='" + school + '\'' +
'}';
}
}
创建service
public interface StudentService {
public Student findStudentById(Integer id);
}
接着一定要install打包
3.创建provider ,并引入依赖(注意导入common工程,因为需要用到实体类和服务)
<dependencies> <!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Dubbo 依赖 -->
<dependency>
<groupId>com.alibaba.spring.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.qw</groupId>
<artifactId>dubbo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- zookeeper依赖 -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
</dependencies>
创建配置文件application.properties
server.port=8088
#dubbo 服务名称
spring.dubbo.application.name=provider
# zookeeper 注册中心
spring.dubbo.registry.address=zookeeper://8.130.166.101:2182
#zookeeper 通信端口
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20881
启动类
@SpringBootApplication
//开启Dubbo
@EnableDubboConfiguration
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class,args);
}
}
提供数据服务(注意Service包,这里导的是dubbo的)
import com.alibaba.dubbo.config.annotation.Service;
import com.qf.entity.Student;
import com.qf.service.StudentService;
import org.springframework.stereotype.Component;
@Service
@Component
public class StudentServiceImpl implements StudentService {
@Override
public Student findStudentById(Integer id) {
//dao层查询
Student student = new Student();
student.setId(100);
student.setName("藏三");
student.setAge(21);
student.setSchool("和平路小学");
return student;
}
}
4.创建消费者comsumer
消费者的依赖 和配置 同provider ,没有任何区别,除了服务名
依赖和provider相同
<dependencies> <!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Test 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Dubbo 依赖 -->
<dependency>
<groupId>com.alibaba.spring.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.qw</groupId>
<artifactId>dubbo-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- zookeeper依赖 -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
</dependencies>
配置文件
server.port=8089
#dubbo 服务名称
spring.dubbo.application.name=consumer
# zookeeper 注册中心
spring.dubbo.registry.address=zookeeper://8.130.166.101:2182
#zookeeper 通信端口
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20881
启动类
@EnableDubboConfiguration// 激活dubbo 相关配置
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
}
远程调用provider 提供数据服务(注意reference的包)
import com.alibaba.dubbo.config.annotation.Reference;
import com.qf.entity.Student;
import com.qf.service.StudentService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StudentController {
@Reference()// @Reference是dubbo 中的注解,就是帮助我们伪装了 接口实现类,搬我们发起远程调用
private StudentService studnetService;
@RequestMapping("/findStudentById")
public Student findStudentById(int id) {
// 远程调用 provider 中的 StudentServiceImpl.findStudentById();
// 发起远程rpc 调用
Student student = studnetService.findStudentById(id);
System.out.println("student = " + student);
return student;
}
}
备注:一定要先启动provider 在启动consumer
4.dubbo 高级配置
consumer 配置
#对每个提供者的最大连接数,rmi 、 http 、hessian等 短连接 协议表示限制连接数,Dubbo等 长连接 协表示建立的长连接个数 默认0
spring.dubbo.customer.connections=10
#每服务消费者每服务每方法最大并发调用数 默认是0
spring.dubbo.customer.actives=20
provider配置
#重试次数
spring.dubbo.service.retries=2
#全局配置 负载均衡配置
spring.dubbo.service.loadbalance=random
#业务处理 线程池 大小 默认200
spring.dubbo.service.threads=5
#服务提供方最大可接受连接数 0表示不限制
spring.dubbo.service.acceptes=5
#服务提供者每服务每方法最大可并行执行请求数 0表示不限制
spring.dubbo.service.executes=5
check
用在@Reference的参数,启动时检查提供者是否存在,true报错,false忽略,默认为true,如果生产者不存在,消费者启动会报错
-
true:默认值,会去检查provider 是否有可用的
-
false:启动时不去检查 provider是否可用
/**
* check = false 启动时不去检查是否有可用 provider
*
*/
@Reference(check = false)// @Reference是dubbo 中的注解,就是帮助我们伪装了 接口实现类,搬我们发起远程调用
private StudentService studentService;
服务分组
当一个接口有多种实现时,可用使用group分组。
根据消费者启动时依赖的服务分组不同,会调用不同的服务
相当于分布式接口的 多态
服务提供者
/**
* // 注意service 是 com.alibaba.dubbo.config.annotation
*
* group = "g1" 当前服务提供者 属于g1分组 凡是想调用给服务的消费者必须属于 g1分组
*
*/
@Service(group = "g1")// 标记当前接口 可以被外部 通过rpc 远程访问
@Component // 加入到容器
public class StudentServiceImpl implements StudentService {
。。。。。。
}
/**
* // 注意service 是 com.alibaba.dubbo.config.annotation
*
* group = "g2" 当前服务提供者 属于g1分组 凡是想调用给服务的消费者必须属于 g2分组
*
*/
@Service(group = "g2")// 标记当前接口 可以被外部 通过rpc 远程访问
@Component // 加入到容器
public class StudentServiceImpl2 implements StudentService {
。。。
}
消费者
/**
* check = false 启动时不去检查是否有可用 provider
* group = "g2" 表明消费者属于g2 也只会调用g2 分组中的服务
*
*/
@Reference(check = false,group = "g2")// @Reference是dubbo 中的注解,就是帮助我们伪装了 接口实现类,搬我们发起远程调用
private StudentService studentService;
多版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
public Student findStudentById(int id) ----> public ResponseData<Student> findStudentById(int id)
provider
/**
* // 注意service 是 com.alibaba.dubbo.config.annotation
*
* group = "g1" 当前服务提供者 属于g1分组 凡是想调用给服务的消费者必须属于 g1分组
* version = "v1" version 一般用于接口的升级 ,解决兼容性问题
* 消费者调用 provider 是根据 group version 作为联合条件选择provider
*
*
*/
@Service(group = "g1",version = "v1")// 标记当前接口 可以被外部 通过rpc 远程访问
@Component // 加入到容器
public class StudentServiceImpl implements StudentService {
。。。。
}
消费者
/**
* check = false 启动时不去检查是否有可用 provider
* group = "g1" 表明消费者属于g1 也只会调用g1 分组中的服务
*
*/
@Reference(check = false,group = "g1",version = "v1")// @Reference是dubbo 中的注解,就是帮助我们伪装了 接口实现类,搬我们发起远程调用
private StudentService studentService;
延迟暴露
如果你的服务需要预热时间,比如初始化缓存,等待相关资源就位等,可以使用 delay 进行延迟暴露。我们在 Dubbo 2.6.5 版本中对服务延迟暴露逻辑进行了细微的调整,将需要延迟暴露(delay > 0)服务的倒计时动作推迟到了 Spring 初始化完成后进行。你在使用 Dubbo 的过程中,并不会感知到此变化,因此请放心使用。
延迟暴露 就是provider 原来启动就注册到注册中,消费者立即发现。现在由于某些原因,provider依赖的资源没有立即到位(初始化缓存),不能立即提供服务,此时就是需要延迟 一段时间,在注册到注册中心,消费者才可以调用
问题:
provider 需要依赖hbase ,hbase连接需要10多秒时间才能就位, 如果provider立即暴露,就会造成消费者报错,拿不到数据,此时需要延迟暴露
/**
* // 注意service 是 com.alibaba.dubbo.config.annotation
*
* group = "g1" 当前服务提供者 属于g1分组 凡是想调用给服务的消费者必须属于 g1分组
* version = "v1" version 一般用于接口的升级 ,解决兼容性问题
* 消费者调用 provider 是根据 group version 作为联合条件选择provider
*
* ,delay=5000 延迟暴露 5s 再被消费者发现使用
*
*
*/
@Service(group = "g1",version = "v1",delay=5000)// 标记当前接口 可以被外部 通过rpc 远程访问
@Component // 加入到容器
public class StudentServiceImpl implements StudentService {
。。。。。。
}
集群容错
Failover Cluster
失败自动切换,当出现失败,重试其它服务器 [1]。通常用于读操作,但重试会带来更长延迟。可通过 retries="2"
来设置重试次数(不含第一次)。
@Service(retries = 2)
@Reference(retries = 2)
<dubbo:service retries="2" />
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2"
来设置最大并行数。
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错 [2]。通常用于通知所有提供者更新缓存或日志等本地资源信息。
服务端配置
* retries = 2,cluster="failover" 配置重试策略,重试 2
* cluster="failfast" 快速失败
*
*
*/
@Service(group = "g1",version = "v1",delay=5000,cluster="failfast")// 标记当前接口 可以被外部 通过rpc 远程访问
@Component // 加入到容器
public class StudentServiceImpl implements StudentService {
。。。。
}
消费者配置
/**
* check = false 启动时不去检查是否有可用 provider
* group = "g1" 表明消费者属于g1 也只会调用g1 分组中的服务
*
* ,cluster = "failfast" 配置快速失败策略
*
*/
@Reference(check = false,group = "g1",version = "v1",cluster = "failfast")// @Reference是dubbo 中的注解,就是帮助我们伪装了 接口实现类,搬我们发起远程调用
private StudentService studentService;
负载均衡
在集群负载均衡时,Dubbo 提供了多种均衡策略。
随机 random
。还可以配置轮询roundrobin
、最不活跃优先 [4] leastactive
和一致性哈希 consistenthash
等
负载均衡的关键字就是:每种的粗略的第一个单词小写
-
random
-
roundrobin
-
leastactive
-
consistenthash
Random LoadBalance
-
随机,按权重设置随机概率。
-
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance
-
轮询,按公约后的权重设置轮询比率。
-
存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance
-
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
-
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
ConsistentHash LoadBalance
-
一致性 Hash,相同参数的请求总是发到同一提供者。
-
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
如何配置
1.全局配置
#全局配置 服务端负载均衡配置
spring.dubbo.service.loadbalance=random
2.通过注解配置单个接口
消费者端配置
/**
* check = false 启动时不去检查是否有可用 provider
* group = "g1" 表明消费者属于g1 也只会调用g1 分组中的服务
*
* ,cluster = "failfast" 配置快速失败策略
* loadbalance="random" 配置远程调用 随机策略
*/
@Reference(check = false,group = "g1",version = "v1",cluster = "failfast",loadbalance="random")// @Reference是dubbo 中的注解,就是帮助我们伪装了 接口实现类,搬我们发起远程调用
private StudentService studentService;
生产者配置(生产者 消费者 配其一 )
@Service(group = "g1",version = "v1",delay=5000,loadbalance = "random")
容错
容错,当服务提供者挂了,消费者依然可以返回兜底数据
dubbo 是通过mock 配置容错
1.在消费者 创建 容错实现类
public class StudentServiceMockImpl implements StudentService {
@Override
public Student findStudentById(Integer id) {
Student student = new Student();
student.setId(id);
student.setName("xxxx-From consumer---容错策略");
student.setAge(-1);
student.setSchool("兜底数据");
System.out.println("student = " + student);
return student ;
}
}
2.使用
@RestController
public class StudentController {
/**
* check = false 启动时不去检查是否有可用 provider
* group = "g1" 表明消费者属于g1 也只会调用g1 分组中的服务
*
* ,cluster = "failfast" 配置快速失败策略
* loadbalance="random" 配置远程调用 随机策略
*
* mock = "com.qfedu.service.impl.StudentServiceMockImpl" 配置客户端容错措施
*
*/
@Reference(check = false,mock = "com.qf.wrong.StudentServiceMockImpl")// @Reference是dubbo 中的注解,就是帮助我们伪装了 接口实现类,搬我们发起远程调用
private StudentService studnetService;
@RequestMapping("/findStudentById")
public Student findStudentById(int id) {
// 远程调用 provider 中的 StudentServiceImpl.findStudentById();
// 发起远程rpc 调用
Student student = studnetService.findStudentById(id);
System.out.println("student = " + student);
return student;
}
}