SpringCloudNetflix学习和实践——服务的通信调用

一、RPC和HTTP

应用服务间通信调用的方式主要有两种,一种是HTTP,另一种是RPC。

RPC形式的常见代表是Dubbo。

Dubbo的定位就是一款RPC服务调用框架,基于Dubbo开发的应用还是要依赖周边的平台和生态,比如配合以Zookeeper实现的服务注册中心一起使用。Dubbo不仅提供了服务注册和发现、负载均衡等面向分布式系统的基础能力,还提供了开发测试阶段的Mock机制。在SpringCloud流行之前,Dubbo应用的十分广泛。

 

HTTP形式的常见代表是SpringCloud。

Dubbo自身的定位就只是一款RPC服务调用框架,而SpringCloud的目标是微服务下的一站式解决方案。

SpringCloud中服务调用采用的是http的restful方式,http-restful方式的特点是轻量,易用,便于跨语言跨平台。

SpringCloud中有两种restful调用方式:RestTemplate、Feign。

在比对完RPC和HTTP之后,我将主要实践SpringCloud中的服务调用。

 

二、基于RestTemplate的服务调用

RestTemplate是一款Http客户端,功能和HttpClient类似,但RestTemplate用法更简单。

 

场景:现在系统中有Product服务和Order服务,我需要在Order服务中去调用Product服务的接口。

 

Product服务(被调用方)

在product服务中定义了一个controller,其中提供了一个获取product信息的接口:

/**
 * @Auther: jesses
 * @Description: product服务中的controller
 */
@RestController
public class ProductController {

    @GetMapping("getMsg")
    public String getMsg(){
        return "this is product's message";
    }
}

Order服务(调用方)

使用RestTemplate方式调用上面的product接口,有三种实现方式。

  • 方式一、直接new RestTemplate()调用:
/**
 * @Auther: jesses
 * @Description: order服务中的controller
 */
@RestController
@Slf4j
public class ClientController {

    @GetMapping("/getProductMsg")
    public String getProductMsg() {
        //第一种方式
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.getForObject("http://127.0.0.1:9080/getMsg", String.class);
        log.info("response={}", response);
        return response;
    }
}

可以看到这种方式是写死Url,如果是在生产环境,别人的product服务部署到的IP地址,调用方却未必知道。

不知道服务地址,如何调用?这种方式肯定是存在缺陷的。

而且可能product被部署了多台实例,做了集群。我们肯定是想调用其中某一台就够了,这就涉及到需要实现负载均衡。

 

  • 方式二、利用LoadBalancerClient获取服务实例

这次我启动了两台product实例。port分别是9080、9081。

可以看到,在product服务中,配置了服务名为product:

SpringCloud提供了LoadBalancerClient,通过服务名来获取product服务的实例对象,从而获得IP和PORT:

/**
 * @Auther: jesses
 * @Description: order服务中的controller
 */
@RestController
@Slf4j
public class ClientController {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/getProductMsg")
    public String getProductMsg() {
        ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCT");
        String url = String.format("http://%s:%s%s",
                serviceInstance.getHost(),
                serviceInstance.getPort(),
                "/getMsg");
        return new RestTemplate().getForObject(url, String.class);
    }
}

  • 方式三、注解配置RestTemplate

在order服务中定义一个配置类,用于配置RestTemplate,使用@LoadBalanced注解配置负载均衡器,将其注册进spring容器。

/**
 * @Auther: jesses
 * @Description: 配置restTemplate,使用注解配置LoadBalanced
 */
@Component
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

直接注入配置了的 restTemplate,将调用的ip和port替换为服务名:

/**
 * @Auther: zhaoshuai
 * @Date: 2020/5/4
 * @Description: order服务中的controller
 */
@RestController
@Slf4j
public class ClientController {

    @Autowired
    private RestTemplate restTemplate;


    @GetMapping("/getProductMsg")
    public String getProductMsg() {
        return restTemplate.getForObject("http://PRODUCT/getMsg", String.class);
    }

}

第三种方式只是使用注解简化了第二种,实际上实现机制是一样的。

这三种方式归根结底还是基于RestTemplate的服务调用。

 

三、基于feign组件的服务调用

3.1 基于Ribbon实现的负载均衡

Feign是一款客户端http调用组件,属于SpringCloudNetflix的组件之一,而Feign是依赖于Ribbon组件的,

所以先来了解Ribbon及其负载均衡策略。

 

Ribbon实现负载均衡的核心要素有三点:

  • 服务发现:根据服务名,找到该服务的所有实例
  • 负载均衡策略:根据负载均衡策略,从多个服务实例中选择一个有效的服务
  • 服务监听:检测失效的服务,做到高效剔除。

概括Ribbon实现负载均衡的流程,大致是先通过ServerList获取所有可用服务列表,之后通过ServerListFilter过滤掉一部分,最后通过IRule选择一个目标实例。

接下来查看部分源码看Ribbon的具体实现:

 

3.1.1 Ribbon的服务发现:

之前在RestTemplate调用服务的第二种方式用到了loadBalancerClient.choose("PRODUCT") 这个API,现在查看这个api的实现。

LoadBalancerClient继承了ServiceInstanceChooser接口,

choose()方法的实现来自于LoadBalancerClient的实现类RibbonLoadBalancerClient:

在choose()实现中调用了getServer(),继续进入getServer()内查看:

getServer()方法的底层可以发现调用了chooseServer()这个API,再次进入查看:

可以发现获取所有服务列表的api方法List<Server> getAllServers();就定义在接口ILoadBalancer中。

查看getAllServers()的实现,在此施加断点。

调用第二种restTemplate方式的controller接口,进入断点后发现的确返回了product服务的实例列表:

 

3.1.2 Ribbon的负载均衡策略

再查看ILoadBalancer接口中的另一个api,即chooseServer(),查看其在BaseLoadBalancer实现类中的方法实现:

可以看到,其中是通过一个rule对象调用choose()方法的,

BaseLoadBalancer构造器中对rule对象进行了初始化,赋予其一个默认的轮询规则RoundRobinRule:

 既然默认的负载均衡策略是轮询,如果想要配置其他规则,如何修改?

https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.2.2.RELEASE/reference/html/#spring-cloud-ribbon

 

3.2 基于Feign实现的服务调用

第一步,在调用方服务引入依赖:

要使用feign,需要先引入其依赖spring-cloud-starter-openfeign。

需要注意的是,在较低版本的springcloud中,使用的是artifactId为spring-cloud-starter-feign的依赖。

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.0.0.M3</version>
        </dependency>

 第二步,在调用方服务的启动类上配置开启feign客户端,@EnableFeignClient:

第三步,定义feign接口:

例如,在被调用方product服务的controller中,我提供了两个接口,查询商品列表接口、扣库存接口。

/**
 * @Auther: jesses
 * @Description: product服务controller
 */
@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**根据商品ids查询商品列表*/
    @PostMapping("/listForOrder")
    public List<ProductInfo> listForOrder(@RequestBody List<String> productIdList){
        return productService.findList(productIdList);
    }

    /**减库存*/
    @PostMapping("/decreaseStock")
    public void decreaseStock(@RequestBody List<CartDTO> cartDTOList){
        productService.decreaseStock(cartDTOList);
    }

}

现在我需要在order服务调用product服务的这两个接口。

在order服务中定义feign客户端及接口:

1.定义一个interface用作feign客户端定义

2.使用@FeignClient(name="${application name of Called App}")注解标注。注解的name属性为被调用方服务名。

3.接口的@RequestMapping中method和value值,必须和被调用方服务定义的一致。

4.feign接口与被调用方服务匹配的要素只在于@FeignClient中name属性值,以及@RequestMapping中的value和method

   方法名可以不同,不影响使用

/**
 * @Auther: jesses
 * @Description: 调用product服务的feign客户端
 */
@FeignClient(name = "product")
public interface ProductClient {

    @PostMapping("/product/listForOrder")
    List<ProductInfo> listForOrder(@RequestBody List<String> productIdList);

    @PostMapping("/product/decreaseStock")
    void decreaseStock(@RequestBody List<CartDTO> cartDTOList);
}

 

之后在order服务的controller使用@Autowired注入ProductClient的Bean。直接调用api即可:

Feign本质上是一款http客户端,基于feign做服务调用,开发体验如通调用本地方法一样,感知不到是在调用远程接口。

Feign内部也使用了Ribbon实现了服务调用的负载均衡。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值