Appearance
Feign 相互调用
概述
Feign 是Spring Cloud Netflix组件中的一量级Restful的 HTTP 服务客户端,实现了负载均衡和 Rest 调用的开源框架,封装了Ribbon和RestTemplate, 实现了WebService的面向接口编程,进一步降低了项目的耦合度。
服务调用
顾名思义,就是服务之间的接口互相调用,在微服务架构中很多功能都需要调用多个服务才能完成某一项功能。
为什么要使用Feign
- Feign 旨在使编写 JAVA HTTP 客户端变得更加简单,Feign 简化了RestTemplate代码,实现了Ribbon负载均衡,使代码变得更加简洁,也少了客户端调用的代码,使用 Feign 实现负载均衡是首选方案,只需要你创建一个接口,然后在上面添加注解即可。
- Feign 是声明式服务调用组件,其核心就是:像调用本地方法一样调用远程方法,无感知远程 HTTP 请求。让开发者调用远程接口就跟调用本地方法一样的体验,开发者完全无感知这是远程方法,无需关注与远程的交互细节,更无需关注分布式环境开发。
Feign vs OpenFeign
- Feign 内置了Ribbon,用来做客户端负载均衡调用服务注册中心的服务。
- Feign 支持的注解和用法参考官方文档:https://github.com/OpenFeign/feign 官方文档,使用 Feign 的注解定义接口,然后调用这个接口,就可以调用服务注册中心的服务。
- Feign本身并不支持Spring MVC的注解,它有一套自己的注解,为了更方便的使用Spring Cloud孵化了OpenFeign。并且支持了Spring MVC的注解,如@RequestMapping,@PathVariable等。OpenFeign的@FeignClient可以解析Spring MVC的@RequestMapping注解下的接口,并通过动态代理方式产生实现类,实现类中做负载均衡调用服务。
如何使用
- 添加依赖
xml
<!-- spring cloud openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 新建FeignGoodsService.java服务接口
java
package cn.bizspring.cloud.goods.common.feign;
import cn.bizspring.cloud.common.core.enums.SecurityEnum;
import cn.bizspring.cloud.common.core.enums.ServiceNameEnum;
import cn.bizspring.cloud.goods.common.entity.Goods;
import cn.bizspring.cloud.goods.common.entity.Sku;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* @author www.bizspring.cn
*/
@FeignClient(contextId = "feignGoodsService", value = ServiceNameEnum.GOODS_SERVICE)
public interface FeignGoodsService {
/**
* 根据实体携带参数,查找实体对象
* @return 实体对象,若不存在则返回null
*/
@PostMapping("/provider/goods/find_all_by_entity")
public List<Goods> findAllByEntity(@RequestBody Goods goods,@RequestHeader(SecurityEnum.FROM) String from);
/**
* 查找实体对象
* @param id
* ID
* @return 实体对象,若不存在则返回null
*/
@GetMapping("/provider/goods/find")
public Goods find(@RequestParam(value = "id") String id, @RequestHeader(SecurityEnum.FROM) String from);
- provider下新建GoodsController,商品逻辑降级实现
java
package cn.bizspring.cloud.goods.admin.controller.provider;
import cn.bizspring.cloud.common.core.controller.BaseController;
import cn.bizspring.cloud.common.core.enums.SecurityEnum;
import cn.bizspring.cloud.common.security.annotation.Inside;
import cn.bizspring.cloud.marketing.common.entity.Marketing;
import cn.bizspring.cloud.goods.admin.service.GoodsService;
import cn.bizspring.cloud.goods.common.entity.Goods;
import cn.bizspring.cloud.goods.common.entity.Sku;
import cn.bizspring.cloud.store.common.entity.Store;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiOperationSupport;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
*Provider Controller - 商品
*/
@Slf4j
@AllArgsConstructor
@Api(value = "goods", tags = "provider - 商品")
@RestController("providerGoodsController")
@RequestMapping("/provider/goods")
public class GoodsController extends BaseController {
/**
* 每页记录数
*/
private static final int PAGE_SIZE = 12;
private GoodsService goodsService;
/**
* 查找实体对象
*
* @param id
* ID
* @return 实体对象,若不存在则返回null
*/
@Inside
@GetMapping("/find")
@ApiOperation(value = "查找实体对象", notes = "查找实体对象")
@ApiOperationSupport(order = 1)
public Goods find(@RequestParam("id") String id, @RequestHeader(SecurityEnum.FROM) String from){
return goodsService.find(id);
}
/**
* 根据实体对象携带参数 查询对象
* @return
*/
@Inside
@ApiOperation(value = "根据实体对象携带参数 查询对象", notes = "根据实体对象携带参数 查询对象")
@ApiOperationSupport(order = 1)
@PostMapping("/find_all_by_entity")
public List<Goods> findAllByEntity(@RequestBody Goods goods,@RequestHeader(SecurityEnum.FROM) String from){
return goodsService.findAllByEntity(goods);
}
- 消费者CartServiceImpl.java 在 getGiftNames 方法中 feignGoodsService.find 调用远程服务
java
package cn.bizspring.cloud.order.admin.service.impl;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.*;
/**
* Service - 购物车
*/
@Service
@AllArgsConstructor
public class CartServiceImpl extends BaseServiceImpl<Cart> implements CartService {
// feign远程Goods(产品)服务
private FeignGoodsService feignGoodsService;
/**
* 获取赠品名称
* @param store 店铺
* @return 赠品名称
*/
public List<String> getGiftNames(Cart cart, Store store) {
List<String> giftNames = new ArrayList<>();
for (Sku gift : getGifts(cart, store)) {
Goods goods = feignGoodsService.find(gift.getGoodsId(), SecurityEnum.FROM_IN);
if (CommonUtils.isNotNull(goods)) {
giftNames.add(goods.getName());
}
}
return giftNames;
}
负载均衡
Feign默认集成了Ribbon,Nacos也很好的兼容了Feign,默认实现了负载均衡的效果。
请求传参
- Get方式传参,使用@PathVariable、@RequestParam注解接收请求参数
java
@GetMapping("/provider/goods/find")
public Goods find(@RequestParam(value = "id") String id, @RequestHeader(SecurityEnum.FROM) String from);
- Post方式传参,使用@RequestBody注解接收JSON对象请求参数。
java
@PostMapping("/provider/goods/find_all_by_entity")
public List<Goods> findAllByEntity(@RequestBody Goods goods,@RequestHeader(SecurityEnum.FROM) String from);
Http连接池
- 两台服务器建立HTTP连接的过程涉及到多个数据包的交换,很消耗时间。采用HTTP连接池可以节约大量的时间提示吞吐量。
- Feign的HTTP客户端支持3种框架:HttpURLConnection、HttpClient、OkHttp。默认是采用java.net.HttpURLConnection,每次请求都会建立、关闭连接,为了性能考虑,可以引入httpclient、okhttp作为底层的通信框架。
如:将Feign的HTTP客户端工具修改为okhttp。
- 添加依赖
xml
<!-- feign httpclient -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
- 全局配置
nacos 中 application-dev.yml配置文件
yaml
# feign 配置
feign:
sentinel:
enabled: true
okhttp:
enabled: true
日志配置
浏览器发起的请求可以通过F12查看请求和响应信息。
Feign的负载均衡底层用的就是Ribbon,所以请求超时其实就只需要配置Ribbon参数。
全局配置
nacos 中 application-dev.yml配置文件
# 请求处理的超时时间
ribbon:
ReadTimeout: 10000
ConnectTimeout: 10000
@Inside注解
接口访问分为两种,一种是由Gateway网关访问进来,一种是内网通过Feign进行内部服务调用。
如果开放了内部访问的接口,如:
java
@GetMapping("/provider/goods/find")
public Goods find(@RequestParam(value = "id") String id);
bizspring-order系统模块
java
/**
* 添加
*/
@ApiOperation(value = "添加", notes = "添加")
@ApiOperationSupport(order = 2)
@PostMapping("/add")
public ResponseEntity add(String skuId, Integer quantity, String currentCartId)
{
....
Goods goods=feignGoodsService.find(sku.getGoodsId());
return R.ok(...);
}
这段代码也能通过网关(任意用户token携带)访问到localhost:8088/goods/provider/goods/find,这样暴露出去就很危险了,所以对于这种情况我们可以使用@Inside内部注解。
bizspring-order服务调用
新增Header参数SecurityEnum.FROM
java
@GetMapping("/provider/goods/find")
public Goods find(@RequestParam(value = "id") String id, @RequestHeader(SecurityEnum.FROM) String from);
bizspring-goods服务降级提供模块
java
@Inside
@GetMapping("/find")
@ApiOperation(value = "查找实体对象", notes = "查找实体对象")
@ApiOperationSupport(order = 1)
public Goods find(@RequestParam("id") String id, @RequestHeader(SecurityEnum.FROM) String from){
return goodsService.find(id);
}