dubbo及zookeeper结合springboot学习

传统互联网架构到分布式架构的架构演变

命令记忆:
netstat -tlun  :查看当前启动的应用占用的端口
systemctl status firewalld:查看防火墙状态
  • 互联网时代演变过程

pc时代-->移动互联网时代-->物联网时代

  • 互联网架构演变过程

传统单体应用

  • 缺点:系统耦合度高​ 开发效率随着时间的增长而降低​ 启动应用的时间长​ 依赖庞大​ 等等
  • 适用场景:初创公司、业务场景简单、功能单一、研发人员较少
  • 微服务

优点:易开发、理解、维护独立部署和启动

不足:

需要管理多个服务、在架构的层面变得复杂

服务切分之后、服务之间的调用可能需要去解决分布式事务的问题


传统单体架构.jpg

传统单体架构的扩展.jpg

单体架构解耦.jpg

异步架构.jpg

soa架构.jpg

soa架构和微服务最大不同就是,微服务没有企业服务总线,微服务是再soa基础上演进出来的


微服务架构.jpg

微服务核心基础知识讲解

讲解微服务核心模块 :网关、服务发现注册、配置中心、链路追踪、负载均衡器、熔断

  • 网关:路由转发 + 过滤器

    • /api/v1/pruduct/ 商品服务​
    • /api/v1/order/ 订单服务​
    • /api/v1/user/ 用户服务
  • 服务注册发现:调用和被调用方的信息维护

  • 配置中心:管理配置,动态更新 application.properties

  • 链路追踪:分析调用链路耗时

例子:下单-》查询商品服务获取商品价格-》查询用户信息-》保存数据库

  • 负载均衡器:分发负载

  • 熔断:保护自己和被调用方

dubbo的发展历程

  • 简介:介绍dubbo的相关历史,对dubbo形成初步认知
  • 2011年由阿里开源dubbo首个版本 --dubbo-2.0.7
  • 随后更新维护频繁
  • 直到2014年发布 dubbo-2.4.11之后,在之后的几年里,几乎没见到更新
  • 同期springboot、springcloud出现
  • 消沉了很长一段时间后,dubbo于2017年,浴火重生,捐献给Apache

dubbo与spring-cloud比较

简介:对目前主流微服务框架进行对比,给技术选型提供相应的参考

dubbo: zookeeper + dubbo + springmvc/springboot

官方地址:http://dubbo.apache.org/#!/?lang=zh-cn

  • 通信方式:rpc 性能高​
  • 注册中心:zookeper/redis​
  • 配置中心:diamond(企业很少用)

springcloud: 全家桶+轻松嵌入第三方组件(Netflix 奈飞)

官网:http://projects.spring.io/spring-cloud/

  • 通信方式:http restful​
  • 注册中心:eruka/consul​
  • 配置中心:config​
  • 断路器:hystrix​
  • 网关:zuul​
  • 分布式追踪系统:sleuth+zipkin

补充:环境搭建参考VMWare设置静态IP以及CentOS7 JDK环境配置

zookeeper讲解

什么是微服务的注册中心

  • 理解注册中心:服务管理,核心是有个服务注册表,心跳机制动态维护
  • 服务提供者provider: 启动的时候向注册中心上报自己的网络信息
  • 服务消费者consumer: 启动的时候向注册中心上报自己的网络信息,拉取provider的相关网络信息

为什么要用

微服务应用和机器越来越多,调用方需要知道接口的网络地址,如果靠配置文件的方式去控制网络地址,对于动态新增机器,维护带来很大问题

主流的注册中心:zookeeper、Eureka、consul、etcd 等

CAP理论知识

指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得。

  • 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(所有节点在同一时间的数据完全一致,越多节点,数据同步越耗时)

    • 强一致性(strong consistency):任何时刻,任何用户都能读取到最近一次成功更新的数据。​
    • 单调一致性(monotonic consistency):任何时刻,任何用户一旦读到某个数据在某次更新后的值,那么就不会再读到比这个值更旧的值。也 就是说,可获取的数据顺序必是单调递增的。
    • 会话一致性(session consistency):任何用户在某次会话中,一旦读到某个数据在某次更新后的值,那么在本次会话中就不会再读到比这值更旧的值会话一致性是在单调一致性的基础上进一步放松约束,只保证单个用户单个会话内的单调性,在不同用户或同一用户不同会话间则没有保障。
    • 最终一致性(eventual consistency):用户只能读到某次更新后的值,但系统保证数据将最终达到完全一致的状态,只是所需时间不能保障。
    • 弱一致性(weak consistency):用户无法在确定时间内读到最新更新的值。
  • 可用性(A):负载过大后,集群整体是否还能响应客户端的读写请求。(服务一直可用,而且是正常响应时间)

  • 分区容错性(P):分区容错性,就是高可用性,一个节点崩了,并不影响其它的节点(100个节点,挂了几个,不影响服务,越多机器越好)

CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容错性是我们必须需要实现的,所以我们只能在一致性可用性之间进行权衡。

cap.jpg

win、linux双环境安装zookeeper

  • 下载zookeeper https://zookeeper.apache.org/
  • 解压下载好的zk
  • 重命名conf目录下的zoo_sample.cfg 文件为zoo.cfg 并修改里面的内容
# 心跳时间
tickTime=2000
# 1o指的是tickTime*10,也就是20s,Follower和Leader之间初始化能容忍的最长时间
initLimit=10
# Follower和Leader之间能容忍的最大失败数,此时是5次
syncLimit=5
# 如果linux则是linux对应的地址
# dataDir=/usr/local/zookeeper-3.4.12/data
dataDir=D:\Soft\zookeeper-3.4.12\data
clientPort=2181
  • 直接运行bin目录下的zkServer,单一实例便启动了
./zkServer.sh start
  • 配置文件主要配置项
    • tickTime​ 心跳基本时间单位,毫秒级,ZK基本上所有的时间都是这个时间的整数倍。
    • initLimit​ 集群中的follower服务器(F)与leader服务器(L)之间初始连接时能容忍的最多心跳数
    • syncLimit​ 集群中的follower服务器与leader服务器之间请求和应答之间能容忍的最多心跳数
    • dataDir​ 内存数据库快照存放地址,如果没有指定事务日志存放地址(dataLogDir),默认也是存放在这个 路径下,建议两个地址分开存放到不同的设备上。

centos下安装zk

  • tar -zxvf zookeeper-3.4.12.tar.gz -C /usr/local/
  • cd /usr/local/zookeeper-3.4.12/conf
  • mv zoo_sample.cfg zoo.cfg
  • vim zoo.cfg
  • 修改dataDir /usr/local/zookeeper-3.4.12/data
  • 新增一个用户名为zookeeper : useradd zookeeper
  • 将整个文件夹所属权赋予zookeeper用户

chown -R zookeeper:zookeeper /usr/local/zookeeper-3.4.12

  • 切换到zookeeper用户 : su zookeeper
  • cd /usr/local/zookeeper-3.4.12/bin 找到对应的zkServer.sh启动
./zkServer.sh start

ZooKeeper数据模型

  • zookeeper的数据模型类似于linux下的文件目录
  • /usr/local /usr/local/tomcat
  • 每一个节点都叫做zNode,可以有子节点,也可以有数据
  • 每个节点都能设置相应的权限控制用户的访问
  • 每个节点存储的数据不宜过大
  • 每个节点都带有一个版本号,数据变更时,版本号变更(乐观锁)
  • 节点分永久节点跟临时节点


    zookeeper数据模型.png

常用命令之zkCli

  • zkCli.sh/cmd :不填后面的参数,默认连接的就是localhost:2181
  • win下面运行的是.cmd结尾的文件,linux下运行的是.sh结尾的文件
  • 连接远程服务器
zkCli.sh -timeout 0 -r -server ip:port

.\zkCli.cmd -timeout 5000 -server 192.168.40.100:2181

//验证是否开启成功
stat /
  • 帮助信息 : ./zkCli.sh h
ZooKeeper -server host:port cmd args​ 
stat path [watch]​ 
set path data [version]​ 
ls path [watch]​ 
delquota [-n|-b] 
path​ ls2 path [watch]​ 
setAcl path 
acl​ setquota -n|-b val path​ history​ redo cmdno​ printwatches on|off​ 
delete path [version]​ 
sync path​ listquota path​ 
rmr path​ get path [watch]​
create [-s] [-e] path data 
acl​ addauth scheme auth​ quit​ 
getAcl path​ close​ connect host:port
  • zookeeper节点

创建节点 create [-s] [-e] path data acl -s表示创建顺序节点 -e表示创建临时节点 data表示创建的节点的数据内容

  • 获取节点的子节点 ls path
  • 获取节点的数据 get path
  • 查看节点状态 stat path
cZxid = 0x3 --事务id​ 
ctime = Tue Dec 04 10:37:58 EST 2018 --节点创建的时候的时间​ mZxid = 0x3 --最后一次更新时的事务id​ 
mtime = Tue Dec 04 10:37:58 EST 2018 最后一次更新的时间​ 
pZxid = 0x3 该节点的子节点列表最后一次被修改的事务id​ 
cversion = 0 子节点列表的版本​ 
# 例如更新数据:加版本信息的话,必须dataVersion`相等`才能更新数据,不加版本信息则自动变化
dataVersion = 0 数据内容的版本​ 
aclVersion = 0 acl版本​ 
ephemeralOwner = 0x0 用于临时节点,表示创建该临时节点的事务id,如果当前的节点不是临时节点,该字段值为0​ 
dataLength = 8 数据内容的长度​ 
numChildren = 0 子节点的数量
  • 获取节点的子节点以及当前节点的状态 ls2 path
  • 修改节点的数据 set path data [version]
  • 删除节点数据

delete path [version] 删除节点,如果此时该节点有子节点,则不允许删除

  • rmr path 递归删除整个节点

zookeeper session机制

  • 用于客户端与服务端之间的连接,可设置超时时间,通过心跳包的机制(客户端向服务端ping包请求)检查心跳结束,session就过期
  • session过期的时候,该session创建的所有临时节点都会被抛弃


    zookeeper session机制.png

zookeeper watcher机制

  • 节点的watcher操作 get stat

针对每一个节点的操作,都可以有一个监控者,当节点发生变化,会触发watcher事件 zk中watcher是一次性的,触发后立即销毁 所有有监控者的节点的变更操作都能触发watcher事件;watcher的一次性,只是针对zookeeper原生指令,不针对代码级别

  • 子节点的watcher操作(监控父节点,当父节点对应的子节点发生变更的时候,父节点上的watcher事件会被触发) ls ls2(ls /ls2后面更上watcher) 增删会触发、修改不会,如果子节点再去新增子节点,不会触发(也就是说,触发watcher事件一定是直系子节点)
    watcher机制.png

zookeeper的acl(access control lists)权限控制

  • 针对节点可以设置相关的读写等权限,目的是为了保证数据的安全性
  • 权限permissions可以指定不同的权限范围及角色

常用的命令

  • getAcl 获取节点权限
  • setAcl 设置节点权限
  • addauth 输入认证授权信息,注册时输入明文密码,但是在zk里,以加密的形式保存
//`设置或者获取权限`直接设置和获取如果提示不行,需要先输入下面一行,然后再设置和获取权限
addauth digest xdclass:xdclass
setAcl /xdclass auth:xdclass:xdclass:cdrwa

acl的组成 [scheme🆔permissions]

scheme 授权机制

  • world下只有一个id,也就是anyone,表示所有人 world:anyone:permissions
  • auth 代表认证登录,需要注册用户有权限才可以 auth:user:password:permissions
  • digest 需要密码加密才能访问 digest:username:BASE64(SHA1(password)):permissions(跟auth区别在于,auth明文,digest为密文)
  • ip ip:localhost:psermissions
  • super 代表超管,拥有所有的权限;
    打开zk目录下的/bin/zkServer.sh服务器脚本文件,找到如下一行:
nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"
加上 "-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="
此处后面的密文为密码加密后的
  • id:允许访问的用户
  • permissions:权限组合字符串
cdrwa
c create 创建子节点
d delete 删除子节点
r read 读取节点的数据
w write 写入数据
a admin 可管理的权限
cdrwa cdr cdw 
  • 栗子
设置访问控制:

方式一:(推荐)
1)增加一个认证用户
addauth digest 用户名:密码明文
eg. addauth digest user1:password1
2)设置权限
setAcl /path auth:用户名:密码明文:权限
eg. setAcl /test auth:user1:password1:cdrwa
3)查看Acl设置
getAcl /path

方式二:
setAcl /path digest:用户名:密码密文:权限

注:这里的加密规则是SHA1加密,然后base64编码。
  • acl的使用场景
    • 开发环境跟测试环境,使用acl就可以进行分离,开发者无权去操作测试的节点
    • 生产环境上控制指定ip的服务可以访问相关的节点

一致性协议之 ZAB

Zookeeper 设计的分布式一致性协议

  1. ZAB 协议全称:Zookeeper Atomic Broadcast(Zookeeper 原子广播协议)。
  2. Zookeeper 是一个为分布式应用提供高效且可靠的分布式协调服务。在解决分布式一致性方面,Zookeeper 并没有使用 Paxos ,而是采用了 ZAB 协议。
  3. ZAB 协议定义:ZAB 协议是为分布式协调服务 Zookeeper 专门设计的一种支持崩溃恢复原子广播协议。
  4. 基于该协议,Zookeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间数据一致性。
    1.jpg
  • 上图显示了 Zookeeper 如何处理集群中的数据。所有客户端写入数据都是写入到 主进程(称为 Leader)中,然后,由 Leader 复制到备份进程(称为 Follower)中。从而保证数据一致性。从设计上看,和 Raft 类似。

  • 那么复制过程又是如何的呢?复制过程类似 2PC,ZAB 只需要 Follower 有一半以上返回 Ack 信息就可以执行提交,大大减小了同步阻塞。也提高了可用性。

  • 简单介绍完,开始重点介绍 消息广播 和 崩溃恢复。整个 Zookeeper 就是在这两个模式之间切换。 简而言之,当 Leader 服务可以正常使用,就进入消息广播模式,当 Leader 不可用时,则进入崩溃恢复模式。

消息广播

ZAB 协议的消息广播过程使用的是一个原子广播协议,类似一个 二阶段提交过程。对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal,将其发送给所有 Follwer ,然后,根据所有 Follwer 的反馈,如果超过半数成功响应,则执行 commit 操作(先提交自己,再发送 commit 给所有 Follwer)。

  • 基本上,整个广播流程分为 3 步骤:

    • 将数据都复制到 Follwer 中


      2.jpg
    • 等待 Follwer 回应 Ack,最低超过半数即成功


      3.jpg
    • 当超过半数成功回应,则执行 commit ,同时提交自己


      4.jpg

通过以上 3 个步骤,就能够保持集群之间数据的一致性。实际上,在 Leader 和 Follwer 之间还有一个消息队列,用来解耦他们之间的耦合,避免同步,实现异步解耦。

  • 还有一些细节:
  1. Leader 在收到客户端请求之后,会将这个请求封装成一个事务,并给这个事务分配一个全局递增的唯一 ID,称为事务ID(ZXID),ZAB 兮协议需要保证事务的顺序,因此必须将每一个事务按照 ZXID 进行先后排序然后处理。
  2. 在 Leader 和 Follwer 之间还有一个消息队列,用来解耦他们之间的耦合,解除同步阻塞。
  3. zookeeper集群中为保证任何所有进程能够有序的顺序执行,只能是 Leader 服务器接受写请求,即使是 Follower 服务器接受到客户端的请求,也会转发到 Leader 服务器进行处理。
  4. 实际上,这是一种简化版本的 2PC,不能解决单点问题。等会我们会讲述 ZAB 如何解决单点问题(即 Leader 崩溃问题)。

崩溃恢复

刚刚我们说消息广播过程中,Leader 崩溃怎么办?还能保证数据一致吗?如果 Leader 先本地提交了,然后 commit 请求没有发送出去,怎么办?

实际上,当 Leader 崩溃,即进入我们开头所说的崩溃恢复模式(崩溃即:Leader 失去与过半 Follwer 的联系)。下面来详细讲述。

假设1:Leader 在复制数据给所有 Follwer 之后崩溃,怎么办?
假设2:Leader 在收到 Ack 并提交了自己,同时发送了部分 commit 出去之后崩溃怎么办?

  • 针对这些问题,ZAB 定义了 2 个原则:
    1. ZAB 协议确保那些已经在 Leader 提交的事务最终会被所有服务器提交。
    2. ZAB 协议确保丢弃那些只在 Leader 提出/复制,但没有提交的事务。

所以,ZAB 设计了下面这样一个选举算法:能够确保提交已经被 Leader 提交的事务,同时丢弃已经被跳过的事务

针对这个要求,如果让 Leader 选举算法能够保证新选举出来的 Leader 服务器拥有集群总所有机器编号(即 ZXID 最大)的事务,那么就能够保证这个新选举出来的 Leader 一定具有所有已经提交的提案。
而且这么做有一个好处是:可以省去 Leader 服务器检查事务的提交和丢弃工作的这一步操作。


5.jpg

这样,我们刚刚假设的两个问题便能够解决。假设 1 最终会丢弃调用没有提交的数据,假设 2 最终会同步所有服务器的数据。这个时候,就引出了一个问题,如何同步?

数据同步

当崩溃恢复之后,需要在正式工作之前(接收客户端请求),Leader 服务器首先确认事务是否都已经被过半的 Follwer 提交了,即是否完成了数据同步。目的是为了保持数据一致。

当所有的 Follwer 服务器都成功同步之后,Leader 会将这些服务器加入到可用服务器列表中。

实际上,Leader 服务器处理或丢弃事务都是依赖着 ZXID 的,那么这个 ZXID 如何生成呢?

答:在 ZAB 协议的事务编号 ZXID 设计中,ZXID 是一个 64 位的数字,其中低 32 位可以看作是一个简单的递增的计数器,针对客户端的每一个事务请求,Leader 都会产生一个新的事务 Proposal 并对该计数器进行 + 1 操作。

而高 32 位则代表了 Leader 服务器上取出本地日志中最大事务 Proposal 的 ZXID,并从该 ZXID 中解析出对应的 epoch 值,然后再对这个值加一。

6.jpg

高 32 位代表了每代 Leader 的唯一性,低 32 代表了每代 Leader 中事务的唯一性。同时,也能让 Follwer 通过高 32 位识别不同的 Leader。简化了数据恢复流程。

基于这样的策略:当 Follower 链接上 Leader 之后,Leader 服务器会根据自己服务器上最后被提交的 ZXID 和 Follower 上的 ZXID 进行比对,比对结果要么回滚,要么和 Leader 同步。

zookeeper选举机制

zookeeper集群中的三种角色以及其各自的作用

  • leader:作为整个zk集群写请求的唯一处理者,并负责进行投票的发起和决议,更新系统的状态。
  • follower:接收客户端请求,处理读请求,并向客户端返回结果;将写请求转给 Leader;在选举 Leader过程中参与投票。
  • observer:可以理解为无选举投票权的 Flollower,其主要是为了协助 Follower 处理更多的读请求。如果 Zookeeper 集群的读请求负载很高,或者客户端非常非常多,多到跨机房,则可以设置一些 Observer 服务器,以提高读取的吞吐量。


    zk集群角色.png
observer作用.png

zk集群选举核心概念及选举时状态

  • myid

zk 集群中服务器的唯一标识,称为 myid。例如,有三个zk服务器,那么编号分别是 1,2,3。

  • zxid
​
高位              低位
0000000000000000  0000000000000000
​
                    epoch                                 xid
00000000000000000000000000000000   00000000000000000000000000000000
zxid 为 Long 类型,其中高 32 位表示 epoch,低 32 位表示 xid。即 zxid 由两部分构成:epoch 与 xid。 
每个 Leader 都会具有一个不同的 epoch 值,表示一个时期、时代;在选举还没成功的时候,所有节点zxid是相同的。新的 Leader 产生,则会更新所有 zkServer 的 zxid 中的 epoch。 而 xid 则为 zk 的事务 id,每一个写操作都是一个事务,都会有一个 xid。每一个写操作都需要由 Leader 发起一个提议,由所有 Follower 表决是否同意本次写操作。
  • 逻辑时钟

逻辑时钟,Logicalclock,是一个整型数,该概念在选举时称为 logicalclock,而在 zxid 中则为 epoch 的值。即 epoch 与 logicalclock 是同一个值,在不同情况下的不同名称。

  • zk的选举状态
  • LOOKING,选举状态(查找 Leader 的状态)。
  • LEADING,领导者状态。处于该状态的服务器称为 Leader。
  • FOLLOWING,随从状态,同步 leader 状态。处于该状态的服务器称为 Follower。
  • OBSERVING,观察状态,同步 leader 状态。处于该状态的服务器称为 Observer。

zk集群选举发生的时机及选举算法

  • 发生时机:整个集群群龙无首的时候(1.服务启动 2.leader宕机之后)
  • 选举机制:集群中,半数zkServer同意,则产生新的leader(搭建集群时,一般都是奇数个)例如:三台服务器,最多允许一台宕机,四台服务器,也是最多允许一台宕机
  • 选举算法:
    对比(myid,zxid),先对比zxid,zxid大者(大表示数据越新)胜出,成为leader,如果zxid一致,则myid大者成为leader


    三种模式.png
选举发生的时机及其选举算法.png

zk集群搭建

  • 端口的作用
    • 2181 对client端提供服务
    • 2888 集群内及其通讯使用的端口
    • 3888 集群选举leader
# 心跳时间
tickTime=2000
# 1o指的是tickTime*10,也就是20s,Follower和Leader之间初始化能容忍的最长时间
initLimit=10
# Follower和Leader之间能容忍的最大失败数,此时是5次
syncLimit=5
dataDir=D:\Soft\zookeeper-3.4.12\data
clientPort=2181
server.1=192.168.40.100:2888:3888 
server.2=192.168.40.101:2888:3888 
server.3=192.168.40.102:2888:3888
  • 在zk的根目录下,新建一个data目录,并在data目录下新增一个myid的文件(myid中内容是1,2,3这种)
  • 三台服务器,分别新增一个叫做zookeeper的用户 useradd zookeeper
  • 将修改好配置的zk,分别放到三台服务器的/usr/local/,并将目录权限改为zookeeper用户
  • chown -R zookeeper:zookeeper zookeeper-3.4.12/
  • 注意:三台服务器,均修改/usr/local/zookeeper-3.4.12/data/目录里的myid文件,文件内容是一个数字,对应server.1=192.168.40.100:2888:3888 里的1
  • 关闭防火墙

systemctl stop firewalld.service

  • 如果不关闭防火墙可以通过以下方式
添加防火墙策略,允许2181端口可用,吧几个所需端口都添加进策略即可
firewall-cmd --zone=public --add-port=2181/tcp --permanent
防火墙重新加载
firewall-cmd --reload
  • 依次启动 ./zkServer.sh start
  • 三台机子均启动完成之后,可以使用zkServer.sh status去查看状态

查看是leader还是follower

分布式锁

  • 为什么要有分布式锁

分布式服务中,如果各个服务节点需要去竞争资源,没办法使用单机多线程中JDK自带的锁,故此时需要分布式锁来协调

  • 企业中有哪些常见的手段来实现分布式锁

zookeeper、redis、memcache

  • 分布式锁的原理
    • zookeeper:去创建相应的节点,创建成功,则表示获取到相应的锁,创建失败(如果已经存在则创建失败),则表示获取锁失败
    • redis、memcache:对应的去设置一个值做为锁的一标志,每次获取锁的时候,判断对应的值是否存在,存在则无法获取,不存在,则设置相应的值,表示获取到锁。(redis 使用setnx,memcache使用add)
  • 创建锁时候,一定要创建临时节点,避免应用获取到锁后,宕机,导致锁一直被持有
  • 如果是单一应用,尽量不要使用分布式锁,毕竟jdk本身锁性能更高
  • zk原生api不支持递归创建节点


    基于zk实现分布式锁的多种方式.png

案例代码

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.13</version>
</dependency>
package com.wiggin.lock;

import org.apache.zookeeper.*;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;

import static org.apache.zookeeper.CreateMode.EPHEMERAL;
import static org.apache.zookeeper.ZooDefs.Ids.OPEN_ACL_UNSAFE;

/**
 * 基于zk实现自定义分布式锁
 */
public class ZkLock {

    private ZooKeeper zooKeeper;

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    private ZkLock() {
        try {
            zooKeeper = new ZooKeeper("192.1.1.101:2181,192.1.1.102:2181,192.1.1.103:2181", 5000, new ZkWatcher());
            System.out.println(zooKeeper.getState());
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("与zk建立连接=====>"+zooKeeper.getState());

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static ZkLock getInstance() {
        return Singleton.getInstance();
    }

    private class ZkWatcher implements Watcher {
        @Override
        public void process(WatchedEvent event) {
            //当连接zk成功会触发这个回调,再判断下状态(因为其他监听也可能触发),调用countDown
            //则上面的countDownLatch.await(); 就会放行
            System.out.println("接收到监听事件=====》"+event);
            if (Event.KeeperState.SyncConnected == event.getState()) {
                countDownLatch.countDown();
            }
        }
    }


    public void lock(Integer id) {
        //注释地方是递归节点,但是zk原生api不支持递归创建,所以如果使用会抛出异常,通过catch内部打印可知
        // String path = "/xdclass/product/lock/" + id;
        String path = "/xdclass-product-lock-" + id;
        //创建临时节点,如果创建成功的话,就表示获取锁,如果失败,则不断尝试
        try {
            //路径,内容,代表不设置acl权限,零时节点
            zooKeeper.create(path,"".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
            System.out.println("成功获取到锁");
        } catch (Exception e) {
            while (true) {
                try {
                    Thread.sleep(500L);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                try {
                    zooKeeper.create(path,"".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                } catch (Exception e1) {
                    continue;
                }
                break;
            }
        }
    }

    /**
     * 释放锁,直接删除zk节点
     * @param id
     */
    public void unLock(Integer id) {
        String path = "/xdclass-product-lock-" + id;
        try {
            zooKeeper.delete(path,-1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }


    private static class Singleton {

        private static ZkLock instance;
        static {
            instance = new ZkLock();
        }

        private static ZkLock getInstance() {
            return instance;
        }

    }
}
package com.wiggin.lock;


import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程不安全操作代码实例
 */
public class UnSafeThread {

    private static int num = 0;

    private static CountDownLatch countDownLatch = new CountDownLatch(10);
    private static ZkLock lock = ZkLock.getInstance();

    /**y
     * 每次调用对num进行++操作
     */
    public static void inCreate() {
        lock.lock(1);
        num++;
        System.out.println(num);
        lock.unLock(1);
    }

    public static void test() {
        System.out.println(Thread.currentThread().getName());
    }


    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    inCreate();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //每个线程执行完成之后,调用countdownLatch
                countDownLatch.countDown();
            }).start();
        }

        while (true) {
            if (countDownLatch.getCount() == 0) {
                System.out.println(num);
                break;
            }
        }
    }
}

dubbo核心架构及流程

1.jpg
节点 角色说明
Provider 暴露服务的服务提供方
Consumer 调用远程服务的服务消费方
Registry 服务注册与发现的注册中心
Monitor 统计服务的调用次数和调用时间的监控中心
Container 服务运行容器

调用关系说明

  • 服务容器负责启动,加载,运行服务提供者。
  • 服务提供者在启动时,向注册中心注册自己提供的服务。
  • 服务消费者在启动时,向注册中心订阅自己所需的服务。
  • 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  • 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

dubbo常见的多种开发方式

  • 通过注解的方式(多用于与springboot整合)
  • 通过xml配置
  • 通过api的方式----日常开发中比较少见

配置相关

虽然整合springboot时候可以通过注解方式,不过因为历史原因以及注解支持力度不足,所以一般还是通过配置文件xml形式开发。注意:dubbo可以单独使用,不整合spring等,只是配置较为复杂而已。

创建多模块项目

  • 首先正常创建maven项目
  • 删除src文件夹
  • 在该项目下面创建module
  • 分别创建user-api(都是interface,然后本身作为其他项目的依赖),
  • user-service(服务提供者),user-web(服务消费者)三个module
user-service的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>dubbo</artifactId>
        <groupId>com.tjsmc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-service</artifactId>

    <properties>
        <spring-boot.version>2.1.1.RELEASE</spring-boot.version>
        <dubbo.version>2.7.0</dubbo.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Aapche Dubbo  -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-dependencies-bom</artifactId>
                <version>${dubbo.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo</artifactId>
                <version>${dubbo.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>javax.servlet</groupId>
                        <artifactId>servlet-api</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>log4j</groupId>
                        <artifactId>log4j</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.tjsmc</groupId>
            <artifactId>user-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>

        <!-- Zookeeper -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
        </dependency>
    </dependencies>
user-web的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>dubbo</artifactId>
        <groupId>com.tjsmc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-web</artifactId>

    <properties>
        <spring-boot.version>2.1.1.RELEASE</spring-boot.version>
        <dubbo.version>2.7.0</dubbo.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Aapche Dubbo  -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-dependencies-bom</artifactId>
                <version>${dubbo.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo</artifactId>
                <version>${dubbo.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>javax.servlet</groupId>
                        <artifactId>servlet-api</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>log4j</groupId>
                        <artifactId>log4j</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <dependency>
            <groupId>com.tjsmc</groupId>
            <artifactId>user-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!-- Dubbo Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>2.7.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Zookeeper -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
        </dependency>
    </dependencies>
</project>
项目的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.tjsmc</groupId>
    <artifactId>dubbo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>user-api</module>
        <module>user-service</module>
        <module>user-web</module>
    </modules>

    <build>
        <!--为了解决默认使用jdk1.5当作版本的提示错误信息-->
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

基于注解的案例

user-api
UserService.java

package com.xdclass.user.service;
public interface UserService {
    String sayHello();
}
user-service
ServiceApplication.java


package com.xdclass.user.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

@EnableAutoConfiguration
public class ServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceApplication.class);
    }
}
user-service
UserService.java


package com.xdclass.user.service.impl;

import com.xdclass.user.service.UserService;
import org.apache.dubbo.config.annotation.Service;

@Service(version = "1.0.0")
public class UserServiceImpl implements UserService {
    public String sayHello() {
        return "hello";
    }
}

user-service
application.yml

spring:
  application:
    name: dubbo-auto-configuration-provider-demo

dubbo:
  scan:
    base-packages: com.xdclass.user.service.impl
  protocol:
    name: dubbo
    port: 12345
  registry:
    address: N/A
user-web
WebApplication.java

package com.xdclass.user;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebApplication.class);
    }
}
user-web
UserController.java

package com.xdclass.user.controller;

import com.xdclass.user.service.UserService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @Reference(version = "1.0.0", url = "dubbo://127.0.0.1:12345")
    private UserService userService;

    @RequestMapping("/sayHello")
    public String sayHello() {
        return userService.sayHello();
    }
}
user-web
application.yml

spring:
  application:
    name: dubbo-auto-configure-consumer-sample

之后的项目都使用xml文件形式配置

dubbo服务注册中心

  • simple 注册中心 ----不支持集群,不适用于生产环境
  • Multicast 注册中心 ----开发阶段使用
  • zookeeper 注册中心 ----目前企业中最常用,也是官方推荐
  • redis 注册中心 ----虽然支持,但是较少使用
  • nacos 注册中心 ----后起之秀

参考(https://nacos.io/zh-cn/docs/use-nacos-with-dubbo.html)

整合zk

Zookeeper 是 Apacahe Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并推荐使用


3.jpg

流程说明:

  • 服务提供者启动时: 向 /dubbo/com.foo.BarService/providers 目录下写入自己的 URL 地址
  • 服务消费者启动时: 订阅 /dubbo/com.foo.BarService/providers 目录下的提供者 URL * 地址。并向 /dubbo/com.foo.BarService/consumers 目录下写入自己的 URL 地址
  • 监控中心启动时: 订阅 /dubbo/com.foo.BarService 目录下的所有提供者和消费者 URL 地址。

支持以下功能:

  • 当提供者出现断电等异常停机时,注册中心能自动删除提供者信息
  • 当注册中心重启时,能自动恢复注册数据,以及订阅请求
  • 当会话过期时,能自动恢复注册数据,以及订阅请求
  • 当设置 <dubbo:registry check="false" /> 时,记录失败注册和订阅请求,后台定时重试
  • 可通过 <dubbo:registry username="admin" password="1234" /> 设置 zookeeper 登录信息
  • 可通过 <dubbo:registry group="dubbo" /> 设置 zookeeper 的根节点,不配置将使用默认的根节点。
  • 支持 * 号通配符 <dubbo:reference group="" version="" />,可订阅服务的所有分组和所有版本的提供者

官方文档

user-service:服务提供者

spring:
  application:
    name: dubbo-auto-configuration-provider-demo


dubbo:
  scan:
    base-packages: com.xdclass.user.service.impl
  protocol:
    name: dubbo
    port: 12345
  registry:
    address: zookeeper://127.0.0.1:2181
user-web:服务消费者
spring:
  application:
    name: dubbo-auto-configure-consumer-sample-terst
dubbo:
  registry:
    address: zookeeper://127.0.0.1:2181

配置如上则,@Reference(version = "1.0.0", url = "dubbo://127.0.0.1:12345")可以只留下@Reference(version = "1.0.0")

另外,不论服务消费还是提供者,都需要有个name代表项目唯一标记,例如上面的spring的name,也就是说如果springname没有则必须添加dubbo的name否则启动报错失败

dubbo使用xml的配置方式

  • 在服务提供方,新增provider.xml(resources目录下),在里面配置服务提供方相应的信息
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
    <dubbo:protocol name="dubbo" port="12345"/>
    <bean id="userService" class="com.xdclass.user.service.impl.UserServiceImpl"/>
    <dubbo:service interface="com.xdclass.user.service.UserService" ref="userService"/>
</beans>
  • 去掉具体服务实现类中@Service注解
  • 在启动类中新增@ImportResource("provider.xml")
  • 在服务消费方,新增consumer.xml的配置文件
  • 在启动类中新增@ImportResource("consumer.xml")
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
    <dubbo:reference id="userService" check="false" interface="com.xdclass.user.service.UserService"/>
</beans>
  • 使用spring的@autowire注解替代@Reference

启动依赖检查

  • Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true";例如消费者先启动,但是提供者还没启动,如果true则消费者启动报错,如果设置false,则

  • 可以通过 check="false"关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false",总是会返回引用,当服务恢复时,能自动连上。

  • 具体配置的方式

    • 针对特定某一服务去设置 @Reference(version = "1.0.0",check = false)
    • 在配置文件中进行配置(实际开发中,比较建议使用配置文件的配置方式)
    • 启动使用 -D 参数
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!--    <dubbo:registry group="aaa" address="zookeeper://127.0.0.1:2181"/>-->
    <dubbo:reference id="userService" timeout="3000" check="false" interface="com.tjsmc.user.service.UserService">
<!--        方法级别的优先级高于整个服务的优先级-->
        <dubbo:method name="sayHello" timeout="5000" retries="4"></dubbo:method>
    </dubbo:reference>
    <dubbo:reference id="fileService" timeout="3000" check="false" interface="com.tjsmc.user.service.FileService"/>
</beans>
  • 各种配置对比
    • dubbo.reference.check=false,强制改变所有 reference 的 check 值,就算配置中有声明,也会被覆盖。
    • dubbo.consumer.check=false,是设置 check 的缺省值,如果配置中有显式的声明,如:<dubbo:reference check="true"/>,不会受影响。
    • dubbo.registry.check=false,前面两个都是指订阅成功,但提供者列表是否为空是否报错,如果注册订阅失败时,也允许启动,需使用此选项,将在后台定时重试。

dubbo配置加载流程

配置来源

  • JVM System Properties,-D参数

-D dubbo.reference.com.xdclass.user.service.UserService.check=true

  • Externalized Configuration,外部化配置(2.7版本),基于dubbo-ops
  • ServiceConfig、ReferenceConfig等编程接口采集的配置
  • 本地配置文件dubbo.properties
  • 配置的优先级: 配置优先级是从上往下一次递减

dubbo超时机制及集群容错机制

  • 超时机制
    • 服务提供方进行配置
    • 服务消费方进行配置
      • 方法级别
      • 服务级别
  • 如果方法跟整个服务同时设置了超时时间,这个时候以方法设置的超时时间为准
  • 如果服务提供方跟服务消费方同时设置了超时时间,则以服务消费方为准

集群容错机制

当服务调用失败的时候,默认情况下,dubbo会进行重试,默认重试次数为2(不包含第一次),所以可见被调用三次;通过retries="4" 设置重试次数

注意:集群容错如果设置,则会优先,而重试次数反而不会执行。集群容错类型

<dubbo:service interface="com.tjsmc.user.service.UserService" ref="userService" cluster="failsafe"/>

cluster="failsafe"就是集群容错的一种方式

容错简介

在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试


2.jpg

各节点关系:

  • 这里的 Invoker 是 Provider 的一个可调用 Service 的抽象,Invoker 封装了 Provider 地址及 Service 接口信息
  • Directory 代表多个 Invoker,可以把它看成 List<Invoker> ,但与 List 不同的是,它的值可能是动态变化的,比如注册中心推送变更
  • Cluster 将 Directory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个
  • Router 负责从多个 Invoker 中按路由规则选出子集,比如读写分离,应用隔离等
  • LoadBalance 负责从多个 Invoker中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选

集群容错模式

  • Failover Cluster

失败自动切换,当出现失败,重试其它服务器 [1]。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。

重试次数配置如下(服务级别和方法级别):

<dubbo:service retries="2" />

<dubbo:reference>
    <dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
  • Failfast Cluster

快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

  • Failsafe Cluster

失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

  • Failback Cluster

失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

  • Forking Cluster

并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。

  • Broadcast Cluster

广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

集群模式配置

按照以下示例在服务提供方和消费方配置集群模式

<dubbo:service cluster="failsafe" />

<dubbo:reference cluster="failsafe" />

dubbo各协议及多协议配置

  • 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-util</artifactId>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-server</artifactId>
</dependency>
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-servlet</artifactId>
</dependency>
  • 配置多协议

provider.xml文件中新增想要的协议

<dubbo:protocol name="http" port="8888"/>

暴露服务时使用 protocol="http" 让其使用特性的协议暴露

基本实例:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <dubbo:registry id="local" address="zookeeper://127.0.0.1:2181"/>
    <dubbo:protocol name="dubbo" port="12345"/>
<!--    地处声明新协议-->
    <dubbo:protocol name="http" port="8888"/>
    <bean id="userService" class="com.tjsmc.user.service.impl.UserServiceImpl"/>
    <bean id="fileService" class="com.tjsmc.user.service.impl.FileServiceImpl"/>
<!--    暴漏给多个注册中心则用逗号隔开id即可-->
    <dubbo:service interface="com.tjsmc.user.service.UserService" ref="userService" cluster="failsafe" registry="local"/>
<!--    此处使用指定协议,不指定默认dubbo-->
    <dubbo:service interface="com.tjsmc.user.service.FileService" ref="fileService" protocol="http"/>
</beans>
  • 修改启动类的方式为非web

new SpringApplicationBuilder(ServiceApplication.class).web(WebApplicationType.NONE).run();

多注册中心及其配置

Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的

  • 新增<dubbo:registry address="zookeeper://192.1.1.101:2181"/>
  • 多个注册中心使用id属性进行区分 id="remote",服务暴露时,多个注册中心使用逗号分隔

多注册中心注册

比如:中文站有些服务来不及在青岛部署,只在杭州部署,而青岛的其它应用需要引用此服务,就可以将服务同时注册到两个注册中心。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <dubbo:application name="world"  />
    <!-- 多注册中心配置 -->
    <dubbo:registry id="hangzhouRegistry" address="10.20.141.150:9090" />
    <dubbo:registry id="qingdaoRegistry" address="10.20.141.151:9010" default="false" />
    <!-- 向多个注册中心注册 -->
    <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" registry="hangzhouRegistry,qingdaoRegistry" />
</beans>

不同服务使用不同注册中心

比如:CRM 有些服务是专门为国际站设计的,有些服务是专门为中文站设计的。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <dubbo:application name="world"  />
    <!-- 多注册中心配置 -->
    <dubbo:registry id="chinaRegistry" address="10.20.141.150:9090" />
    <dubbo:registry id="intlRegistry" address="10.20.154.177:9010" default="false" />
    <!-- 向中文站注册中心注册 -->
    <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" registry="chinaRegistry" />
    <!-- 向国际站注册中心注册 -->
    <dubbo:service interface="com.alibaba.hello.api.DemoService" version="1.0.0" ref="demoService" registry="intlRegistry" />
</beans>

多注册中心引用

比如:CRM 需同时调用中文站和国际站的 PC2 服务,PC2 在中文站和国际站均有部署,接口及版本号都一样,但连的数据库不一样。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <dubbo:application name="world"  />
    <!-- 多注册中心配置 -->
    <dubbo:registry id="chinaRegistry" address="10.20.141.150:9090" />
    <dubbo:registry id="intlRegistry" address="10.20.154.177:9010" default="false" />
    <!-- 引用中文站服务 -->
    <dubbo:reference id="chinaHelloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" registry="chinaRegistry" />
    <!-- 引用国际站站服务 -->
    <dubbo:reference id="intlHelloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" registry="intlRegistry" />
</beans>

如果只是测试环境临时需要连接两个不同注册中心,使用竖号分隔多个不同注册中心地址:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
    <dubbo:application name="world"  />
    <!-- 多注册中心配置,竖号分隔表示同时连接多个不同注册中心,同一注册中心的多个集群地址用逗号分隔 -->
    <dubbo:registry address="10.20.141.150:9090|10.20.154.177:9010" />
    <!-- 引用服务 -->
    <dubbo:reference id="helloService" interface="com.alibaba.hello.api.HelloService" version="1.0.0" />
</beans>

Dubbo服务分组及其配置

当一个接口有多种实现时,可以用 group 区分;服务提供者,在标签里加多个group进行区分

<dubbo:service group="user1" interface="com.xdclass.user.service.UserService" ref="userService" /> 
<dubbo:service group="user2" interface="com.xdclass.user.service.UserService" ref="userService2" />

服务消费者在引用的时候,也在标签里加group

<dubbo:reference group="user2" id="userService" check="false" interface="com.xdclass.user.service.UserService"/>

任意组

<dubbo:reference id="barService" interface="com.foo.BarService" group="*" />

Dubbo服务多版本化及其配置

当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。

可以按照以下的步骤进行版本迁移:

  • 在低压力时间段,先升级一半提供者为新版本
  • 再将所有消费者升级为新版本
  • 然后将剩下的一半提供者升级为新版本

老版本服务提供者配置:

<dubbo:service interface="com.foo.BarService" version="1.0.0" />

新版本服务提供者配置:

<dubbo:service interface="com.foo.BarService" version="2.0.0" />

老版本服务消费者配置:

<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />

新版本服务消费者配置:

<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />

如果不需要区分版本,可以按照以下的方式配置:

<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />

新版管控台dubbo-ops的编译安装

旧版:dubbo-admin 新版 dubbo-ops

新版管理控制台主要的作用

  • 服务查询,服务治理(包括Dubbo2.7中新增的治理规则)以及服务测试、配置管理

  • 步骤

克隆项目到本地,并编译安装和启动

git clone https://github.com/apache/incubator-dubbo-ops.git

cd incubator-dubbo-ops

mvn clean package

修改配置文件 dubbo-admin-server/src/main/resources/application-production.properties中的注册中心的地址

cd dubbo-distribution/target

java -jar dubbo-admin-0.1.jar

启动完成后,直接访问http://localhost:8080

动态配置中心及配置外部化

  • 动态配置中心的责职

外部化配置,启动配置的集中式存储,简单理解为dubbo.properties的外部化存储, 服务治理,服务治理规则的存储与通知。

  • 如何使用?
在dubbo-ops 进行配置信息配置
在项目里使用<dubbo:config-center address="zookeeper://127.0.0.1:2181"/>
配置中心中的配置的作用域

如果全局配置跟应用级别均配置了相同的信息,这个时候以应用级别的为准

元数据中心及服务测试

  • dubbo provider中的服务配置项有接近30个配置项。 排除注册中心服务治理需要之外,很大一部分配置项是provider自己使用,不需要透传给消费者。这部分数据不需要进入注册中心,而只需要以key-value形式持久化存储。 dubbo consumer中的配置项也有20+个配置项。在注册中心之中,服务消费者列表中只需要关注application,version,group,ip,dubbo版本等少量配置,其他配置也可以以key-value形式持久化存储。 这些数据是以服务为维度注册进入注册中心,导致了数据量的膨胀,进而引发注册中心(如zookeeper)的网络开销增大,性能降低。
  • 除了上述配置项的存储之外,dubbo服务元数据信息也需要被存储下来。元数据信息包括服务接口,及接口的方法信息。这些信息将被用于服务mock,服务测试。
  • 官方文档