SpringCloud Alibaba系列Dubbo高级特性篇

Posted 蓝染-惣右介

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud Alibaba系列Dubbo高级特性篇相关的知识,希望对你有一定的参考价值。

文章目录


Dubbo-高级特性篇

本章我们介绍Dubbo的常用高级特性。


一、序列化

Java对象 -> 序列化 -> 流数据
流数据 -> 反序列化 -> Java对象
依赖一个公共的相同模块即保证序列化版本号在序列和反序列时相同。

  • dubbo内部已经将序列化和反序列化的过程内部封装了
  • 我们只需要在定义pojo类时实现serializable接口即可
  • 一般会定义一个公共的pojo模块,让生产者和消费者都依赖该模块。内部已经将序列化和反序列化的过程内部封装了,我们只需要在定义类时实现可序列化的接口即可,一般会定义一个公共的POJO模块,让生产者和消费者都依赖该模块。

1)创建一个公共实体类依赖

public class User 
    private int id;
    private String username;
    private String password;

    public User() 
    

    public User(int id, String username, String password) 
        this.id = id;
        this.username = username;
        this.password = password;
    

    public int getId() 
        return id;
    

    public void setId(int id) 
        this.id = id;
    

    public String getUsername() 
        return username;
    

    public void setUsername(String username) 
        this.username = username;
    

    public String getPassword() 
        return password;
    

    public void setPassword(String password) 
        this.password = password;
    

2)在dubbo-interface里去添加一个查询用户的接口,首先需要引入dubbo-pojo的依赖。

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>dubbo-interface</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.itheima</groupId>
            <artifactId>dubbo-pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
import com.itheima.pojo.User;

public interface UserService 
    String sayHello();

    /**
     * 查询用户
     */
    User findUserById(int id);

3)在dubbo-service中实现findUserById方法

import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;

// 将这个类提供的方法(服务)对外发布。将访问地址(ip、端口、访问路径)注册到ZK注册中心
@Service
public class UserServiceImpl implements UserService 
    public String sayHello() 
        return "Hello Dubbo RPC Zookeeper!~";
    

    @Override
    public User findUserById(int id) 
        User user = new User(1, "zhangsan", "123");
        return user;
    

4)在dubbo-web中实现调用服务消费

import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController 
    @Reference
    private UserService userService;

    @RequestMapping("/sayHello")
    public String sayHello() 
        return userService.sayHello();
    

    /**
     * 根据id查询用户信息
     * @param id
     * @return
     */
    @RequestMapping("/find")
    public User find(int id) 
        return userService.findUserById(id);
    

此时interface模块依赖pojo模块,web模块依赖service模块、web和service模块依赖interface模块。interface和pojo上都有修改,都需要进行mvn clean install。

重启dubbo-service和dubbo-web服务,访问http://localhost:8000/user/find.do?id=1


这个原因写的很明确,就是pojo实体类上需要实现Serializable接口,因为Java对象的数据需要在不同的机器上通过流来传输的。

5)所有的实体类都实现Serializable接口

import java.io.Serializable;

/*
    注意!!!
    将来所有的pojo类都需要实现serializable接口
 */
public class User implements Serializable 
    private int id;
    private String username;
    private String password;

    public User() 
    

    public User(int id, String username, String password) 
        this.id = id;
        this.username = username;
        this.password = password;
    

    public int getId() 
        return id;
    

    public void setId(int id) 
        this.id = id;
    

    public String getUsername() 
        return username;
    

    public void setUsername(String username) 
        this.username = username;
    

    public String getPassword() 
        return password;
    

    public void setPassword(String password) 
        this.password = password;
    

注意:被依赖的Java工程改动后,一定要install重新安装。

6)对修改的服务进行clean install重新安装,重启相关依赖服务,重新访问


二、地址缓存


面试题:注册中心挂了,服务是否能正常访问?

  • 可以,因为dubbo服务消费者在第一次调用时,会从注册中心中获取服务提供者的地址,然后进行访问,之后会将服务提供方地址缓存到本地,以后在调用则不会访问注册中心,直接从本地缓存中去获取,不用再跟注册中心去交互。
  • 当服务提供者地址发生变化时,注册中心会通知服务消费者需要缓存更新。

我们可以进行测试一下,将Linux服务器上的ZK关掉。

再次访问老的服务地址,是可以访问的。但是如果注册中心挂了,新的服务就无法注册了,还是需要进行修复的。


三、超时与重试机制

1. 超时机制

  • 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
  • 在某个峰值时刻,大量的请求都在同时请求服务消费者,会造成线程的大量堆积,当服务器资源耗尽,势必会造成雪崩。

  • dubbo利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接,将线程释放,这样就不会造成积压导致服务雪崩。
  • 使用timeout属性配置超时时间,默认值1000,单位毫秒。

我们来模拟一下超时情况。在@Service中配置timeout超时时间)、retries超时后重试的次数(默认2次)。

import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;

// 将这个类提供的方法(服务)对外发布。将访问地址(ip、端口、访问路径)注册到ZK注册中心
@Service(timeout = 3000, retries = 0)   // 当前服务3秒超时,超时后,重试0次
public class UserServiceImpl implements UserService 
    public String sayHello() 
        return "Hello Dubbo RPC Zookeeper!~";
    

    @Override
    public User findUserById(int id) 
        User user = new User(1, "zhangsan", "123");
        // 模拟数据库查询耗时5秒
        try 
            Thread.sleep(5000);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        return user;
    

在Controller服务调用方模拟线程异步调用1秒打印一个数字。

import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController 
    @Reference
    private UserService userService;

    @RequestMapping("/sayHello")
    public String sayHello() 
        return userService.sayHello();
    

    /**
     * 根据id查询用户信息
     * @param id
     * @return
     */
    int i = 1;
    @RequestMapping("/find")
    public User find(int id) 
        new Thread(new Runnable() 
            @Override
            public void run() 
                while (true) 
                    System.out.println(i++);
                    try 
                        Thread.sleep(1000);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
            
        ).start();
        return userService.findUserById(id);
    

重启服务,访问测试

发现打印了4个数字然后报错timeout,第4个数字是因为service的线程睡眠时间影响,所以大概可以认为用了3秒触发超时。

@Reference(timeout = 1000)
private UserService userService;

注意:如果两边都配置了服务超时时间,那么consumer优先provider,服务调用方的超时时间会覆盖服务提供方。

建议:在服务提供者上配置超时时间,因为定义服务的人写具体service逻辑时才能估计服务耗时多久。

2. 重试机制

  • 设置了超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
  • 如果出现网络抖动,则这一次请求就会失败。
  • Dubbo提供重试机制来避免类似问题的发生。
  • 通过retries属性来设置重试次数。默认为2次。
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;

// 将这个类提供的方法(服务)对外发布。将访问地址(ip、端口、访问路径)注册到ZK注册中心
@Service(timeout = 3000, retries = 2)   // 当前服务3秒超时,超时后,重试2次,一共3次
public class UserServiceImpl implements UserService 
    public String sayHello() 
        return "Hello Dubbo RPC Zookeeper!~";
    

    int i = 1;
    @Override
    public User findUserById(int id) 
        System.out.println("该服务一共被调用:" + i++ + "次。");
        User user = new User(1, "zhangsan", "123");
        // 模拟数据库查询耗时5秒
        try 
            Thread.sleep(5000);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        return user;
    


四、多版本

  • 灰度发布:当出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能。

  • dubbo中使用version属性来设置和调用同一个接口的不同版本。
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;

@Service(version = "v1.0")   // 指定当前服务的版本
public class UserServiceImpl implements UserService 
    public String sayHello() 
        return "Hello Dubbo RPC Zookeeper!~";
    

    @Override
    public User findUserById(int id) 
        System.out.println("old version is v1.0");
        User user = new User(1, "zhangsan", "123");
        return user;
    

import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;

@Service(version = "v2.0")   // 指定当前服务的版本
public class UserServiceImpl2 implements UserService 
    public String 

以上是关于SpringCloud Alibaba系列Dubbo高级特性篇的主要内容,如果未能解决你的问题,请参考以下文章

SpringCloud-Alibaba系列教程2.搭建用户微服务模块

深入浅出Dubbo3原理及实战「SpringCloud-Alibaba系列」基于Nacos作为注册中心进行发布SpringCloud-alibaba生态的RPC接口实战

SpringCloud Alibaba系列Dubbo基础入门篇

SpringCloud Alibaba系列Dubbo高级特性篇

深入浅出SpringCloud原理及实战「SpringCloud-Alibaba系列」微服务模式搭建系统基础架构实战指南及版本规划踩坑分析

【SpringCloud-Alibaba系列教程】5.负载均衡