一. 背景常识
有时候微服务须要提供给多个消费者, 而不通过的消费者可能心愿根据本身状况应用不同的协定. 另一方面, 有时候如果本来服务以 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
@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.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
@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) 的消费者. 本例重视消费者.
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 install
或 mvn 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;