Doubbox 入门
官方网站
Apache Dubbo
前言
- Dubbox是一个分布式服务框架
- 其前身是阿里巴巴开源项目Dubbo,被国内电商及互联网项目中使用, 后期阿里巴巴停止了Dubbo项目的维护(2018年 Dubbo已捐献给Apache基金会)
- 因为 阿里巴巴内部的HSF框架比Dubbo更高效,更贴合他们业务。
当当网便在Dubbo基础上进行优化,并继续进行维护,为了与原有的Dubbo区别,故将其命名为
Dubbox
Dubbox作用
- Dubbox 致力于提供高性能和透明化的RPC远程服务调用方案 以及SOA服务治理方案。
- 简单的说,dubbox就是个服务框架,如果没有分布式的需求,其实是不需要用的
- 只有在分布式的时候,才有dubbox这样的分布式服务框架的需求
- 说白了就是个
远程服务调用的分布式框架。
分布式基础理论
- 什么是分布式系统?
- 发展演变
什么叫RPC
- RPC【Remote Procedure Call】是指远程过程调用,是一种**
进程间通信方式
**是一种技术的思想,而不是规范
- 它允许程序调用另一个地址空间
(通常指共享网络的另一台机器上)
的过程或方法, 而不用程序员显式编码这个远程调用的细节。 即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
RPC基本原理
- 首先调用方需要有个RPCclient,被调用方需要有 RCPServer,这两个服务用于RPC通信。
- 调用者通过RPCClient调用指定方法 RPCClient则将请求封装后(将方法参数值进行二进制序列化),传递给server
- server收到请求后,反序列化参数,恢复成请求原本的形式 然后找到对应的方法进行本地调用。将方法的返回值通过RPC返回给client
- client收到结果后,将结果返回给调用者作为返回值。
Dubbox基本概念
- 服务提供者(Provider): 暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者(Consumer): 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 注册中心(Registry): 注册中心返回服务提供者地址列表给消费者 如果有变更,注册中心将基于长连接推送变更数据给消费者
- 监控中心(Monitor): 服务消费者和提供者,在内存中累计调用次数和调用时间 定时每分钟发送一次统计数据到监控中心
环境搭建:
工具环境:
- windows 操作系统
- zookeeper消费、提供服务。 注册中心
- 官方提供了一个可视化的监控程序
dubbo-admin
Github 下载地址 这个可以不用必须安装只是一个管控界面…Git上下载源码之后,通过Maven 打个war包,部署本地Tomcat 或其它容器
直接运行即可!
Zookeeper
- 这里Zookeeper 只是提供一个注册中心,**
并不需要细致讲解..
** 安装启动即可!
Zookeeper 配置:
- dataDir 日志文件地址
- clientPort 服务端口
注意:
Zookeeper 解压启动报错:
- 这是因为缺少 zoo.cof配置文件
- 因为,Linux上是
zoo_sample.cfg
直选Copy一份改名zoo.cfg
则可以正常启动!
dubbo-admin
- CMD 项目目录下:
mvn install -Dmaven.test.skip=true
Maven打包 - Maven学习地址
配置dubbo-admin-2.8.4 链接 zookeepk 管控信息
- dubbo默认的注册机制是zookeeper,地址也是本地地址:127.0.0.1:2181 假如你此时zookeeper的实例的地址不是127.0.0.1:2181,或者注册机制是Redis的话,需要修改dubbo.properties的配置
- 默认用户:root 密码:guest
随后启动tomcat
- 打完的包,放在一个指向容器中
Tomcat
的webapps - 随后启动tomcat bin目录下
./startup.sh
控制台
- 访问
http://localhost:8080/dubbo-admin-2.8.4
/访问后台 - 8080是我的Tomcat默认端口,自行更改!
- 账户:root 密码:root
SpringBoot整合 Double
- 使用SpringBoot与Dubbo整合,测试远程调用 演示用户服务(consumer)查找订单(provider)的操作
- 项目案例下载地址!
父工程依赖:
代码语言:javascript复制 <!-- 父工程依赖! -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
common 公共实体类
两个实体类就不解释了…
Order.Java
import java.io.Serializable;
public class Order implements Serializable { //因为要进行网络传输,对实体类进行序列化操作
private Integer id;
private String title;
private Integer price;
private Integer uid;
//省略 get/set..
}
User.Java
public class User {
private Integer id;
private String nickname;
private List<Order> orders;
//省略 get/set。。。
}
rpcServier RPC业务接口包:
- 主要因为:Order提供方,User调用方调用代码需要调用api方法,而服务跨模块了并调用不到…
- 将接口,声明在公共的模块中,user调用方 和order实现方就都可以获取到调用或实现方法!
RPCOrderService.Java
import com.wsm.entity.Order;
import java.util.List;
public interface RPCOrderService {
public List<Order> findUserOrder(Integer uid);
}
Order 订单模块
实现 RPCService 接口:
RPCOrderServiceImpl.Java
import com.alibaba.dubbo.config.annotation.Service;
import com.wsm.entity.Order;
import com.wsm.rpcServier.*;
import java.util.ArrayList;
import java.util.List;
//注意这里是 dubbo 的服务注解
@Service
public class RPCOrderServiceImpl implements RPCOrderService {
//造假数据!
private static List<Order> list = new ArrayList<>();
static {
list.add(new Order(1, "张三的订单1.0", 10, 1));
list.add(new Order(2, "张三的订单1.0", 100, 1));
list.add(new Order(3, "李四的订单1.0", 10, 2));
list.add(new Order(4, "张三的订单1.0", 105, 1));
list.add(new Order(5, "李四的订单1.0", 10, 2));
}
//根据输入用户id 获取对应的订单数据!
@Override
public List<Order> findUserOrder(Integer uid) {
List<Order> orders = new ArrayList<>();
for (Order order : list) {
if (uid == order.getUid()) {
orders.add(order);
}
}
return orders;
}
}
注意
这里是的 @service 是dubbo 的服务注解import com.alibaba.dubbo.config.annotation.Service;
- 也就是通过该注解,配置进行扫描时候进行加载配置…
pom.xml依赖:
pom.xml
<dependencies>
<!-- dubbo-starter依赖 -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<!-- web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<artifactId>mydubbo.common</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
yml配置:
application.yml
#dubbo 配置
dubbo:
#服务名
application:
name: order-server
#dubbo注册的端口..
protocol:
name: dubbo
port: 20881
#注册中心的配置
registry:
protocol: zookeeper
address: 127.0.0.1:2181
#扫描包下注解!
scan:
base-packages: com.wsm.service
#配置Boot程序服务端口
server:
port: 8001
主程序启动:
MyOrderApp.Java
- 注意:@EnableDubbo
启动Doubbo注解
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo //启动Doubbo
public class MyOrderApp {
public static void main(String[] args) {
SpringApplication.run(com.wsm.MyOrderApp.class,args);
}
}
服务注册成功!
User 用户模块
Controller 控制层请求:
- 因为是调用方这个还是得要滴…
UserController.Java
import com.wsm.entity.User;
import com.wsm.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/info/{id}")
public User info(@PathVariable Integer id){
return userService.findUserByorder(id);
}
}
Service RPC调用远程Order模块
接口 UserService.Java
接口实现 UserServiceImpl.Java
import com.alibaba.dubbo.config.annotation.Reference;
import com.wsm.entity.Order;
import com.wsm.entity.User;
import com.wsm.rpcServier.RPCOrderService;
import com.wsm.service.UserService;
import org.springframework.stereotype.Service;
import java.util.List;
//正常的Spring的 @Service注解!
@Service
public class UserServiceImpl implements UserService {
@Reference
private RPCOrderService rpcOrderService;
@Override
public User findUserByorder(Integer id) {
User user = new User();
user.setId(id);
user.setNickname("张三");
List<Order> userOrder = rpcOrderService.findUserOrder(id);
user.setOrders(userOrder);
return user;
}
}
- 这里因为是
正常的调用方
直接Spring的@service
即可 - 内部调用了 rpcOrderService 的实现类… 并通过
@Reference
注解注入分布式的远程服务的对象
需要dubbo配置使用。 - 当你调用方服务启动时候,根据 yml配置 扫描指定包下,
即可得知当前服务需要
调用的服务模块....
pom依赖:
- 与Order 模块一致即可!
yml配置
application.yml
dubbo:
application:
name: user-server
registry:
protocol: zookeeper
address: 127.0.0.1:2181
scan:
base-packages: com.wsm.service
server:
port: 8082
主程序运行 并调用!
MyUserApp.Java
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class MyUserApp {
public static void main(String[] args) {
SpringApplication.run(com.wsm.MyUserApp.class, args);
}
}
结果案例
Dubbo 配置Config
:
- Dubbo有很配置可供使用 并支持很多种配置…
配置方式:
Dubbo提供启动时配置、XML配置、properties以及yml配置
优先级
启动参数 > XML > yml > prop
- 如果prop和yml都存在,那么yml的优先级会大于prop
yml 配置覆盖之前的配置!
- 启动参数 JVM 启动 -D 参数优先,这样可以使用户在部署和启动时进行参数重写,比如在启动时需改变协议的端口。
- XML
如果在 XML 中有配置,则 dubbo.properties 中的相应配置项无效。
常用于 Spring整合**
Dubbo
** - yml 目前流行的一种,SpringBoot的配置编写方式…
- properties 相当于缺省值,只有 XML 没有配置时, dubbo.properties 的相应配置项才会生效,通常用于共享公共配置,比如应用名。
启动建材 配置:
- 在**
consumer消费者
** 程序启动时如果**provider提供者
**程序未启动会造成启动报错 - 在生产环境中可能会存在provider晚于consumer启动的情况。
- 启动检查默认开启,也可以将启动检查关闭。
局部关闭
在Consumer**调用方
获取接口注解添加属性check,启动时会自动忽略提供provider 的必须启动**
@Reference(check = false) //自动检查配置 true/false
private RPCOrderService rpcOrderService;
全局关闭
- 在配置文件中关闭,该操作会关闭
该 调用者 全部的 provider提供者 检查
yml
dubbo:
consumer:
check: false
超时 配置:
- 由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。
- 为了避免这种问题导致客户端资源(线程)挂起耗尽,必须要设置超时间。
- 默认超时时间为1s,如过1s未响应
返回结果
会进入 重试阶段
局部配置:
- Consumer获取接口的注解添加属性timeout,以毫秒为单位,仅限当前这个provider
@Reference(vtimeout = 3000)
private RPCOrderService rpcOrderService;
全局配置:
- 当前调用者的所有,提供服务进行超时配置!
dubbo:
consumer:
timeout: 3000
重试阶段 配置:
provider超时未应答,会进行重试发送。默认重试两次 第一次用户发起的不算
超时后程序自动重试的次数
局部配置:
- 通过 retries=“2” 来设置重试次数(不含第一次)
@Reference(retries = 5)
private RPCOrderService rpcOrderService;
全局配置:
- 当前调用者的所有,提供者服务都会重试…
dubbo:
consumer:
registry: 5
注意:
重试需要考虑幂等性问题
- 幂等(设置重试次数) 查询 删除 修改
因为查不会更改数据库,删 改就算第一次成功了,后面在带着相同的参数还是只是在执行一次而已
对数据库更改也只有一次! - 非幂等 (不能设置重试次数) 新增 会,可能存在对数据库的多次修改!
多版本 灰度发布
- 灰度发布(又名金丝雀发布)是指在黑与白之间
能够平滑过渡的一种发布方式。
- 即: 当某一程序政策需要更新时候, 让一部分用户继续用产品特性A,一部分用户开始用产品特性B 如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。
- 灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
- 灰度期:灰度发布开始到结束期间的这一段时间,称为**
灰度期
**
那么dubbo支持这一功能,可以选择版本去获取,非常的方便
提供者,提供多个版本的实现.
- 并指定版本
- 消费者,消费时候,指定要消费调用的**
服务版本!
**
消费者,调用需要的版本!
代码语言:javascript复制@Reference(version = "1.0.0") //指定要调用的版本!
private RPCOrderService rpcOrderService;
Dubbo直连
- 调用者可以通过
@Refernce配置url
指定,调用服务的地址直接连接
@Reference(url = "127.0.0.1:20881")
private RPCOrderService rpcOrderService;
注意不是Boot的服务器地址,而是 Dubbo的网络端口
Dubbo高可用
- 现象:
zookeeper注册中心宕机,还可以消费dubbo暴露的服务。
当程序正在运行时,注册中心突然挂了,并不会立刻影响程序使用!
就是高可用**健壮性
**
健壮性
- 监控中心宕机不影响使用,只会丢失部分采样数据.
新的服务不会在注册 但 老的服务还是可以正常访问的
- 数据库宕机后,注册中心仍能通过缓存提供服务列表查询,但不能注册新的服务
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台 zookeeper 集群环境下
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
负载均衡策略
- Dubbo 提供了**
多种均衡策略
**,缺省为 random 随机调用。 - 在 多个服务提供者 情况下,消费者调用,Dubbox本身存在默认的
负载均衡进行 调用
Random LoadBalance 权重随机 默认
默认
**随机**,按权重设置随机概率
只是概率,但并不是绝对!默认权重都是100
- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
Random LoadBalance 轮询
- 轮循(RoundRobin),按公约后的权重设置轮循比率。
- 存在 慢的提供者
累积请求的问题
比如:第二台机器很慢,但没死掉,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance 最少活跃
- 最少活跃 相同活跃数的随机,活跃数指调用前后计数差。
- Dubbo会查看上次的请求时间进行比对,如果发现速度较慢则转移到速度快的节点上。
ConsistentHash LoadBalance 一致性哈希
- 一致性哈希 相同参数的请求总是发到同一提供者。
- 当某一台提供者不工作时,原本发往该提供者的请求,
- 基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。算法参见wikipedia
测试实现:
Copy 两个几乎一样的 提供者模块
- 修改SpringBoot启动端口
- 修改Dubbox 注册端口
- 为了区分,方法调用时候输出一个 当前的信息
- 启动!
User 模块消费调用:
总结:
Dubbo是基于客户端的负载均衡,相关配置需要在客户端配置。
- 可以在@Service通过weight设置权重
@Service(weight = 权重数值)
- 调用者通过 @Reference(loadbalance = “roundrobin”) 设置 轮询的规则
- 设置了轮询,就忽略了权重!
整合 hystrix 熔断服务降级
服务降级
- 当服务器压力剧增的情况下,根据实际业务情况及流量
- 对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
控制台设置 dubbo-admin管控台
- 管控台也可以直接设置 权重…屏蔽 禁止等一些操作…
- 可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
屏蔽
- mock=force:return null 表示消费方对该服务的方法调用都直接返回 null 值. 不发起远程调用。 用来屏蔽不重要服务不可用时对调用方的影响。
禁止
- 还可以改为 mock=fail:return null 表示消费方对该服务的方法调用在失败后 再返回 null 值,不抛异常
- 用来容忍不重要服务不稳定时对调用方的影响。
在控制台可以很方便的设置
整合 hystrix
- Hystrix 旨在通过控制那些访问远程系统,服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。
- Hystrix具备拥有回退机制和断路器功能的线程和信号隔离 请求缓存和请求打包,以及监控和配置等功能
依赖
由于dubbo不自带hystrix 这是SpringCloud的东西,所以需要加入Cloud的依赖等
代码语言:javascript复制<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
要进行服务降级的方法:
- 添加注解,并编写熔断降级方法!
UserServiceImpl.Java
import com.alibaba.dubbo.config.annotation.Reference;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.wsm.entity.Order;
import com.wsm.entity.User;
import com.wsm.rpcServier.RPCOrderService;
import com.wsm.service.UserService;
import org.springframework.stereotype.Service;
import java.util.List;
//正常的Spring的 @Service注解!
@Service
public class UserServiceImpl implements UserService {
/*局部配置:
@Reference(check = false) //自动检查配置 true/false
@Reference(vtimeout = 3000) //设置超时重试
@Reference(retries = 5) //重试机制,指定重试次数!
@Reference(url = "127.0.0.1:20881") //直连
@Reference(version = "1.0.0") //指定要调用的版本!
@Reference(loadbalance = "roundrobin") //设置轮询规则!
* */
// @Reference
@Reference(loadbalance = "roundrobin")
private RPCOrderService rpcOrderService;
@Override
@HystrixCommand(fallbackMethod = "findUserByorder_fallback") //指定降级方法!
public User findUserByorder(Integer id) {
//手动抛出异常,查看降级结果!用户id 1的就抛出异常!
if (id==1){
throw new RuntimeException("异常了!");
}
User user = new User();
user.setId(id);
user.setNickname("张三");
List<Order> userOrder = rpcOrderService.findUserOrder(id);
user.setOrders(userOrder);
return user;
}
//改降级方法要与,服务方法参数一模一样!
public User findUserByorder_fallback(Integer id) {
User user = new User();
user.setId(id);
user.setNickname("熔断方法");
return user;
}
}
主程序运行测试:
- 主程序类添加
启动注解
- @EnableHystrix
//熔断降级Hystrix 启动注解!