在微服务架构中,每个业务模块都被看待为一个独立的服务。服务与服务之间基于Restful 风格的接口相互通信。Spring cloud有两种调用方式,分别是使用ribbon + restTemplate 和 feign。
1、事先准备(可快速略过)
在开始之前,我在本地写了一个Order服务,两个Product服务,并实现了一些逻辑
jiaxini.img
以findByProductIdIn接口为例,这个接口是供订单调用的接口。逻辑是订单服务拿到几种产品的productId的List集合,商品服务查询这几种商品的详情并返回结果。首先我在postman测试一下这个接口的功能。(但我自己非常不推荐传入集合作为参数的操作)
request
response
2、Ribbon + Rest 服务消费
在消费服务的order项目中加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
这儿我分别用三种方式去实现接口的调用;
方式一:利用restTemplate
我之前写过一些介绍restTemplate的基本使用的文章,这里可以直接拿来使用
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:3003/product/findByProductIdIn";
List<String> stringList = Arrays.asList("8216e50c-3826-4a2f-bb54-3a4b6f408b10", "4ea84cda-fca8-443b-a957-51c621bc8330");
List list = restTemplate.postForObject(url, stringList, List.class);
log.info(list.toString());
这样做的弊端是我的Url比较固定,ip和端口号集中,我只能通过一些restTemplate提供的添加参数的方法去实现逻辑。在微服务的情况下,如果一台服务器部署了两个微服务,就只能去找某一个了,也没有了高可用。
方法二:使用loadBalancerClient类
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping(value = "/method2")
public void useLoadBalancerClient(){
RestTemplate restTemplate = new RestTemplate();
//发现服务
ServiceInstance serviceInstance = loadBalancerClient.choose("product");
String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/product/findByProductIdIn";
List<String> stringList = Arrays.asList("8216e50c-3826-4a2f-bb54-3a4b6f408b10",
"4ea84cda-fca8-443b-a957-51c621bc8330");
List list = restTemplate.postForObject(url, stringList, List.class);
log.info(list.toString());
}
这样做对比直接使用restTemplate固定Url的形式,有一个好处就是首先利用LoadBalancerClient 去发现serviceId为“product”的服务,然后再取这个服务的host和port去拼接请求。这样做就避免了写死ip和端口的情况。再者在LoadBalancerClient中实际上是引入了一种 IRULE 的东西,它定义了如何去使用服务的规则。比如说product提供了多个服务,可以选择使用轮询、随机等方式去访问服务,具体情况具体实现。
源码阅读:
ServiceInstance serviceInstance = loadBalancerClient.choose("product");
可以发现springCloud默认使用了RoundRobinRule去定义调用规则,也就是轮询,如果过服务会按照1->2->3->1->2->3->1这样的方式去依次调用我们指定的服务。就像下图的结果这样,3003和3004是商品服务的端口。
当然除了轮询之外还有一些别的emmmm(并不认识,但很感人)的规则,当然也可以自己实现IRULE并注册一个自定义的方式到Bean里。
切换这个Rule也比较简单,这个类里给我们了一个setRule的方法,将要用的IRULE的Bean注册到里面应该就能生效了,这里我并没有尝试,有兴趣的大哥麻烦试一下并传唤我。不过我在网上看到另外一种更改的方法,即直接修改配置。如下图所示
PRODUCT是服务名serviceId
IRULE类是全路径
方法三:
先创建一个配置类,定义RestTemplate的Bean,加入注解@LoadBalanced
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@Autowired
private RestTemplate restTemplate;
@GetMapping(value = "/method3")
public void useLoadBalancedAnnotation(){
List<String> stringList = Arrays.asList("8216e50c-3826-4a2f-bb54-3a4b6f408b10", "4ea84cda-fca8-443b-a957-51c621bc8330");
// 第一个product是服务名serviceId,第二个是请求路径的一部分
List result = restTemplate.postForObject("http://product/product/findByProductIdIn", stringList, List.class);
log.info(result.toString());
}
请求成功
这个注解的原理和方法二类似,它的底层用的就是LoadBalancerClient实现。
3、Feign
feign是在ribbon的基础上做了一些改进。它采用接口的方式, 只需要创建一个接口,然后在上面添加注解即可 ,将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建http请求。
个人选择使用feign的原因是因为feign接口较ribbon拼接请求而言,更加的直观地展示了需要的参数和返回类型,而接口调用的方式,会让我觉得它就是一个service,我可以去正常调用它的接口。
首先加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在启动类上加入注解
/** basePackages 限定包路径 */
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = "com.jiaxini.order.feign")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
feign接口类编写
@FeignClient(value = "product")
public interface ProductFeign {
/** 查询根据idList查询商品列表 */
@PostMapping("/product/findByProductIdIn")
public List<ProductInfoSimplifyVO> findByProductIdIn(@RequestBody List<String> idList);
/** 订单减库:根据商品Id减去一定数量的库存 */
@PostMapping("/product/reduceProductStock")
public boolean reduceProductStock(@RequestBody List<ReduseProductForm> reduseProductFormList);
}
@FeignClient:表明这是一个feign接口类
value:填写调用服务的serviceId
接口方法则与调用接口一致(方法名可以自定义)
打完收工!
网友评论