Feign在K8s中的使用

之前在SpringCloud中使用过@FeignClient的方式对服务进行调用,感觉使用起来还是很方便的,所以想要探索一下是否可以把@FeignClient用在K8s集群中进行服务间的调用;

feign是一个声明式web服务调用的客户端,创建一个接口并加上注解就能使用Feign了(同时支持JAX-RS类型的注解,可插入式的编码和解码),Spring Cloud Feign组件为他加入了spring mvc的注解(@RequestMappging, @RequestBody, @ResponseBody, @RequestParam, @PathVariable等)支持,以及在spring web开发过程中默认使用同样的HttpMessageConverters ,同时Spring Cloud整合了Ribbon和Eureka为使用feign的过程中提供了一个负载均衡的http客户端。

简单示例 

Feign示例(github-OpenFeign-feign):

//feign客户端声明
interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  @RequestLine("POST /repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);

}
//DTO定义
public static class Contributor {
  String login;
  int contributions;
}

public static class Issue {
  String title;
  String body;
  List<String> assignees;
  int milestone;
  List<String> labels;
}

//启动类
public class MyApp {
  public static void main(String... args) {
    //注册feign客户端
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                          //注册feign客户端,并指定服务URL
                         .target(GitHub.class, "https://api.github.com");
  
    //feign客户端进行服务调用
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

SpringCloud @FeignClient示例(Declarative REST Client: Feign):

//FeignClient客户端声明(其中"stores"为SpringCloud中服务名称)
@FeignClient("stores")
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);
}

//启动类
@EnableAutoConfiguration
@EnableEurekaClient
//启动FeignClient自动配置
@EnableFeignClients
public class Application {

    //自动注入FeignClient代理
    @Resource
    private StoreClient storeClient;
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        
        //FeignClient进行服务调用
        storeClient.update(1, new Store());
    }

}

//maven依赖
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
   <version>2.1.1.RELEASE</version>
</dependency>

优化

结合Maven多module的特性,可以将Controller抽象出接口定义,即在单独的一个module(如api)中,定义Controller的接口,然后在其他模块(如web) 中去实现Controller的接口,而后将api打包后上传到maven私服供公司内其他服务依赖并调用;

例如Maven项目多module结构如下:

api:对外提供服务的参数、返回结果DTO定义,Controller接口定义
domain:domain对象定义(entity, vo, bo, dto等)
dao:数据访问层(数据库访问、缓存访问)
service:具体服务的定义
web:controller实现层的定义

优化示例

即在api模块中进行controller接口定义:

@RequestMapping("/base")
public interface BaseController {

    @RequestMapping(value = "/reqParam", method = RequestMethod.POST)
    @ResponseBody
    Object reqParam(@SpringQueryMap ParamVo paramVo);

    @RequestMapping(value = "/reqBody", method = RequestMethod.POST)
    @ResponseBody
    Object reqBody(@RequestBody ParamVo paramVo);

    @RequestMapping(value = "/reqPath/{batteryCode}/{page}/{rows}", method = RequestMethod.POST)
    @ResponseBody
    Object reqPath(@PathVariable("batteryCode") String batteryCode, @PathVariable("page") Integer page, @PathVariable("rows") Integer rows);
}

在web模块中实现controller接口:

@Controller
@RequestMapping("/base")
public class BaseControllerImpl implements BaseController {
    private static final Logger logger = LogManager.getLogger(BaseControllerImpl.class);

    @RequestMapping(value = "/reqParam", method = RequestMethod.POST)
    @ResponseBody
    public Object reqParam(@SpringQueryMap ParamVo paramVo) {
        logger.info("reqParam param:{}", JsonUtils.toJson(paramVo));
        return MxHttpRespUtility.successResp();
    }

    @RequestMapping(value = "/reqBody", method = RequestMethod.POST)
    @ResponseBody
    public Object reqBody(@RequestBody ParamVo paramVo) {
        logger.info("reqBody param:{}", JsonUtils.toJson(paramVo));
        return MxHttpRespUtility.successResp();
    }

    @RequestMapping(value = "/reqPath/{batteryCode}/{page}/{rows}", method = RequestMethod.POST)
    @ResponseBody
    public Object reqPath(@PathVariable("batteryCode") String batteryCode, @PathVariable("page") Integer page, @PathVariable("rows") Integer rows) {
        logger.info("reqPath param: batteryCode={}, page={}, rows={}", batteryCode, page, rows);
        return MxHttpRespUtility.successResp();
    }
}

注:
@SpringQueryMap为Feign中注解,用于解析QeuryString 形式的参数,
即controller中方法参数为Object reqParam(ParamVo paramVo)形式(参数没有被@RequestParam, @RequestBody等所修饰)时,
需在controller接口定义的相应方法参数上添加@SpringQueryMap,如:Object reqParam(@SpringQueryMap ParamVo paramVo);

在其他服务中,依赖dev-web中api模块后,即可通过继承该controller接口的方式进行FeignClient调用:

//启动类添加注解(FeignClient自动配置)
@EnableFeignClients


//声明FeignClient,继承被调用服务的controller接口
//指定url后,即实际请求的路径即为url/requestPath(仅指定name则走SpringCloud的服务名称的负载均衡调用)
@FeignClient(name="base", url = "http://localhost:8089/dev-springboot-template")
public interface BaseControllerFeign extends BaseController {
}

//自动注入FeignClient代理
@Resource
private BaseControllerFeign baseControllerFeign;
...
//FeignClient服务调用
Object result = baseControllerFeign.reqBody(paramVo);

在k8s(或Istio)中,我们可以通过指定url=serviceName.namespace的方式来进行k8s间服务的调用,即无需通过name指定服务名称,在K8s集群中可直接通过serviceName.namespace进行服务间的调用,由K8s(或Istio)集群环境负责服务的路由与负载均衡;

示例类图如下:

总结

通过FeignClient的调用方式,可以将Controller抽象出接口定义,服务端来实现Controller的具体实现逻辑,而客户端依赖Controller接口定义并以此进行服务调用(由OpenFeign客户端对Controller中SpringMVC的注解进行解析并转换为相应的Http调用),此种方式中Controller接口定义为客户端和服务端二者之间连接的桥梁,通过Maven多module的形式使得Controller接口定义得以在多个应用间进行传递复用,而在K8s(Istio)中可以通过指定url=serviceName.namespace/contextPath来进行服务间的请求调用,由K8s(或Istio)集群环境负责服务的路由与负载均衡;

问题:FeignClient接口定义并不是完全符合SpringMVC规范,可能会对原Controller接口进行调整方能完全适配;例如@SpringQueryMap注解的使用,以及@PathVariable需要对应到每个参数上,例如原Controller实现定义如下:

@RequestMapping(value = "/reqPath/{batteryCode}/{page}/{rows}", method = RequestMethod.POST)
@ResponseBody
public Object reqPath(ParamVo paramVo) {
    logger.info("reqPath param:{}", JsonUtils.toJson(paramVo));
    return MxHttpRespUtility.successResp();
}

但是FeignClient需要修改如下形式才可正确调用:

@RequestMapping(value = "/reqPath/{batteryCode}/{page}/{rows}", method = RequestMethod.POST)
@ResponseBody
Object reqPath(@PathVariable("batteryCode") String batteryCode, @PathVariable("page") Integer page, @PathVariable("rows") Integer rows);

以上只是简单的了解,若想真正像使用Feign进行K8s间服务调用,还需对spring-cloud-starter-openfeign源码进行分析与重构,删除不必要的依赖...

发布了56 篇原创文章 · 获赞 6 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/luo15242208310/article/details/102504584