SpringBoot整合Dubbo & zookeeper & Dubbo-admin

一、分布式概述

发展演变

在这里插入图片描述

1.1 单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。

缺点

  • 性能扩展比较难
  • 协同开发问题
  • 不利于升级维护

在这里插入图片描述

1.2 垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。

缺点

  • 公用模块无法重复利用,开发性的浪费

在这里插入图片描述

1.3 分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的**分布式服务框架****(RPC)**是关键。

在这里插入图片描述

1.4 流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。

在这里插入图片描述

1.5 RPC概念

概述

RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

在这里插入图片描述

RPC两个核心模块

  • 通讯
  • 序列化

二、Dubbo概述

概述

Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。同时 Dubbo 是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。

基本工作模式

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

三、Dubbo环境搭建

3.1 配置Zookeeper注册中心

  1. 官网下载Zookeeper

    zookeeper官网: https://zookeeper.apache.org/index.html

  2. 解压zookeeper,修改配置

    • 将conf文件下的zoo_sample.cfg,复制一份改名为zoo.cfg(为zookeeper的默认配置文件名,默认不修改直接启动会报错)
    • 修改zoo.cfg中的dataDir,这为临时数据存储的目录,可自定义存放的位置,这里配置为datDir=…/data,然后在conf同级目录新建data文件夹
    • clientPort=2181为zookeeper的端口号,可自定义修改,这里就不改了
    • maxClientCnxns=60为zookeeper的最大连接数量
    • tickTime=2000为zookeeper服务注册的超时时间
  3. 进入bin目录,控制台中打开,输入zkServer.cmd命令启动注册中心

  4. 进入bin目录,控制台打开,输入zkCli.cmd客户端连接注册中心,检查注册中心是否启动成功

    • ls / :列出注册中心的所有节点
    • get /zookeeper:获取该节点的值
    • create -e /haha 123:创建一个haha节点,值为123

3.2 配置Dubbo-Admin管理控制台

  1. 下载dubbo-admin管理控制台源码

    https://github.com/apache/dubbo-admin

    或者直接克隆项目:git clone https://github.com/apache/dubbo-admin.git

  2. 由于当前的后台管理为前后端分离的项目,所以这里要配置后台源码的一些配置,和修改前端的代理端口地址

  3. 修改dubbo-admin-server后台项目中的application.properties配置自己定义的zookeeper的注册中心的端口和ip,也可以自定义登录用户名和密码,最重要修改后台的启动端口号这里配置为7001,不修改的话后面会有端口冲突问题

在这里插入图片描述

  1. 修改完成后保存,返回到dubbo-admin-server目录下,在cmd窗口打开执行mvn clean package -Dmaven.test.skip=true打包命令,生成jar包,然后在控制台使用java -jar 命令运行生成的jar包,至此后台部分配置完成

  2. 进入前端dubbo-admin-ui项目中,修改vue.config.js,修改代理的端口为上面后台的端口地址7001

    在这里插入图片描述

  3. 启动前端项目要安装node.js,进入dubbo-admin-ui,控制台打开使用npm i 下载项目所需的依赖,下载后使用npm run dev 运行前端项目,前提后台项目和zookeeper要先启动

  4. 浏览器输入localhost:9001访问后台管理,输入用户名和密码后如下,至此配置全部完成

    在这里插入图片描述
    在这里插入图片描述

3.3 springboot整合dubbo

1. 创建order-service-comsumer、user-service-provider、common-api模块

在这里插入图片描述

2. 引入dubbo和curator 依赖以及common-api
  		<!--自定义comm-api依赖-->
		<dependency>
            <groupId>com.example</groupId>
            <artifactId>common-api</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency> 		
		<dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>5.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>5.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-x-discovery</artifactId>
            <version>5.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>3.0.7</version>
        </dependency>
3. 在common-api中定义bean和要远程调用的接口

在这里插入图片描述

UserAddress

/**
 * 用户地址
 *
 */
public class UserAddress implements Serializable {
	
	private Integer id;
    private String userAddress; //用户地址
    private String userId; //用户id
    private String consignee; //收货人
    private String phoneNum; //电话号码
    private String isDefault; //是否为默认地址    Y-是     N-否
    
    public UserAddress() {
		super();
	}
    
	public UserAddress(Integer id, String userAddress, String userId, String consignee, String phoneNum,
                       String isDefault) {
		super();
		this.id = id;
		this.userAddress = userAddress;
		this.userId = userId;
		this.consignee = consignee;
		this.phoneNum = phoneNum;
		this.isDefault = isDefault;
	}
	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getUserAddress() {
		return userAddress;
	}
	public void setUserAddress(String userAddress) {
		this.userAddress = userAddress;
	}
	public String getUserId() {
		return userId;
	}
	public void setUserId(String userId) {
		this.userId = userId;
	}
	public String getConsignee() {
		return consignee;
	}
	public void setConsignee(String consignee) {
		this.consignee = consignee;
	}
	public String getPhoneNum() {
		return phoneNum;
	}
	public void setPhoneNum(String phoneNum) {
		this.phoneNum = phoneNum;
	}
	public String getIsDefault() {
		return isDefault;
	}
	public void setIsDefault(String isDefault) {
		this.isDefault = isDefault;
	}
    

}

OrderService

public interface OrderService {
	
	/**
	 * 初始化订单
	 * @param userId
	 */
	public List<UserAddress> initOrder(String userId);

}

UserService

/**
 * 用户服务
 *
 */
public interface UserService {
	
	/**
	 * 按照用户id返回所有的收货地址
	 * @param userId
	 * @return
	 */
	public List<UserAddress> getUserAddressList(String userId);

}
4. 配置order和user的注册中心地址信息和启用dubbo

在两个模块的启动类上添加@EnableDubbo注解,开启dubbo的自动配置

order模块的application.properties

# 配置服务的名,以下两种配置方式相同
#spring.application.name=order-service
dubbo.application.name=order-service
# 项目启动端口
server.port=8001

# 配置包扫描的路径
#dubbo.scan.base-packages=com.example.orderservicecomsumer.service

# 配置注册中心的地址,指定注册中心的类型和地址信息,以下两种方式配置相同
#dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper

user模块的application.properties

# 配置服务的名,以下两种配置方式相同
dubbo.application.name=user-service
# 项目启动端口
server.port=8002

# 配置注册中心的地址,指定注册中心的类型和地址信息
dubbo.registry.address=zookeeper://127.0.0.1:2181

# 配置服务提供者远程通信的协议和端口
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881
5. order和user模块配置对应的service和controller

order模块

在这里插入图片描述

OrderController

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    OrderService orderService;

    @GetMapping("/getOrder")
    public List<UserAddress> getOrder(@RequestParam("userId") String userId){
        return orderService.initOrder(userId);
    }
}

OrderServiceImpl

@Service
public class OrderServiceImpl implements OrderService {
    /**
     * 调用远程接口,指定接口,关闭自启动检查
     */
    @DubboReference(interfaceClass = UserService.class, check = false)
    UserService userService;


    @Override
    public List<UserAddress> initOrder(String userId) {
        return userService.getUserAddressList(userId);
    }
}

user模块

在这里插入图片描述

UserServiceImpl

/**
 * 暴露远程调用的接口,对外提供服务
 */
@DubboService(interfaceClass = UserService.class)
public class UserServiceImpl implements UserService {


    @Override
    public List<UserAddress> getUserAddressList(String userId) {
        UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
        UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
        return Arrays.asList(address1,address2);
    }
}
6. 启动项目,到控制台查看

控制台查看

在这里插入图片描述
在这里插入图片描述

使用order服务调用user服务

在这里插入图片描述

四、Dubbo配置

4.1 启动时检查

Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check="true"。可以通过 check="false" 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。

@Service
public class OrderServiceImpl implements OrderService {
    /**
     * 调用远程接口,指定接口,关闭自启动检查
     */
    @DubboReference(interfaceClass = UserService.class, check = false)
    UserService userService;

    @Override
    public List<UserAddress> initOrder(String userId) {
        return userService.getUserAddressList(userId);
    }
}

4.2 重试次数

失败自动切换,当出现失败,重试其它服务器,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。

@Service
public class OrderServiceImpl implements OrderService {
    /**
     * 调用远程接口,指定接口,关闭自启动检查
     */
    @DubboReference(interfaceClass = UserService.class, check = false, retries = 3)
    UserService userService;


    @Override
    public List<UserAddress> initOrder(String userId) {
        return userService.getUserAddressList(userId);
    }
}

4.3 超时时间

由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间。

order端配置

@Service
public class OrderServiceImpl implements OrderService {
    /**
     * 调用远程接口,指定接口,关闭自启动检查
     */
    @DubboReference(interfaceClass = UserService.class, check = false, retries = 3, timeout = 5000)
    UserService userService;


    @Override
    public List<UserAddress> initOrder(String userId) {
        return userService.getUserAddressList(userId);
    }
}

user端配置

@DubboService(interfaceClass = UserService.class, timeout = 5000)
public class UserServiceImpl implements UserService {


    @Override
    public List<UserAddress> getUserAddressList(String userId) {
        UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
        UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
        return Arrays.asList(address1,address2);
    }
}

在这里插入图片描述

配置优先级

  • 方法级配置别优于接口级别,接口级别优先与全局配置
  • 同一级别消费端配置大于提供端

4.4 版本号

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

order端

@Service
public class OrderServiceImpl implements OrderService {
    /**
     * 调用远程接口,指定接口,关闭自启动检查,version=*,表示随机调用,新版本和旧版本都可调用
     */
    @DubboReference(interfaceClass = UserService.class, check = false, version = "*")
    UserService userService;


    @Override
    public List<UserAddress> initOrder(String userId) {
        return userService.getUserAddressList(userId);
    }
}

user端

/**
 * 暴露远程调用的接口,对外提供服务
 */
@DubboService(interfaceClass = UserService.class, version = "1.0.0")
public class UserServiceImpl implements UserService {


    @Override
    public List<UserAddress> getUserAddressList(String userId) {
        UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
        UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
        return Arrays.asList(address1,address2);
    }
}

4.5 本地存根

远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。

在这里插入图片描述
在这里插入图片描述

OrderServiceStub

public class OrderServiceStub implements UserService {

    public final UserService userService;

    // 构造函数传入真正的远程代理对象
    public OrderServiceStub(UserService userService) {
        this.userService = userService;
    }

    @Override
    public List<UserAddress> getUserAddressList(String userId) {
        // 这里可以对参数进行校验
        System.out.println("OrderServiceStub----1");
        if (StringUtils.hasText(userId)){
            return userService.getUserAddressList(userId);
        }
        return null;
    }
}

OrderServiceImpl

@Service
public class OrderServiceImpl implements OrderService {
    /**
     * 调用远程接口,指定接口,关闭自启动检查
     */
    @DubboReference(check = false, stub = "com.example.orderservicecomsumer.service.OrderServiceStub", methods = @Method(name = "getUserAddressList", timeout = 6000, retries = 3))
    UserService userService;


    @Override
    public List<UserAddress> initOrder(String userId) {
        return userService.getUserAddressList(userId);
    }
}

user端

UserServiceImpl

/**
 * 暴露远程调用的接口,对外提供服务
 */
@DubboService(interfaceClass = UserService.class)
public class UserServiceImpl implements UserService {

    @Override
    public List<UserAddress> getUserAddressList(String userId) {
        UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
        UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
        return Arrays.asList(address1,address2);
    }
}

五、高可用

5.1 与dubbo直连

现象:zookeeper注册中心宕机,还可以消费dubbo暴露的服务。

OrderServiceImpl配置

@Service
public class OrderServiceImpl implements OrderService {
    /**
     * 调用远程接口,关闭自启动检查,跳过注册中心
     */
    @DubboReference(check = false, url = "127.0.0.1:20882")
    UserService userService;


    @Override
    public List<UserAddress> initOrder(String userId) {
        return userService.getUserAddressList(userId);
    }
}

5.2 负载均衡

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。

负载均衡策略:

  • RandomLoadBalance:加权随机,默认算法,默认权重相同
  • RoundRobinLoadBalance:加权轮询
  • LeastActiveLoadBalance:最少活跃优先 + 加权随机
  • ShortestResponseLoadBalance:最短响应优先 + 加权随机,更加关注响应速度
  • ConsistentHashLoadBalance:一致性 Hash,确定的入参,确定的提供者,适用于有状态请求

配置方式

注解配置

@DubboReference(check = false, loadbalance = LoadbalanceRules.RANDOM)
UserService userService;

xml配置

<!-- 客户端配置 -->
<dubbo:reference interface="..." loadbalance="roundrobin" />

<!-- 服务端配置 -->
<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>

<!-- 客户端方法配置 -->
<dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>

5.3 集群容错

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

在这里插入图片描述

容错模式

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

    在这里插入图片描述

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

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

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

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

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

    /**
    Broadcast Cluster 配置 broadcast.fail.percent。
    
    broadcast.fail.percent=20 代表了当 20% 的节点调用失败就抛出异常,不再调用其他节点。
    */
    @reference(cluster = "broadcast", parameters = {"broadcast.fail.percent", "20"})
    
  • Available Cluster:调用目前可用的实例(只调用一个),如果当前没有可用的实例,则抛出异常。通常用于不需要负载均衡的场景。

  • Mergeable Cluster:将集群中的调用结果聚合起来返回结果,通常和group一起配合使用。通过分组对结果进行聚合并返回聚合后的结果,比如菜单服务,用group区分同一接口的多种实现,现在消费方需从每种group中调用一次并返回结果,对结果进行合并之后返回,这样就可以实现聚合菜单项。

  • ZoneAware Cluster:多注册中心订阅的场景,注册中心集群间的负载均衡。对于多注册中心间的选址策略有如下四种
    在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐