1.HTTP vs RPC
- 应用(微服务)间通讯方式主要有两种:HTTP和RPC
- 两种方式的主角:RPC–Dubbo HTTP–SpringCloud
- Dubbo定位始终是一个RPC远程调用框架,而SpringCloud是微服务下的一站式解决方式
- SpringCloud微服务下服务调用使用的是:HTTP Restful,HTTP Restful本身轻量、适应性强、可以很容易跨语言跨平台。
- SpringCloud中服务间两种restful调用方式:1. RestTemplate 2.Feign
2.RestTemplate的三种使用方式
- 1.RestTemplate是一个HTTP客户端,类似于HttpClient,功能差不多,但是用法上更加简单。
2.1 RestTemplate例子
- 1.使用 “订单服务->商品服务”为例
- 2.订单服务调用商品服务,我们把商品服务当做server端,订单服务当做client端
- 3.为了不影响之前的代码逻辑,我们新建单独的包为例子。
- 4.product端新建ServerController
|
|
/**
- @author yxm
@date 2019/4/20 0:18:18
*/
@RestController
@Slf4j
public class ClientController {@GetMapping(“/getProductMsg”)
public String getProductMsg(){//1.RestTemplate第一种方式 RestTemplate restTemplate = new RestTemplate(); String response = restTemplate.getForObject("http://localhost:8080/msg", String.class); log.info("response{}",response); return response;
}
}12345678910由于product服务已经启用了8080端口,我们用-D指令在order服务中用-Dserver.port=8081,不建议直接写到配置文件。![](https://raw.githubusercontent.com/startshineye/img/master/2019/04/84.png)![](https://raw.githubusercontent.com/startshineye/img/master/2019/04/85.png)以上是第一种调用方式。缺点:1.url为固定写死的,上线时候部署多台服务器,有时候连部署到哪台服务器都不知道。2.对方可能启动了多个实例,如果写死到一台实现不了负载均衡,其他服务器就不能访问了。6. 6.RestTemplate第二种方式
@RestController
@Slf4j
public class ClientController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping("/getProductMsg")
public String getProductMsg(){
//2.第二种方式:通过LoadBalancerClient获取服务名,ip和port
/**
* SpringCloud提供了LoadBalancerClient,将其注入到Spring中
*/
RestTemplate restTemplate = new RestTemplate();
ServiceInstance product = loadBalancerClient.choose("PRODUCT");
String url = String.format("http://%s:%s", product.getHost(), product.getPort());
String response = restTemplate.getForObject(url, String.class);
log.info("response{}",response);
return response;
}
}
@Component
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@RestController
@Slf4j
public class ClientController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getProductMsg")
public String getProductMsg(){
//3.第三种方式:通过RestTemplate的配置加LoadBalancerClient注解
String response = restTemplate.getForObject("http://PRODUCT/msg", String.class);
log.info("response{}",response);
return response;
}
}
public interface ProductInfoRepository extends JpaRepository
List
List
}
public interface ProductService {
/**
* 查询所有在架商品列表
*/
List<ProductInfo> findUpAll();
/**
* 查询商品列表
*/
List<ProductInfo> findList(List<String> productIdList);
}
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductInfoRepository repository;
@Override
public List
return repository.findByProductStatus(ProductStatusEnum.UP.getCode());
}
@Override
public List
return repository.findByProductIdIn(productIdList);
}
}
@RestController
@RequestMapping(“/product”)
public class ProductController {
@Autowired
private ProductService productService;
@Autowired
private ProductCategoryService productCategoryService;
/**
* 获取商品列表(给订单服务使用)
* @param productIdList
* @return
*/
@GetMapping("/listForOrder")
public List<ProductInfo> listForOrder(@RequestBody List<String> productIdList){
return productService.findList(productIdList);
}
}
@FeignClient(name = “product”)
public interface ProductClient {
@GetMapping(“/listForOrder”)
List
}
@RestController
@Slf4j
public class ClientController {
@Autowired
private ProductClient productClient;
@GetMapping(“/getProductMsg”)
public String getProductMsg(){
String msg = productClient.productMsg();
log.info(“getProductMsg()”,msg);
return msg;
}
@GetMapping(“/getProductList”)
public String getProductList(){
List
return “ok”;
}
}
public class CartDTO {
/**
- 商品id
/
private String productId;
/*- 商品数量
*/
private Integer productQuantity;
public CartDTO(){
}
public CartDTO(String productId,Integer productQuantity){
this.productId = productId;
this.productQuantity = productQuantity;
}
}1232. 2.Product-->Exception自定义异常
- 商品数量
public class ProductException extends RuntimeException {
private Integer code;
public ProductException(Integer code,String message){
super(message);
this.code =code;
}
public ProductException(ResultEnum resultEnum){
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}
}
public enum ResultEnum {
PRODUCT_NOT_EXIST(1,”商品不存在”),
PRODUCT_STOCK_ERROE(2,”商品库存错误” );
private Integer code;
private String message;
ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
- 5.Product–>controller
|
|
- 6.在Order服务中client里面的ProductClient添加:减库存方法
|
|
- 7.Order服务中:ClientController
|
|
- 8.测试
库存减少了
8.整合接口打通下单流程(Feign)
之前我们已经完善了,商品的查询,扣库存,那么我们这一节就要完成整套业务的打通。我们之前在Order服务中,创建订单时候,里面还有需要实现的如下几步:
- 1.现在我们补充完毕:
|
|
- 2.测试
9.项目改造成多模块
多模块改造参考:https://blog.csdn.net/yangshangwei/article/details/88809468
9.1 项目缺陷
虽然我们之前已经完成了下单并扣除库存,虽然通信是完成了,但是有些地方做的不好。有如下问题:
1.在Product微服务中,获取商品列表(给订单服务使用)
返回的ProductInfo是数据库实体:
基本上,我们不会把自己数据库映射的表对象暴露给外部的。
2.在订单服务和商品服务之间都有共有的对象:CartDTO,ProductInfo.重复定义。对象属于哪一个服务就在那一个服务中定义。
3.在Order(订单)服务里,定义了ProductClient接口使用Feign主键调用,我们在项目中可能一个服务就是很多人来做,各个服务费透明的,所以我们在Oder服务中不能把Product的服务代码请求路径等写到Order服务中.
9.2 Product多模块拆分
9.2.1模块之间职责
针对解决以上情况,我们把项目分成3个模块:product-server、prodect-client、product-common
- 1.product-server:存放所有业务逻辑:包括,controller、service
- 2.prodect-client:对外暴露的接口,商品模块对外暴露的接口:商品列表和扣库存。
- 3.product-common:公用的对象:既会被内部模块调用,也会被外部模块调用。
9.2.2模块之间依赖关系
1.product-common是公用的对象,所以product-server会依赖product-common,返回的商品对象prodect-client也会依赖product-common
2.product商品多模块划分可以参考:https://blog.csdn.net/qq_29479041/article/details/84230669
- 3.在本地打包 1mvn -Dmaven.test.skip=true -U clean install
使用以上命令,会将对应模块打包并安装到本地maven仓库
9.3 Order多模块拆分
- 1.以此类推,Order服务也分为3个模块,但是order-client目前是没有代码:其目前不需要给外提供服务 order-common目前也没有代码:目前没有需求
- 2.在订单服务需要注意的是:订单服务调用商品服务,那么需要在订单服务里面的启动类中添加扫描到商品服务的路径:@EnableFeignClients(basePackages = “com.yxm.product.client”)
- 3.最外层pom文件引入product-client的jar包
- 4.
是管理jar包版本,不会下载对应依赖
9.5 测试
启动注册中心,通过product-server和order-server中的main函数启动俩微服务
下单
|
|
源码:github地址:https://github.com/startshineye/SpringCloud_Shell/tree/develop_multimodulw
10.同步或者异步
- 1.当前订单服务和商品服务,两个服务之间的通讯机制是同步的。订单会调用商品服务的扣库存接口。微服务中除了同步,有很多时候会集成到异步的场景下,通过队列和订阅主题,实现消息的 发布和订阅,一个微服务可以是消息的发布者,通过异步的方式发送到队列和订阅主题下,作为消费者的微服务,可以从队列或者主题中获取消息。通过消息中间件。把服务间的直接调用解耦。
- 比如以下图中:用户登录的时候,用户服务需要调用短信服务发短信。要给用户加积分,需要调用积分服务。还可能有其他服务,如果都采用同步的机制,服务间耦合过大,用户登录成功,需要向多个服务同步响应后才会成功,就会造成不好的用户体验。这个时候,我们通过消息队列可以实现很好的异步调用。
- 3.再比如:订单服务在口库存前会调用查询商品服务,之后再调用减库存的接口来扣库存。我们对其改造:商品服务在更改库存的时候、发布库存变化的消息、订单服务来订阅这个消息、可以获取到商品的部分信息,比如:可购买的商品个数、商品id。订单服务在下单的时候不必同步的去查询数据确定商品的库存信息 而是查询自己服务中数据 然后在扣库存时候订单服务发布一个扣库存的消息、商品服务订阅这个消息。拿到消息后,减少本库存消息的库存量。
- 4.消息中间件:目前常见的消息队列为:RabbitMQ、Kafka、ActiveMQ,我们现在使用RabbitMQ
11.RabbitMQ的安装
进入下载网址:https://www.rabbitmq.com/download.html
我们使用docker安装RabbitMQ
我们使用带有管理界面的mq:3.8.0-beta.4-management
https://blog.csdn.net/antma/article/details/81334932
https://www.jianshu.com/p/f3e49b495d74