SpringBoot整合Dubbo
SpringBoot整合Dubbo & zookeeper & Dubbo-admin一、分布式概述发展演变1.1 单一应用架构当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。缺点性能扩展比较难协同开发问题不利于升级维护1.
SpringBoot整合Dubbo & zookeeper & Dubbo-admin
一、分布式概述
发展演变
1.1 单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点
- 性能扩展比较难
- 协同开发问题
- 不利于升级维护
1.2 垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
缺点
- 公用模块无法重复利用,开发性的浪费
1.3 分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的**分布式服务框架****(RPC)**是关键。
1.4 流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。
1.5 RPC概念
概述
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
RPC两个核心模块
- 通讯
- 序列化
二、Dubbo概述
概述
Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。同时 Dubbo 是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。
基本工作模式
- 服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者(Consumer): 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
- 监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
三、Dubbo环境搭建
3.1 配置Zookeeper注册中心
-
官网下载Zookeeper
zookeeper官网: https://zookeeper.apache.org/index.html
-
解压zookeeper,修改配置
- 将conf文件下的zoo_sample.cfg,复制一份改名为zoo.cfg(为zookeeper的默认配置文件名,默认不修改直接启动会报错)
- 修改zoo.cfg中的dataDir,这为临时数据存储的目录,可自定义存放的位置,这里配置为datDir=…/data,然后在conf同级目录新建data文件夹
- clientPort=2181为zookeeper的端口号,可自定义修改,这里就不改了
- maxClientCnxns=60为zookeeper的最大连接数量
- tickTime=2000为zookeeper服务注册的超时时间
-
进入bin目录,控制台中打开,输入zkServer.cmd命令启动注册中心
-
进入bin目录,控制台打开,输入zkCli.cmd客户端连接注册中心,检查注册中心是否启动成功
ls /
:列出注册中心的所有节点get /zookeeper
:获取该节点的值create -e /haha 123
:创建一个haha节点,值为123
3.2 配置Dubbo-Admin管理控制台
-
下载dubbo-admin管理控制台源码
https://github.com/apache/dubbo-admin
或者直接克隆项目:git clone https://github.com/apache/dubbo-admin.git
-
由于当前的后台管理为前后端分离的项目,所以这里要配置后台源码的一些配置,和修改前端的代理端口地址
-
修改dubbo-admin-server后台项目中的application.properties配置自己定义的zookeeper的注册中心的端口和ip,也可以自定义登录用户名和密码,最重要修改后台的启动端口号这里配置为7001,不修改的话后面会有端口冲突问题
-
修改完成后保存,返回到dubbo-admin-server目录下,在cmd窗口打开执行
mvn clean package -Dmaven.test.skip=true
打包命令,生成jar包,然后在控制台使用java -jar 命令运行生成的jar包,至此后台部分配置完成 -
进入前端dubbo-admin-ui项目中,修改vue.config.js,修改代理的端口为上面后台的端口地址7001
-
启动前端项目要安装node.js,进入dubbo-admin-ui,控制台打开使用
npm i
下载项目所需的依赖,下载后使用npm run dev
运行前端项目,前提后台项目和zookeeper要先启动 -
浏览器输入localhost:9001访问后台管理,输入用户名和密码后如下,至此配置全部完成
3.3 springboot整合dubbo
1. 创建order-service-comsumer、user-service-provider、common-api模块
2. 引入dubbo和curator 依赖以及common-api
<!--自定义comm-api依赖-->
<dependency>
<groupId>com.example</groupId>
<artifactId>common-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.0.7</version>
</dependency>
3. 在common-api中定义bean和要远程调用的接口
UserAddress
/**
* 用户地址
*
*/
public class UserAddress implements Serializable {
private Integer id;
private String userAddress; //用户地址
private String userId; //用户id
private String consignee; //收货人
private String phoneNum; //电话号码
private String isDefault; //是否为默认地址 Y-是 N-否
public UserAddress() {
super();
}
public UserAddress(Integer id, String userAddress, String userId, String consignee, String phoneNum,
String isDefault) {
super();
this.id = id;
this.userAddress = userAddress;
this.userId = userId;
this.consignee = consignee;
this.phoneNum = phoneNum;
this.isDefault = isDefault;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserAddress() {
return userAddress;
}
public void setUserAddress(String userAddress) {
this.userAddress = userAddress;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getConsignee() {
return consignee;
}
public void setConsignee(String consignee) {
this.consignee = consignee;
}
public String getPhoneNum() {
return phoneNum;
}
public void setPhoneNum(String phoneNum) {
this.phoneNum = phoneNum;
}
public String getIsDefault() {
return isDefault;
}
public void setIsDefault(String isDefault) {
this.isDefault = isDefault;
}
}
OrderService
public interface OrderService {
/**
* 初始化订单
* @param userId
*/
public List<UserAddress> initOrder(String userId);
}
UserService
/**
* 用户服务
*
*/
public interface UserService {
/**
* 按照用户id返回所有的收货地址
* @param userId
* @return
*/
public List<UserAddress> getUserAddressList(String userId);
}
4. 配置order和user的注册中心地址信息和启用dubbo
在两个模块的启动类上添加@EnableDubbo
注解,开启dubbo的自动配置
order模块的application.properties
# 配置服务的名,以下两种配置方式相同
#spring.application.name=order-service
dubbo.application.name=order-service
# 项目启动端口
server.port=8001
# 配置包扫描的路径
#dubbo.scan.base-packages=com.example.orderservicecomsumer.service
# 配置注册中心的地址,指定注册中心的类型和地址信息,以下两种方式配置相同
#dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper
user模块的application.properties
# 配置服务的名,以下两种配置方式相同
dubbo.application.name=user-service
# 项目启动端口
server.port=8002
# 配置注册中心的地址,指定注册中心的类型和地址信息
dubbo.registry.address=zookeeper://127.0.0.1:2181
# 配置服务提供者远程通信的协议和端口
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881
5. order和user模块配置对应的service和controller
order模块
OrderController
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
OrderService orderService;
@GetMapping("/getOrder")
public List<UserAddress> getOrder(@RequestParam("userId") String userId){
return orderService.initOrder(userId);
}
}
OrderServiceImpl
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口,指定接口,关闭自启动检查
*/
@DubboReference(interfaceClass = UserService.class, check = false)
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
user模块
UserServiceImpl
/**
* 暴露远程调用的接口,对外提供服务
*/
@DubboService(interfaceClass = UserService.class)
public class UserServiceImpl implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
return Arrays.asList(address1,address2);
}
}
6. 启动项目,到控制台查看
控制台查看
使用order服务调用user服务
四、Dubbo配置
4.1 启动时检查
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"
。可以通过 check="false"
关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口,指定接口,关闭自启动检查
*/
@DubboReference(interfaceClass = UserService.class, check = false)
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
4.2 重试次数
失败自动切换,当出现失败,重试其它服务器,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口,指定接口,关闭自启动检查
*/
@DubboReference(interfaceClass = UserService.class, check = false, retries = 3)
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
4.3 超时时间
由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间。
order端配置
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口,指定接口,关闭自启动检查
*/
@DubboReference(interfaceClass = UserService.class, check = false, retries = 3, timeout = 5000)
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
user端配置
@DubboService(interfaceClass = UserService.class, timeout = 5000)
public class UserServiceImpl implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
return Arrays.asList(address1,address2);
}
}
配置优先级
- 方法级配置别优于接口级别,接口级别优先与全局配置
- 同一级别消费端配置大于提供端
4.4 版本号
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
order端
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口,指定接口,关闭自启动检查,version=*,表示随机调用,新版本和旧版本都可调用
*/
@DubboReference(interfaceClass = UserService.class, check = false, version = "*")
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
user端
/**
* 暴露远程调用的接口,对外提供服务
*/
@DubboService(interfaceClass = UserService.class, version = "1.0.0")
public class UserServiceImpl implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
return Arrays.asList(address1,address2);
}
}
4.5 本地存根
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
OrderServiceStub
public class OrderServiceStub implements UserService {
public final UserService userService;
// 构造函数传入真正的远程代理对象
public OrderServiceStub(UserService userService) {
this.userService = userService;
}
@Override
public List<UserAddress> getUserAddressList(String userId) {
// 这里可以对参数进行校验
System.out.println("OrderServiceStub----1");
if (StringUtils.hasText(userId)){
return userService.getUserAddressList(userId);
}
return null;
}
}
OrderServiceImpl
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口,指定接口,关闭自启动检查
*/
@DubboReference(check = false, stub = "com.example.orderservicecomsumer.service.OrderServiceStub", methods = @Method(name = "getUserAddressList", timeout = 6000, retries = 3))
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
user端
UserServiceImpl
/**
* 暴露远程调用的接口,对外提供服务
*/
@DubboService(interfaceClass = UserService.class)
public class UserServiceImpl implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
return Arrays.asList(address1,address2);
}
}
五、高可用
5.1 与dubbo直连
现象:zookeeper注册中心宕机,还可以消费dubbo暴露的服务。
OrderServiceImpl配置
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口,关闭自启动检查,跳过注册中心
*/
@DubboReference(check = false, url = "127.0.0.1:20882")
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
5.2 负载均衡
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。
负载均衡策略:
- RandomLoadBalance:加权随机,默认算法,默认权重相同
- RoundRobinLoadBalance:加权轮询
- LeastActiveLoadBalance:最少活跃优先 + 加权随机
- ShortestResponseLoadBalance:最短响应优先 + 加权随机,更加关注响应速度
- ConsistentHashLoadBalance:一致性 Hash,确定的入参,确定的提供者,适用于有状态请求
配置方式
注解配置
@DubboReference(check = false, loadbalance = LoadbalanceRules.RANDOM)
UserService userService;
xml配置
<!-- 客户端配置 -->
<dubbo:reference interface="..." loadbalance="roundrobin" />
<!-- 服务端配置 -->
<dubbo:service interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>
<!-- 客户端方法配置 -->
<dubbo:reference interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>
5.3 集群容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
容错模式
-
Failover Cluster:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过
retries="2"
来设置重试次数(不含第一次)。该配置为默认配置。 -
Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
-
Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
-
Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
-
Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过
forks="2"
来设置最大并行数。 -
Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
/** Broadcast Cluster 配置 broadcast.fail.percent。 broadcast.fail.percent=20 代表了当 20% 的节点调用失败就抛出异常,不再调用其他节点。 */ @reference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})
-
Available Cluster:调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常。通常用于不需要负载均衡的场景。
-
Mergeable Cluster:将集群中的调用结果聚合起来返回结果,通常和group一起配合使用。通过分组对结果进行聚合并返回聚合后的结果,比如菜单服务,用group区分同一接口的多种实现,现在消费方需从每种group中调用一次并返回结果,对结果进行合并之后返回,这样就可以实现聚合菜单项。
-
ZoneAware Cluster:多注册中心订阅的场景,注册中心集群间的负载均衡。对于多注册中心间的选址策略有如下四种
更多推荐
所有评论(0)