6

Dubbo 学习笔记 - 低吟不作语

 2 years ago
source link: https://www.cnblogs.com/Yee-Q/p/16156951.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

分布式基础理论

1. 什么是分布式系统?

分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个系统

2. 应用架构演变

  1. 单一应用架构

    当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本,适用于小型网站,小型管理系统

  2. 垂直应用架构

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

  3. 分布式服务架构

    当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求

  4. 流动计算架构

    当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率

3. RPC

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

一次完整的 RPC 调用流程如下:

  1. 服务消费方(client)调用以本地调用方式调用服务

  2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体

  3. client stub找到服务地址,并将消息发送到服务端

  4. server stub收到消息后进行解码

  5. server stub根据解码结果调用本地的服务

  6. 本地服务执行并将结果返回给server stub

  7. server stub将返回结果打包成消息并发送至消费方

  8. client stub接收到消息,并进行解码

  9. 服务消费方得到最终结果

RPC框架的目标就是把 2~8 这些步骤都封装起来,这些细节对用户来说是透明的,不可见的

Dubbo 核心概念

Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现

2. 设计架构

相关组件说明如下:

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

调用关系说明如下:

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

Dubbo 环境搭建

1. 安装 Zookeeper

下载 zookeeper,网址:https://archive.apache.org/dist/zookeeper/

解压,将 conf 下的 zoo_sample.cfg 复制一份改名为 zoo.cfg,注意如下配置

dataDir=./   临时数据存储的目录(可写相对路径)
clientPort=2181   zookeeper的端口号

运行 zkServer.cmd 启动 zookeeper,使用 zkCli.cmd 测试

2. 安装管理控制台

下载 dubbo-admin,网址:https://github.com/apache/incubator-dubbo-ops

进入目录,修改 src\main\resources\application.properties 指定 zookeeper 地址

打包 dubbo-admin,以 Jar 包形式运行,默认使用 root/root 登陆

3. 安装监控中心

下载 dubbo-monitor,网址:https://github.com/apache/incubator-dubbo-ops

进入目录,修改 src\main\resources\conf\dubbo.properties

dubbo.registry.address=zookeeper://127.0.0.1:2181 # 注册中心地址
dubbo.jetty.port=8080 # http访问端口号

打包 dubbo-monitor,找到解压后的 assembly.bin 文件,start.bat 启动

在服务提供者和消费者的 xml 中配置以下内容,再次启动服务提供和消费者启动类

<!-- dubbo-monitor-simple 监控中心发现的配置-->
<dubbo:monitor protocol="registry"></dubbo:monitor>

监控中心即可捕获到服务提供方和消费方信息

Dubbo 开发

1. 创建服务提供和服务消费工程

假设有如下需求场景:订单服务作为服务消费者,用户服务作为服务提供者,订单服务需要从用户服务查询用户的所有收货地址

基于上述要求,用户服务作为服务提供方,要提供一个对应的接口,具体的实现和实体这里不详细列出

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

订单服务作为服务消费方,直接使用服务提供方的接口

public class OrderServiceImpl implements OrderService {
    
    public UserService userService;
    
    public void initOrder(String userID) {
        // 查询用户的收货地址
        List<UserAddress> userAddressList = userService.getUserAddressList(userID);
    }
}

因为服务提供方和消费方用到了同样的接口和实体,所以可以将服务接口,服务实体等单独放在一个公共项目中,方便调用

2. 服务提供方配置

<!-- dubbo -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.2</version>
</dependency>
<!-- 注册中心是 zookeeper,引入 zookeeper 客户端 -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>

在 resource 文件夹中创建 provider.xml

<?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://code.alibabatech.com/schema/dubbo"
       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
		http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
    
    <!-- 1.指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名) -->
   <dubbo:application name="user-service-provider"></dubbo:application>
    <!-- 2.指定注册中心的位置 -->
    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>
    <!-- 3.指定通信规则 -->
    <dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>
    <!-- 4.暴露服务,让别人调用ref指向服务的真正实现对象-->
    <dubbo:service interface="com.lemon.gmail.service.UserService" ref="userServiceImpl"></dubbo:service>
    <!-- 5.服务的实现-->
    <bean id="userServiceImpl" class="com.lemon.gmail.service.impl.UserServiceImpl"></bean>
</beans>

3. 服务消费方配置

<!-- dubbo -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.2</version>
</dependency>
<!-- 注册中心是 zookeeper,引入 zookeeper 客户端 -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>

在 resource 文件夹中创建 consumer.xml

<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
		http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- 1.指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名) -->
    <dubbo:application name="order-service-consumer"></dubbo:application>
    <!-- 2.指定注册中心的位置 -->
    <dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>
    <!-- 3.调用远程暴露的服务,生成远程服务代理 -->
    <dubbo:reference interface="com.yeeq.service.UserService" id="userService"></dubbo:reference>
</beans>

在当前 OrderServiceImpl 实现类中加上注解

@Service
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    public UserService userService;
    
    public void initOrder(String userID) {
        // 查询用户的收货地址
        List<UserAddress> userAddressList = userService.getUserAddressList(userID);
    }
}

Dubbo 整合 SpringBoot

1. 服务提供方

<dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>0.2.0</version>
</dependency>

接口和实体和之前一样,配置 application.properties

dubbo.application.name=boot-user-service-provider
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper

dubbo.protocol.name=dubbo
dubbo.protocol.port=20880

# 连接监控中心
dubbo.monitor.protocol=registry

在启动类加上注解

@EnableDubbo // 开启基于注解的dubbo功能
@SpringBootApplication
public class BootProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootProviderApplication.class, args);
    }
}

2. 服务消费方

<dependency>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>0.2.0</version>
</dependency>

接口和实体和之前一样,配置 application.properties

server.port=8081
dubbo.application.name=boot-order-service-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181

# 连接监控中心 注册中心协议
dubbo.monitor.protocol=registry

在启动类加上注解

@EnableDubbo // 开启基于注解的dubbo功能
@SpringBootApplication
public class BootConsumerApplication {
    public static void main(String[] args){
        SpringApplication.run(BootConsumerApplication.class,args);
    }
}

Dubbo 配置

1. 配置原则

  • JVM 启动 -D 参数优先,这样可以使用户在部署和启动时进行参数重写,比如在启动时需改变协议的端口
  • XML 次之,如果在 XML 中有配置,则 dubbo.properties 中的相应配置项无效
  • Properties 最后,相当于缺省值,只有 XML 没有配置时,dubbo.properties 的相应配置项才会生效,通常用于共享公共配置,比如应用名

2. 启动检查

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

以消费者为例,在 consumer.xml 中添加配置

<!-- 配置当前消费者的统一规则,开启启动检查,默认true -->
<dubbo:consumer check="true"></dubbo:consumer>

3. 超时时间

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

<!-- 全局超时配置 -->
<dubbo:consumer timeout="5000" />

<!-- 指定接口以及特定方法超时配置 -->
<dubbo:reference interface="com.foo.BarService" timeout="2000">
    <dubbo:method name="sayHello" timeout="3000" />
</dubbo:reference>

Dubbo 消费端和服务端都可以设置,推荐在 Provider 上尽量多配置 Consumer 端属性,原因如下:

  • 一般来说,服务提供者比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数等等
  • 在 Provider 配置后,Consumer 不配置则会使用 Provider 的配置值,即 Provider 配置可以作为 Consumer 的缺省值。否则,Consumer 会使用 Consumer 端的全局设置,这对于 Provider 是不可控的,并且往往是不合理的

4. 重试次数

当出现调用失败,一般会进行重试。但重试会带来更长延迟,可通过 retries="2" 来设置重试次数(不含第一次)

<!-- 有三种方式配置 -->
<dubbo:service retries="2" />
<dubbo:reference retries="2" />
<dubbo:reference>
    <dubbo:method name="findFoo" retries="2" />
</dubbo:reference>

5. 多版本

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

服务端配置

<!-- 老版本服务提供者配置 -->
<bean id="BarServiceImpl01" class="com.foo.BarService.impl.BarServiceImpl01" />
<dubbo:service interface="com.foo.BarService" ref="BarServiceImpl01" version="1.0.0" />

<!-- 新版本服务提供者配置 -->
<bean id="BarServiceImpl02" class="com.foo.BarService.impl.BarServiceImpl02" />
<dubbo:service interface="com.foo.BarService" ref="BarServiceImpl02" 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="*" />

6. 本地存根

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

消费端开发对应服务的本地存根

public class UserServiceStub implements UserService {
 
    private final IUserService userService;
 
    // 构造函数传入真正的远程代理对象
    public UserServiceStub(IUserService userService){
        this.userService = userService;
    }
 
    @Override
    public String hello(String name) {
        if(name == null || "".equals(name)){
            return "validate param";
        }
        return userService.hello(name);
    }
}

设置存根配置

@Reference(version = "*",stub = "com.yeeq.stub.UserServiceStub")
private UserService userService;

Dubbo 高可用

1. Dubbo 的健壮性

  • 监控中心宕掉不影响使用,只是丢失部分采样数据
  • 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者无状态,任意一台宕掉后,不影响使用
  • 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

2. Dubbo 集群下负载均衡

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

  • Random LoadBalance:随机,按权重设置随机概率
  • RoundRobin LoadBalance:轮循,按公约后的权重设置轮循比率
  • LeastActive LoadBalance:最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差
  • ConsistentHash LoadBalance:一致性 Hash,相同参数的请求总是发到同一提供者

服务端和客户端均可以设置

<!-- 服务级别 -->
<dubbo:service interface="..." loadbalance="roundrobin" />
<!-- 方法级别 -->
<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="roundrobin" />
</dubbo:service>

3. 服务降级

当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略

向注册中心写入动态配置覆盖规则:

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));
  • mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用,用来屏蔽不重要服务不可用时对调用方的影响
  • mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常,用来容忍不重要服务不稳定时对调用方的影响

4. 集群容错

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

  • Failover Cluster:失败自动切换,当出现失败,重试其它服务器,通常用于读操作,但重试会带来更长延迟,可通过 retries="2" 来设置重试次数(不含第一次)
  • Failfast Cluste:快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作,比如新增记录
  • Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作
  • Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作
  • Forking Cluster:并行调用多个服务器,只要一个成功即返回,通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数
  • Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错,通常用于通知所有提供者更新缓存或日志等本地资源信息

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

<!-- 两种方式配置 -->
<dubbo:service cluster="failsafe" />
<dubbo:reference cluster="failsafe" />

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK