一. 背景常识

有时候微服务须要提供给多个消费者, 而不通过的消费者可能心愿根据本身状况应用不同的协定. 另一方面, 有时候如果本来服务以 dubbo 协定提供服务, 然而为了调试或者监控不便, 咱们也提供 rest 协定.

本文示例服务者同时提供 dubbo 和 rest 协定. 应用的 dubbo 版本为 2.7.1, springboot 版本为 2.1.5.

为了实在地模仿不同微服务之间的调用, 本文将服务者和生产离开. 对应的我的项目有两个, 别离为dubboshop-inventory(服务者)和dubboshop-order(消费者). 其构造如下:

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相干的依赖都蕴含以下几局部.

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 依赖

<!-- 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:

留神肯定要定义默认构造函数和实现Serializable接口.

@Data@NoArgsConstructor@AllArgsConstructorpublic 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.java

package fun.faceless.dubboshop.comms.entity;public interface CommRetCode {    /** 所有 ok */    public final static String OK = "00000";    // 其余省略...}

3) 定义服务提供者接口API: InventoryProvider.java

留神 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@Servicepublic 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)的消费者. 本例重视消费者.

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@AllArgsConstructorpublic 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.Serializablejava.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@Servicepublic 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;