mercyblitz

mercyblitz 查看完整档案

杭州编辑长沙学院  |  数信系 编辑知名互联网  |  半个程序员 编辑 github.com/mercyblitz 编辑
编辑

小马哥,Java 劝退师,《Spring Boot 编程思想》作者,Apache 和 Spring Cloud 等知名开源架构成员,点击查看详情。(交流QQ群:719291662

最新发布:
Spring Boot 2.0深度实践之核心技术篇
Java 微服务实践系列课堂
「一入 Java 深似海 」系列课程

个人动态

mercyblitz 关注了用户 · 2020-12-25

码邦德 @mr_zan

与其感慨路难行,不如马上出发

关注 0

mercyblitz 关注了标签 · 2020-09-07

关注 19

mercyblitz 关注了标签 · 2020-09-02

关注 4261

mercyblitz 收藏了文章 · 2020-09-02

ZooKeeper的十二连问,你顶得了嘛?

前言

一线大厂ZooKeeper的十二连问,你顶得了嘛?

本文已经收录到github

https://github.com/whx123/Jav...

1. 面试官:工作中使用过Zookeeper嘛?你知道它是什么,有什么用途呢?

小菜鸡的我:

  • 有使用过的,使用ZooKeeper作为dubbo的注册中心,使用ZooKeeper实现分布式锁
  • ZooKeeper,它是一个开放源码的分布式协调服务,它是一个集群的管理者,它将简单易用的接口提供给用户。
  • 可以基于Zookeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能
  • Zookeeper的用途:命名服务、配置管理、集群管理、分布式锁、队列管理

用途跟功能不是一个意思咩?给我一个眼神,让我自己体会

2. 面试官:说下什么是命名服务,什么是配置管理,又什么是集群管理吧

小菜鸡的我(幸好我刷过面试题),无所畏惧

    • 命名服务就是
    命名服务是指通过指定的名字来获取资源或者服务地址。Zookeeper可以创建一个全局唯一的路径,这个路径就可以作为一个名字。被命名的实体可以是集群中的机器,服务的地址,或者是远程的对象等。一些分布式服务框架(RPC、RMI)中的服务地址列表,通过使用命名服务,客户端应用能够根据特定的名字来获取资源的实体、服务地址和提供者信息等。
    • 配置管理:
    实际项目开发中,我们经常使用.properties或者xml需要配置很多信息,如数据库连接信息、fps地址端口等等。因为你的程序一般是分布式部署在不同的机器上(如果你是单机应用当我没说),如果把程序的这些配置信息保存在zk的znode节点下,当你要修改配置,即znode会发生变化时,可以通过改变zk中某个目录节点的内容,利用watcher通知给各个客户端,从而更改配置。
    • 集群管理
    集群管理包括集群监控和集群控制,其实就是监控集群机器状态,剔除机器和加入机器。zookeeper可以方便集群机器的管理,它可以实时监控znode节点的变化,一旦发现有机器挂了,该机器就会与zk断开连接,对用的临时目录节点会被删除,其他所有机器都收到通知。新机器加入也是类似酱紫,所有机器收到通知:有新兄弟目录加入啦。

    3. 面试官:你提到了znode节点,那你知道znode有几种类型呢?zookeeper的数据模型是怎样的呢?

    小菜鸡的我(我先想想):

    zookeeper的数据模型

    ZooKeeper的视图数据结构,很像Unix文件系统,也是树状的,这样可以确定每个路径都是唯一的。zookeeper的节点统一叫做znode,它是可以通过路径来标识,结构图如下:

    znode的4种类型

    根据节点的生命周期,znode可以分为4种类型,分别是持久节点(PERSISTENT)、持久顺序节点(PERSISTENT_SEQUENTIAL)、临时节点(EPHEMERAL)、临时顺序节点(EPHEMERAL_SEQUENTIAL)

    • 持久节点(PERSISTENT)
    这类节点被创建后,就会一直存在于Zk服务器上。直到手动删除。
    • 持久顺序节点(PERSISTENT_SEQUENTIAL)
    它的基本特性同持久节点,不同在于增加了顺序性。父节点会维护一个自增整性数字,用于子节点的创建的先后顺序。
    • 临时节点(EPHEMERAL)
    临时节点的生命周期与客户端的会话绑定,一旦客户端会话失效(非TCP连接断开),那么这个节点就会被自动清理掉。zk规定临时节点只能作为叶子节点。
    • 临时顺序节点(EPHEMERAL_SEQUENTIAL)
    基本特性同临时节点,添加了顺序的特性。

    4、面试官:你知道znode节点里面存储的是什么吗?每个节点的数据最大不能超过多少呢?

    小菜鸡的我:

    znode节点里面存储的是什么?

    Znode数据节点的代码如下

    public class DataNode implements Record {
        byte data[];                    
        Long acl;                       
        public StatPersisted stat;       
        private Set<String> children = null; 
    }

    哈哈,Znode包含了存储数据、访问权限、子节点引用、节点状态信息,如图:

    • data: znode存储的业务数据信息
    • ACL: 记录客户端对znode节点的访问权限,如IP等。
    • child: 当前节点的子节点引用
    • stat: 包含Znode节点的状态信息,比如事务id、版本号、时间戳等等。

    每个节点的数据最大不能超过多少呢

    为了保证高吞吐和低延迟,以及数据的一致性,znode只适合存储非常小的数据,不能超过1M,最好都小于1K。

    5、面试官:你知道znode节点上的监听机制嘛?讲下Zookeeper watch机制吧。

    小菜鸡的我:

    • Watcher机制
    • 监听机制的工作原理
    • Watcher特性总结

    Watcher监听机制

    Zookeeper 允许客户端向服务端的某个Znode注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher通知状态和事件类型做出业务上的改变。

    可以把Watcher理解成客户端注册在某个Znode上的触发器,当这个Znode节点发生变化时(增删改查),就会触发Znode对应的注册事件,注册的客户端就会收到异步通知,然后做出业务的改变。

    Watcher监听机制的工作原理

    • ZooKeeper的Watcher机制主要包括客户端线程、客户端 WatcherManager、Zookeeper服务器三部分。
    • 客户端向ZooKeeper服务器注册Watcher的同时,会将Watcher对象存储在客户端的WatchManager中。
    • 当zookeeper服务器触发watcher事件后,会向客户端发送通知, 客户端线程从 WatcherManager 中取出对应的 Watcher 对象来执行回调逻辑。

    Watcher特性总结

    • 一次性:一个Watch事件是一个一次性的触发器。一次性触发,客户端只会收到一次这样的信息。
    • 异步的: Zookeeper服务器发送watcher的通知事件到客户端是异步的,不能期望能够监控到节点每次的变化,Zookeeper只能保证最终的一致性,而无法保证强一致性。
    • 轻量级: Watcher 通知非常简单,它只是通知发生了事件,而不会传递事件对象内容。
    • 客户端串行: 执行客户端 Watcher 回调的过程是一个串行同步的过程。
    • 注册 watcher用getData、exists、getChildren方法
    • 触发 watcher用create、delete、setData方法

    6、面试官:你对Zookeeper的数据结构都有一定了解,那你讲下Zookeeper的特性吧

    小菜鸡的我:(我背过书,啊哈哈)

    Zookeeper 保证了如下分布式一致性特性:

    • 顺序一致性:从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
    • 原子性:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
    • 单一视图:无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
    • 可靠性: 一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下来。
    • 实时性(最终一致性): Zookeeper 仅仅能保证在一定的时间段内,客户端最终一定能够从服务端上读取到最新的数据状态。

    7、面试官:你刚提到顺序一致性,那zookeeper是如何保证事务的顺序一致性的呢?

    小菜鸡的我:(完蛋了这题不会)

    这道题可以看下这篇文章(本题答案来自该文章):聊一聊ZooKeeper的顺序一致性

    需要了解事务ID,即zxid。ZooKeeper的在选举时通过比较各结点的zxid和机器ID选出新的主结点的。zxid由Leader节点生成,有新写入事件时,Leader生成新zxid并随提案一起广播,每个结点本地都保存了当前最近一次事务的zxid,zxid是递增的,所以谁的zxid越大,就表示谁的数据是最新的。

    ZXID的生成规则如下:

    ZXID有两部分组成:

    • 任期:完成本次选举后,直到下次选举前,由同一Leader负责协调写入;
    • 事务计数器:单调递增,每生效一次写入,计数器加一。
    ZXID的低32位是计数器,所以同一任期内,ZXID是连续的,每个结点又都保存着自身最新生效的ZXID,通过对比新提案的ZXID与自身最新ZXID是否相差“1”,来保证事务严格按照顺序生效的。

    8、面试官:你提到了Leader,你知道Zookeeper的服务器有几种角色嘛?Zookeeper下Server工作状态又有几种呢?

    小菜鸡的我:

    Zookeeper 服务器角色

    Zookeeper集群中,有Leader、Follower和Observer三种角色

    Leader

    Leader服务器是整个ZooKeeper集群工作机制中的核心,其主要工作:
    • 事务请求的唯一调度和处理者,保证集群事务处理的顺序性
    • 集群内部各服务的调度者

    Follower

    Follower服务器是ZooKeeper集群状态的跟随者,其主要工作:
    • 处理客户端非事务请求,转发事务请求给Leader服务器
    • 参与事务请求Proposal的投票
    • 参与Leader选举投票

    Observer

    Observer是3.3.0 版本开始引入的一个服务器角色,它充当一个观察者角色——观察ZooKeeper集群的最新状态变化并将这些状态变更同步过来。其工作:
    • 处理客户端的非事务请求,转发事务请求给 Leader 服务器
    • 不参与任何形式的投票

    Zookeeper下Server工作状态

    服务器具有四种状态,分别是 LOOKING、FOLLOWING、LEADING、OBSERVING。
    • 1.LOOKING:寻找Leader状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。
    • 2.FOLLOWING:跟随者状态。表明当前服务器角色是Follower。
    • 3.LEADING:领导者状态。表明当前服务器角色是Leader。
    • 4.OBSERVING:观察者状态。表明当前服务器角色是Observer。

    9、面试官:你说到服务器角色是基于ZooKeeper集群的,那你画一下ZooKeeper集群部署图吧?ZooKeeper是如何保证主从节点数据一致性的呢?

    小菜鸡的我:

    ZooKeeper集群部署图


    ZooKeeper集群是一主多从的结构:

    • 如果是写入数据,先写入主服务器(主节点),再通知从服务器。
    • 如果是读取数据,既读主服务器的,也可以读从服务器的。

    ZooKeeper如何保证主从节点数据一致性

    我们知道集群是主从部署结构,要保证主从节点一致性问题,无非就是两个主要问题:

    • 主服务器挂了,或者重启了
    • 主从服务器之间同步数据~

    Zookeeper是采用ZAB协议(Zookeeper Atomic Broadcast,Zookeeper原子广播协议)来保证主从节点数据一致性的,ZAB协议支持崩溃恢复和消息广播两种模式,很好解决了这两个问题:

    • 崩溃恢复:Leader挂了,进入该模式,选一个新的leader出来
    • 消息广播: 把更新的数据,从Leader同步到所有Follower
    Leader服务器挂了,所有集群中的服务器进入LOOKING状态,首先,它们会选举产生新的Leader服务器;接着,新的Leader服务器与集群中Follower服务进行数据同步,当集群中超过半数机器与该 Leader服务器完成数据同步之后,退出恢复模式进入消息广播模式。Leader 服务器开始接收客户端的事务请求生成事务Proposal进行事务请求处理。

    10、面试官:Leader挂了,进入崩溃恢复,是如何选举Leader的呢?你讲一下ZooKeeper选举机制吧

    小菜鸡的我:

    服务器启动或者服务器运行期间(Leader挂了),都会进入Leader选举,我们来看一下~假设现在ZooKeeper集群有五台服务器,它们myid分别是服务器1、2、3、4、5,如图:

    服务器启动的Leader选举

    zookeeper集群初始化阶段,服务器(myid=1-5)依次启动,开始zookeeper选举Leader~

    • 服务器1(myid=1)启动,当前只有一台服务器,无法完成Leader选举
    • 服务器2(myid=2)启动,此时两台服务器能够相互通讯,开始进入Leader选举阶段
    1. 每个服务器发出一个投票
    服务器1 和 服务器2都将自己作为Leader服务器进行投票,投票的基本元素包括:服务器的myid和ZXID,我们以(myid,ZXID)形式表示。初始阶段,服务器1和服务器2都会投给自己,即服务器1的投票为(1,0),服务器2的投票为(2,0),然后各自将这个投票发给集群中的其他所有机器。
    1. 接受来自各个服务器的投票
    每个服务器都会接受来自其他服务器的投票。同时,服务器会校验投票的有效性,是否本轮投票、是否来自LOOKING状态的服务器。
    1. 处理投票

    收到其他服务器的投票,会将被人的投票跟自己的投票PK,PK规则如下:

    • 优先检查ZXID。ZXID比较大的服务器优先作为leader。
    • 如果ZXID相同的话,就比较myid,myid比较大的服务器作为leader。

    服务器1的投票是(1,0),它收到投票是(2,0),两者zxid都是0,因为收到的myid=2,大于自己的myid=1,所以它更新自己的投票为(2,0),然后重新将投票发出去。对于服务器2呢,即不再需要更新自己的投票,把上一次的投票信息发出即可。

    1. 统计投票
    每次投票后,服务器会统计所有投票,判断是否有过半的机器接受到相同的投票信息。服务器2收到两票,少于3(n/2+1,n为总服务器),所以继续保持LOOKING状态
    • 服务器3(myid=3)启动,继续进入Leader选举阶段
    跟前面流程一致,服务器1和2先投自己一票,因为服务器3的myid最大,所以大家把票改投给它。此时,服务器为3票(大于等于n/2+1),所以服务器3当选为Leader。 服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING;
    • 服务器4启动,发起一次选举。
    此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。选票信息结果:服务器3为4票,服务器4为1票。服务器4并更改状态为FOLLOWING;
    • 服务器5启动,发起一次选举。
    同理,服务器也是把票投给服务器3,服务器5并更改状态为FOLLOWING;
    • 投票结束,服务器3当选为Leader

    服务器运行期间的Leader选举

    zookeeper集群的五台服务器(myid=1-5)正在运行中,突然某个瞬间,Leader服务器3挂了,这时候便开始Leader选举~

    • 1.变更状态
    Leader 服务器挂了之后,余下的非Observer服务器都会把自己的服务器状态更改为LOOKING,然后开始进入Leader选举流程。
    • 2.每个服务器发起投票
    每个服务器都把票投给自己,因为是运行期间,所以每台服务器的ZXID可能不相同。假设服务1,2,4,5的zxid分别为333,666,999,888,则分别产生投票(1,333),(2,666),(4,999)和(5,888),然后各自将这个投票发给集群中的其他所有机器。
    • 3.接受来自各个服务器的投票
    • 4.处理投票
    投票规则是跟Zookeeper集群启动期间一致的,优先检查ZXID,大的优先作为Leader,所以显然服务器zxid=999具有优先权。
    • 5.统计投票
    • 6.改变服务器状态

    11、面试官: 你前面提到在项目中使用过Zookeeper的分布式锁,讲一下zk分布式锁的实现原理吧?

    小菜鸡的我:

    Zookeeper就是使用临时顺序节点特性实现分布式锁的。

    • 获取锁过程 (创建临时节点,检查序号最小)
    • 释放锁 (删除临时节点,监听通知)

    获取锁过程

    • 当第一个客户端请求过来时,Zookeeper客户端会创建一个持久节点/locks。如果它(Client1)想获得锁,需要在locks节点下创建一个顺序节点lock1.如图

    • 接着,客户端Client1会查找locks下面的所有临时顺序子节点,判断自己的节点lock1是不是排序最小的那一个,如果是,则成功获得锁。

    • 这时候如果又来一个客户端client2前来尝试获得锁,它会在locks下再创建一个临时节点lock2

    • 客户端client2一样也会查找locks下面的所有临时顺序子节点,判断自己的节点lock2是不是最小的,此时,发现lock1才是最小的,于是获取锁失败。获取锁失败,它是不会甘心的,client2向它排序靠前的节点lock1注册Watcher事件,用来监听lock1是否存在,也就是说client2抢锁失败进入等待状态。

    • 此时,如果再来一个客户端Client3来尝试获取锁,它会在locks下再创建一个临时节点lock3

    • 同样的,client3一样也会查找locks下面的所有临时顺序子节点,判断自己的节点lock3是不是最小的,发现自己不是最小的,就获取锁失败。它也是不会甘心的,它会向在它前面的节点lock2注册Watcher事件,以监听lock2节点是否存在。

    释放锁

    我们再来看看释放锁的流程,zookeeper的客户端业务完成或者故障,都会删除临时节点,释放锁。如果是任务完成,Client1会显式调用删除lock1的指令

    如果是客户端故障了,根据临时节点得特性,lock1是会自动删除的

    lock1节点被删除后,Client2可开心了,因为它一直监听着lock1。lock1节点删除,Client2立刻收到通知,也会查找locks下面的所有临时顺序子节点,发下lock2是最小,就获得锁。

    同理,Client2获得锁之后,Client3也对它虎视眈眈,啊哈哈~

    12. 面试官:好的,最后一道题,你说说dubbo和Zookeeper的关系吧,为什么选择Zookeeper作为注册中心?

    小菜鸡的我(答了这么多道题,不会还不给我过吧?):

    dubbo的注册中心可以选Zookeeper,memcached,redis等。为什么选择Zookeeper,因为它的功能特性咯~

    • 命名服务,服务提供者向Zookeeper指定节点写入url,完成服务发布。
    • 负载均衡,注册中心的承载能力有限,而Zookeeper集群配合web应用很容易达到负载均衡。
    • zk支持监听事件,特别适合发布/订阅的场景,dubbo的生产者和消费者就类似这场景。
    • 数据模型简单,数据存在内存,可谓高性能
    • Zookeeper其他特点都可以搬出来讲一下~

    个人公众号

    参考与感谢

    查看原文

    mercyblitz 发布了文章 · 2020-05-11

    Apache Dubbo 云原生服务自省架构设计

    原文链接:Apache Dubbo 云原生服务自省架构设计,来自于微信公众号:次灵均阁

    背景

    随着微服务架构的推广和普及,服务之间的耦合度在逐步降低。在演化的过程中,伴随着应用组织架构的变化以及基础设施的衍进,服务和应用之间的边界变得更为模糊。Java 作为一门面向对象的编程语言,Java 接口(interface)作为服务之间通讯的一等公民,配合文档(JavaDoc)便于开发人员理解和维护。基于相同的编程哲学,Apache Dubbo 作为传统的 RPC 服务治理框架,通过接口实现分布式服务。然而对于微服务治理而言,应用(或“服务”)才是基础设施的核心要素。面对云原生(Cloud Native)技术的兴起,传统的 Dubbo 架构不断地面临着新的的挑战。下面内容将以 Apache Dubbo 2.7.5 为基础,介绍全新架构 - Apache Dubbo 服务自省(后文简称“服务自省”),了解 Dubbo 传统架构所面临的现实挑战,以及服务自省架构的设计和解决之道。

    术语约定

    • Service:SOA 或微服务中的“服务”,或称之为“应用”,具有全局唯一的名称
    • Service Name: 服务名称,或应用名称
    • Servce Instance:服务实例,或称为应用实例(Application Instance),表示单个 Dubbo 应用进程
    • Registry:注册中心
    • Dubbo 服务:又称之为“Dubbo 业务服务”,包含 Java 接口、通讯协议,版本(version)和分组(group)等元信息
    • Dubbo 服务 ID:唯一鉴定 Dubbo 服务的元数据,用于 Dubbo 服务暴露(发布)和订阅
    • Provider:Dubbo 服务提供方
    • Consumer:Dubbo 服务消费方
    • Dubbo 服务暴露:也称之为 Dubbo 服务发布,或英文中的“export”、"exported"
    • Dubbo 应用服务:也称之为 Dubbo 业务服务,或业务 Dubbo 服务

    使用场景

    服务自省是 Dubbo 应用在运行时处理和分析 Dubbo 服务元信息(Metadata)的过程,如当前应用暴露 的Dubbo 服务以及各自的通讯协议等。期间会伴随着事件的广播和处理,如服务暴露事件。Dubbo 服务自省架构是其传统架的一种补充,更是未来 Dubbo 架构,它更适合以下使用场景:

    • 超大规模 Dubbo 服务治理场景
    • 微服务架构和元原生应用
    • Dubbo 元数据架构的基石

    超大规模 Dubbo 服务治理场景

    如果 Dubbo 集群规模超过一千以上,或者集群扩缩容已无法自如地执行,如 Zookeeper 管理数万 Dubbo 服务,服务自省可极大化减轻注册中心的压力,尤其在内存足迹、网络传输以及变更通知上体现。

    微服务架构和元原生应用

    如果想要 Dubbo 应用更好地微服务化,或者更接近于云原生应用,那么服务自省是一种不错的选择,它能够提供已应用为粒度的服务注册与发现模型,全面地支持最流行的 Spring Cloud 和 Kubernetes 注册中心,并且能与 Spring Cloud 或 Spring Boot 应用交互。

    Dubbo 元数据架构的基石

    Dubbo 元数据架构是围绕 Dubbo DevOps 而引入,包括 Dubbo 配置元数据(如:属性配置、路由规则等)和结构元数据(如:Java 注解、接口和文档等)。服务自省作为 Dubbo 元数据的基础设施,不仅支持所有元数据的存储和平滑升级,而且不会对注册中心、配置中心和元数据中心产生额外的负担。

    传统架构

    Apache Dubbo 是一款面向接口代理的高性能 RPC 框架,提供服务注册与发现的特性,其基础架构如下图所示:

    image.png

    (图 1)

    • Provider 为服务提供方,提供 Java 服务接口的实现,并将其元信息注册到 Dubbo 注册中心(过程 1.register 所示)
    • Consumer 为服务消费端,从 Dubbo 注册中心检索订阅的 Java 服务接口的元信息(过程 2.subscribe 所示),通过框架处理后,生成代理程序执行远程方法调用(过程 4.invoke 所示)
    • Registry 为注册中心,属于注册元信息中心化基础设施(如 Apache Zookeeper 或 Alibaba Nacos),为 Provider 提供注册通道,为 Cosumer 提供订阅渠道。同时,注册中心支持注册元信息变更通知,通知 Consumer 上游 Provider 节点的变化(如扩容或缩容)。而注册元信息均以 Dubbo URL 的形式存储
    • Monitor 为服务治理平台,提供开发和运维人员服务查询、路由规则、服务 Mock 和测试等治理能力

    综上所述,Dubbo 注册与发现的对象是 Dubbo 服务(Java 接口),而其载体为注册元信息,即Dubbo URL,如:dubbo://192.168.1.2:20880/com.foo.BarService?version=1.0.0&group=default,通常包含必须信息,如服务提供方 IP 和端口、 Java 接口,可选包含版本(version)和分组(group)等。服务 URL 所包含的信息能够唯一界别服务提供方的进程。

    现实挑战

    为了更好地符合 Java 开发人员的编程习惯,Dubbo 以 Java 服务接口作为注册对象,所面临的现实挑战主要有:

    • 如何解决或缓解注册中心压力过载
    • 如何支持以应用为粒度的服务注册与发现
    • 如何精简 Dubbo URL 元数据

    如何解决或缓解注册中心压力过载

    注册中心内存压力

    Dubbo 注册中心是中心化的基础设施,大多数注册中心的实现为内存型存储,比如 Zookeeper、Nacos 或 Consul、Eureka。注册中心的内存消耗与 Dubbo 服务注册的数量成正比,任一 Dubbo Provider 允许注册 N 个 Dubbo 服务接口,当 N 越大,注册中心的负载越重。根据不完全统计,Dubbo 核心 Provider 用通常会暴露 20 ~ 50 个服务接口。注册中心是中心化的基础设施,其稳定性面临严峻考验。尽管微服务架构不断地深化,然而现实情况是,更多开发者仍旧愿意在单一 Provider 上不断地增加 Dubbo 服务接口,而非更细粒度的 Dubbo Provider 组织。

    注册中心网络压力

    为了避免单点故障,主流的注册中心均提供高可用方案。为解决集群环境数据同步的难题,内建一致性协议,如 Zookeeper 使用的 Zab 协议,Consul 采用的 Raft 协议。无论哪种方式,当 Dubbo URL 数量变化频繁时,网络和 CPU 压力也会面临考验。如果注册中心与客户端之间维持长连接状态的话,如 Zookeeper,注册中心的网络负担会更大。

    注册中心通知压力

    假设某个 Dubbo Provider 注册了 N 个 Dubbo 服务接口,当它扩容或缩容 M 个实例(节点)时,N 数量越大,注册中心至少有 M * N 个 Dubbo URL 注册或移除。同时,大多数注册中心实现支持注册变化通知,如 Zookeeper 节点变化通知。当 Dubbo Consumer 订阅该 Provider 的 Dubbo 服务接口数为 X 时,X 数值越大,通知的次数也就越多。实际上,对于来自同一 Provider 的服务接口集合而言,X-1 次通知是重复和无价值的。

    如果 Dubbo 注册实体不再是服务 URL,而是 Dubbo Provider 节点的话,那么上述情况所描述的注册中心压力将得到很大程度的缓解。(负载只有过去的 1/N 甚至更少),然而 Dubbo 如何以应用为粒度来注册又是一个新的挑战。

    如何支持以应用为粒度的服务注册与发现

    尽管 Dubbo 也存在应用(Application)的概念,不过传统的使用场景并非核心要素,仅在 Dubbo Monitor 或 Dubbo Admin 场景下做辨识之用。随着微服务架构和云原生技术的兴起,以应用为粒度的注册模型已是大势所趋,如 Spring Cloud 和 Kubernetes 服务注册与发现模型。注册中心所管理的对象通常与业务无关,甚至不具备 RPC 的语义。在术语上,微服务架构中的“服务”(Services)与云原生中“应用”(Applications)是相同的概念,属于逻辑名称,而它们的成员则以服务实例(Service Instances)体现,服务和服务实例的数量关系为 1:N。

    单个服务实例代表一个服务进程,而多个 Dubbo 服务 URL 可隶属一个 Dubbo Provider 进程,因此,Dubbo URL 与服务实例的数量关系是 N : 1。假设一个 Dubbo Provider 进程仅提供一个 Dubbo 服务(接口)的话,即 N = 1 的情况,虽然以应用为粒度的服务注册与发现能够基于 Dubbo 传统的 Registry SPI 实现,不过对于现有 Dubbo 应用而言,将存在巨大的应用微服务化工作。

    支持 Spring Cloud 服务注册与发现模型

    Spring Cloud 是 VMware 公司(前为 Pivotal)推出的,一套以 Spring 为技术栈的云原生(Cloud-Native)解决方案,在 Java 微服务领域具备得天独厚的优势,拥有超大规模的全球用户。Spring Cloud 官方支持三种注册中心实现,包括:Eureka、Zookeeper 和 Consul,Spring Cloud Alibaba 扩展了 Nacos 注册中心实现。 尽管 Zookeeper、Consul 和 Nacos 也被 Apache Dubbo 官方支持,然而两者的服务注册与发现的机制不尽相同。

    若要 Dubbo 支持 Spring Cloud 服务注册与发现模型,Dubbo 则需基于 Dubbo Registry SPI 实现,否则底层的变化和兼容性存在风险。

    支持 Kubernetes 服务注册与发现模型

    Kubernetes 源自 Google 15 年生产环境的运维经验,是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务。Kubernetes 原生服务发现手段主要包括:DNS 和 API Server。DNS 服务发现是一种服务地址的通用方案,不过对于相对复杂 Dubbo 元数据而言,这种服务发现机制或许无法直接被 Dubbo Registry SPI 适配。相反,API Server 所支持相对更便利,毕竟 Spring Cloud Kubernetes 同样基于此机制实现,并已在生产环境得到验证。换言之,只要 Dubbo 支持 Spring Cloud 服务注册与发现模型,那么基于 Kubernetes API Server 的支持也能实现。

    兼容 Dubbo 传统服务注册与发现模型

    所谓兼容 Dubbo 传统服务注册与发现模型,包含两层含义:

    • 基于 Dubbo Registry SPI 同时支持 Spring Cloud 和 Kubernetes 服务注册与发现模型
    • 传统和新的 Dubbo 服务注册与发现模型之间能够相互发现

    如何精简 Dubbo URL 元数据

    Dubbo 从 2.7.0 开始增加了简化 URL 元数据的特性,被“简化”的数据存放至元数据中心。由于 Dubbo 传统服务注册与发现模型并未减少 Dubbo 服务 URL 注册数量。因此,精简后的 URL 并未明显地减少注册中心所承受的压力。同时,Dubbo URL 元数据精简模式存在一定的限制,即所有的 Dubbo Provider 节点必须是无状态的,每个节点中的 URL 元信息均是一致的,现实中,这个要求非常难以保证,尤其在同一 Provider 节点存在不同的版本或配置的情况下。综上所述,Dubbo URL 元数据需要进一步精简,至少压力应该避免聚集在注册中心之上。

    架构设计

    架构上,Dubbo 服务自省不仅要解决上述挑战,而且实际场景则更为复杂,因此,架构细节也将循序渐进地展开讨论,整体架构可由以下子架构组成:

    • 服务注册与发现架构
    • 元数据服务架构
    • 事件驱动架构

    服务注册与发现架构

    Dubbo 服务自省首要需求是减轻注册中心的承载的压力,同时,以应用为粒度的服务注册与发现模型不但能够最大化的减少 Dubbo 服务元信息注册数量,而且还能支持 Spring Cloud 和 Kubernetes 环境,可谓是一举两得,架构图如下所示:

    image.png

    (图 2)

    注册实体

    图中所示,从 Provider 和 Consumer 向注册中心注册的实体不再是 Dubbo URL,而是服务实例(Service Instance),一个服务实例代表一个 Provider 或 Consumer Dubbo 应用进程。服务实例属性包括:

    • 服务名(Service Name):该名称必须在注册中心全局唯一
    注:名称规则架构上不做约束,不过不同注册中心的规则存在差异
    • 主机地址(Host/IP):能够被解析的主机名或者 TCP IP 地址
    • 服务端口(Port):应用进程所暴露的 Dubbo 协议端口,如 Dubbo 默认端口 20880
    注:如果应用进程暴露多个 Dubbo 协议端口,如 dubbo 和 rest,那么,服务端口随机挑选其一,架构上不强制检验端口是否可用
    • 元数据(Metadata):服务实例的附加信息,用于存储 Dubbo 元信息,类似于通讯协议头或附件
    • 激活状态(Enabled):用于标记当前实例是否对外提供服务

    上述服务实例模型的支持依赖于注册中心的实现。换言之,并非所有注册中心实现满足服务自省架构的要求。

    注册中心

    除了满足服务实例模型的要求之外,注册中心还得具备以下能力:

    • 服务实例变化通知(Notification):如上图步骤 4 所示,当 Consumer 订阅的 Provider 的服务实例发生变化时,注册中心能够实时地通知 Consumer
    • 心跳检测(Heartbeats):注册中心能够检测失效的服务实例,并且合理地移除它们

    业界主流的注册中心中满足上述要求的有:

    总之,Spring Cloud 与Kubernetes注册中心均符合服务自省对注册中心的要求。不过,在 Dubbo 传统 RPC 使用场景中,Provider 和 Consumer 关注的是 Dubbo 服务接口,而非 Service 或服务实例。假设需要将现有的 Dubbo 应用迁移至服务自省架构,Provider 和 Consumer 做大量的代码调整是不现实的。理想的情况下,两端实现代码均无变化,仅修改少量配置,就能达到迁移的效果。那么,Dubbo 服务接口是如何与 Service 进行映射的呢?

    Dubbo 服务与 Service 映射

    前文曾讨论,单个 Dubbo Service 能够发布多个 Dubbo 服务,所以,Dubbo 服务与 Service 的数量关系是 N 对 1。不过,Dubbo 服务与 Dubbo Service 之间并不存在强绑定关系,换言之,某个 Dubbo 服务也能部署在多个 Dubbo Services 中,因此,Dubbo 服务与 Service 数量关系是 N 对 M(N, M >= 1),如下图所示:

    image.png

    (图 3)

    上图中 P1 Service 到 P3 Service 为 Dubbo Service,com.acme.Interface1 到 com.acme.InterfaceN 则为 Dubbo 服务接口全称限定名(QFN)。值得注意的是,Dubbo 服务的 Java 接口(interface)允许不同的版本(version)或分组(group),所以仅凭 Java 接口无法唯一标识某个 Dubbo 服务,还需要增加通讯协议(protocol)方可,映射关系更新如下:

    image.png(图 4)

    Dubbo 服务 ID 字符表达模式为: ${protocol}:${interface}:${version}:${group} , 其中,版本(version)或分组(group)是可选的。当 Dubbo Consumer 订阅 Dubbo 服务时,构建对应 ID,通过这个 ID 来查询 Dubbo Provider 的 Service 名称列表。

    由于 Dubbo 服务与 Service 的映射关系取决于业务场景,架构层面无从预判。因此,这种映射关系只能在 Dubbo 服务暴露时(运行时)才能确定,否则,Dubbo 服务能被多个 Consumer 应用订阅时,Consumer 无法定位 Provider Service 名称,进而无法完成服务发现。同时,映射关系的数据通常采用配置的方式来存储,服务自省提供两种配置实现,即 “中心化映射配置” 和 “本地化映射配置”。

    中心化映射配置

    明显地,注册中心来扮演动态映射配置的角色并不适合,不然,Dubbo Service 与映射关系在注册中心是平级的,无论在理解上,还是设计上是混乱的。结合 Dubbo 现有基础设施分析,这个存储设施可由 Dubbo 配置中心承担。

    其中 Dubbo 2.7.5 动态配置 API(DynamicConfiguration )支持二级结构,即:group 和 key,其中,group 存储 Dubbo 服务 ID,而 key 则关联对应的 Dubbo Service 名称,对应的 "图 4” 的数据结构则是:

    image.png

    (图 5)

    如此设计的原因如下:

    1. 获取 Dubbo 服务对应 Services

    利用 DynamicConfiguration#getConfigKeys(String group) 方法,能够轻松地通过 Dubbo 服务 ID 获取其发布的所有 Dubbo Services,结合服务发现接口获取服务所部署的 Service 实例集合,最终转化为 Dubbo URL 列表。

    1. 避免 Dubbo Services 配置相互覆盖

    以 Dubbo 服务 ID dubbo:com.acme.Interface1:default 为例,它的提供者 Dubbo Services 分别:P1 Service 和 P2 Service。假设配置 Group 为 "default"(任意名字均可), Key 为 "dubbo:com.acme.Interface1:default",而内容则是 Dubbo Service 名称的话。当 P1 Service 和 P2 Service 同时启动时,无论哪个 Services 最后完成 Dubbo 服务暴露,那么,该配置内容必然是二选其一,无论配置中心是否支持原子操作。即使配置中心支持内容追加的特性,由于两个 Service 服务实例过程不确定,配置内容可能会出现重复,如:“P1 Service,P2 Service,P1 Service”。

    1. 获取 Dubbo 服务发布的 timestamp
    配置中心潜在的压力

    假设当 P1 Service 存在 5 个服务实例,当 Dubbo 服务 dubbo:com.acme.Interface1:default(ID)发布时,配置所关联的 key 就是当前 Dubbo Service 名称,即(P1 Service),而内容则是最后发布该 Dubbo 服务的时间戳(timestamp)。当服务实例越多时,配置中心和网络传输所承受的写入压力也就越大。当然架构设计上,服务自省也希望避免重复推送配置,比如在 DynamicConfiguration API 增加类似于 publishConfigIfAbsent 这样的方法,不过目前大多数配置中心产品(如:Nacos、Consul)不支持这样的操作,所以未来服务自省架构会有针对性的提供支持(如:Zookeeper)。

    注册中心作为配置中心

    由于服务自省架构必须依赖注册中心,同时动态映射配置又依赖配置中心的话,应用的架构复杂度和维护成本均有所提升,不过 Apache Dubbo 所支持的部分注册中心也可作为配置中心使用,情况如下所示:

    基础软件注册中心配置中心
    Apache Zookeeper
    HashiCorp Consul
    Alibaba Nacos
    Netflix Eureka
    Kubernetes API Server

    其中,ZookeeperConsulNacos 是目前业界流行的注册中心,这对于大多数选择开源产品的应用无疑是一个福音。

    本地化映射配置

    如果开发人员认为配置中心的引入增加了架构的复杂性,那么,静态映射配置或许是一种解决方案。

    该特性并未在最新 Dubbo 2.7.6 全面发布,部分特性已在 Dubbo Spring Cloud 中发布
    接口映射配置

    在 Dubbo 传统的编程模型中, 常以 Java 注解 @Reference 或 XML 元素 ` 订阅目标 Dubbo 服务。服务自省架构在此基础上增加 service` 属性的映射一个或多个 Dubbo Service 名称,如:

    <reference services="P1 Service,P2 Service" interface="com.acme.Interface1" />

    @Reference(services="P1 Service,P2 Service") 
    private com.acme.Interface1 interface1;

    如此配置后,Dubbo 服务 com.acme.Interface1 将向 p1-servicep2-service 订阅服务。如果开发人员认为这种方式会侵入到代码,服务自省还提供外部化配置方式配置映射。

    外部化映射配置

    服务自省架构支持外部化配置的方式声明“Dubbo 服务与 Service 映射”,配置格式为 Properties ,以图 4 为例,内容如下:

    dubbo\:com.acme.Interface1\:default = P1 Service,P2 Service
    thirft\:com.acme.InterfaceX = P1 Service,P3 Service
    rest\:com.acme.interfaceN = P1 Service
    应用级别映射配置

    除此之外,Dubbo Spring Cloud 提供应用级别的 Dubbo 服务映射配置,即 dubbo.cloud.subscribed-services ,例如:

    dubbo:
        cloud:
        subscribed-services: P1 Service,P3 Service

    总之,无论是映射配置的方式是中心化还是本地化,服务 Consumer 依赖这些数据来定位 Dubbo Provider Services,再通过服务发现 API 结合 Service 名称(列表)获取服务实例集合,为合成 Dubbo URL 做准备:

    image.png

    (图 6)

    不过,映射关系并非是一种强约束,Dubbo Provider 的服务是否可用的检验方法是探测目标 Dubbo Service 是否存在,并需确认订阅的 Dubbo 服务在目标 Services 是否真实暴露,因此,服务自省引入了 Dubbo 元数据服务架构,来完成 Dubbo 服务 URL 的存储。

    元数据服务架构

    Dubbo 元数据服务是一个常规的 Dubbo 服务,为服务订阅端提供 Dubbo 元数据的服务目录,类似于 WebServices 中的 WDSL 或 REST 中的 HATEOAS,帮助 Dubbo Consumer 获取订阅的 Dubbo 服务的 URL 列表。元数据服务架构无法独立于服务注册与发现架构而存在,下面通过“整体架构”的讨论,了解两者之间的关系。

    整体架构

    架构上,无论 Dubbo Service 属于 Provider 还是 Consumer,甚至是两者的混合,每个 Dubbo (Service)服务实例有且仅有一个 Dubbo 元数据服务。换言之,Dubbo Service 不存在纯粹的 Consumer,即使它不暴露任何业务服务,那么它也可能是 Dubbo 运维平台(如 Dubbo Admin)的 Provider。不过出于行文的习惯,Consumer 仍旧被定义为 Dubbo 服务消费者(应用)。由于每个 Dubbo Service 均发布自身的 Dubbo 元数据服务,那么,架构不会为不同的 Dubbo Service 设计独立的元数据服务接口(Java)。换言之,所有的 Dubbo Service 元数据服务接口是统一的,命名为 MetadataService

    微观架构

    从 Dubbo 服务(URL)注册与发现的视角, MetadataService 扮演着传统 Dubbo 注册中心的角色。综合服务注册与发现架构(Dubbo Service 级别),微观架构如下图所示:

    image.png

    (图 7)

    **
    **

    对于 Provider(服务提供者)而言,Dubbo 应用服务暴露与传统方式无异,而 MetadataService 的暴露时机必须在它们完成后,同时, MetadataService 需要收集这些 Dubbo 服务的 URL(存储细节将在“元数据服务存储模式“ 小节讨论)。假设某个 Provider 的 Dubbo 应用服务暴露数量为 N,那么,它所有的 Dubbo 服务暴露数量为 N + 1。

    对于 Consumer(服务消费者)而言,获 Dubbo 应用服务订阅 URL 列表后,Dubbo 服务调用的方式与传统方式是相同的。不过在此之前,Consumer 需要通过 MetadataService 合成订阅 Dubbo 服务的 URL。该过程之所以称之为“合成”,而非“获取,是因为一次 MetadataService 服务调用仅在其 Provider 中的一台服务实例上执行,而该 Provider 可能部署了 N 个服务实例。具体“合成”的细节需要结合“宏观架构”来说明。

    宏观架构

    元数据服务的宏观架构依赖于服务注册与发现架构,如下图所示:

    image.png

    (图 8)

    图 8 中 p 和 c 分别代表 Provider 和 Consumer 的执行动作,后面紧跟的数字表示动作的次序,从 0 开始计数。执行动作是串行的,并属于 Fast-Fail 设计,如果前阶段执行失败,后续动作将不会发生。之所以如此安排是为了确保 MetadataService 能够暴露和消费。首先从 Provider 执行流程开始说明。

    Provider 执行流程
    • p0:发布所有的 Dubbo 应用服务,声明和定义方式与传统方式完全相同。
    • p1:暴露 MetadataService ,该步骤完全由框架自行处理,无论是否 p0 是否暴露 Dubbo 服务
    • p2:在服务实例注册之前, 框架将触发并处理事件(Event),将 MetadataService 的元数据先同步到服务实例(Service Instance)的元数据。随后,执行服务实例注册
    • p3:建立所有的 Dubbo 应用服务与当前 Dubbo Service 名称的映射,并同步到配置源(抽象)
    Consumer 执行流程
    • c0:注册当前 Dubbo Service 的服务实例,可选步骤,架构允许 Consumer 不进行服务注册
    • c1:通过订阅 Dubbo 服务元信息查找配置源,获取对应 Dubbo Services 名称(列表)
    • c2:利用已有 Dubbo Service 名称(可能存在多个),通过服务发现 API 获取 Provider 服务实例集合。假设 Service 名称 P,服务实例数量为 N
    • c3:
      1. 随机选择 Provider 一台服务实例 Px,从中获取 MetadataService 的元数据
      2. 将元数据组装 MetadataService Dubbo 调用客户端(代理)
      3. 发起 MetadataService Dubbo 调用,获取该服务实例 Px 所暴露的 Dubbo 应用服务 URL 列表
      4. 从 Dubbo 应用服务 URL 列表过滤出当前订阅 Dubbo 应用服务的 URL
      5. 理论上,步骤 c 和 d 还需要执行 N-1 次才能获取 P 所有服务实例的 Dubbo URL 列表。为了减少调用次数,步骤 d 的结果作为模板,克隆其他 N-1 台服务实例 URL 列表
      6. 将所有订阅 Dubbo 应用服务的 URL 同步到 Dubbo 客户端(与传统方式是相同的)
    • c4:发起 Dubbo 应用服务调用(与传统方式是相同的)

    不难看出,上述架构以及流程结合了“服务注册与发现”与“元数据服务”双架构,步骤之间会触发相关 Dubbo 事件,如“服务实例注册前事件”等。换言之,三种架构综合体也就是服务自省架构。

    至此,关于 Dubbo 服务自省架构设计方面,还存在一些细节亟待说明,比如:

    1. 不同的 Dubbo Service 的 MetadataService 怎样体现差异呢?
    2. MetadataService 作为一个常规的 Dubbo 服务,它的注册元信息存放在何处?
    3. MetadataService 作为服务目录,它管理的 Dubbo 应用服务 URL 是如何存储的?
    4. 在 Consumer 执行流程的 c3.e 中,克隆 N - 1 条 URL 的前提是该 Provider 的所有服务实例均部署了相同 Dubbo 应用服务。如果 Provider 处于升级的部署过程,同一 Dubbo 应用服务接口在不同的服务实例上存在差异,那么该服务的 URL 应该如何获取?
    5. 除了 Dubbo 服务 URL 发现之外,元数据服务还支持哪些元数据类型呢?

    元数据服务 Metadata

    元数据服务 Metadata,称之为“元数据服务的元数据”,主要包括:

    • inteface:Dubbo 元数据服务所暴露的接口,即 MetadataService
    • serviceName : 当前 MetadataService 所部署的 Dubbo Service 名称,作为 MetadataService 分组信息
    • group:当前 MetadataService 分组,数据使用 serviceName
    • version:当前 MetadataService 的版本,版本号通常在接口层面声明,不同的 Dubbo 发行版本 version 可能相同,比如 Dubbo 2.7.5 和 2.7.6 中的 version 均为 1.0.0。理论上,version 版本越高,支持元信息类型更丰富
    • protocol: MetadataService 所暴露协议,为了确保 Provider 和 Consumer 通讯兼容性,默认协议为:“dubbo”,也可以支持其他协议。
    • port:协议所使用的网络端口
    • host:当前 MetadataService 所在的服务实例主机或 IP
    • params:当前 MetadataService 暴露后 URL 中的参数信息

    不难得出,凭借以上元数据服务的 Metadata,可将元数据服务的 Dubbo 服务 ID 确定,辅助 Provider 服务暴露和 Consumer 服务订阅 MetadataService 。不过对于 Provider,这些元信息都是已知的,而对 Consumer 而言,它们直接能获取的元信息仅有:

    • serviceName:通过“Dubbo 接口与 Service 映射”关系,可得到 Provider Service 名称
    • interface:即 MetadataService ,因为 Provider 和 Consumer 公用 MetadataService 接口
    • group:即 serviceName

    不过 Consumer 合成 MetadataService Dubbo URL 还需获取 version、host、port、protocol 以及 params:

    • version:尽管 MetadataService 接口是统一接口,然而 Provider 和 Consumer 可能引入的 Dubbo 版本不同,从而它们使用的 MetadataService version 也会不同,所以这个信息需要 Provider 在暴露MetadataService 时,同步到服务实例的 Metadata 中,方便 Consumer 从 Metadata 中获取
    • host:由于 Consumer 已得到 serviceName,可通过服务发现 API 获取服务实例对象,该对象包含 host 属性,直接被 Consumer 获取即可。
    • port:与 version 类似,从 Provider 服务实例中的 Metadata 中获取
    • params:同上

    通过元数据服务 Metadata 的描述,解释了不同 Dubbo Services 是怎样体现差异性的,并且说明了 MetadataService 元信息的存储介质,这也就是服务自省架构为什么强依赖支持 Metadata 的注册中心的原因。下个小节将讨论 MetadataService 所存储 Dubbo 应用服务 URL 存放在何处。

    元数据服务存储模式

    Dubbo 2.7.5 在引入 MetadataService 的同时,也为其设计了两种存储方式,适用于不同的场景,即“本地存储模式”和“远程存储模式”。其中,本地存储模式是默认选项。

    元数据服务本地存储模式

    本地存储模式又称之为内存存储模式(In-Memory),如 Dubbo 应用服务发现和注册场景中,暴露和订阅的 URL 直接存储在内存中。架构上,本地存储模式的 MetadataService 相当于去中心化的 Dubbo 应用服务的注册中心。

    元数据服务远程存储模式

    远程存储模式,与去中心化的本地存储模式相反,采用 Dubbo 元数据中心来管理 Dubbo 元信息,又称之为元中心化存储模式(Metadata Center)。

    选择存储模式

    为了减少负载压力和维护成本,服务自省中的元数据服务推荐使用地存储模式”

    回顾前文“Consumer 执行流程”中的步骤 c3.e,为了减少 MetadataService 调用次数,服务自省将第一次的调用结果作为模板,再结合其他 N-1 服务实例的元信息,合成完整的 N 台服务实例的 Dubbo 元信息。假设,Dubbo Service 服务实例中部署的 Dubbo 服务数量和内容不同,那么,c3.e 的执行步骤是存在问题的。因此,服务自省引入“Dubbo 服务修订版本”的机制来解决不对等部署的问题。

    尽管“Dubbo 服务修订版本”机制能够介绍 MetadataService 整体消费次数,然而当新修订版本的服务实例过少,并且 Consumer 过多时,如新的版本 Provider 应用分批部署,每批的服务实例为 1 台,而其 Consumer 服务实例成千上万。为了确保这类场景的稳定性,Provider 和 Consumer 的 MetadataService 可选择“远程存储模式”,避免消费热点的发生。

    Dubbo 服务修订版本

    当业务出现变化时,Dubbo Service 的 Dubbo 服务也会随之升级。通常,Provider 先行升级,Consumer 随后跟进。

    考虑以下场景,Provider “P1” 线上已发布 interface 为 com.acme.Interface1,group 为 group , version 为 v1 ,即 Dubbo 服务 ID 为:dubbo:com.acme.Interface1:v1:default 。P1 可能出现升级的情况有:

    1. Dubbo 服务 interface 升级

    由于 Dubbo 基于 Java 接口来暴露服务,同时 Java 接口通常在 Dubbo 微服务中又是唯一的。如果 interface 的全类名调整的话,那么,相当于 com.acme.Interface1 做下线处理,Consumer 将无法消费到该 Dubbo 服务,这种情况不予考虑。如果是 Provider 新增服务接口的话,那么 com.acme.Interface1 则并没有变化,也无需考虑。所以,有且仅有一种情况考虑,即“Dubbo interface 方法声明升级”,包括:

    • 增加服务方法
    • 删除服务方法
    • 修改方法签名
    1. Dubbo 服务 group、version 和 protocol 升级

    假设 P1 在升级过程中,新的服务实例部署仅存在调整 group 后的 Dubbo 服务,如 dubbo:com.acme.Interface1:v1:test ,那么这种升级就是不兼容升级,在新老交替过程中,Consumer 仅能消费到老版本的 Dubbo 服务。当新版本完全部署完成后,Consumer 将无法正常服务调用。如果,新版本中 P1 同时部署了 dubbo:com.acme.Interface1:v1:default

    dubbo:com.acme.Interface1:v1:test 的话,相当于 group 并无变化。同理,version 和 protocol 变化,相当于 Dubbo 服务 ID 变化,这类情况无需处理

    1. Dubbo 服务元数据升级

    这是一种比较特殊的升级方法,即 Provider 所有服务实例 Dubbo 服务 ID 相同,然而 Dubbo 服务的参数在不同版本服务实例存在差异,假设 Dubbo Service P1 部署 5 台服务,其中 3 台服务实例设置 timeout 为 1000 ms,其余 2 台 timeout 为 3000 ms。换言之,P1 拥有两个版本(状态)的 MetadataService

    综上所述,无论是 Dubbo interface 方法声明升级,还是 Dubbo 服务元数据升级,均可认为是 Dubbo 服务升级的因子,这些因子所计算出来的数值称之为“Dubbo 服务修订版本”,服务自省架构将其命名为“revision”。架构设设计上,当 Dubbo Service 增加或删除服务方法、修改方法签名以及调整 Dubbo 服务元数据,revision 也会随之变化,revision 数据将存放在其 Dubbo 服务实例的 metadata 中。当 Consumer 订阅 Provider Dubbo 服务元信息时,MetadataService 远程调用的次数取决于服务实例列表中出现 revision 的个数,整体执行流程如下图所示:

    image.png

    (图 9)

    1. Consumer 通过服务发现 API 向注册中心获取 Provider 服务实例列表
    2. 注册中心返回 6 台服务实例,其中 revision 为 1 的服务实例为 Instance 1 到 3, revision 为 2 的服务实例是 Instance 4 和 Instance 5,revision 为 3 的服务实例仅有 Instance 6
    3. Consumer 在这 6 台服务实例中随机选择一台,如图中 Instance 3
    4. Consumer 向 Instance 3 发起 MetadataService 的远程调用,获得 Dubbo URL 列表,并建立 revision 为 1 的 URL 列表缓存,用 cache = { 1:urls(r1) } 表示
    5. (重复步骤 4)Consumer 再从剩余的 5 台服务实例中随机选择一台,如图中的 Instance 5,由于 Instance 5 与 Instance 3 的 revision 分为为 2 和 1,此时缓存 cache = { 1:urls(r1) } 未命中,所以 Consumer 将再次发起远程调用,获取新的 Dubbo URL 列表,并更新缓存,即 cache = { 1:urls(r1) , 2:urls(r2) }
    6. (重复步骤 4)Consumer 再从剩余的 4 台服务实例中随机选择一台,假设服务实例是 Instance 6,由于此时 revision 为3,所以缓存 cache = { 1:urls(r1) , 2:urls(r2) } 再次未命中,再次发起远程调用,并更新缓存 cache = { 1:urls(r1) , 2:urls(r2) , 3:urls(r3) }
    7. (重复步骤 4)由于缓存 cache = { 1:urls(r1) , 2:urls(r2) , 3:urls(r3) } 已覆盖三个 revision 场景,如果该步骤选择服务实例落在 revision 为 1 的子集中,只需克隆 urls(r1),并根据具体服务实例替换部分 host 和 port 等少量元信息即可,组成成新的 Dubbo URL 列表,依次类推,计算直到剩余服务实例为 0。

    大多数情况,revision 的数量不会超过 2,换言之,Consumer 发起 MetadataService 的远程调用不会超过 2次。无论 revision 数量的大小,架构能够保证获取 Dubbo 元信息的正确性。

    当然 MetadataService 并非仅支持 Dubbo URL 元数据,还有其他类型的支持。

    元数据类型

    架构上,元数据服务(MetadataService)未来将逐步替代 Dubbo 2.7.0 元数据中心,并随着 Dubbo 版本的更迭,所支持的元数据类型也将有所变化,比如 Dubbo 2.7.5 元数据服务支持的类型包括:

    • Dubbo 暴露的服务 URL 列表
    • Dubbo 订阅的服务 URL 列表
    • Dubbo 服务定义

    Dubbo 暴露的服务 URL 列表

    当前 Dubbo Service 暴露或发布 Dubbo 服务 URL 集合,如:[ dubbo://192.168.1.2:20880/com.acme.Interface1?group=default&version=v1 , thirft://192.168.1.2:20881/com.acme.InterfaceX , rest://192.168.1.2:20882/com.acme.interfaceN ]

    Dubbo 订阅的服务 URL 列表

    当前 Dubbo Service 所有订阅的 Dubbo 服务 URL 集合,该元数据主要被 Dubbo 运维平台来收集。

    Dubbo 服务定义

    Dubbo 服务提供方(Provider)在服务暴露的过程中,将元信息以 JSON 的格式同步到注册中心,包括服务配置的全部参数,以及服务的方法信息(方法名,入参出参的格式)。在服务自省引入之前,该元数据被 Dubbo 2.7.0 元数据中心 存储,如:

    {
     "parameters": {
      "side": "provider",
      "methods": "sayHello",
      "dubbo": "2.0.2",
      "threads": "100",
      "interface": "org.apache.dubbo.samples.metadatareport.configcenter.api.AnnotationService",
      "threadpool": "fixed",
      "version": "1.1.1",
      "generic": "false",
      "revision": "1.1.1",
      "valid": "true",
      "application": "metadatareport-configcenter-provider",
      "default.timeout": "5000",
      "group": "d-test",
      "anyhost": "true"
     },
     "canonicalName": "org.apache.dubbo.samples.metadatareport.configcenter.api.AnnotationService",
     "codeSource": "file:/../dubbo-samples/dubbo-samples-metadata-report/dubbo-samples-metadata-report-configcenter/target/classes/",
     "methods": [{
      "name": "sayHello",
      "parameterTypes": ["java.lang.String"],
      "returnType": "java.lang.String"
     }],
     "types": [{
      "type": "java.lang.String",
      "properties": {
       "value": {
        "type": "char[]"
       },
       "hash": {
        "type": "int"
       }
      }
     }, {
      "type": "int"
     }, {
      "type": "char"
     }]
    }

    更多元数据类型支持

    在架构上,元数据服务(MetadataService)所支持元数据类型是不限制的,如下图所示:

    image.png

    (图 10)

    除上文曾讨论的三种元数据类型,还包括“Dubbo 服务 REST 元信息” 和 “其他元信息”。其中,Dubbo 服务 REST 元信息包含 Dubbo 服务 与 REST 映射信息,可用于 Dubbo 服务网关,而其他元信息可能包括 Dubbo 服务 JavaDoc 元信息,可用于 Dubbo API 文档。

    元数据服务升级

    考虑到 Dubbo Provider 和 Consumer 可能依赖不同发行版本的 MetadataService ,因此,Provider 提供的和 Consumer 所需要的元数据类型并不对等,如 Provider 使用 Dubbo 版本为 2.7.5,该发行版本仅支持“Dubbo 暴露的服务 URL 列表”,“Dubbo 订阅的服务 URL 列表”和“Dubbo 服务定义”,这三种元数据分别来源于接口的三个方法。当 Consumer 使用了更高的 Dubbo 版本,并需要获取“Dubbo 服务 REST 元信息”时,自然无法从 Provider 端获取。假设 MetadataService 为其新增一个方法,那么,当 Consumer 发起调用时,那么这个调用自然会失败。即使两端使用的版本相同,那么 Provider 仍有可能选择性支持特定的元数据类型。为了确保元数据接口的兼容性,MetadataService 应具备元数据类型支持的判断。如此设计,MetadataService 在元数据类型上支持更具有弹性。

    事件驱动架构

    相较于传统的 Dubbo 架构,服务自省架构的执行流程更为复杂,执行动作之间的关联非常紧密,如 Dubbo Service 服务实例注册前需要完成 Dubbo 服务 revision 的计算,并将其添加至服务实例的 metadata 中。又如当 Dubbo Service 服务实例出现变化时,Consumer 元数据需要重新计算。这些动作被 “事件”(Event)驱动,驱动者被定义为“事件分发器”( EventDispatcher ),而动作的处理则由“事件监听器”(EventListener)执行,三者均为 “Dubbo 事件"的核心组件,同样由 Dubbo 2.7.5 引入。不过,Dubbo 事件是相对独立的架构,不过被服务自省中的“服务注册与发现架构”和“元数据服务架构”依赖。

    Dubbo 内建事件

    Dubbo 内建事件可归纳为以下类型:

    • Dubbo 服务类型事件
    • Dubbo Service 类型事件
    • Dubbo 服务实例类型事件
    • Dubbo 服务注册和发现类型事件

    Dubbo 服务类型事件

    事件类型事件触发时机
    ServiceConfigExportedEvent当 Dubbo 服务暴露完成时
    ServiceConfigUnexportedEvent当 Dubbo 服务下线后
    ReferenceConfigInitializedEvent当 Dubbo 服务引用初始化后
    ReferenceConfigDestroyedEvent当 Dubbo 服务引用销毁后

    Dubbo Service 类型事件

    事件类型事件触发时机
    DubboShutdownHookRegisteredEvent当 Dubbo ShutdownHook 注册后
    DubboShutdownHookUnregisteredEvent当 Dubbo ShutdownHook 注销后
    DubboServiceDestroyedEvent当 Dubbo 进程销毁后

    Dubbo 服务实例类型事件

    事件类型事件触发时机
    ServiceInstancePreRegisteredEvent当 Dubbo 服务实例注册前
    ServiceInstanceRegisteredEvent当 Dubbo 服务实例注册后
    ServiceInstancePreUnregisteredEvent当 Dubbo 服务实例注销前
    ServiceInstanceUnregisteredEvent当 Dubbo 服务实例注销后
    ServiceInstancesChangedEvent当 某个 Dubbo Service 下的服务实例列表变更时

    Dubbo 服务注册和发现类型事件

    事件类型事件触发时机
    ServiceDiscoveryInitializingEvent当 Dubbo 服务注册与发现组件初始化中
    ServiceDiscoveryInitializedEvent当 Dubbo 服务注册与发现组件初始化后
    ServiceDiscoveryExceptionEvent当 Dubbo 服务注册与发现组件异常发生时
    ServiceDiscoveryDestroyingEvent当 Dubbo 服务注册与发现组件销毁中
    ServiceDiscoveryDestroyedEvent当 Dubbo 服务注册与发现组件销毁后

    课程推荐

    查看原文

    赞 10 收藏 4 评论 1

    mercyblitz 赞了文章 · 2020-04-02

    simviso 国外MIT CMU 斯坦福计算机科班顶级课程翻译系列

    前言

    关于simviso的介绍?一个小的民间组织而已,不想过多扯那么多没用的,只想尽一些绵薄之力,做点事情。同时,我们尽自己最大的努力,去做好每一点翻译,做好每一点知识分享。

    如果觉得不错,对自己很有帮助,可以帮我们分享一下,我们只想把更多的精力放在知识输出上,翻译或者自有的分享发出来,真的没多少人看,其实对我们来说也是一种打击。

    同时,本人也有关于很多编程方面的分享,比如Rxjava的源码解读 Spring Reactor的源码解读,Reactor-Netty的源码解读,Netty的源码解读,JDK中关于nio 并发库JUC的源码解读,Spring Framework5.2.2的源码解读,感兴趣的可以自行b站查找

    今年任务

    课程名称课程时长
    编译原理19h40min
    MIT 6.004 Computation Structures, Spring 201719h12min
    CMU 数据库 初级 201933h
    CMU 数据库 高级 201931h57min
    MIT 6.824 分布式系统30h
    MIT 6.858 计算机系统安全29h22min
    MIT 6.172 软件系统性能工程29h44min
    CMU CSAPP 2015 计算机操作系统25h48min
    CS 144 Introduction to Computer Networking23h10min

    编译原理入门 (已完结)

    合集地址-bilibili

    01 为什么要了解编译器

    02 编译阶段概述

    03 词法分析概述

    04 语法分析之语法规则分析 上

    05 语法分析之语法规则分析 下

    06 语法分析之语法规则及其他

    07 中间码生成

    08 目标代码生成

    斯坦福编译原理

    合集地址-simtoco

    合集地址-Bilibili

    01 编译原理入门

    02 编译器结构

    03 编程语言的性价比

    04 Cool语言概述 1

    05 Cool语言概述 2

    06 Cool语言概述 3

    07 词法分析01

    08 词法分析案例

    09 词法分析03 正则语言

    10 词法分析04 Formal Language

    11 词法分析05 词法规则

    12 词法分析06 Deduceit Demo

    13 词法规范

    14 有限自动机

    15 从正则表达式到NFA

    16 从NFA到DFA

    17 实现有限自动机

    18 解析器介绍

    19 上下文无关文法

    20 推导

    21 Ambiguity(歧义性)

    21 歧义性(Ambiguity)

    22 错误处理

    23 抽象语法树

    24 递归下降解析

    25 递归下降算法

    26 递归下降的局限性

    27 左递归

    28 预测解析

    29 First集

    30 Follow集

    31 LL(1)解析表

    32 自下而上的解析(Bottom-up Parsing)

    33 移位-归约解析(Shift Reduce Parsing)

    34 句柄(Handles)

    35 句柄识别

    36 识别可行前缀

    37 有效 item(Valid Items)

    38 SLR解析

    39 SLR解析案例

    斯坦福编译原理配套补充解读

    合集地址-simtoco

    合集地址-bilibili

    01 编译器各个阶段概述

    02 对token的理解认知

    03 对token的理解认知补充

    04 Lexical Analysis Examples

    05 Regular Languages

    06 Formal Languages

    07 词法规则

    08 词法规范

    09 Finite Automata

    10 Regular Expressions to NFAs

    11 NFA to DFA

    12 NFA to DFA补充

    13 实现有限自动机

    14 由词法分析到语法解析

    15 上下文无关文法

    16 终结符的一点补充

    CMU 15-445/645 Intro to Database System 数据库导论 2019年秋季版本

    合集地址-simtoco

    合集地址-bilibili

    01 课程简介与关系模型 01

    02 课程简介与数据模型 02

    03 课程简介与数据模型 03

    04 课程简介与关系模型 04

    05 高级SQL 01

    06 高级SQL 02

    07 高级SQL 03

    08 数据库存储1-01

    09 数据库存储1-02

    MIT 6.824 Distributed Systems 分布式系统 2020春季版

    合集地址-simtoco

    合集地址-bilibili

    01 课程简介 01

    02 课程简介 02

    03 课程简介 03

    04 课程简介 04

    05 RPC和多线程 01

    06 RPC和多线程 02

    07 RPC和多线程03

    08 RPC和多线程04

    MIT 6.004 Computation Structures

    合集地址:https://www.bilibili.com/video/BV1uE411e7SW/?p=1

    查看原文

    赞 4 收藏 1 评论 0

    mercyblitz 赞了文章 · 2020-03-14

    JetBrains 第二轮:再为免费全家桶续命三个月

    昨天分享了如何通过参与JetBrains的解密任务来获取正版全家桶的兑换码。今天 JetBrains 一早继续在Twitter推出第二波任务:

    file

    下面,我们就继续来一起参与一下,为我们的正版JetBrains全家桶续个命!

    线索一:Twitter

    第一条线索还是比较容易的,通过观察不难发现,Twitter上推出字符串中的单词是倒置的!

    所以,我们写个小程序来处理一下:

    @Test
    public void jetbrains() {
        String str = ".spleh A+lrtC/dmC .thgis fo tuo si ti semitemos ,etihw si txet nehw sa drah kooL .tseretni wohs dluohs uoy ecalp a si ,dessecorp si xat hctuD erehw esac ehT .sedih tseuq fo txen eht erehw si ,deificeps era segaugnal cificeps-niamod tcudorp ehT" ;
        String[] split = str.split(" ");
        String result = "";
        for (int i =0;i < split.length;i++) {
            result += new StringBuffer(split[i]).reverse() + " ";
        }
        System.out.println(result);
    }

    得到如下内容:

    helps. Cmd/Ctrl+A sight. of out is it sometimes white, is text when as hard Look interest. show should you place a is processed, is tax Dutch where case The hides. quest of next the where is specified, are languages domain-specific product The 

    貌似还不对,句子是反的!改造一下上面的算法:

    @Test
    public void jetbrains() {
        String str = ".spleh A+lrtC/dmC .thgis fo tuo si ti semitemos ,etihw si txet nehw sa drah kooL .tseretni wohs dluohs uoy ecalp a si ,dessecorp si xat hctuD erehw esac ehT .sedih tseuq fo txen eht erehw si ,deificeps era segaugnal cificeps-niamod tcudorp ehT" ;
        String[] split = str.split(" ");
        String result = "";
        for (int i = 0;i < split.length;i++) {
            result += new StringBuffer(split[split.length - 1 - i]).reverse() + " ";
        }
        System.out.println(result);
    }

    获取完整句子如下:

    The product domain-specific languages are specified, is where the next of quest hides. The case where Dutch tax is processed, is a place you should show interest. Look hard as when text is white, sometimes it is out of sight. Cmd/Ctrl+A helps. 

    线索二:The product domain-specific languages

    file

    根据线索一的提示:Dutch tax is processed,搜索一下看看:

    file

    找到这里,点击Read MPS case study,进入下面这个PDF

    file

    继续想线索一的另外一个提示:白色背景看不到,需要Cmd/Ctrl+A帮助!

    file

    可以看到有一段空白地方是有东西的,我们选中复制出来:

    This is our 20th year as a company,
    we have shared numbers in our JetBrains
    Annual report, sharing the section with
    18,650 numbers will progress your quest.

    线索三:20周年报告

    根据线索二得到的那段话,我们找到JetBrains的20周年页面:https://www.jetbrains.com/company/annualreport/2019/

    file

    继续根据线索二的提示,找一下数字:18,650。直接搜索是没找到,看来可能又是要做啥变换吧,仔细看这个页面。

    file

    原来是这里的数字加起来等于18,650。点下分享,看到任务描述:

    file

    I have found the JetBrains Quest! Sometimes you just need to look closely at the Haskell language, Hello,World! in the hackathon lego brainstorms project https://blog.jetbrains.com/blog/2019/11/22/jetbrains-7th-annual-hackathon/ #JetBrainsQuest https://www.jetbrains.com/company/annualreport/2019/ 来自 @JetBrains

    大意就是你需要仔细查看Haskell语的Hello World,在hackathon lego brainstorms项目中。

    线索三:hackathon lego brainstorms project

    通过线索提示中的链接进入https://blog.jetbrains.com/blog/2019/11/22/jetbrains-7th-annual-hackathon

    搜索lego brainstorms,可以看到之前提示的要素:

    file

    在图中有一段乱码需要我们翻译,通过页面审查工具,可以获得这段文字:

    file

    d1D j00 kN0w J378r41n2 12 4lW4Y2 H1R1N9? ch3CK 0u7 73h K4r33r2 P493 4nD 533 1f 7H3r3 12 4 J08 F0r J00 0R 4 KW357 cH4LL3n93 70 90 fUr7h3r @ l3457.

    这段卡了很久,后来看网上说这尼玛是英文版的火星文,大致找到一些对应关系主要是字符与实际字母的形状相似、发音相似等规律,比如:1=i、3=e、0=O、7=t...,连蒙带猜大致就是这段话:

    Did you know Jetbrains is always hiring? Check out the careers page and see if there is a job for you or for quest challenge to go further at least.

    大致意思是他们一直在招聘,到招聘页面有一个工作在等待你去接受挑战。

    线索四:招聘页的挑战

    根据上面的线索,直冲:https://www.jetbrains.com/jobs/

    搜索“quest”,可以看到这个职位:

    file

    点击进入职位介绍,可以找到这段关键指引:

    file

    它叫我们去为游戏开发者准备的页面。同时记住你以前在Konami游戏中作弊的方法,尝试一下在这个页面上作弊!

    找了很久没找到游戏开发者页面,只能去Google搜了一波,得到这个地址:https://www.jetbrains.com/gamedev/

    file

    炫酷的页面!下面请上:Konami游戏中作弊的方法!

    file

    这里有段历史,现在很多年轻人可能不太会不知道。Konami(科乐美)公司以前创建的游戏有一个经典的作弊方式:上上下下左右左右BA。这段熟悉的操作,曾经伴随我的童年。前段时间创造该秘籍的作者桥本和久先生于2020年2月25日晚去世,但是他的这段秘籍与我们的回忆长存!

    在输入秘籍之后,会进入一个打砖块的游戏:

    file

    消除掉所有砖块之后就能看到本次挑战的最后结果信息啦!

    这次DD就不放最终结果啦!强烈推荐读者们一起从线索一开始玩玩这个有趣的解密游戏!体会思考、解决问题的成就感!最后在为你的正版JetBrains全家桶续上三个月!

    这次放上我的截图吧,亲测有效!还一直留言说无效、垃圾的,建议再多试试。

    file

    作弊直接获取结果

    依然推荐自己玩一下,实在没耐心就从下面获取吧:

    1. 关注公众号「程序猿DD」
    2. 回复关键词:jetbrains第二弹,直接获取领奖地址和领奖Code!
    如果您对我的专题内容感兴趣,也可以关注我的博客:didispace.com
    查看原文

    赞 2 收藏 1 评论 0

    mercyblitz 关注了用户 · 2020-03-06

    敖丙 @aobing

    关注 5781

    mercyblitz 关注了专栏 · 2020-02-22

    code-craft

    spring boot , docker and so on 欢迎关注微信公众号: geek_luandun

    关注 725

    认证与成就

    • 认证信息 Java 技术专家
    • SegmentFault 讲师
    • 获得 452 次点赞
    • 获得 34 枚徽章 获得 4 枚金徽章, 获得 12 枚银徽章, 获得 18 枚铜徽章

    擅长技能
    编辑

    开源项目 & 著作
    编辑

    注册于 2017-05-09
    个人主页被 40.8k 人浏览