Dubbo同時支持多種協議(以dubbo和rest爲例)

一. 背景知識

有時候微服務須要提供給多個消費者, 而不經過的消費者可能但願依據自身狀況使用不一樣的協議. 另外一方面, 有時候若是本來服務以 dubbo 協議提供服務, 可是爲了調試或者監控方便, 咱們也提供 rest 協議.java

本文示例服務者同時提供 dubbo 和 rest 協議. 使用的 dubbo 版本爲 2.7.1, springboot 版本爲 2.1.5.spring

爲了真實地模擬不一樣微服務之間的調用, 本文將服務者和消費分開. 對應的項目有兩個, 分別爲dubboshop-inventory(服務者)和dubboshop-order(消費者). 其結構以下:apache

dubboshop-inventory (庫存微服務. 這個項目主要演示服務提供者角色)
 |- dubbo-api: 包含服務接口和 DTO 對象. 打包成 `dubboshop-inventory:1.0.0-snapshot.jar`, 經過 `mvn install`到本地或者`mvn deploy`部署到私服, 在下面`dubboshop-order`項目中引用依賴.
     |- fun.faceless.dubboshop.comms.entity.Result.java
     |- fun.faceless.dubboshop.comms.entity.CommRetCode.java
 |- dubbo-provider: 服務接口具體實現類
     |- fun.faceless.dubboshop.inventory.dubboprovider.DubboApplication.java
     |- fun.faceless.dubboshop.inventory.dubboprovider.impl.InventoryProviderImpl.java
 
dubboshop-order (訂單微服務)
 |- dubbo-provider: 訂單服務的提供者, 同時是dubboshop-inventory服務的消費者. 這裏主要演示其做爲消費者的角色.
     |- fun.faceless.dubboshop.order.dubboprovider.impl.OrderProviderImpl.java

二. 通用dubbo配置

本文兩個項目都做爲服務者, 也都支持dubbo和rest協議, 因此二者dubbo相關的依賴都包含如下幾部分.api

1) 首先引入通用 dubbo 依賴緩存

<!-- Dubbo dependencies -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-bom</artifactId>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>${dubbo.version}</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.thrift</groupId>
            <artifactId>libthrift</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>


<!-- dubbo registry: zookeeper dependencies-->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-dependencies-zookeeper</artifactId>
    <version>2.7.1</version>
    <type>pom</type>
    <exclusions>
        <exclusion>
            <artifactId>log4j</artifactId>
            <groupId>log4j</groupId>
        </exclusion>
        <exclusion>
            <artifactId>slf4j-log4j12</artifactId>
            <groupId>org.slf4j</groupId>
        </exclusion>
    </exclusions>
</dependency>

2) dubbo rest 依賴tomcat

<!-- for dubbo rest protocol -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-rpc-rest</artifactId>
</dependency>
<!-- for dubbo rest protocol with tomcat server -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-core</artifactId>
</dependency>

三. 服務者(Provider)實現

3.1 服務接口定義 dubbo-api

1) 數據傳輸對象定義 Result.java:springboot

注意必定要定義默認構造函數和實現Serializable接口.mybatis

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result implements Serializable {

    private static final long serialVersionUID = 1L;

    public static String SUCCESS_MSG = "SUCC";
    public static String DEFAULT_FAIL_MSG = "FAIL";

    private String code;
    private String msg;
    private String subCode;
    private String subMsg;
    private String sign;

    private Object data;

    private Result(String code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;

        this.subCode = "";
        this.subMsg = "";
        this.sign = "";
    }

    /**
     * Return succ result with given data.
     * @return
     */
    public static Result succ() {
        return new Result(CommRetCode.OK, SUCCESS_MSG, null);
    }

    /**
     * Return succ result with given data.
     * @param msg
     * @return
     */
    public static Result succ(String msg) {
        return new Result(CommRetCode.OK, msg, null);
    }
    
    /**
     * Return failed result with given code, msg, and data
     * @param code
     * @param msg
     * @param data
     * @return
     */
    public static Result fail(String code, String msg, Object data) {
        return new Result(code, msg, data);
    }
    // 其餘省略...
}

2) 通用返回值 CommRetCode.javaapp

package fun.faceless.dubboshop.comms.entity;

public interface CommRetCode {
    /** 一切 ok */
    public final static String OK = "00000";
    // 其餘省略...
}

3) 定義服務提供者接口API: InventoryProvider.javaless

注意 REST 相關的註解都放在接口類中.

package fun.faceless.dubboshop.inventory.dubboprovider;

import javax.ws.rs.*;

import fun.faceless.dubboshop.comms.entity.Result;
import org.apache.dubbo.config.annotation.Service;
import org.apache.dubbo.rpc.protocol.rest.support.ContentType;

/**
 * Order Dubbo Service
 */
@Service
@Path("inventory")
@Produces({ContentType.APPLICATION_JSON_UTF_8})
@Consumes({ContentType.APPLICATION_JSON_UTF_8})
public interface InventoryProvider {

    @GET
    @Path("hello")
    Result hello();

    /**
     * 減扣商品庫存
     */
    @POST
    @Path("debit")
    Result debit(@QueryParam("goodsId") int goodsId, @QueryParam("amount") int amount);
}

3.2 服務實現具體實現

本節示例 Inventory 微服務即庫存服務提供者.

1) 提供的服務的具體實現類 InventoryProviderImpl.java

package fun.faceless.dubboshop.inventory.dubboprovider.impl;

import fun.faceless.dubboshop.comms.entity.Result;
import fun.faceless.dubboshop.inventory.dao.InventoryDao;
import fun.faceless.dubboshop.inventory.dubboprovider.InventoryProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;

@Slf4j
@Service
public class InventoryProviderImpl implements InventoryProvider {

    @Autowired
    InventoryDao inventoryDao;

    @Override
    public Result hello() {
        return Result.succ("hello from inventory center");
    }

    @Override
    public Result debit(int goodsId, int amount) {
        log.debug("debit() goodsId: {}", goodsId);
        log.debug("debit() amount: {}", amount);
        inventoryDao.debit(goodsId, amount);
        return Result.succ();
    }
}

2) 服務啓動類 DubboApplication.java

package fun.faceless.dubboshop.inventory.dubboprovider;

import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@EnableDubbo
@ComponentScan("fun.faceless.dubboshop.inventory")
@MapperScan("fun.faceless.dubboshop.inventory.dao.mapper")
public class DubboApplication {

    public static void main(String[] args) {
        SpringApplication.run(DubboApplication.class, args);
    }
}

3) 模塊配置文件 application-dev.yaml.

多協議支持的配置主要包含:

  • dubbo.config.multiple = true: 開啓多個 protocol 配置綁定.
  • dubbo.protocols: 支持的 prottocols 列表.
# 此處省略項目其餘配置...

# ========= Dubbo Provider ==============
dubbo:
  config:
    # 開啓多個protocol配置綁定
    multiple: true
  # 註冊中心配置
  registry:
    id: dubboshop-registry
    address: zookeeper://yyadmin:2181
    group: dubboshop
    simplified: true
  application:
    name: dubboshop-inventory
    id: dubboshop-inventory
    logger: slf4j
    qos-enable:  false
    qos-accept-foreign-ip: true
    qos-port: 22223
  protocols:
    dubbo:
      name: dubbo
      port: 20882
      server: netty4
    rest:
      name: rest
      server: tomcat
      port: 8082
  scan:
    # dubbo 服務提供者實現類所在包
    base-packages: fun.faceless.dubboshop.inventory.dubboprovider

四. 消費者(Consumer)實現

本節示例 Order 微服務即做爲訂單的模塊的服務者, 同時做爲上面庫存(Inventory)的消費者. 本例注重消費者.
image.png

1) 在pom.xml中添加服務者api的依賴:

<!-- inter-project dependencies -->
<dependency>
    <groupId>fun.faceless.dubboshop.inventory</groupId>
    <artifactId>dubboapi</artifactId>
</dependency>

2) 在 OderProviderImpl.java 中消費接口.

package fun.faceless.dubboshop.order.dubboprovider.impl;

public class OrderProviderImpl implements OrderProvider {

    @Autowired
    private OrderDao orderDao;

    // 經過 protocol="dubbo" 或者 protocol="rest" 指定使用的協議
    @Reference(protocol="dubbo")
    private InventoryProvider inventoryProvider;

    @Override
    public int createOrder(int userId, int goodsId, int orderCount) {
        // 調用服務接口
        Result debitResult = inventoryProvider.debit(goodsId, orderCount);
        
        return this.saveOrder(userId, goodsId, orderCount);
    }

    // 省略其餘代碼...
}

五. 注意事項

5.1 返回值對象必定要有 默認構造函數 並實現 Serializiable 接口.

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result implements Serializable {

    private static final long serialVersionUID = 1L;
    
    // ...

若是沒有默認構造函數, 則在使用 rest protocol 時, 返回值沒法消費端反序列化. 消費端會報相似以下異常:

org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class fun.faceless.xxx.Result]: can not instantiate from JSON object (need to add/enable type information?)

若是沒有實現 Serializiable 接口, 則在使用 dubbo protocol 時, 服務提供者沒法正常序列化返回值. 服務 (provider) 端會報相似以下異常:

Caused by: org.apache.dubbo.remoting.RemotingException: Failed to send response: Response [id=2, version=2.0.2, status=20, event=false, error=null, result=RpcResult [result=Result(code=00000, msg=SUCC, subCode=, subMsg=, sign=, data=null), exception=null]], cause: java.lang.IllegalStateException: Serialized class fun.faceless.dubboshop.comms.entity.Result must implement java.io.Serializable
java.lang.IllegalStateException: Serialized class fun.faceless.dubboshop.comms.entity.Result must implement java.io.Serializable

5.2 調試時修改依賴模塊的代碼, 注意及時install/deploy

若是修改公共項目, 或者服務提供者接口所在的module, 在 mvn installmvn deploy 以後, 必定也要記得再調用這些 module 的項目從新導入依賴, 不然會由於緩存, 即使重啓消費者服務, 也無濟於事.

5.3 REST 註解放到服務接口上

對於 rest protocol, 若是是將接口單獨打包 (即不帶實現類在包內) 提供給消費者. 那麼須要將 JAX-RS 相關的註解放到接口上. 不然會報以下錯誤:

RESTEASY004600: You must use at least one, but no more than one http method annotation on XXX

這是由於 resteasy jax-rs 2客戶端彷佛不直接接受實現類。要使其工做,必須建立一個正確註釋的接口。示例接口註解:

// InventoryProvider.java, 在 `dubboshop-inventory`項目`dubbo-api`模塊中, 單獨發佈給消費者使用.

@Service
@Path("inventory")
@Produces({ContentType.APPLICATION_JSON_UTF_8})
@Consumes({ContentType.APPLICATION_JSON_UTF_8})
public interface InventoryProvider {

    @GET
    @Path("hello")
    Result hello();

    /**
     * 減扣商品庫存
     */
    @POST
    @Path("debit")
    Result debit(@QueryParam("goodsId") int goodsId, @QueryParam("amount") int amount);
}

對應的實現類以下:

// InventoryProviderImpl.java, 在 `dubboshop-inventory`項目`dubbo-provider`模塊中, 與消費者無關.

@Slf4j
@Service
public class InventoryProviderImpl implements InventoryProvider {

    @Autowired
    InventoryDao inventoryDao;

    @Override
    public Result hello() {
        return Result.succ("hello from inventory center");
    }

    @Override
    public Result debit(int goodsId, int amount) {
        inventoryDao.debit(goodsId, amount);
        return Result.succ();
    }
}

5.4 消費端能夠指定服務的服務方式

@Reference(protocol="dubbo")
// 或 @Reference(protocol="rest")
private InventoryProvider inventoryProvider;
相關文章
相關標籤/搜索