dubbo的使用和基本的功能用法

1. dubbo 有什么用

在分布式系统中,服务与服务之间怎么通信是一个问题,目前主流的方式就是通过RPC或HTTP协议进行通信。像Spring Cloud就是通过http协议进行服务之间的通信,而dubbo是一个RPC框架,它实现了RPC调用。这两种方式对比起来的话,HTTP协议稍微简单点,但是由于它需要3次握手和4次挥手,性能较差,而dubbo实现的RPC,底层是用netty这种非阻塞I/O,速度会快很多。

性能:

  • http: 由于它是无状态的,每次调用它都需要3次握手和4次挥手,性能较差。
  • dubbo: 底层是用netty使种非阻塞I/O,性能较好。

编程过程中的使用方式:

  • http: 两个系统的代码没有任何交集,只需要一个提供服务,一个调用服务,通过json串来传输数据
  • dubbo:由于RPC的调用就像调用本地方法一样调用远程方法。那在分布式系统中,各个系统的文件都是独享的,那我怎么知道你把我需要的服务写在哪个方法了?此时,就需要一个中间媒介,这个媒介就是存放接口的一个项目,该项目中只放一些公共的接口和domain,然后服务提供方和服务消费方都去依赖这个项目,并且服务提供方去实现方法,并把方法暴露出来,服务消费方,就可以引入接口,然后调用方法。由此来完成服务的调用

那除了解决分布式系统之间的通信问题,dubbo还提供了一些其他的功能,:

服务自动注册与发现,服务熔断,服务降级,软负载均衡等,功能还在持续更新

由于Spring Cloud 是一个分布式一站式的解决方案,且代码都是通过上层接口去实现各个组件,所以可以直接在spring cloud中使用dubbo作为RPC通信框架,来解决Spring Cloud使用http协议的性能问题。

Spring Cloud Alibaba 集成了Alibaba的一些开源组件,使得Spring Cloud中的组件选择更加丰富。

2.  dubbo架构

官网上有:具体说明官网上有

dubbo的使用和基本的功能用法_第1张图片

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

 3. 特点

连通性、健壮性、伸缩性、以及向未来架构的升级性。官方文档都有,我自己归纳整理了一下:

连通性

  • 服务提供者和消费者只在启动时与注册中心交互,服务提供者注册服务到注册中心,服务消费者向注册中心请求获取服务清单
  • 之后的请求,消费者直接通过初始时获取的清单,并使用负载均衡来决定访问哪一台服务器
  • 注册中心,服务提供者,服务消费者三者之间均为长连接,当注册中心感知到有服务的上下线,那么会立即推送事件通知消费者,然后消费者更新本地服务清单
  • 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
  • 注册中心和监控中心都是可选的,当不使用注册中心,服务消费者可以直连服务提供者,当然这样写代码就比较麻烦了

健壮性

  • zk的数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 注册中心在集群模式下,任意一台宕掉后,都不影响使用,但是宕机的机器不能超过集群总数的一半以上,否则zk就无法使用了。且如果宕掉的是leader,那么需要重新选择leader,该过程比较耗时,且该过程中,服务无法使用
  • 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
  • 服务提供者宕机对zk没有任何影响,只是需要通知一下消费者,如果服务提供者全部宕机,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复

伸缩性

  • 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
  • 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者

 4. 使用springboot 集成dubbo

集成dubbo的方式有很多种,我们这里就用springboot来集成。

需要创建3个项目,

  • gmall-interface:提供公共接口和公共pojo
  • gmall-user: 服务提供者,提供一个查询用户列表的服务
  • gmall-order:服务消费者,消费用户服务

dubbo-spring-boot-starter 依赖有两个,一个是:

dubbo的使用和基本的功能用法_第2张图片

另一个是

dubbo的使用和基本的功能用法_第3张图片

 看更新时间就能知道,apache.dubbo是最新的,且之后的dubbo-spring-boot-starter都是交给apache的。我们这里就使用apache.dubbo。

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

dubbo-spring-boot地址:https://github.com/apache/dubbo-spring-boot-project

我的spring boot 版本是2.2.6.RELEASE,

dubbo-spring-boot-starter的版本是2.7.3

4.1 gmall-interface

该项目只存放公共的资源,所以不需要使用spring boot,创建一个普通的maven工程即可

结构如下:

dubbo的使用和基本的功能用法_第4张图片

UserService:

public interface UserService {
    List getUserList();
}

4.2 gmall-user

创建一个springboot项目

引入依赖


    
        org.springframework.boot
        spring-boot-starter-web
    

    
        com.tanfp.dubbo-study.dubbo-demo
        gmall-interface
        1.0.0
    


    org.apache.dubbo
    dubbo-spring-boot-starter
    2.7.3

 
            org.apache.curator
            curator-framework
            4.2.0
        
        
            org.apache.curator
            curator-recipes
            4.2.0
        
        
            io.netty
            netty-all
            4.1.48.Final
        


必须要引入curator-framework,curator-recipes这两个包,当你使用zookeeper作为注册中心时,它就会去者两个包中找一些类来操作zookeeper。

netty-all这个包当你遇到io/netty的问题再加吧,我这里其实不加也是可以正常访问的

结构如下,里面放gmall-interface项目中接口的实现类

dubbo的使用和基本的功能用法_第5张图片

UserserviceImpl.class

// 注意引入包时,引入dubbo的Service包
@Service(version = "1.0")
public class UserServiceImpl implements UserService {
    @Override
    public List getUserList() {
        List userList = new ArrayList<>();
        User user = new User("张三z",1,new Date());
        User user2 = new User("李四",2,new Date());
        userList.add(user);
        userList.add(user2);
        return userList;
    }
}

@Service表示对外暴露了这个类,即暴露了服务,可以让消费者来直接在本地调用UserserviceImpl类中的方法

添加注解

启动类上添加@EnableDubbo注解

修改application.yml

server.port=8082
#指定当前dubbo引用的名称
dubbo.application.name=gmall-user
#指定使用什么注册中心
dubbo.registry.protocol=zookeeper
#指定注册中心的地址
dubbo.registry.address=127.0.0.1:2181
#指定使用什么通信协议
dubbo.protocol.name=dubbo

4.3 gmall-order

创建一个spring boot项目,依赖与gmall-user项目一样

结构如下

dubbo的使用和基本的功能用法_第6张图片

 OrderController:

@RestController
public class OrderController {

    @Autowired
    private OrderServiceImpl orderService;

    @GetMapping("/users")
    public List getUserList() {
        return orderService.getUserList();
    }
}

OrderServiceImpl:

@Service
public class OrderServiceImpl {

    // 调用dubbo服务
    @Reference(version = "1.0")
    private UserService userService;

    public List getUserList() {
        return userService.getUserList();
    }
}

启动类添加注解

启动类添加@EnableDubbo注解

修改application.yml配置

server.port=8081
dubbo.application.name=gmall-order-web
dubbo.registry.protocol=zookeeper
dubbo.registry.address=127.0.0.1:2181
dubbo.protocol.name=dubbo

4.4 启动访问

启动zookeeper集群,然后分别启动gmall-user(服务提供者),gmall-order(服务消费者)

访问http://localhost:8081/users

5. 具体配置

dubbo中具体的配置项,在官网中都有

dubbo的使用和基本的功能用法_第7张图片

除了dubbo:service和dubbo:reference,其他的基本上都有,这两个分别使用@Service,@Reference去配置

对应application.yml中就是:

dubbo.protocol.xxx=yyy
dubbo.registry.xxx=yyy

xxx表示具体的属性,yyy表示值

6. 主要功能

6.1 服务降级

当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。

可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。

一般都是在消费者的reference属性上添加mock值来判断,也只能消费方才能加,提供方加这个mock会报错。其实想想也能知道,它有一种是不进行调用,直接返回错误,如果放在提供服务方,还怎么直接返回?

mock的写法有以下几种:

  • return empty: 代表空,基本类型的默认值,或者集合类的空值
  • return null
  • return true
  • return false
  • return JSON 格式: 反序列化 JSON 所得到的对象
  • fail:return null   表示会进行远程调用,如果调用失败,则返回null
  • force:return null,直接不进行远程调用了,方法直接返回null
  • throw    直接抛出异常对象
  • 自定义返回类
    @Reference(version = "1.0",mock = "fail:return null",timeout = 2000)
    @Reference(version = "1.0",mock = "force:return null",timeout = 2000)
    private UserService userService;

自定义返回:

自定义返回的类放在公共接口的项目:gmall-interface下,有两点注意:

  1. 注意类的名称是接口名+Mock
  2. 要放在与UserService接口相同的包下

dubbo的使用和基本的功能用法_第8张图片

 UserServiceMock:

public class UserServiceMock implements UserService {
    public List getUserList() {
        System.out.println("调用了Mock");
        return new ArrayList();
    }
}

消费方启用mock功能:

 @Reference(version = "1.0",mock = "true",timeout = 2000)
 private UserService userService;

6.2 服务熔断(集群容错)

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

一般来说,服务提供方和服务消费方都能设置,但是能在服务提供方进行配置的就尽量在服务提供方设置,可以更好的管理服务

Failover Cluster

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

在服务提供方添加重试次数。不设置cluster,则默认是Failover重试机制

@Service(version = "1.0",retries = 2, cluster="failsafe")

Failfast Cluster

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

非幂等性表示多次调用结果都是一样的,比如查询,删除,更新

Failsafe Cluster

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

Failback Cluster

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

Forking Cluster

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

Broadcast Cluster

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

6.3 负载均衡

默认采用随机算法

Random LoadBalance

  • 随机,按权重设置随机概率。
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin LoadBalance

  • 轮询,按公约后的权重设置轮询比率。
  • 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

LeastActive LoadBalance

  • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHash LoadBalance

  • 一致性 Hash,相同参数的请求总是发到同一提供者。

使用:

@Service(version = "1.0",retries = 2, cluster="failsafe", loadbalance="roundrobin")

6.4 服务分组

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

@Service(version = "1.0",retries = 2, group = "impl-1")
public class UserServiceImpl implements UserService {}

@Service(version = "1.0",retries = 2, group = "impl-2")
public class UserServiceImpl implements UserService {}

服务消费方引用的时候也加一个group属性执行即可,也可以使用*:表示使用任意一个实现类

@Reference(version = "1.0",mock = "fail:return null",timeout = 2000,group = "impl-1")
// 使用任意组
@Reference(version = "1.0",mock = "fail:return null",timeout = 2000,group = "*")

6.5 多版本

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

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

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

使用方式就是指定version版本号,服务消费者只能消费指定版本的服务

@Service(version = "2.0.0")
@Reference(version = "2.0.0")

 

如果消费方不区分服务版本号,那么消费方可以使用*

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

@Reference( version="*")

6.6 本地存根 

当服务消费方想在调用服务提供方前,去做一些事情,比如说注册,服务消费方可以先执行一部分逻辑,比如参数的校验,只有校验成功,我们再去调用服务提供方的注册方法,否则就直接返回错误。那这个具体怎么实现呢?

分两步:

  1. 先在gmall-interface项目的service包下创建一个UserServiceStub类,注意类名是接口名+Stub
  2. 开启本地存根

创建一个UserServiceStub类

dubbo的使用和基本的功能用法_第9张图片

 一定要在与UserService接口同包的路径下去创建UserServiceStub,否则会找不到这个类

public class UserServiceStub implements UserService {

    // 相当于是UserService的一个代理对象,当我们校验完成后,可以通过这个代理对象调用服务
    private final UserService userService;

    public UserServiceStub(UserService userService) {
        this.userService = userService;
    }

    public List getUserList() {
        System.out.println("校验参数中......");
        if (true) {
            // 校验成功,我们就继续调用服务
           return userService.getUserList();
        } else {
            // 校验失败
            return new ArrayList();
        }
    }
}

开启本地存根

在这个版本中,如果你是以接口+Stub的这种形式创建的类,那么当你创建完成后,本地存根其实就默认开启了,但是最好还是显示声明一下,声明即可在服务提供方声明,也可以在服务消费方声明,但是我们最好在提供方去声明,统一管理服务。

设置stub为true即可

@Service(version = "1.0",stub = "true")

6.7 本地伪装

这个就是用服务降级来实现的,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回结果

你可能感兴趣的:(dubbo)