ZooKeeper-Dubbo

ZooKeeper-Dubbo

系统架构演变

传统单体架构 => 传统单体架构扩展 => 单体架构解耦 => 异步架构 => soa架构 => 微服务架构

微服务核心知识

type desc
网关 路由转发+过滤器(商品服务:/api/product)
负载均衡器 分发负载
服务注册中心 调用和被调用方的信息维护
配置中心 管理配置,动态更新配置文件
链路追踪 分析调用链路耗时( 下单-》查询商品服务获取商品价格-》查询用户信息-》保存数据库)
熔断 保护自己和调用方

ZooKeeper

微服务注册中心

服务管理,核心是有个服务注册表,心跳机制动态维护

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

  • 注册中心
    • 服务提供者provider: 启动的时候向注册中心上报自己的网络信息
    • 服务消费者consumer: 启动的时候向注册中心上报自己的网络信息,拉取provider的相关网络信息
  • 主流的注册中心
    • zookeeper、Eureka、consul、etcd 等

CAP

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

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

ZooKeeper数据模型

数据模型.png
  • zookeeper的数据模型类似于linux下的文件目录

    /usr/local/software /usr/local/software/jdk

  • 每一个节点都叫做zNode,可以有子节点,也可以有数据

  • 每个节点都能设置相应的权限控制用户的访问

  • 每个节点存储的数据不宜过大

  • 每个节点都带有一个版本号,数据变更时,版本号变更(乐观锁)

  • 节点分永久节点跟临时节点

zkCli常用命令

  • 连接远程服务器

    # 不填后面的参数,默认连接的就是localhost:2181
    zkCli.sh -timeout 0 -r -server ip:port
    
  • 帮助信息

    zkCli.sh h
    
  • 创建节点

    create [-s] [-e] path data acl
    # -s 创建顺序节点
    # -e 创建临时节点
    # path 节点名称
    # data 节点数据内容
    # acl 权限设置
    
  • 查看节点

    ls path [watch]
    # 获取节点的子节点
    # path 节点名称
    # watch 开启节点watch机制
    
    ls2 path [watch]
    # 获取节点的子节点以及当前节点的状态
    # path 节点名称
    # watch 开启节点watch机制
    
    get path [watch]
    # 获取节点的数据
    # path 节点名称
    # watch 开启节点watch机制
    
    stat path [watch]
    # 查看节点状态
    # path 节点名称
    # watch 开启节点watch机制
    
    # 查询结果说明
    cZxid = 0x4   #事务id
    ctime = Mon Mar 09 17:58:27 CST 2020 #节点创建时间
    mZxid = 0x4 #最后一次更新时的事务id
    mtime = Mon Mar 09 17:58:27 CST 2020 #最后一次更新的时间
    pZxid = 0x4 #该节点的子节点列表最后一次被修改的事务id
    cversion = 0 #子节点列表的版本
    dataVersion = 0 #数据内容的版本
    aclVersion = 0 #acl版本
    ephemeralOwner = 0x0 #用于临时节点,表示创建该临时节点的事务id,如果当前的节点不是临时节点,该字段值为0
    dataLength = 2 #数据内容的长度
    numChildren = 0 #子节点的数量
    
  • 修改节点的数据

    set path data [version]
    # path 节点名称
    # data 节点新数据内容
    # version 在哪个数据版本上修改
    
  • 删除节点数据

    delete path [version]
    # 删除节点,如果此时该节点有子节点,则不允许删除
    # path 节点名称
    # version  数据版本
    
    rmr path 
    # 递归删除整个节点
    # path 节点名称
    
  • 断开client链接

    close
    # 运行zkCli.sh之后断开client链接
    

session机制

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

watcher机制

watcher.png
  • 对节点的watcher操作 get、stat

    针对每一个节点的操作,都可以有一个监控者,当节点发生变化,会触发watcher事件 zk中watcher是一次性的,触发后立即销毁,所有有监控者的节点的变更操作都能触发watcher事件

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

acl

acl(access control lists)权限控制-目的是为了保证数据的安全性

acl的组成 -- scheme id 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 cdr cdw 
    c create 创建子节点
    d delete 删除子节点
    r read 读取节点的数据
    w write 写入数据
    a admin 可管理的权限
    
  • 常用的命令

    • getAcl 获取节点权限

      getAcl path
      
    • setAcl 设置节点权限

      setAcl path acl
      
    • addauth 输入认证授权信息,注册时输入明文密码,但是在zk里,以加密的形式保存

      addauth scheme auth
      
    # 测试
    create /net net # 创建/net节点
    getAcl /net # 获取/net的acl
    addauth digest fqx:fqx # 添加用户
    setAcl /net auth:fqx:fqx:cdw # 设置/net的acl
    
  • acl的使用场景

    • 开发环境跟测试环境,使用acl就可以进行分离,开发者无权去操作测试的节点
    • 生产环境上控制指定ip的服务可以访问相关的节点

Zookeeper 集群

ZK集群三种角色

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

ZK集群三种模式

zk的核心是广播机制,该机制保证了各个zk之间数据同步(数据一致性);zk实现的机制为ZAB协议

  • 恢复模式:如果leader崩溃,这个时候zk集群就会进入恢复模式,使整个zk集群恢复到正常的工作状态
  • 同步模式:新的leader选举出来后,就会进入同步模式(各个follower会去同步新的leader上的数据),当大多数zkServer完成了与leader的状态同步之后,恢复模式就结束
  • 广播模式:客户端想写入数据,这个时候leader发起提议,当leader的提议被大多数的zkServer同意之后,leader就会去修改自身的数据,并将修改后的数据广播给其他的follower

ZK集群选举核心概念

  • myid

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

  • zxid

                epoch                                 xid
    00000000000000000000000000000000   00000000000000000000000000000000
    zxid 为 Long 类型,其中高 32 位表示 epoch,低 32 位表示 xid。即 zxid 由两部分构成:epoch 与 xid。 
    每个 Leader 都会具有一个不同的 epoch 值,表示一个时期、时代。新的 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集群选举算法

  • 发送时机

    • 服务启动
    • leader宕机之后
  • 选举机制

    集群中,半数zkServer同意,则产生新的leader(搭建集群时,一般都是奇数个)

    1:服务启动时

    serverStart.png

2:Leader宕机之后

leaderDown.png
  • 选举算法:

    对比(myid,zxid),先对比zxid,zxid大者(大表示数据越新)胜出,成为leader;如果zxid一致,则myid大者成为leader

分布式锁

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

  • 方式及原理

    • zookeeper

      创建相应的节点,创建成功,则表示获取到相应的锁;创建失败,则表示获取锁失败,此时程序休眠等待,直到创建节点成功,即获取到相应的分布式锁。

      注意事项: 创建节点的时候,一定要创建临时节点,避免应用获取到锁后,宕机,导致锁一致被持有。

      zk-lock.png
  • redis、memcache

    设置一个值做为锁的一标志,每次获取锁的时候,判断对应的值是否存在,存在则无法获取,不存在,则设置相应的值,表示获取到锁。(redis 使用setnx,memcache使用add)

  • 代码

    import org.apache.zookeeper.*;
    import java.io.IOException;
    import java.util.concurrent.CountDownLatch;
    
    /**
     * 基于zk实现分布式锁
     */
    public class ZkLock {
        private static ZooKeeper zooKeeper;
        private static CountDownLatch countDownLatch = new CountDownLatch(1);
        /**
         * 私有化构造
         */
        private ZkLock() {
            try {
                // 连接zk服务器
                zooKeeper = new ZooKeeper("139.224.101.91:2181,139.224.101.91:2182,139.224.101.91:2183", 5000, new ZkWatcher());
                System.out.println("zkState=====" + zooKeeper.getState());
    
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * Watcher
         */
        private static class ZkWatcher implements Watcher {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("触发监听事件---------------》" + watchedEvent);
                // 链接成功
                if(Event.KeeperState.SyncConnected == watchedEvent.getState()) {
                    countDownLatch.countDown();
                }
            }
        }
    
        /**
         * 单例模式
         */
        private static class ZkLockSingleton {
            private static ZkLock zkLockInstance;
            static {
                zkLockInstance = new ZkLock();
            }
    
            private static ZkLock getZkLockInstance() {
                return zkLockInstance;
            }
    
        }
    
        /**
         *  获取zkLock实例
         */
        public static ZkLock getZkLockInstance() {
            return ZkLockSingleton.getZkLockInstance();
        }
    
        /**
         * 获取zk锁,即创建zk节点
         */
        public void getZkLock(Integer id) {
            while (true) {
                try {
                    zooKeeper.create("/cn-order" + id, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                    System.out.println("=====获取到zk锁=====");
                    break;
                } catch (Exception e) {
                    try {
                        Thread.sleep(500L);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                    System.out.println("=====创建zk节点失败=====");
                    e.printStackTrace();
                    continue;
    
                }
            }
        }
    
        /**
         * 释放锁,即删除zk节点
         */
        public void  unZkLock(Integer id) {
            try {
                zooKeeper.delete("/cn-order" + id, -1);
            } catch (Exception e) {
                System.out.println("======删除zk节点失败======");
                e.printStackTrace();
            }
        }
    }
    
    public class ConcurrentTest {
        private static int i = 0;
        private static ZkLock lock = ZkLock.getZkLockInstance();
    
        private static synchronized void addNumI() {
            lock.getZkLock(1);
            i++;
            System.out.println("addNumI==== " + i);
            lock.unZkLock(1);
        }
    
        public static void main(String[] args) {
            for (int j = 0; j < 100; j++) {
                new Thread(() -> addNumI()).start();
            }
        }
    }
    
    

Dubbo

官网:http://dubbo.apache.org/zh-cn/

架构讲解

dubbo.jpg
  • 节点角色说明

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

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

服务中心

  • simple 注册中心:不支持集群,不适用于生产环境
  • Multicast 注册中心:开发阶段使用
  • zookeeper 注册中心 :官方推荐使用,常用
  • redis 注册中心:虽然支持,但是较少使用
  • nacos 注册中心:后起之秀 https://nacos.io/zh-cn/

注解版代码实现

参考:https://github.com/apache/dubbo-spring-boot-project/blob/master/README_CN.md

  • 项目结构
DubboDemo.png
  • user-api

    Dubbo RPC API ,由服务提供方为服务消费方暴露接口

    package cn.net.user.service;
    public interface UserService {
        public String sayHello(String name);
    }
    
  • user-service

    Dubbo 服务提供方

    1: 配置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>cn.net</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
        <artifactId>user-service</artifactId>
    
        <properties>
            <spring-boot.version>2.2.2.RELEASE</spring-boot.version>
            <dubbo.version>2.7.5</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>
    
                <!-- Apache 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>
            <!-- user-api -->
            <dependency>
                <groupId>cn.net</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.5</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 dependencies -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-dependencies-zookeeper</artifactId>
                <version>${dubbo.version}</version>
                <type>pom</type>
                <exclusions>
                    <exclusion>
                        <groupId>org.slf4j</groupId>
                        <artifactId>slf4j-log4j12</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    </project>
    

    2: 实现 user-api 中 UserService 接口

    package cn.net.user.service.Impl;
    import cn.net.user.service.UserService;
    import org.apache.dubbo.config.annotation.Service;
    
    @Service(version = "1.0.0")
    public class UserServiceImpl  implements UserService {
        public String sayHello(String name) {
            return "hello " + name;
        }
    }
    

    3: 编写启动类

    package cn.net.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, args);
        }
    }
    

    4: 配置application.properties

    server.port=8081
    # Spring boot application
    spring.application.name=user-provider
    # Base packages to scan Dubbo Component: @org.apache.dubbo.config.annotation.Service
    dubbo.scan.base-packages=cn.net.user.service.Impl
    
    # Dubbo Protocol
    dubbo.protocol.name=dubbo
    dubbo.protocol.port=20890
    
    ## Dubbo Registry
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    
  • user-web

    Dubbo 服务消费方

    1: 配置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>cn.net</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
        <artifactId>user-web</artifactId>
    
        <properties>
            <spring-boot.version>2.2.2.RELEASE</spring-boot.version>
            <dubbo.version>2.7.5</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>
    
                <!-- Apache 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>
            <!-- user-api -->
            <dependency>
                <groupId>cn.net</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.5</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 dependencies -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-dependencies-zookeeper</artifactId>
                <version>${dubbo.version}</version>
                <type>pom</type>
                <exclusions>
                    <exclusion>
                        <groupId>org.slf4j</groupId>
                        <artifactId>slf4j-log4j12</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
    </project>
    

    2: 访问入口UserController

    package cn.net.user.controller;
    import cn.net.user.service.UserService;
    import org.apache.dubbo.config.annotation.Reference;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class UserController {
    
        @Reference(version = "1.0.0")
        private UserService userService;
    
        @GetMapping("/sayHello")
        public String sayHello(String name) {
            return userService.sayHello(name);
        }
    }
    

    3:启动类

  package cn.net.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, args);
      }
  }

4: 配置application.properties

  spring.application.name=user-consumer
  dubbo.registry.address=zookeeper://127.0.0.1:2181
  • zookeeper结构图
dubbo-ZooKeeper-jiegou.png

xml版代码实现

参考:http://dubbo.apache.org/zh-cn/docs/user/configuration/xml.html

  • user-api

    同注解版

  • user-service

    Dubbo 服务提供方

    1: 配置pom.xml

    同注解版

    2:配置application.properties

    server.port=8081
    # Spring boot application
    spring.application.name=user-provider
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    

    3:实现 user-api 中 UserService 接口

    package cn.net.user.service.Impl;
    import cn.net.user.service.UserService;
    
    public class UserServiceImpl implements UserService {
        @Override
        public String sayHello(String name) {
            return "hello " + name;
        }
    }
    

    4:加入provider.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:protocol name="dubbo" port="20890"/>
        <bean id="userService" class="cn.net.user.service.Impl.UserServiceImpl"/>
        <dubbo:service interface="cn.net.user.service.UserService" ref="userService"/>
    </beans>
    

    5:启动类

    package cn.net.user.service;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
    import org.springframework.context.annotation.ImportResource;
    
    @EnableAutoConfiguration
    @ImportResource("provider.xml")
    public class ServiceApplication {
        public static void main(String[] args) {
            SpringApplication.run(ServiceApplication.class, args);
        }
    }
    
  • user-web

    Dubbo 服务消费方

    1: 配置pom.xml

    同注解版

    2:配置application.properties

    spring.application.name=user-consumer
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    

    3:加入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:reference id="userService" check="false" interface="cn.net.user.service.UserService"/>
    </beans>
    

    4:访问入口UserController

    package cn.net.user.controller;
    import cn.net.user.service.UserService;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import javax.annotation.Resource;
    
    @RestController
    public class UserController {
    
        @Resource
        private UserService userService;
    
        @GetMapping("/sayHello")
        public String sayHello(String name) {
            return userService.sayHello(name);
        }
    }
    

    5:启动类

    package cn.net.user;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ImportResource;
    
    @SpringBootApplication
    @ImportResource("consumer.xml")
    public class WebApplication {
        public static void main(String[] args) {
            SpringApplication.run(WebApplication.class, args);
        }
    }
    

Dubbo-Admin

Dubbo管理控制台

下载地址:https://github.com/apache/dubbo-admin

安装教程:https://github.com/apache/dubbo-admin/blob/develop/README_ZH.md

控制台介绍:http://dubbo.apache.org/zh-cn/docs/admin/introduction.html

dubbo-admin.png

Dubbo高级特性

启动检查

参考:http://dubbo.apache.org/zh-cn/docs/user/demos/preflight-check.html

Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"

可以通过 check="false" 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。

另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check="false",总是会返回引用,当服务恢复时,能自动连上。

<dubbo:reference id="userService" check="true" interface="cn.net.user.service.UserService"/>

加载流程

参考:http://dubbo.apache.org/zh-cn/docs/user/configuration/configuration-load-process.html

  • 配置来源

    • JVM System Properties,-D参数
    • Externalized Configuration,外部化配置(2.7版本)
    • ServiceConfig、ReferenceConfig等编程接口采集的配置
    • 本地配置文件dubbo.properties
  • 配置的优先级

    配置优先级是从上往下一次递减

  • 配置实例

    -Ddubbo.reference.cn.net.user.service.UserService.check=false

超时机制

  • 服务提供方、服务消费方分别进行配置

    <!-- 提供方 --> 
    <dubbo:service interface="cn.net.user.service.UserService" ref="userService" timeout="3000"/>
    <!-- 消费方 --> 
    <dubbo:reference id="userService" check="true" interface="cn.net.user.service.UserService" timeout="6000"/>
    <!-- 若提供方、服务方同时配置以消费方为主 --> 
    
  • 服务提供方方法级别进行配置

    <!-- 若提供方服务级别与方法级别同时配置,以方法级别为准 -->
    <dubbo:service interface="cn.net.user.service.UserService" ref="userService" timeout="3000">
          <dubbo:method name="sayHello" timeout="6000"></dubbo:method>
    </dubbo:service>
    

集群容错机制

参考:http://dubbo.apache.org/zh-cn/docs/user/demos/fault-tolerent-strategy.html

当服务调用失败的时候,默认情况下,dubbo会进行重试,默认重试次数为2(不包含第一次)

<dubbo:reference id="userService" check="true" interface="cn.net.user.service.UserService" retries="4"/>

集群容错模式

  • Failover Cluster

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

  • Failfast Cluster

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

  • Failsafe Cluster

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

  • Failback Cluster

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

  • Forking Cluster

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

  • Broadcast Cluster

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

<dubbo:reference id="userService" check="true" interface="cn.net.user.service.UserService" retries="4" cluster="failfast"/>

多协议

参考:http://dubbo.apache.org/zh-cn/docs/user/references/protocol/introduction.html

参考:http://dubbo.apache.org/zh-cn/docs/user/demos/multi-protocols.html

  • user-api添加依赖

    <!-- https://mvnrepository.com/artifact/com.github.briandilley.jsonrpc4j/jsonrpc4j -->
    <dependency>
      <groupId>com.github.briandilley.jsonrpc4j</groupId>
      <artifactId>jsonrpc4j</artifactId>
      <version>1.2.0</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-util -->
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-util</artifactId>
      <version>9.4.24.v20191120</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-server -->
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-server</artifactId>
      <version>9.4.24.v20191120</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.eclipse.jetty/jetty-servlet -->
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-servlet</artifactId>
      <version>9.4.24.v20191120</version>
    </dependency>
    
  • 配置多协议

    <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:protocol name="dubbo" port="20890"/>-->
        <dubbo:protocol name="http" port="8085"/>
        <bean id="userService" class="cn.net.user.service.Impl.UserServiceImpl"/>
        <dubbo:service interface="cn.net.user.service.UserService" ref="userService" protocol="http"/>
    </beans>
    

多注册中心

参考:http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html

参考:http://dubbo.apache.org/zh-cn/docs/user/demos/multi-registry.html

  • 配置多注册中心

    <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:registry id="romete" address="zookeeper://139.224.101.91:2181"/>
        <dubbo:protocol name="dubbo" port="20890"/>
        <bean id="userService" class="cn.net.user.service.Impl.UserServiceImpl"/>
        <dubbo:service interface="cn.net.user.service.UserService" ref="userService" register="local,romete"/>
    </beans>
    

服务分组

参考:http://dubbo.apache.org/zh-cn/docs/user/demos/service-group.html

当一个接口有多种实现时,可以用 group 区分。

  • 提供者配置分组

    <bean id="userService" class="cn.net.user.service.Impl.UserServiceImpl"/>
    <dubbo:service group="user1" interface="cn.net.user.service.UserService" ref="userService" register="local,romete"/>
    
    <bean id="userService2" class="cn.net.user.service.Impl.UserServiceImpl2"/>
    <dubbo:service group="user2" interface="cn.net.user.service.UserService" ref="userService2" register="local,romete"/>
    
  • 消费端配置分组

    <dubbo:reference group="user1" id="userService" check="true" interface="cn.net.user.service.UserService"/>
    

多版本

参考:http://dubbo.apache.org/zh-cn/docs/user/demos/multi-versions.html

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

一旦服务提供者制定了版本,那么所有的服务消费者也是需要去指定相应的版本

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

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

    <dubbo:service interface="cn.net.user.service.UserService" ref="userService" version="1.0.0"/>
    
  • 消费者配置

    <dubbo:reference id="userService" check="true" interface="cn.net.user.service.UserService" version="1.0.0"/>
    
  • 建议:暴露服务的时候,最好都指定下服务的版本,方便后续接口的不兼容升级

动态配置中心

动态配置即外部化配置(使用Dubbo-Admin的配置管理功能进行配置)。启动配置的集中式存储 (简单理解为dubbo.properties的外部化存储)。 服务治理。服务治理规则的存储与通知

参考:http://dubbo.apache.org/zh-cn/docs/admin/serviceGovernance.html

  • 控制台配置
dubbo-admin-global.png
  • 程序配置

    <dubbo:config-center address="zookeeper://127.0.0.1:2181"/>
    
  • 注意:如果全局配置跟应用级别均配置了相同的信息,这个时候以应用级别的为准

元数据中心

参考:http://dubbo.apache.org/zh-cn/docs/user/references/metadata/introduction.html

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

  • maven依赖

    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-metadata-report-zookeeper</artifactId>
    </dependency>
    
  • 配置项

    dubbo.metadata-report.address=zookeeper://127.0.0.1:2181
    dubbo.metadata-report.username=xxx        ##非必须
    dubbo.metadata-report.password=xxx        ##非必须
    dubbo.metadata-report.retry-times=30       ##非必须,default值100
    dubbo.metadata-report.retry-period=5000    ##非必须,default值3000
    dubbo.metadata-report.cycle-report=false   ##非必须,default值true
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容