天天看點

Ribbon&OpenFeign實作服務治理——注冊、發現、負載均衡

一、網絡模型

1、OSI的七層模型

七層的模型展現着程式設計過程中的單一職責的特性,每個子產品都負責這自己的東西。

  • 應用層——老闆下達收購Google的指令,并寫完收購意向書

為計算機使用者提供應用接口,也為使用者直接提供各 種網絡服務。我們常見應用層的網絡服務協定有:HTTP,HTTPS,FTP,POP3、SMTP等。
  • 表示層——将中文意向受翻譯成英文

表示層提供各種用于應用層資料的編碼和轉換功能,確定一個系統的應用層發送的資料能 被另一個系統的應用層識别。如果必要,該層可提供一種标準表示形式,用于将計算機内 部的多種資料格式轉換成通信中采用的标準表示形式。資料壓縮和加密也是表示層可提供 的轉換功能之一
  • 會話層——找到Google的内部通信位址并将與收購意向書封裝成一份信

負責建立、管理和終止表示層實體之間的通信會話。該層的通信由不同裝置 中的應用程式之間的服務請求和響應組成。
  • 傳輸層——收發室的人根據重要重要程度選擇快遞公司

建立了主機端到端的連結,傳輸層的作用是為上層協定提供端到端的可靠和透明 的資料傳輸服務,包括處理差錯控制和流量控制等問題。該層向高層屏蔽了下層資料通信 的細節,使高層使用者看到的隻是在兩個傳輸實體間的一條主機到主機的、可由使用者控制和 設定的、可靠的資料通路。我們通常說的,RPC,TCP,UDP就是在這一層。端口号既是這 裡的“端”
  • 網絡層——利用快遞公司的網絡将信件發送到Google

本層通過IP尋址來建立兩個節點之間的連接配接,為遠端的傳輸層送來的分組,選擇合适的 路由和交換節點,正确無誤地按照位址傳送給目的端的運輸層。就是通常說的IP層。這一層 就是我們經常說的IP協定層。IP協定是Internet的基礎
  • 資料鍊路層——底層具體的物流工具和快遞打包的封裝規則

将比特組合成位元組,再将位元組組合成幀,使用鍊路層位址 (以太網使用MAC位址)來通路 媒體,并進行差錯檢測。資料鍊路層又分為2個子層:邏輯鍊路控制子層(LLC)和媒體訪 問控制子層(MAC)。MAC子層處理CSMA/CD算法、資料出錯校驗、成幀等;LLC子層定 義了一些字段使上次協定能共享資料鍊路層。 在實際使用中,LLC子層并非必需的
  • 實體層——底層具體的物流工具和快遞打包的封裝規則

實際最終信号的傳輸是通過實體層實作的。通過實體媒體傳輸比特流。規定了電平、速 度和電纜針腳。常用裝置有(各種實體裝置)集線器、中繼器、數據機、網線、雙絞 線、同軸電纜。這些都是實體層的傳輸媒體。

2、HTTP與RPC的差別

  • 兩者一個在網絡七層一個在網絡四層
  • SpringCloud屬于是一個生态在這個生态中語言不确定所有他傾向于采用HTTP
  • 當在一個應用内部各個子產品的語言已知這時候預設采用RPC的技術來提高通路速度

3、RestFul了解

此處了解參照的是阮一峰的博文:

了解RESTful架構 - 阮一峰的網絡日志 (ruanyifeng.com)

RESTful API 設計指南 - 阮一峰的網絡日志 (ruanyifeng.com)

RestFul我們可以了解為是一種系統設計的風格,所有符合這種風格的架構都可以被稱之為RestFul架構。其中的Rest是幾個英文單詞的縮寫——Representational State Transfer

Representational:具象派的;代表性的,在阮一峰的部落格中将它解釋為表現層狀态裝換,我們或許也可以将它了解為資源的表現層狀态裝換

Resources:資源,它可以是一段文本、一張圖檔、一首歌曲、一種服務,總之就是一個具體的實在。你可以用一個URI(統一資源定位符)指向它,每種資源對應一個特定的URI。要擷取這個資源,通路它的URI就可以,是以URI就成了每一個資源的位址或獨一無二的識别符。

State Transfer:通路一個網站,就代表了用戶端和伺服器的一個互動過程。在這個過程中,勢必涉及到資料和狀态的變化。

3、什麼是RestTemplate

傳統情況下在java代碼裡通路restful服務,一般使用Apache的HttpClient。不過此 種方法使用起來太過繁瑣。spring提供了一種簡單便捷的模闆類來進行操作,這 就是RestTemplate。

RestTemplate 是從 Spring3.0 開始支援的一個 HTTP 請求工具,它提供了常見的 REST請求方案的模版,例如 GET 請求、POST 請求、PUT 請求、DELETE 請求以 及一些通用的請求執行方法 exchange 以及 execute。RestTemplate 繼承自 InterceptingHttpAccessor 并且實作了 RestOperations 接口,其中 RestOperations 接口定義了基本的 RESTful 操作,這些操作在 RestTemplate 中 都得到了實作。

二、Ribbon

1、微服務中的Ribbon

Spring Cloud Ribbon是一個基于HTTP和TCP的用戶端負載均衡工具,他基于Netflix Ribbon實作,通過Spring Cloud的封裝,可以讓我們輕松的将面向服務的Rest模闆請求自動轉換成用戶端負載均衡的調用。

Ribbon隻具有負載均衡的能力,也就是說他可以在一些列的位址中根據一定的算法挑選出一個合适的位址,但這個位址的通信還是需要通過通信元件來進行,這樣的元件如:RestTemplate、Feign等

Ribbon&OpenFeign實作服務治理——注冊、發現、負載均衡

2、 RestTemplat基礎使用與項目搭建

  • Producer

建立一個producer的服務,此服務中主要配置一個注冊中心:

server:
  port: 7000

spring:
  application:
    name: ribbon-producer
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8847
           

提供者的啟動類:

@EnableDiscoveryClient
@SpringBootApplication
public class RibbonProducerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RibbonProducerApplication.class);
    }
}
           
  • Consumer

在consumer的服務的啟動類中我們需要添加RestTemplat的Bean以供後面的使用:

@EnableDiscoveryClient
@SpringBootApplication
public class RibbonConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RibbonConsumerApplication.class);
    }

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

在consumer的服務中我們先在Controller的類中添加如下的屬性:

@Slf4j
@RequestMapping("/consumer")
@RestController
public class ConsumerController {

    @Resource
    //import org.springframework.web.client.RestTemplate;
    private RestTemplate restTemplate;

    @Resource
    //import org.springframework.cloud.client.discovery.DiscoveryClient;
    private DiscoveryClient discoveryClient;

    @Resource
    //import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
    private LoadBalancerClient loadBalancerClient;
}
           

RestTemp的基礎使用時簡單的就就是new一個對象,然後調用這個對象的getForObject方法:

/**
     * 使用new的RestTemplate(http://localhost:7002/consumer/restTemplateTest)
     */
    @GetMapping("/restTemplateTest")
    public String restTemplateTest() {
        log.info("restTemplateTest invoke!");
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.getForObject("http://localhost:7000/provider/hello", String.class);
    }
           

這時候我們的服務就可以調通了,當然我們也可以不自己建立對象而是通過注解引入容器中的對象:

@GetMapping("/restTemplateTestUseBean")
public String restTemplateTestUseBean() {
    log.info("restTemplateTestUseBean invoke!");
    return restTemplate.getForObject("http://localhost:7000/provider/hello", String.class);
}
           

3、DiscoveryClient + 自實作負載均衡 + RestTemplat

/**
 * 使用DiscoveryClient擷取服務清單進行随機服務調用(http://localhost:7002/consumer/discoveryClient)
 * 【注意】不能加@LoadBalanced,否則請求失敗
 */
@GetMapping("/discoveryClient")
public String discoveryClient() {
    log.info("discoveryClient invoke!");
    // 獲得服務id下的服務資訊
    List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("ribbon-producer");
    serviceInstanceList.forEach(serviceInstance -> log.info("host={}, port={}, uri={}", serviceInstance.getHost()
            , serviceInstance.getPort(), serviceInstance.getUri()));
    int num = new Random().nextInt(serviceInstanceList.size());
    URI uri = serviceInstanceList.get(num).getUri();
    log.info("num={}, uri={}", num, uri);
    return restTemplate.getForObject( uri + "/provider/hello", String.class);
}
           

discoveryClient是SpringCloud中的一個實體,他會內建的服務發現管理工具——Nacos來發現服務,并擷取對應的服務清單,我們拿到這個清單後根據自定義的政策取出對應的服務然後進行請求。

4、LoadBalancerClient + RestTempl

/**
 * 使用LoadBalancerClient自帶的負載均衡功能執行服務調用(http://localhost:7002/consumer/loadBalancerClient)
 * 【注意】不能加@LoadBalanced,否則請求失敗
 */
@GetMapping("/loadBalancerClient")
public String loadBalancerClient() {
    log.info("loadBalancerClient invoke!");
    // 獲得負載均衡執行個體
    ServiceInstance serviceInstance = loadBalancerClient.choose("ribbon-producer");
    log.info("uri={}", serviceInstance.getUri());
    return restTemplate.getForObject( serviceInstance.getUri() + "/provider/hello", String.class);
}
           

5、@LoadBalanced + RestTemplat

/**
 * 使用@LoadBalanced修飾的RestTemplate來執行具有負載均衡功能的服務調用(http://localhost:7002/consumer/loadBalancerAnnotation)
 */
@GetMapping("/loadBalancerAnnotation")
public String loadBalancerAnnotation() {
    log.info("loadBalancerAnnotation invoke!");
    // 獲得負載均衡執行個體
    return restTemplate.getForObject("http://ribbon-producer/provider/hello", String.class);
}
           

在這個方法中我們往容器中添加了一個@LoadBalanced的注解:

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

6、自定義負載均衡政策

如果要是想要指定負載均衡的政策可以在yaml的配置檔案中添加如下配置:

ribbon-producer:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
           

後面值是負載均衡政策的全路徑名,其他的一個政策為:

  • RoundRobinRule 輪詢政策,按順序循環選擇服務執行個體 
  • RandomRule 随機政策,随機選擇服務執行個體 
  • AvaliabilityFilteringRule 可用過濾政策,會先過濾由于多次通路故障而處于斷路 器跳閘狀态的服務,還有并發的連接配接數量超過門檻值的服務。然後對剩餘的服務列 表按照輪詢政策進行通路。 
  • WeightedResponseTimeRule 響應時間權重政策(對于不同配置或負載的服務, 請求偏向于打到負載小或性能高的服務上),根據平均響應的時間計算所有服務 的權重,響應時間越快服務權重越大,也就越大機率被選中,剛啟動時如果統計 資訊不足,則使用RoundRobinRule政策,等統計資訊足夠了,會切換到本政策。
  • RetryRule 重試政策(會使客戶對于服務清單中不可用服務的調用無感,因為會 retry到别的服務),先按照RoundRobinRule的政策擷取服務,如果擷取失敗,則 在制定時間内進行重試,擷取可用服務。
  • BestAvailableRule 最低并發政策

7、負載均衡政策的原理

可以側重看各個政策的實作原理

三、OpenFeign

1、什麼是OpenFeign?

由于Netflix的Feign元件進入的維護階段,是以Spring Cloud團隊開始吸收開源的 Feign元件的簡化,封裝開發了OpenFeign元件,而使用上,OpenFeign與Feign是一 樣的。

OpenFeign是一個聲明式的僞Http用戶端(即:封裝了Http請求,底層還是使用的 RestTemplate發送的http請求),它使得編寫Http用戶端變得更簡單。隻需要創 建一個接口并加入相關注解。它具有可插拔的注解特性(可以使用SpringMVC注 解),可以使用Feign注解和JAX-RS注解。它将支援可插拔的編碼器和解碼器。默 認繼承了Ribbon,預設實作了負載均衡。并且SpringCloud團隊為Feign添加了 Spring MVC注解的支援。

2、項目架構的準備

  • Producer

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>
           

在pom檔案中主要添加的是Nacos、以及openfeign相關的依賴,配置檔案中進行對Nacos相關的配置:

server:
  port: 8000
spring:
  application:
    name: openfeign-producer
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
           

在項目的主類中添加Nacos與Openfeign相關的注解:

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class OpenFeignProducerApplication {
    public static void main(String[] args) {
        SpringApplication.run(OpenFeignProducerApplication.class);
    }
}
           
  • Consumer

在Consumer項目的依賴檔案中的需要添加的依賴于Producer的是相同的,上面的我們也可以看到沒有引用Ribbon的依賴這是因為OpenFeign中預設已經內建了Ribbon,有關Ribbon的負載均衡政策我們都是可以在yaml的配置檔案中正常配置的

server:
  port: 8002

spring:
  application:
    name: openfeign-consumer
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
feign:
  client:
    config:
      openfeign-producer:
        connectTimeout: 5000
        readTimeout: 5000
           

主類中與Producer的項目一樣也是添加Nacos與Openfeign相關的注解,這裡就不再做展示

3、使用OpenFeign實作服務間的請求

使用OpenFeign實作服務之間的請求就像Mybatis進行CURD操作似的,我們需要一個接口來進行代理,在這個接口中指明此接口對應的是哪個服務:

@FeignClient(name = "openfeign-producer")
public interface ProviderService {
    @GetMapping("/provider/hello")
    String hello();
    /**
     * 傳遞基本類型參數
     */
    @GetMapping("/provider/baseParam")
    String baseParam(@RequestParam("name") String name, @RequestParam("age") Integer age);
    /**
     * 傳遞基本類型參數——路徑
     */
    @GetMapping("/provider/baseParamPath/{name}/{age}")
    String baseParamPath(@PathVariable("name") String name, @PathVariable("age") Integer age);
    /**
     * 傳遞對象類型參數
     */
    @PostMapping("/provider/objectParam")
    String objectParam(@RequestBody User user);
    /**
     * 傳遞數組類型參數,通過queryString方式傳遞,即:/provider/arrayParam?params=1&params=2
     */
    @GetMapping("/provider/arrayParam")
    String arrayParam(@RequestParam("params") String[] params);
    /**
     * 傳遞集合類型參數
     */
    @GetMapping("/provider/listParam")
    String listParam(@RequestParam("params") List<String> params);
    /**
     * 傳遞集合類型參數
     * http://localhost:8000/provider/responseObj?userId=111
     */
    @GetMapping("/provider/responseObj")
    User responseObj(@RequestParam("userId") String userId);
    /**
     * 測試OpenFeign預設1秒請求逾時
     * http://localhost:8000/producer/timeoutDemo
     */
    @GetMapping("/provider/timeoutDemo")
    String timeoutDemo();
}
           

這裡面通過/provider/timeoutDemo來實作是個服務與代理之間的映射關系,@FeignClient(name = “openfeign-producer”)注解中相當于是建立了服務層面的關系。

@Slf4j
@RequestMapping("/consumer")
@RestController
public class ConsumerController {
    @Resource
    private ProviderService providerService;
    // http://localhost:8002/consumer/hello
    @GetMapping("/hello")
    public String hello() {
        log.info("Feign invoke!");
        return providerService.hello();
    }
    /**
     * 傳遞基本類型參數
     * http://localhost:8002/consumer/baseParam?name=muse&age=20
     */
    @GetMapping("/baseParam")
    public String baseParam(@RequestParam("name") String name, @RequestParam("age") Integer age) {
        log.info("consumer baseParam name={} age={}", name, age);
        return providerService.baseParam(name, age);
    }
    /**
     * 傳遞基本類型參數——路徑
     * http://localhost:8002/consumer/baseParamPath/bob/30
     */
    @GetMapping("/baseParamPath/{name}/{age}")
    public String baseParamPath(@PathVariable("name") String name, @PathVariable("age") Integer age) {
        log.info("consumer baseParamPath name={} age={}", name, age);
        return providerService.baseParamPath(name, age);
    }
    /**
     * 傳遞對象類型參數
     * http://localhost:8002/consumer/objectParam
     */
    @GetMapping("/objectParam")
    public String objectParam() {
        User user = new User();
        user.setName("John");
        user.setAge(40);
        log.info("consumer objectParam name={} age={}", user.getName(), user.getAge());
        return providerService.objectParam(user);
    }
    /**
     * 傳遞數組類型參數
     * http://localhost:8002/consumer/arrayParam
     */
    @GetMapping("/arrayParam")
    public String arrayParam() {
        log.info("consumer arrayParam");
        String[] params = new String[]{"muse", "bob", "john"};
        return providerService.arrayParam(params);
    }
    /**
     * 傳遞集合類型參數
     * http://localhost:8002/consumer/listParam
     */
    @GetMapping("/listParam")
    public String listParam() {
        log.info("consumer listParam");
        List params = Lists.newArrayList("zhang3", "lee4", "wang5");
        return providerService.listParam(params);
    }
    /**
     * 傳遞集合類型參數
     * http://localhost:8002/consumer/responseObj?userId=111
     */
    @GetMapping("/responseObj")
    public User responseObj(@RequestParam("userId") String userId) {
        User user = providerService.responseObj(userId);
        log.info("consumer responseObj userId={} user={}", userId, user);
        return user;
    }
    /**
     * 測試OpenFeign預設1秒請求逾時
     * http://localhost:8002/consumer/timeoutDemo
     */
    @GetMapping("/timeoutDemo")
    public String timeoutDemo() {
        log.info("consumer timeoutDemo!");
        providerService.timeoutDemo();
        return "consumer timeoutDemo!";
    }
}
           

在Controller層進行使用的時候我們直接通過代理的接口即可。

Dubbo是通過生産者與消費者都實作同一個接口同時通過配置檔案來實作服務之間的一個注冊、發現以及映射關系。而OpenFeign則是通過接口來實作,從使用者的層面來看,Dubbo好現實把對服務的調用隐藏的更深。

個人了解Dubbo項目的耦合度更高一些OpenFeign的耦合度低一些,因為他不直接對接口進行依賴,更像是直接通過網絡請求進行的一種調用。但是總的來說,如果想要系統之間的調用可行就還是需要對彼此之間做一些限制,從個人的角度來說還是更喜歡Dubbo一些。

繼續閱讀