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實現了服務調用的負載均衡。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章