Dubbo 对象序列化、超时、重试、多版本控制

之前已经快速搭建了 Dubbo 入门 Demo,本篇博客将继续在此 Demo 的基础上,介绍一下 Dubbo 在实际项目开发中必然会用到的一些简单实用的技术点,只需要编写很少的代码或者进行一些注解配置即可实现,大大提高了开发效率。在本篇博客的最后,会提供源代码的下载,需要注意的是:在运行本 Demo 代码时,必须先启动 Zookeeper 作为注册中心。


一、对象序列化

我们在编写服务接口时,如果接口参数或者接口返回值,需要使用实体类对象的话,那么实体类必须要实现 Serializable 接口,否则在运行过程中会报错。既然实体类是在接口定义中使用,因此 Dubbo 服务端和客户端都必须使用,所以本 Demo 就将实体类也放到了 dubbo_interface 这个公共模块中,如下图所示:

image

在 dubbo_interface 公共模块中,增加了 com.jobs.domain 包,增加实体类 User

package com.jobs.domain;

import java.io.Serializable;

//实体类承载数据,要想在 Dubbo 客户端和服务端之间传输,
//必须实现 Serializable 接口
public class User implements Serializable {

    private String name;
    private Integer age;

    public User() {
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

在 TestService 接口中定义一个方法使用 User 实体类:

package com.jobs.service;

import com.jobs.domain.User;

public interface TestService {

    User GetUserDetail(int id);
}

在 Dubbo 服务端实现该接口:

package com.jobs.service.impl;

import com.jobs.domain.User;
import com.jobs.service.TestService;
import org.apache.dubbo.config.annotation.Service;

//需要注意:这里的 @Service 注解是 Dubbo 提供的
//不要使用 Spring 提供的 @Service 注解
@Service
public class TestServiceImpl implements TestService {

    @Override
    public User GetUserDetail(int id) {
        User user;
        if (id == 1) {
            user = new User("任肥肥", 38);
        } else {
            user = new User("候胖胖", 40);
        }
        return user;
    }
}

在 Dubbo 客户端进行调用和测试:

package com.jobs.controller;

import com.jobs.domain.User;
import com.jobs.service.TestService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/test")
public class TestController {

    //这里生成远程接口代理,从而可以调用远程服务接口
    @Reference
    private TestService testService;

    //测试实现了 Serializable 接口的实体类承载数据进行传输
    //采用 restful 风格,把传递的参数写在路径上
    @GetMapping("/getuser/{id}")
    public User GetCal(@PathVariable("id") int uid) {
        User user = testService.GetUserDetail(uid);
        return user;
    }
}

二、地址缓存

当 Dubbo 客户端调用过一次 Dubbo 服务端的接口之后,服务端接口的地址就被缓存到 Dubbo 客户端了,所以 Dubbo 客户端并不是每次调用服务端接口,都得向 Zookeeper 注册中心获取接口服务地址。

当接口服务地址发生变化时,Zookeeper 注册中心会通知 Dubbo 客户端,此时 Dubbo 客户端会重新到注册中心获取最新的接口地址,然后缓存到 Dubbo 客户端中。因此如果 Zookeeper 注册中心宕机挂掉了,不会影响 Dubbo 客户端调用之前已经调用过的接口服务,但是新的接口服务就无法调用了。


三、超时和重试

我们既可以在 Dubbo 客户端的 @Reference 注解上配置超时和重试,也可以在 Dubbo 服务端的 @Service 注解上配置超时和重试。但是比较推荐的做法是在 Dubbo 服务端进行配置超时和重试,因为服务的开发者知道具体接口的执行耗费时间。需要注意的是:如果同时在客户端和服务端都配置了超时和重试的话,客户端上的配置会覆盖服务端的配置。

如果不进行配置的话,超时的时间默认为 1000 毫秒,重试的次数默认为 2 次,下面列出服务端的配置:

package com.jobs.service.impl;

import com.jobs.domain.User;
import com.jobs.service.TestService;
import org.apache.dubbo.config.annotation.Service;

//需要注意:这里的 @Service 注解是 Dubbo 提供的
//不要使用 Spring 提供的 @Service 注解

//一般情况下,超时和重试,配置在 Dubbo 服务端,
//如果在 Dubbo 客户端配置超时和重试的话,会覆盖服务端的配置
//以下表明超时时间为 2 秒,先尝试请求 1 次,如果超时则再重试 1 次,一共最多请求 2 次
//如果不配置的话,默认超时是 1 秒钟,默认重试次数是 2 次
@Service(timeout = 2000, retries = 1)
public class TestServiceImpl implements TestService {

    @Override
    public User GetUserDetail(int id) {
        User user;
        if (id == 1) {
            user = new User("任肥肥", 38);
        } else {
            user = new User("候胖胖", 40);
        }
        return user;
    }
}

需要注意的是:根据业务重要性的实际情况,建议在服务端中,把读服务和写服务,进行分离。因为对于读取数据的服务,在超时后进行重试,一般情况下对业务不会有影响。但是对于写数据的接口服务来说,如果超时后进行重试,可能会影响业务,因此一般对于写服务,配置重试的次数为 0。当然要解决重复写数据对业务影响的问题,也是解决方案的,以上建议仅供参考。


四、多版本控制

对于 Java 来说,同一个接口的实现类,可以有多个。因此对于 Dubbo 服务端来说,同一个服务的实现类可以有多个,可以给不同的实现类通过 @Service 注解配置版本号,版本号是字符串,可以随意配置。在 Dubbo 客户端只需要在 @Reference 注解上配置要调用接口服务的版本,即可调用具体版本的服务接口。如下图所示,我们新建了一个 TestServiceImpl2 类实现 TestService 接口,作为一个新版本的服务。

image

下面列出两个版本服务的代码:

package com.jobs.service.impl;

import com.jobs.domain.User;
import com.jobs.service.TestService;
import org.apache.dubbo.config.annotation.Service;

//需要注意:这里的 @Service 注解是 Dubbo 提供的
//不要使用 Spring 提供的 @Service 注解
@Service(timeout = 2000, retries = 1, version = "1.0")
public class TestServiceImpl implements TestService {

    @Override
    public User GetUserDetail(int id) {
        User user;
        if (id == 1) {
            user = new User("任肥肥", 38);
        } else {
            user = new User("候胖胖", 40);
        }
        return user;
    }
}
package com.jobs.service.impl;

import com.jobs.domain.User;
import com.jobs.service.TestService;
import org.apache.dubbo.config.annotation.Service;

//这里编写了另外一个版本的服务,多个版本的服务可以同时存在
@Service(timeout = 2000, retries = 1, version = "2.0")
public class TestServiceImpl2 implements TestService {

    @Override
    public User GetUserDetail(int id) {
        User user;
        if (id == 1) {
            user = new User("李墩墩", 38);
        } else {
            user = new User("乔豆豆", 40);
        }
        return user;
    }
}

在 Dubbo 客户端通过 @Reference 注解上配置要调用接口服务的版本:

package com.jobs.controller;

import com.jobs.domain.User;
import com.jobs.service.TestService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/test")
public class TestController {

    //这里生成远程接口代理,从而可以调用远程服务接口
    //通过 version 配置,可以指定调用哪个版本的接口服务
    @Reference(version = "2.0")
    private TestService testService;

    //测试实现了 Serializable 接口的实体类承载数据进行传输
    //采用 restful 风格,把传递的参数写在路径上
    @GetMapping("/getuser/{id}")
    public User GetCal(@PathVariable("id") int uid) {
        User user = testService.GetUserDetail(uid);
        return user;
    }
}

多版本控制的情况,比较适合灰度发布。接口在大规模更新之前,大部分用户仍然使用原来的接口服务,先让一部分用户使用新接口服务,如果用了一段时间没有问题的话,就可以让所有的用户都使用新接口服务,从而完成灰度发布。


本篇博客源代码下载地址:https://files.cnblogs.com/files/blogs/699532/DubboDemo2.zip

posted @ 2022-06-11 18:41  乔京飞  阅读(10118)  评论(0编辑  收藏  举报