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注册中心
- 官网下载Zookeeperzookeeper官网: 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
/**
* 用户地址
*
*/publicclassUserAddressimplementsSerializable{privateInteger id;privateString userAddress;//用户地址privateString userId;//用户idprivateString consignee;//收货人privateString phoneNum;//电话号码privateString isDefault;//是否为默认地址 Y-是 N-否publicUserAddress(){super();}publicUserAddress(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;}publicIntegergetId(){return id;}publicvoidsetId(Integer id){this.id = id;}publicStringgetUserAddress(){return userAddress;}publicvoidsetUserAddress(String userAddress){this.userAddress = userAddress;}publicStringgetUserId(){return userId;}publicvoidsetUserId(String userId){this.userId = userId;}publicStringgetConsignee(){return consignee;}publicvoidsetConsignee(String consignee){this.consignee = consignee;}publicStringgetPhoneNum(){return phoneNum;}publicvoidsetPhoneNum(String phoneNum){this.phoneNum = phoneNum;}publicStringgetIsDefault(){return isDefault;}publicvoidsetIsDefault(String isDefault){this.isDefault = isDefault;}}
OrderService
publicinterfaceOrderService{/**
* 初始化订单
* @param userId
*/publicList<UserAddress>initOrder(String userId);}
UserService
/**
* 用户服务
*
*/publicinterfaceUserService{/**
* 按照用户id返回所有的收货地址
* @param userId
* @return
*/publicList<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")publicclassOrderController{@AutowiredOrderService orderService;@GetMapping("/getOrder")publicList<UserAddress>getOrder(@RequestParam("userId")String userId){return orderService.initOrder(userId);}}
OrderServiceImpl
@ServicepublicclassOrderServiceImplimplementsOrderService{/**
* 调用远程接口,指定接口,关闭自启动检查
*/@DubboReference(interfaceClass =UserService.class, check =false)UserService userService;@OverridepublicList<UserAddress>initOrder(String userId){return userService.getUserAddressList(userId);}}
user模块
UserServiceImpl
/**
* 暴露远程调用的接口,对外提供服务
*/@DubboService(interfaceClass =UserService.class)publicclassUserServiceImplimplementsUserService{@OverridepublicList<UserAddress>getUserAddressList(String userId){UserAddress address1 =newUserAddress(1,"北京市昌平区宏福科技园综合楼3层","1","李老师","010-56253825","Y");UserAddress address2 =newUserAddress(2,"深圳市宝安区西部硅谷大厦B座3层(深圳分校)","1","王老师","010-56253825","N");returnArrays.asList(address1,address2);}}
6. 启动项目,到控制台查看
控制台查看
使用order服务调用user服务
四、Dubbo配置
4.1 启动时检查
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认
check="true"
。可以通过
check="false"
关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
@ServicepublicclassOrderServiceImplimplementsOrderService{/**
* 调用远程接口,指定接口,关闭自启动检查
*/@DubboReference(interfaceClass =UserService.class, check =false)UserService userService;@OverridepublicList<UserAddress>initOrder(String userId){return userService.getUserAddressList(userId);}}
4.2 重试次数
失败自动切换,当出现失败,重试其它服务器,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。
@ServicepublicclassOrderServiceImplimplementsOrderService{/**
* 调用远程接口,指定接口,关闭自启动检查
*/@DubboReference(interfaceClass =UserService.class, check =false, retries =3)UserService userService;@OverridepublicList<UserAddress>initOrder(String userId){return userService.getUserAddressList(userId);}}
4.3 超时时间
由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间。
order端配置
@ServicepublicclassOrderServiceImplimplementsOrderService{/**
* 调用远程接口,指定接口,关闭自启动检查
*/@DubboReference(interfaceClass =UserService.class, check =false, retries =3, timeout =5000)UserService userService;@OverridepublicList<UserAddress>initOrder(String userId){return userService.getUserAddressList(userId);}}
user端配置
@DubboService(interfaceClass =UserService.class, timeout =5000)publicclassUserServiceImplimplementsUserService{@OverridepublicList<UserAddress>getUserAddressList(String userId){UserAddress address1 =newUserAddress(1,"北京市昌平区宏福科技园综合楼3层","1","李老师","010-56253825","Y");UserAddress address2 =newUserAddress(2,"深圳市宝安区西部硅谷大厦B座3层(深圳分校)","1","王老师","010-56253825","N");returnArrays.asList(address1,address2);}}
配置优先级
- 方法级配置别优于接口级别,接口级别优先与全局配置
- 同一级别消费端配置大于提供端
4.4 版本号
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
order端
@ServicepublicclassOrderServiceImplimplementsOrderService{/**
* 调用远程接口,指定接口,关闭自启动检查,version=*,表示随机调用,新版本和旧版本都可调用
*/@DubboReference(interfaceClass =UserService.class, check =false, version ="*")UserService userService;@OverridepublicList<UserAddress>initOrder(String userId){return userService.getUserAddressList(userId);}}
user端
/**
* 暴露远程调用的接口,对外提供服务
*/@DubboService(interfaceClass =UserService.class, version ="1.0.0")publicclassUserServiceImplimplementsUserService{@OverridepublicList<UserAddress>getUserAddressList(String userId){UserAddress address1 =newUserAddress(1,"北京市昌平区宏福科技园综合楼3层","1","李老师","010-56253825","Y");UserAddress address2 =newUserAddress(2,"深圳市宝安区西部硅谷大厦B座3层(深圳分校)","1","王老师","010-56253825","N");returnArrays.asList(address1,address2);}}
4.5 本地存根
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
OrderServiceStub
publicclassOrderServiceStubimplementsUserService{publicfinalUserService userService;// 构造函数传入真正的远程代理对象publicOrderServiceStub(UserService userService){this.userService = userService;}@OverridepublicList<UserAddress>getUserAddressList(String userId){// 这里可以对参数进行校验System.out.println("OrderServiceStub----1");if(StringUtils.hasText(userId)){return userService.getUserAddressList(userId);}returnnull;}}
OrderServiceImpl
@ServicepublicclassOrderServiceImplimplementsOrderService{/**
* 调用远程接口,指定接口,关闭自启动检查
*/@DubboReference(check =false, stub ="com.example.orderservicecomsumer.service.OrderServiceStub", methods =@Method(name ="getUserAddressList", timeout =6000, retries =3))UserService userService;@OverridepublicList<UserAddress>initOrder(String userId){return userService.getUserAddressList(userId);}}
user端
UserServiceImpl
/**
* 暴露远程调用的接口,对外提供服务
*/@DubboService(interfaceClass =UserService.class)publicclassUserServiceImplimplementsUserService{@OverridepublicList<UserAddress>getUserAddressList(String userId){UserAddress address1 =newUserAddress(1,"北京市昌平区宏福科技园综合楼3层","1","李老师","010-56253825","Y");UserAddress address2 =newUserAddress(2,"深圳市宝安区西部硅谷大厦B座3层(深圳分校)","1","王老师","010-56253825","N");returnArrays.asList(address1,address2);}}
五、高可用
5.1 与dubbo直连
现象:zookeeper注册中心宕机,还可以消费dubbo暴露的服务。
OrderServiceImpl配置
@ServicepublicclassOrderServiceImplimplementsOrderService{/**
* 调用远程接口,关闭自启动检查,跳过注册中心
*/@DubboReference(check =false, url ="127.0.0.1:20882")UserService userService;@OverridepublicList<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:referenceinterface="..."loadbalance="roundrobin"/><!-- 服务端配置 --><dubbo:serviceinterface="..."><dubbo:methodname="..."loadbalance="roundrobin"/></dubbo:service><!-- 客户端方法配置 --><dubbo:referenceinterface="..."><dubbo:methodname="..."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:多注册中心订阅的场景,注册中心集群间的负载均衡。对于多注册中心间的选址策略有如下四种
版权归原作者 veno_冰 所有, 如有侵权,请联系我们删除。