一、从单体到微服务架构演变
1.1单体架构
所有的应用程序都部署在一个单体的项目上。
image优点
- 小项目开发快 成本低
- 架构简单
- 易于测试
- 易于部署
缺点
- 大项目模块耦合严重 不易开发 维护 沟通成本高
- 新增业务困难
- 核心业务与边缘业务混合在一块,出现问题互相影响
1.2垂直架构
根据业务把项目垂直切割成多个项目,因此这种架构称之为垂直架构。
为了避免上面提到的那些问题,将模块的垂直划分,做垂直划分的原则是基于拉勾的业务特性,核心目标,第一个是为了业务之间互不影响,第二个是在研发团队的壮大后为了提高效率,减少之间的依赖。
image优点
- 系统拆分实现了流量分担,解决了并发问题
- 可以针对不同系统进行优化
- 方便水平扩展,负载均衡,容错率提高
- 系统间相互独立,互不影响,新的业务迭代时更加高效
缺点
- 服务系统之间接口调用硬编码
- 搭建集群之后,实现负载均衡比较复杂
- 服务系统接口调用监控不到位 调用方式不统一
- 服务监控不到位
- 数据库资源浪费,充斥慢查询,主从同步延迟大
1.3分布式架构(SOA)
SOA全称为Service Oriented Architecture,即面向服务的架构 。它是在垂直划分的基础上,将每个项目拆分出多个具备松耦合的服务,一个服务通常以独立的形式存在于操作系统进程中。各个服务之间通过网络调用,这使得构建在各种各样的系统中的服务可以 以一种统一和通用的方式进行交互。
在做了垂直划分以后,模块随之增多,系统之间的RPC逐渐增多,维护的成本也越来越高,一些通用的业务和模块重复的也越来越多,这个时候上面提到的接口协议不统一、服务无法监控、服务的负载均衡等问题更加突出,为了解决上面的这些问题,我们将通用的业务逻辑下沉到服务层,通过接口暴露,供其他业务场景调用。同时引入了阿里巴巴开源的Dubbo,一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
image说明:
分层:按照业务性质分层 每一层要求简单 和 容易维护
应用层:距离用户最近的一层 也称之为接入层使用tomcat 作为web容器接收用户请求使用下游的dubbo供的接口来返回数据 并且该层禁止访问数据库业务服务层:根据具体的业务场景 演变而来的模块 比如 简历投递 职位搜索 职位推荐等
基础业务层:拉勾网招聘业务的核心 账号 简历 公司 职位
基础服务层:这一层 是与业务无关的模块 是一些通用的服务。这类服务的特点:请求量大 逻辑简单 特性明显 功能独立 消息服务(发邮件 短信 微信)附件解析 50% 自己上传附件简历 需要解析成pdf
存储层:不同的存储类型 Mysql Mongodb ES fastDFS
分级:按照业务性质分层同一层的业务也要做好分级依据业务的重要性进行分级。按照二八定律网站80%的流量都在核心功能上面要优先保证核心业务的稳定。
隔离:不同性质不同重要性的业务做好隔离 包括 业务 缓存 DB 中间件 都要做好隔离 比如 核心业务的数据库 要和活动相关的数据库隔离。
调用:总体上调用要单向 可以跨层调用 但不能出现逆向调用。
优点
- 服务以接口为粒度,为开发者屏蔽远程调用底层细节 使用Dubbo 面向接口远程方法调用屏蔽了底层调用细节
- 业务分层以后架构更加清晰 并且每个业务模块职责单一 扩展性更强
- 数据隔离,权限回收,数据访问都通过接口 让系统更加稳定 安全
- 服务应用本身无状态化 这里的无状态化指的是应用本身不做内存级缓存 而是把数据存入db
- 服务责任易确定 每个服务可以确定责任人 这样更容易保证服务质量和稳定
缺点
- 粒度控制复杂 如果没有控制好服务的粒度,服务的模块就会越来越多 就会引发超时分布式事务等问题
- 服务接口数量不宜控制 容易引发接口爆炸 所以服务接口建议以业务场景进行单位划分 并对相近的业务做抽象 防止接口爆炸
- 版本升级兼容困难 尽量不要删除方法 字段 枚举类型的新增字段也可能不兼容
- 调用链路长 服务质量不可监控 调用链路变长 下游抖动可能会影响到上游业务,最终形成连锁反应 服务质量不稳定 同时链路的变成使得服务质量的监控变得困难
1.4微服务架构
微服务架构是一种将单个应用程序 作为一套小型服务开发的方法,每种应用程序都在其自己的进程中独立运行,并使用轻量级机制(通常是HTTP资源的API)进行通信。这些服务是围绕业务功能构建的,可以通过全自动部署机制进行独立部署。这些服务的集中化管理非常少,它们可以用不同的编程语言编写,并使用不同的数据存储技术。
微服务是在SOA上做的升华, 粒度更加细致,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”。
1.5架构相关概念
1、相关术语
系统容量与系统容量预估
系统容量指系统所能承受的最大访问量,而系统容量预估则是在峰值流量到达之前系统架构师所给出的若干技术指标值。
QPS
QPS,Query Per Second,每秒查询量。在分布式系统中 QPS 的定义是,单个进程每秒请求服务器的成功次数。
QPS = 总请求数/进程总数/请求时间 = 总请求数/(进程总数*请求时间)
UV
Unique Visitor,独立访客数量,指一定时间范围内站点访问所来自的 IP 数量。同一 IP多次访问站点只计算一次。
PV
Page View,页面访问量,指一定时间范围内打开或刷新页面的次数。
2、系统容量预估基本计算
带宽计算
带宽的计算公式为:
| 平均带宽 = 总流量数(bit) / 产生这些流量的时长(秒)=(PV * 页面平均大小 * 8) / 统计时间(秒)带宽需求(峰值带宽) = 平均带宽 * 峰值因子 |
| 例子:日均 PV 10w,页面平均大小 0.4M,则平均带宽 =( 10w * 0.4M * 8) / (60 * 60 *24) |
并发量计算
并发量,也称为并发连接数,一般是指单台服务器每秒处理的连接数。平均并发连接数的计算公式是:
| 平均并发连接数 = (站点 PV * 页面平均衍生连接数) / (统计时间 * web 服务器数量) |
日均 PV 50w,页面平均衍生连接数是 30,服务器数量为 5 台。
平均并发连接数 = (10w * 30)/(606024 * 5)
峰值并发量 = 平均并发连接数 * 峰值因子
服务器预估量
根据往年同期活动获得的日均 PV、并发量、页面衍生连接数,及公司业务扩展所带来的流量增涨率,就可以计算出服务器预估值。
| 服务器预估值 = 站点每秒处理的总连接数 / 单机并发连接数 = (PV * 页面衍生连接数*(1 + 增涨率)) / 统计时间 /单机并发连接数 |
二、Dubbo入门
2.1Dubbo概述
1、简介
Apache Dubbo是一款高性能的Java RPC框架。其前身是阿里巴巴公司开源的一个高性能、轻量级的开源Java RPC框架,可以和Spring框架无缝集成。官网地址:https://dubbo.apache.org/zh/
核心功能:面向接口代理的高性能RPC调用、智能负载均衡、服务自动注册与发现。
Dubbo版本与Spring版本说明:Dubbo 的使用是基于 Spring 环境下的,即 Dubbo 是依赖于 Spring 框架的。Dubbo2.7.0依赖的Spring是4.3.16。所以,在Dubbo的开发过程中最好使用与该Spring版本相同的Spring,这样可以避免可能的版本冲突问题。
2、Dubbo 的服务治理
服务治理(SOA governance),企业为了确保项目顺利完成而实施的过程,包括最佳实践、架构原则、治理规程、规律以及其他决定性的因素。服务治理指的是用来管理SOA的采用和实现的过程。
3、Dubbo架构
image说明:
- 虚线 代表异步调用 实线代表同步访问
- 蓝色虚线 是在启动时完成的功能
- 红色虚线 是程序运行中执行的功能
角色说明:
| 节点 | 说明 | 作用 |
| Provider | 暴露服务的服务提供方 | 服务提供者在启动时,向注册中心注册自己提供的服务。 |
| Consumer | 调用远程服务的服务消费方 | 服务消费者在启动时,向注册中心订阅自己所需的服务。服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 |
| Registry | 服务注册与发现的注册中心 | 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 |
| Monitor | 统计服务的调用次数和调用时间的监控中心 | 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 |
| Container | 服务运行容器 | 服务容器负责启动,加载,运行服务提供者。 |
服务注册中心Zookeeper
通过前面的Dubbo架构图可以看到,Registry(服务注册中心)在其中起着至关重要的作用。Dubbo官方推荐使用Zookeeper作为服务注册中心。Zookeeper是 Apache Hadoop 的子项目,作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并推荐使用。
2.2入门Demo
POM
<properties>
<dubbo.version>2.7.5</dubbo.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-dubbo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-netty4</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-hessian2</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 日志配置 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<!-- json数据化转换 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
API
提供端和消费端都依赖API
public interface HelloService {
String sayHello(String name);
}
生产者(Provider)
import org.apache.dubbo.config.annotation.Service;
@Service
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello " + name;
}
}
启动类:
@Configuration
@PropertySource("classpath:/dubbo-provider.properties")
//启动dubbo模块并开启组件扫描
@EnableDubbo(scanBasePackages = "com.dubbo.demo. provider.service.impl")
public class ProviderBootstrap {
//启动服务
public static void main(String[] args) throws IOException {
//启动容器
AnnotationConfigApplicationContext context = new
AnnotationConfigApplicationContext(ProviderBootstrap.class);
context.start();
//阻塞
System.in.read();
}
}
配置文件
dubbo.application.name=service-provider
dubbo.protocol.name=dubbo
dubbo.registry.address=zookeeper://192.168.186.10:2181?timeout=60000
消费者(Consumer)
import org.apache.dubbo.config.annotation.Reference;
@Component
public class HelloHandler {
@Reference
private HelloService helloService;
public String sayHello(String name){
return helloService.sayHello(name);
}
}
启动类
@Configuration
@EnableDubbo
@ComponentScan("com.dubbo.demo.consumer.handler")
@PropertySource("classpath:/dubbo-consumer.properties")
public class ConsumerBootstrap {
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerBootstrap.class);
HelloHandler handler = context.getBean(HelloHandler.class);
while (true){
Scanner sc = new Scanner(System.in);
String text = sc.nextLine();
String dubbo = handler.sayHello(text);
System.out.println(dubbo);
}
}
}
配置文件
dubbo.application.name=service-consumer
dubbo.registry.address=zookeeper://192.168.186.10:2181?timeout=60000
2.3配置说明
1、application配置说明
dubbo:application:对应 org.apache.dubbo.config.ApplicationConfig, 代表当前应用的信息
1. name: 当前应用程序的名称,在dubbo-admin中我们也可以看到,这个代表这个应用名称。我们在真正时是时也会根据这个参数来进行聚合应用请求。
2. owner: 当前应用程序的负责人,可以通过这个负责人找到其相关的应用列表,用于快速定位到责任人。
3. qosEnable : 是否启动QoS 默认true
4. qosPort : 启动QoS绑定的端口 默认22222【运维相关的】
5. qosAcceptForeignIp: 是否允许远程访问 默认是false
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="service-provider2" owner="kang">
<dubbo:parameter key="qos.enable" value="true"/>
<dubbo:parameter key="qos.port" value="33333"/>
</dubbo:application>
官方文档说明:https://dubbo.apache.org/zh/docs/v2.7/user/configuration/properties/
2、dubbo:registry配置说明
org.apache.dubbo.config.RegistryConfig, 代表该模块所使用的注册中心。一个模块中的服务可以将其注册到多个注册中心上(如果使用多个注册中心通过id来区分),也可以注册到一个上。后面再service和reference也会引入这个注册中心。
- id :当前服务中provider或者consumer中存在多个注册中心时,则使用需要增加该配置。
2. address : 当前注册中心的访问地址。
3. protocol : 当前注册中心所使用的协议是什么。也可以直接在 address 中写入,比如使用zookeeper,就可以写成 zookeeper://xx.xx.xx.xx:2181
4. timeout : 当与注册中心不再同一个机房时,大多会把该参数延长
3、dubbo:protocol协议配置说明
org.apache.dubbo.config.ProtocolConfig, 指定服务在进行数据传输所使用的协议。
1. id : 选择使用不同的协议进行交互。这里在多个协议使用时,需要指定。
2. name : 指定协议名称。默认使用 dubbo 。
各种协议说明
| 协议名称 | 连接个数 | 连接方式 | 传输协议 | 传输方式 | 使用范围 |
| dubbo | 单连接 | 长连接 | TCP | NIO异步传输 | 传入传出参数数据包较小(建议小于 100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用 dubbo 协议传输大文件或超大字符串。 |
| rmi | 多连接 | 短连接 | TCP | BIO 同步传输 | 传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件。JDK 标准的 java.rmi.* 实现 |
| hession | 多连接 | 短连接 | HTTP | BIO 同步传输 | 传入传出参数数据包较大,提供者比消费者个数多,提供者抗压能力较大,可传文件 |
| http | 多连接 | 短连接 | HTTP | BIO 同步传输 | 传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或 URL 传入参数,暂不支持传文件。 |
| webService | 多连接 | 短连接 | HTTP | BIO 同步传输 | 系统集成,跨语言调用。 |
| thrift | 多连接 | 短链接 | TCP | NIO同步传输 | Thrift 是 Facebook 捐给 Apache 的一个 RPC 框架,其消息传递采用的协议即为 thrift协议。当前 dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展。Thrift 协议不支持 null值的传递。 |
| rest | | | | | 若需要开发具有 RESTful 风格的服务,则需要使用该协议。 |
总结:小数据大并发使用 Dubbo,大数据小并发使用其它。
注意:Dubbo中同一个服务中可以配置不同的协议,例子:
<dubbo:protocol threadpool="dubbo" port="20880"/>
<dubbo:protocol threadpool="rmi" port="1099"/>
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.dubbo.demo.api.HelloService" ref="helloService" protocol="dubbo,rmi"/>
这里需要理解这个服务暴露协议的意义。其是指出,消费者若要连接当前的服务,就需要通过这里指定的协议及端口号进行访问。这里的端口号可以是任意的,不一定非要使用默认的端口号。这里指定的协议名称及端口号,在当前服务注册到注册中心时会一并写入到服务映射表中。当消费者根据服务名称查找到相应主机时,其同时会查询出消费此服务的协议、端口号等信息。其底层就是一个 Socket 编程,通过主机名与端口号进行连接。
4、dubbo:service配置说明
org.apache.dubbo.config.ServiceConfig, 用于指定当前需要对外暴露的服务信息,后面也会具体讲解。和 dubbo:reference 大致相同。
1. interface : 指定当前需要进行对外暴露的接口是什么。
2. ref : 具体实现对象的引用,一般我们在生产级别都是使用Spring去进行Bean托管的,所以这里面一般也指的是Spring中的BeanId。
3. version : 对外暴露的版本号。不同的版本号,消费者在消费的时候只会根据固定的版本号进行消
费。
5、dubbo:reference配置说明
org.apache.dubbo.config.ReferenceConfig, 消费者的配置。
1.id : 指定该Bean在注册到Spring中的id。
2.interface: 服务接口名
3.version : 指定当前服务版本,与服务提供者的版本一致。
4.registry : 指定所具体使用的注册中心地址。这里面也就是使用上面在 dubbo:registry 中所声
明的id。
6、dubbo:method
org.apache.dubbo.config.MethodConfig, 用于在制定的 dubbo:service 或者 dubbo:reference 中的更具体一个层级,指定具体方法级别在进行RPC操作时候的配置,可以理解为对这上面层级中的配置针对于具体方法的特殊处理。
1. name : 指定方法名称,用于对这个方法名称的RPC调用进行特殊配置。
2. async: 是否异步 默认false
7、dubbo:service和dubbo:reference详解(消费者)
| 属性 | 说明 | 注解实现方式 | Xml实现方式 |
| mock | 用于在方法调用出现错误时,当做服务降级来统一对外返回结果。【相当于服务降级】注意:需要提供降级实现方法。 | @Reference(mock = "true") | <dubbo:referenceid="helloService" interface="com.dubbo.demo.api.HelloService" mock="true"/> 或者<dubbo:consumer mock="true" />说明:同时存在的话以dubbo:consumer标签配置的为主。 |
| timeout | 配置接口调用超时时间 | @Reference(timeout = 2000) | <dubbo:reference id="helloService" interface="com.dubbo.demo. api.HelloService" timeout="2000"/>或者<dubbo:consumer mock="true" timeout="2000"/>说明:同时存在的话以dubbo:consumer标签配置的为主。 |
| check | 消费者在启动时候是否校验provider已经注册好,我们一般设置为false防止循环依赖。 | @Reference(check = false) | <dubbo:reference id="helloService" interface="com.dubbo.demo.api.HelloService" check="false"/>或者<dubbo:consumer mock="true" timeout="2000" check="false"/>说明:同时存在的话以dubbo:consumer标签配置的为主 |
| retries | 接口调用重试次数 | @Reference(retries = 3) | <dubbo:reference id="helloService" interface="com.dubbo.demo. api.HelloService" retries="3"/>或者<dubbo:consumer retries="3"/>说明:同时存在的话以dubbo:consumer标签配置的为主;重试必须要保证接口的幂等性。 |
接口讲解类
public class HelloServiceMock implements HelloService {
@Override
public String sayHello(String name) {
System.out.println("降级接口");
return null;
}
}
说明:这个降级接口必须要和HelloService接口在同一个包下边。
2.4Dubbo管理控制台
1、简介
Dubbo管理控制台主要包含:服务管理 、路由规则、动态配置、服务降级、访问控制、权重调整、负载均衡等管理功能。我们在开发时,需要知道Zookeeper注册中心都注册了哪些服务,有哪些消费者来消费这些服务。我们可以通过部署一个管理中心来实现。其实管理中心就是一个web应用,原来是war(2.6版本以前)包需
要部署到tomcat即可。现在是jar包可以直接通过java命令运行。
2、部署Dubbo管理控制台
- 从git 上下载项目 https://github.com/apache/dubbo-admin
- 修改项目下的dubbo.properties文件。注意dubbo.registry.address对应的值需要对应当前使用的Zookeeper的ip地址和端口号
- dubbo.registry.address=zookeeper://zk所在机器ip:zk端口
- dubbo.admin.root.password=root
- dubbo.admin.guest.password=root
- 切换到项目所在的路径 使用mvn 打包 mvn clean package -Dmaven.test.skip=true
- java 命令运行 java -jar 对应的jar包
三、Dubbo高级使用
3.1SPI技术
1、JDK原生的SPI
简介
SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。 很多框架用它来做服务的扩展发现,简单来说,它就是一种动态替换发现的机制。使用SPI机制的优势是实现解耦,使得第三方服务模块的装配控制逻辑与调用者的业务代码分离。
说明
[图片上传失败...(image-288cce-1612863631159)]
Java中如果想要使用SPI功能,先提供标准服务接口,然后再提供相关接口实现和调用者。这样就可以通
过SPI机制中约定好的信息进行查询相应的接口实现。
SPI约定
1、当服务提供者提供了接口的一种具体实现后,在META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
2、接口实现类所在的jar包放在主程序的classpath中;
3、主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
4、SPI的实现类必须携带一个无参构造方法;
Demo
API:定义一个标准的接口。
public interface HelloService {
String sayHello();
}
Provider:提供一些接口的实现类
public class CatHelloService implements HelloService {
@Override
public String sayHello() {
return "喵喵~";
}
}
public class DogHelloService implements HelloService {
@Override
public String sayHello() {
return "汪汪~";
}
}
在provider中配置
META-INF/services
文件名:com.advince.demo. api.service.HelloService
文件内容:
com.advince.demo.provider.impl.CatHelloService
com.advince.demo.provider.impl.DogHelloService
测试类:
public class TestDemo {
public static void main(String[] args) {
ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class);
helloServices.forEach(helloService -> {
System.out.println(helloService.getClass().getName() + "::" + helloService.sayHello());
});
}
}
2、Dubbo中的SPI
Dubbo的SPI说明
Dubbo中大量的使用了SPI来作为扩展点,通过实现同一接口的前提下,可以进行定制自己的实现类。比如比较常见的协议,负载均衡,都可以通过SPI的方式进行定制化,自己扩展。Dubbo中已经存在的所有已经实现好的扩展点。
说明,Dubbo中没有使用JDK原生的SPI技术来进行加载,而是自己实现了一套SPI机制。那么为啥Dubbo要自己实现一套SPI呢?
1. JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
2. 如果有扩展点加载失败,则所有扩展点无法使用。
3. 提供了对扩展点包装的功能(Adaptive),并且还支持通过set的方式对其他的扩展点进行注入。
Dubbo中SPI实现Demo
POM
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.5</version>
</dependency>
API
@SPI
public interface DubboHelloService {
String sayHello();
}
Provider
提供者就是正常的实现类
配置文件
路径:META-INF/dubo
文件名:接口的全路径名比如:com.advince.demoapi.DubboHelloService
内容:
dog=com.advince.demo._02dubboSPIDemo.provider.DogHelloService
cat=com.advince.demo._02dubboSPIDemo.provider.CatHelloService
Consumer:
public class DubboSpiTest {
public static void main(String[] args) {
ExtensionLoader<DubboHelloService> extensionLoader = ExtensionLoader.getExtensionLoader(DubboHelloService.class);
//遍历所有的支持的扩展点 META-INF.dubbo
Set<String> extensions = extensionLoader.getSupportedExtensions();
for (String extension : extensions) {
DubboHelloService helloService = extensionLoader.getExtension(extension);
System.out.println(helloService.sayHello());
}
}
}
3、Dubbo SPI中Adaptive功能
Dubbo中的Adaptive功能,动态的选择具体的扩展点。通过getAdaptiveExtension 统一对指定接口对应的所有扩展点进行封装,通过URL的方式对扩展点来进行动态选择。 (dubbo中所有的注册信息都是通过URL的形式进行处理的。)
Demo
API
@SPI //@SPI//("dog") 来指定默认的实现方式
public interface DubboHelloService {
@Adaptive
String sayHello(URL url);
}
Consumer
public class DubboSpiTest {
public static void main(String[] args) {
//如果是测试的话这前边的地址随便写但是?后边的需要写接口名=配置文件中的key
URL url = URL.valueOf("test://localhost/hello?dubbo.hello.service=cat");
DubboHelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(DubboHelloService.class).getAdaptiveExtension();
String msg = adaptiveExtension.sayHello(url);
System.out.println(msg);
}
}
3.2Dubbo Filter过滤器
1、简介
Dubbo的Filter机制,是专门为服务提供方和服务消费方调用过程进行拦截设计的,每次远程方法执
行,该拦截都会被执行。这样就为开发者提供了非常方便的扩展性,比如为dubbo接口实现ip白名单功
能、监控功能 、日志记录等。
Dubbo的Filter机制是用的是Dubbo的SPI机制进行动态加载的,扩展性非常好
2、Demo
POM
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.5</version>
</dependency>
过滤器
@Activate(group = {CommonConstants.CONSUMER, CommonConstants.PROVIDER})
public class InvokeFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
long start = System.currentTimeMillis();
try {
return invoker.invoke(invocation);
} finally {
System.out.println("耗时:" + (System.currentTimeMillis() - start));
}
}
}
SPI配置
org.apache.dubbo.rpc.Filter
timeFilter=com.advince.demo.filter.InvokeFilter
如果那个服务使用的话直接引入这个filter的依赖即可。
3.3负载均衡
1、简介
Dubbo 提供了多种均衡策略(包括随机、轮询、最少活跃调用数、一致性Hash),缺省为random随机调用。官网地址https://dubbo.apache.org/zh/docs/v2.7/user/examples/loadbalance/#负载均衡策略
2、使用
| //在服务消费者一方配置负载均衡策略 @Reference(check = false,loadbalance = "random")注意:使用注解的话是接口级别的xml可以控制到方法级别对的 | 配置的值random 【随机】(存在消息堆积)roundrobin【轮询】leastactive【最小负载】(存在消息堆积)consistenthash【一致性hash】(存在消息堆积) |
3、自定义负载均衡
需求:调用ip值最小的服务器如果相等调用端口最小的。
public class MyLoadBalance implements LoadBalance {
@Override
public <T> Invoker<T> select(List<Invoker<T>> list, URL url, Invocation invocation) throws RpcException {
return list.stream().sorted((i1,i2)->{
int i = i1.getUrl().getIp().compareTo(i2.getUrl().getIp());
if (i == 0) {
return Integer.compare(i1.getUrl().getPort(),i2.getUrl().getPort());
}
return i;
}).findFirst().get();
}
}
配置
org.apache.dubbo.rpc.cluster.LoadBalance
myLoadBalance=com.advince.demo.loadbalance.MyLoadBalance
使用:
使用的话直接引入自定义负载均衡的pom
@Reference(check = false,loadbalance = "myLoadBalance ")
3.4Dubbo异步调用
Dubbo提供了异步调用的方式应用于提供者接口响应耗时明显,消费者端可以利用调用接口的时间去做一些其他的接口调用,利用 Future 模式来异步等待和获取结果即可。这种方式可以大大的提升消费者端的利用率。 目前这种方式可以通过XML的方式进行引入。
使用
配置:
<dubbo:reference id="helloService" interface="com.dubbo.demo.HelloService">
<dubbo:method name="sayHello" async="true" /><!--针对方法级别的-->
</dubbo:reference>
Code:
使用future模式来进行调用。
//调用业务方法
String hello = service.sayHello("world");
// 利用Future 模式来获取
Future<Object> future = RpcContext.getContext().getFuture();
System.out.println("result :" + hello);
System.out.println("future result:"+future.get());
注意
需要特别说明的是,该方式的使用,请确保dubbo的版本在2.5.4及以后的版本使用。 原因在于在2.5.3
及之前的版本使用的时候,会出现异步状态传递问题。比如我们的服务调用关系是 A -> B -> C , 这时候如果A向B发起了异步请求,在错误的版本时,B向C发起的请求也会连带的产生异步请求。这是因为在底层实现层面,他是通过 RPCContext 中的attachment 实现的。在A向B发起异步请求时,会在 attachment 中增加一个异步标示字段来表明异步等待结果。B在接受到A中的请求时,会通过该字段来判断是否是异步处理。但是由于值传递问题,B向C发起时同样会将该值进行传递,导致C误以为需要异步结果,导致返回空。这个问题在2.5.4及以后的版本进行了修正。
3.5Dubbo线程池
1、已有的线程池
dubbo在使用时,在provider中都是通过创建真实的业务线程池进行操作的。目前已知的线程池模型有两个和java中的相互对应:
- fix: 表示创建固定大小的线程池。也是Dubbo默认的使用方式,默认创建的执行线程数为200,并且是没有任何等待队列的。所以再极端的情况下可能会存在问题,比如某个操作大量执行时,可能存在堵塞的情况。后面也会讲相关的处理办法。
- cache: 创建非固定大小的线程池,当线程不足时,会自动创建新的线程。但是使用这种的时候需要注意,如果突然有高TPS的请求过来,方法没有及时完成,则会造成大量的线程创建,对系统的CPU和负载都是压力,执行越多反而会拖慢整个系统。
2、自定义线程池
在真实的使用过程中可能会因为使用fix模式的线程池,导致具体某些业务场景因为线程池中的线程数量不足而产生错误,而很多业务研发是对这些无感知的,只有当出现错误的时候才会去查看告警或者通过客户反馈出现严重的问题才去查看,结果发现是线程池满了。所以可以在创建线程池的时,通过某些手段对这个线程池进行监控,这样就可以进行及时的扩缩容机器或者告警。下面的这个程序就是这样子的,会在创建线程池后进行对其监控,并且及时作出相应处理。
自定义线程池需求:线程池实现, 这里主要是基于对 FixedThreadPool 中的实现做扩展出线程监控的部分
Demo
POM
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>2.7.5</version>
</dependency>
<!-- 日志配置 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
Code
public class MyThreadPool extends FixedThreadPool implements Runnable {
//自定义log监听机制
private static final Logger LOGGER = LoggerFactory.getLogger(MyThreadPool.class);
//指定阈值 当达到0.9的时候会报警
private static final double ALARM_PERCENT = 0.90;
//用来装当前执行的线程数量
private final Map<URL, ThreadPoolExecutor> THREAD_POOLS = new ConcurrentHashMap<>();
public MyThreadPool() {
// 每隔3秒打印线程使用情况
Executors.newSingleThreadScheduledExecutor()
.scheduleWithFixedDelay(this, 1, 3, TimeUnit.SECONDS);
}
@Override
public Executor getExecutor(URL url) {
// 从父类中创建线程池
final Executor executor = super.getExecutor(url);
//放入到当前的map中
if (executor instanceof ThreadPoolExecutor) {
THREAD_POOLS.put(url, ((ThreadPoolExecutor) executor));
}
return executor;
}
@Override
public void run() {
for (Map.Entry<URL, ThreadPoolExecutor> entry : THREAD_POOLS.entrySet()) {
final URL url = entry.getKey();
final ThreadPoolExecutor executor = entry.getValue();
// 当前执行中的线程数
final int activeCount = executor.getActiveCount();
// 总计线程数
final int poolSize = executor.getCorePoolSize();
double used = (double) activeCount / poolSize * (1.0);
LOGGER.info("线程池执行状态:[{}/{}]:{}%", activeCount, poolSize, (used * 100));
if (used >= ALARM_PERCENT) {
LOGGER.error("超出警戒值!host:{}, 当前已使用量:{}%, URL:{}", url.getIp(), (used * 100), url);
}
}
}
}
配置文件
文件名:org.apache.dubbo.common.threadpool.ThreadPool
内容:myThreadpool=com.advince.demo.MyThreadPool
使用
引入这个pom之后进行配置
| Xml配置 | properties配置 |
| <dubbo:protocol threadpool="myThreadpool"/> | dubbo.provider.threadpool= myThreadpool |
3.6Dubbo路由
1、简介
Dubbo在默认情况下会利用负载均衡将流量打到对应的provider上,但是我们在针对某一台服务器调试的时候需要将流量专门达到这个provider,Dubbo提供的路由功能就是可以限制consumer和provider,将流量打到我们指定的服务器上。
路由是决定一次请求中需要发往目标机器的重要判断,通过对其控制可以决定请求的目标机器。我们可以通过创建这样的规则来决定一个请求会交给哪些服务器去处理。
2、Demo
public static void main(String[] args) {
//通过rmi获取到注册在红心
RegistryFactory registryFactory = ExtensionLoader.
getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
//获取到注册中心
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://127.0.0.1:2181"));
//编写自己的路由规则
registry.register(
URL.valueOf("condition://0.0.0.0/com.dubbo.service.HelloService ?category=routers&force=true&dynamic=true&rule="
//providerIP
+ URL.encode(" =>host!=127.0.0.1")));
}
3、URL规则说明
我们实际本质上就是通过在zookeeper中保存一个节点数据,来记录路由规则。消费者会通过监听这个服务的路径,来感知整个服务的路由规则配置,然后进行适配。这里主要介绍路由配置的参数。官网文档说明。
- route:// 【condition】 表示路由规则的类型,支持条件路由规则和脚本路由规则,可扩展,必填。
- 0.0.0.0表示对所有 IP 地址生效,如果只想对某个 IP 的生效,请填入具体 IP,必填。
- com.demo.service.HelloService 表示只对指定服务生效,必填。
- category=routers 表示该数据为动态配置类型,必填。
- dynamic : 是否为持久数据,当指定服务重启时是否继续生效。必填。
- runtime : 是否在设置规则时自动缓存规则,如果设置为true则会影响部分性能。
- rule : 是整个路由最关键的配置,用于配置路由规则。
- ... => ... 在这里 => 前面的就是表示消费者方的匹配规则,可以不填(代表全部)。 => 后方则必
须填写,表示当请求过来时,如果选择提供者的配置。host 参数是根据ip进行过滤。 必填
3.7路由应用
1、需求说明
利用路由的功能来提供对上线的服务进行保护,比如有三个provider,一台重启的话流量就无法打到这台服务器上。
2、思路分析
1.利用zookeeper的路径感知能力,在服务准备进行重启之前将当前机器的IP地址和应用名写入zookeeper。
2.服务消费者监听该目录,读取其中需要进行关闭的应用名和机器IP列表并且保存到内存中。
3.当前请求过来时,判断是否是请求该应用,如果是请求重启应用,则将该提供者从服务列表中移除。
3、代码实现
POM
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.10.0</version>
</dependency>
zkClient与路径管理器
public class ZKClient {
public static CuratorFramework client = null;
private static ZKClient instance = new ZKClient();
static{
client = CuratorFrameworkFactory.newClient("127.0.0.1:2181",
new ExponentialBackoffRetry(1000,3));
client.start();
}
public static CuratorFramework getZkClient(){
return client;
}
private ZKClient() {
}
}
public class ReadyRestartInstanceManager implements PathChildrenCacheListener {
//日志管理
public static final Logger LOGGER = LoggerFactory.getLogger(ReadyRestartInstanceManager.class);
private static final CuratorFramework zkClient = ZKClient.getZkClient();
//存储的路径
private static final String READY_INSTANCE_PATH = "/ready/instances";
//存储的机器集合
private volatile Set<String> restartInstances = new HashSet<>();
//创建实例
public static ReadyRestartInstanceManager create(){
//初始化监控路径
initMonitorPath();
final ReadyRestartInstanceManager instances = new ReadyRestartInstanceManager();
//创建监听
createListener(instances);
return instances;
}
/**
* 创建监听
*/
private static void createListener(ReadyRestartInstanceManager instances) {
// 创建一个NodeCache
PathChildrenCache nodeCache = new PathChildrenCache(zkClient, READY_INSTANCE_PATH, false);
// 给节点缓存对象 加入监听
nodeCache.getListenable().addListener(instances);
try {
nodeCache.start();
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("启动路径监听失败");
}
}
/**
* 初始化监控路径
*/
private static void initMonitorPath() {
try {
final Stat stat = zkClient.checkExists().forPath(READY_INSTANCE_PATH);
if (stat == null){
zkClient.create().creatingParentsIfNeeded().forPath(READY_INSTANCE_PATH);
}
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("确保基础路径存在");
}
}
//监听
@Override
public void childEvent(CuratorFramework zkClient, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
// 查询出监听路径下 所有的目录配置信息
List<String> instances = zkClient.getChildren().forPath(READY_INSTANCE_PATH);
restartInstances.addAll(instances);
}
/**
* 添加注册实例方法
*/
public void addRegistryInstance(String applicationName,String ip) throws Exception {
zkClient.create().creatingParentsIfNeeded()
.forPath(READY_INSTANCE_PATH + "/" + buildPath(applicationName, ip));
}
private String buildPath(String applicationName, String ip) {
return applicationName + "_" + ip;
}
/**
* 删除重启实例的配置信息方法
*/
public void removeRestartingInstance(String applicationName, String ip) throws Exception {
zkClient.delete().forPath(READY_INSTANCE_PATH + "/" + buildPath(applicationName, ip));
}
/**
* 判断节点信息 是否存在于 restartInstances
*/
public boolean hasRestartingInstance(String applicationName,String host){
return restartInstances.contains(buildPath(applicationName,host));
}
}
路由
public class RestartingInstanceRouter implements Router {
private final ReadyRestartInstanceManager pathManager;
private final URL url;
public RestartingInstanceRouter(URL url) {
this.url = url;
this.pathManager = ReadyRestartInstanceManager.create();
}
@Override
public URL getUrl() {
return url;
}
@Override
public <T> List<Invoker<T>> route(List<Invoker<T>> list, URL url, Invocation invocation) throws RpcException {
return list.stream()
.filter(i -> !pathManager.hasRestartingInstance(i.getUrl().getParameter("remote.application"),
i.getUrl().getIp()))
.collect(Collectors.toList());
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean isForce() {
return false;
}
@Override
public int getPriority() {
return 0;
}
}
路由工厂
@Activate
public class RestartingInstanceRouterFactory implements RouterFactory {
@Override
public Router getRouter(URL url) {
return new RestartingInstanceRouter(url);
}
}
SPI配置文件
名字:org.apache.dubbo.rpc.cluster.RouterFactory
内容:restartInstances=com.demo.RestartingInstanceRouterFactory
思考:为啥需要通过工厂来加载路由呢?而不直接加载路由?
由于 Router 机制比较特殊,所以需要利用一个专门的 RouterFactory 来生成,原因在于并不是所有的都需要添加路由,所以需要利用 @Activate 来锁定具体哪些服务才需要生成使用。
3.8服务降级
1、定义
服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务有策略的降低服务级别,以释放服务器资源,保证核心任务的正常运行。
2、为啥要使用服务降级
而为什么要使用服务降级,这是防止分布式服务发生雪崩效应,什么是雪崩?就是蝴蝶效应,当一个请求发生超时,一直等待着服务响应,那么在高并发情况下,很多请求都是因为这样一直等着响应,直到服务资源耗尽产生宕机,而宕机之后会导致分布式其他服务调用该宕机的服务也会出现资源耗尽宕机,这样下去将导致整个分布式服务都瘫痪,这就是雪崩。
3、Dubbo 服务降级实现方式
屏蔽和容错
- mock=force:return+null:表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
- mock=fail:return+null:表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。【推荐】
指定返回简单值或者null
| Xml方式<dubbo:reference id="xxService" check="false" interface="com.xx.XxService" timeout="3000" mock="return null" /><dubbo:reference id="xxService2" check="false" interface="com.xx.XxService2" timeout="3000" mock="return 1234" /> |
| 注解方式@Reference(mock="return null") @Reference(mock="return 简单值") |
使用java代码 动态写入配置中心
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(
RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://IP:端口"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService? category=configurators&dynamic=false&application=foo&mock=force:return+null"));
3.9集群容错
集群容错指的是,当消费者调用提供者集群时发生异常的处理方案。
1、Dubbo内置的容错方案
Failover
故障转移策略。当消费者调用提供者集群中的某个服务器失败时,其会自动尝试着调用其它服务器。该策略通常用于读操作,例如,消费者要通过提供者从 DB 中读取某数据。但重试会带来服务延迟。【默认的实现方案】
Failfast
快速失败策略。消费者端只发起一次调用,若失败则立即报错。通常用于非幂等性的写操作,比如新增记录。
幂等性:在请求参数相同的前提下,请求一次与请求 n 次,对系统产生的影响是相同的。
- GET:幂等
- POST:非幂等
- PUT:幂等
- DELETE:幂等
Failsafe
失败安全策略。当消费者调用提供者出现异常时,直接忽略本次消费操作。该策略通常用于执行相对不太重要的服务,例如,写入审计日志等操作。
Failback
失败自动恢复策略。消费者调用提供者失败后,Dubbo 会记录下该失败请求,然后定时自动重新发送该请求。该策略通常用于实时性要求不太高的服务,例如消息通知操作。
Forking
并行策略。消费者对于同一服务并行调用多个提供者服务器,只要一个成功即调用结束并返回结果。通常用于实时性要求较高的读操作,但其会浪费较多服务器资源。
Broadcast
广播策略。广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
2、配置集群容错策略
容错策略可以设置在消费者端,也可以设置在提供者端。若消费者与提供者均做了设置,则消费者端的优先级更高。
<dubbo:service interface="com.dubbo.demo._02ByXml.api.HelloService" ref="helloService" cluster="Failover"/>
3.10集群限流
对待高并发的三把利器:缓存、降级、限流。
为了防止某个消费者的 QPS 或是所有消费者的 QPS 总和突然飙升而导致的重要服务的失效,系统可以对访问流量进行控制,这种对集群的保护措施称为服务限流。
Dubbo 中能够实现服务限流的方式较多,可以划分为两类:直接限流与间接限流。
直接限流:通过对连接数量直接进行限制来达到限流的目的。(官方方案汇总)
间接限流:通过一些非连接数量设置来达到限制流量的目的。(个人经验总结)
1、直接限流
executes限流–仅提供者端
该属性仅能设置在提供者端。可以设置为接口级别,也可以设置为方法级别。限制的是服务(方法)并发执行数量。
<dubbo:service interface="com.dubbo.demo.api.HelloService" ref="helloService" executes="10"/>
accepts限流–仅提供者端
该属性仅可设置在提供者端的<dubbo:provider/>与<dubbo:protocol/>。用于对指定协议的连接数量进行限制。
<dubbo:provider protocol="dubbo" accepts="10"/>
actives限流–两端
该限流方式与前两种不同的是,其可以设置在提供者端,也可以设置在消费者端。可以设置为接口级别,也可以设置为方法级别。
提供者限流:
长连接:表示当前长连接最多可以处理的请求个数。与长连接的数量没有关系。
短连接:表示当前服务可以同时处理的短连接数量。
<dubbo:service interface="com.dubbo.demo.api.HelloService" ref="helloService" actives="10"/>
消费者端限流:
长连接:表示当前消费者所发出的长连接中最多可以提交的请求个数。与长连接的数量没有关系。
短连接:表示当前消费者可以提交的短连接数量。
<dubbo:reference id="helloService" interface="com.dubbo.demo.api.HelloService" actives="10" />
connections限流
可以设置在提供者端,也可以设置在消费者端。限定连接的个数。对于短连接,该属性效果与 actives 相同。但对于长连接,其限制的是长连接的个数。
一般情况下,我们会使 connectons 与 actives 联用,让 connections 限制长连接个数,让actives 限制一个长连接中可以处理的请求个数。联用前提:使用默认的 Dubbo 服务暴露协议。
消费端
<dubbo:reference id="helloService" interface="com.dubbo.demo.api.HelloService" actives="10" connections="10"/>
2、间接限流
延迟连接–仅消费者端
仅可设置在消费者端,且不能设置为方法级别。仅作用于 Dubbo 服务暴露协议。将长连接的建立推迟到消费者真正调用提供者时。可以减少长连接的数量。
<dubbo:reference id="helloService" interface="com.dubbo.demo.api.HelloService" lazy="true"/>
粘连连接–仅消费者
仅能设置在消费者端,其可以设置为接口级别,也可以设置为方法级别。仅作用于Dubbo 服务暴露协议。
其会使客户端尽量向同一个提供者发起调用,除非该提供者挂了,其会连接另一台。只要启用了粘连连接,其就会自动启用延迟连接。
其限制的是流向,而非流量。
<dubbo:reference id="helloService" interface="com.dubbo.demo.api.HelloService" sticky="true"/>
负载均衡
可以设置在消费者端,亦可设置在提供者端;可以设置在接口级别,亦可设置在方法级别。其限制的是流向,而非流量。
3.11声明式缓存
为了进一步提高消费者对用户的响应速度,减轻提供者的压力,Dubbo 提供了基于结果的声明式缓存。该缓存是基于消费者端的,所以使用很简单,只需修改消费者配置文件,与提供者无关。
设置:
<dubbo:reference id="helloService" interface="com.dubbo.demo.api.HelloService" cache="true"/>
方法级别
<dubbo:reference id="helloService" interface="com.dubbo.demo.api.HelloService" cache="true">
<dubbo:method name="sayHello" cache="true"/>
</dubbo:reference>
缓存说明
声明式缓存中可以缓存多少个结果呢?默认可以缓存 1000 个结果。若超出 1000,将采
用 LRU 策略来删除缓存,以保证最热的数据被缓存。注意,该删除缓存的策略不能修改。
应用场景
应用于查询结果不会发生改变的情况,例如,查询某产品的序列号、订单、身份证号等。
3.12SpringBoot中使用Dubbo
POM
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!--curator客户端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
Provider
@SpringBootApplication
@EnableDubbo//开启Dubbo自动配置功能
@PropertySource("classpath:/provider-config.properties")
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
//service导入的是dubbo的包
@Service
@Component
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello() {
return "hello dubbo";
}
}
配置文件
server.port=8082
# dubbo
dubbo.application.name=provider-service
# zookeeper
dubbo.registry.address=192.168.159.128:2181?timeout=60000
dubbo.registry.protocol=zookeeper
# 可以不配置monitor
# dubbo.monitor.protocol=registry
Consumer
@RestController
public class DemoController {
@Reference
private HelloService helloService;
@GetMapping("hello")
public String sayHello(){
return helloService.sayHello();
}
}
@EnableDubbo
@SpringBootApplication
@PropertySource("classpath:/consumer-config.properties")
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
配置文件
server.port=8083
# dubbo
dubbo.application.name=consumer-service
# zookeeper
dubbo.registry.address=192.168.159.128:2181?timeout=60000
dubbo.registry.protocol=zookeeper
# 可以不配置monitor
# dubbo.monitor.protocol=registry
网友评论