91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring?Cloud負載均衡組件Ribbon原理是什么

發布時間:2022-04-13 14:26:37 來源:億速云 閱讀:115 作者:iii 欄目:開發技術

這篇文章主要介紹“Spring Cloud負載均衡組件Ribbon原理是什么”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Spring Cloud負載均衡組件Ribbon原理是什么”文章能幫助大家解決問題。

    微服務體系下的 Spring Cloud Netflix 套件中 Ribbon 的主要用于負載均衡,底層默認使用 RestTemplate 通訊,并提供了 7 種負載均衡策略

    前言

    在微服務中,對服務進行拆分之后,必然會帶來微服務之間的通信需求,而每個微服務為了保證高可用性,又會去部署集群,那么面對一個集群微服務進行通信的時候,如何進行負載均衡也是必然需要考慮的問題。那么有需求自然就有供給,由此一大批優秀的開源的負載均衡組件應運而生,本文就讓我們一起來分析一下 Spring Cloud Netflix 套件中的負載均衡組件 Ribbon

    一個問題引發的思考

    首先我們來看一個問題,假如說我們現在有兩個微服務,一個 user-center,一個 user-order,我現在需要在 user-center 服務中調用 user-order 服務的一個接口。

    這時候我們可以使用 HttpClientRestTemplate 等發起 http 請求,user-center 服務端口為 8001,如下圖所示:

    @RestController
    @RequestMapping(value = "/user")
    public class UserController {
        @Autowired
        private RestTemplate restTemplate;
    
        @Bean
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    
        @GetMapping("/order")
        public String queryOrder(){
            return restTemplate.getForObject("http://localhost:8002/order/query",String.class);
        }
    }

    user-order 服務中只是簡單的定義了一個接口,user-order 服務端口為 8002

    @RestController
    @RequestMapping(value = "/order")
    public class UserOrderController {
    
        @GetMapping(value = "/query")
        public String queryAllOrder(){
            return "all orders";
        }
    }

    這時候只需要將兩個服務啟動,訪問 http://localhost:8001/user/order 就可以獲取到所有的訂單信息。

    可以看到,這樣是可以在兩個微服務之間進行通訊的,但是,假如說我們的 user-order 服務是一個集群呢?這時候怎么訪問呢?因為 user-order 服務已經是集群,所以必然需要一種算法來決定應該請求到哪個 user-order 服務中,最簡單的那么自然就是隨機或者輪詢機制,輪詢或者隨機其實就是簡單的負載均衡算法,而 Ribbon 就是用來實現負載均衡的一個組件,其內部支持輪詢,等算法。

    Ribbon的簡單使用

    接下來我們看看 Ribbon 的簡單使用。

    首先改造 user-order 服務,在 user-order 服務中定義一個服務名配置:

    spring.application.name=user-order-service

    user-order 服務中的 UserOrderController 稍微改造一下,新增一個端口的輸出來區分:

    @RestController
    @RequestMapping(value = "/order")
    public class UserOrderController {
    
        @Value("${server.port}")
        private int serverPort;
    
        @GetMapping(value = "/query")
        public String queryAllOrder(){
            return "訂單來自:" + serverPort;
        }
    }
    • 通過 VM 參數 -Dserver.port=8002-Dserver.port=8003 分別來啟動兩個 user-order 服務。

    • 接下來改造 user-center 服務,在 user-center 服務中引入 Ribbon 的相關依賴:

     <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
          <version>2.2.3.RELEASE</version>
        </dependency>
    • user-center 服務中新增一個 Ribbon 相關配置,列舉出需要訪問的所有服務:

    user-order-service.ribbon.listOfServers=\
      localhost:8002,localhost:8003
    • user-center 服務中的 UserController 進行改造:

    @RestController
    @RequestMapping(value = "/user")
    public class UserController {
        @Autowired
        private RestTemplate restTemplate;
    
        @Autowired
        private LoadBalancerClient loadBalancerClient;
    
        @Bean
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    
        @GetMapping("/order")
        public String queryOrder(){
            //獲取一個 user-order 服務
            ServiceInstance serviceInstance = loadBalancerClient.choose("user-order-service");
            String url = String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort()) + "/order/query";
            return restTemplate.getForObject(url,String.class);
        }
    }

    這時候我們再次訪問 http://localhost:8001/user/order 就可以看到請求的 user-order 服務會在 80028003 之間進行切換。

    Ribbon 原理分析

    看了上面 Ribbon 的使用示例,會不會覺得有點麻煩,每次還需要自己去獲取 ip 和端口,然后格式化 url,但是其實實際開發過程中我們并不會通過這么原始的方式來編寫代碼,接下來我們再對上面的示例進行一番改造:

    @RestController
    @RequestMapping(value = "/user")
    public class UserController3 {
        @Autowired
        private RestTemplate restTemplate;
    
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    
        @GetMapping("/order")
        public String queryOrder(){
            return restTemplate.getForObject("http://user-order-service/order/query",String.class);
        }
    }

    在這個示例中,主要就是一個關鍵主鍵起了作用:@LoadBalanced。

    @LoadBalanced 注解

    進入 @LoadBalanced 注解中,我們可以看到,這個注解其實沒有任何邏輯,只是加了一個 @Qualifier 注解:

    Spring?Cloud負載均衡組件Ribbon原理是什么

    這個注解大家應該很熟悉了,常用語同一個 Bean 有多個不同名稱注入的場景。

    @Qualifier注解

    下面我們通過一個例子來演示一下 Qualifier注解的用法。

    新建一個空的 TestDemo 類,并新增一個 TestConfiguration 類來創建不同名稱的 TestDemo

    @Configuration
    public class TestConfiguration {
        @Bean("testDemo1")
        public TestDemo testDemo(){
            return new TestDemo();
        }
    
        @Bean("testDemo2")
        public TestDemo testDemo2(){
            return new TestDemo();
        }
    }

    這時候我們如果需要注入 TestDemo,那么有很多種辦法,具體的使用就需要看業務需要來決定。

    • 方法一:直接使用 @Autowired,并使用 List 集合來接收 Bean,這樣所有 TestDemo 類型的 Bean 都會被注入。

    • 方法二:通過使用 @Resource(name = "testDemo1") 注解來指定名稱,這樣就可以只注入一個 Bean

    • 方法三:通過使用 @Resource@Qualifier(value = "testDemo1") 來指定一個 Bean,其實這種方式和方法二的效果基本一致。

    • 方法四:使用 @Autowired@Qualifier 注解來注入,不指定任何名稱,如下所示:

    @Configuration
    public class TestConfiguration {
        @Bean("testDemo1")
        public TestDemo testDemo(){
            return new TestDemo();
        }
    
        @Bean("testDemo2")
        public TestDemo testDemo2(){
            return new TestDemo();
        }
    }

    這時候運行之后我們發現不會有任何 Bean 被注入到集合中,這是因為當使用這種方式來注入時,Spring 會認為當前只需要注入被 @Qualifier 注解標記的 Bean,而我們上面定義的兩個 TestDemo 都沒有被 @Qualifier 修飾。

    這時候,我們只需要在 TestConfiguration 稍微改造,在 TestDemo 的定義上加上 @Qualifier 修飾即可:

    @Configuration
    public class TestConfiguration {
    
        @Bean("testDemo1")
        @Qualifier
        public TestDemo testDemo(){
            return new TestDemo();
        }
    
        @Bean("testDemo2")
        @Qualifier
        public TestDemo testDemo2(){
            return new TestDemo();
        }
    }

    這時候再去運行,就會發現,testDemo1testDemo2 都會被注入。

    LoadBalancerAutoConfiguration 自動裝配

    SpringCloud 是基于 SpringBoot 實現的,所以我們常用的這些分布式組件都會基于 SpringBoot 自動裝配來實現,我們進入 LoadBalancerAutoConfiguration 自動裝配類可以看到,RestTemplate 的注入加上了 @LoadBalanced,這就是為什么我們前面的例子中加上了 @LoadBalanced 就能被自動注入的原因:

    Spring?Cloud負載均衡組件Ribbon原理是什么

    RestTemplateCustomizer

    上面我們看到,RestTemplate 被包裝成為了 RestTemplateCustomizer,而 RestTemplateCustomizer 的注入如下:

    Spring?Cloud負載均衡組件Ribbon原理是什么

    可以看到這里面加入了一個攔截器 LoadBalancerInterceptor,事實上即使不看這里,我們也可以猜測到,我們直接使用服務名就可以進行通訊的原因必然是底層有攔截器對其進行轉換成 ip 形式,并在底層進行負載均衡選擇合適的服務進行通訊。

    LoadBalancerInterceptor

    LoadBalancerInterceptorRibbon 中默認的一個攔截器,所以當我們調用 RestTemplategetObject 方法時,必然會調用攔截器中的方法。

    從源碼中可以看到,LoadBalancerInterceptor 中只有一個 intercept() 方法:

    Spring?Cloud負載均衡組件Ribbon原理是什么

    RibbonLoadBalancerClient#execute

    繼續跟進 execute 方法會進入到 RibbonLoadBalancerClient 類(由 RibbonAutoConfiguration 自動裝配類初始化)中:

    Spring?Cloud負載均衡組件Ribbon原理是什么

    這個方法中也比較好理解,首先獲取一個負載均衡器,然后再通過 getServer 方法獲取一個指定的服務,也就是當我們有多個服務時,到這里就會選出一個服務進行通訊。

    進入 getServer 方法:

    Spring?Cloud負載均衡組件Ribbon原理是什么

    我們看到,最終會調用 ILoadBalancer 中的 chooseServer 方法,而 ILoadBalancer 是一個頂層接口,這時候具體會調用哪個實現類那么就需要先來看一下類圖:

    Spring?Cloud負載均衡組件Ribbon原理是什么

    這里直接看類圖也無法看出到底會調用哪一個,但是不論調用哪一個,我們猜測他肯定會有一個地方去初始化這個類,而在 Spring 當中一般就是自動裝配類中初始化或者 Configuration 中初始化,而 ILoadBalancer 正是在 RibbonClientConfiguration 類中被加載的:

    Spring?Cloud負載均衡組件Ribbon原理是什么

    ZoneAwareLoadBalancer 負載均衡器

    ZoneAwareLoadBalancer 的初始化會調用其父類 DynamicServerListLoadBalancer 進行初始化,然后會調用 restOfInit 方法進行所有服務的初始化。

    如何獲取所有服務

    使用 Ribbon 后,我們通訊時并沒有指定某一個 ip 和端口,而是通過服務名來調用服務,那么這個服務名就可能對應多個真正的服務,那么我們就必然需要先獲取到所有服務的 ip 和端口等信息,然后才能進行負載均衡處理。

    獲取所有服務有兩種方式:

    • 從配置文件獲取

    • Eureka 注冊中心獲取(需要引入注冊中心)。

    初始化服務的方式是通過啟動一個 Scheduled 定時任務來實現的,默認就是 30s 更新一次,其實在很多源碼中都是通過這種方式來定時更新的,因為源碼要考慮的使用的簡單性所以不太可能引入一個第三方中間件來實現定時器。

    具體的源碼如下所示:enableAndInitLearnNewServersFeature() 方法啟動的定時任務最終仍然你是調用 updateListOfServers() 方法來更新服務。

    Spring?Cloud負載均衡組件Ribbon原理是什么

    最終在獲取到服務之后會調用父類 BaseLoadBalancer 中的將所有服務設置到 allServerList 集合中(BaseLoadBalancer 類中維護了一些負載均衡需要使用到的服務相關信息)。

    如何判斷服務是否可用

    當我們獲取到配置文件(或者 Eureka 注冊中心)中的所有服務,那么這時候能直接執行負載均衡策略進行服務分發嗎?顯然是不能的,因為已經配置好的服務可能會宕機(下線),從而導致服務不可用,所以在 BaseLoadBalancer 中除了有一個 allServerList 集合來維護所有服務器,還有一個集合 upServerList 用來維護可用服務集合,那么如何判斷一個服務是否可用呢?答案就是通過心跳檢測來判斷一個服務是否可用。

    心跳檢測 Task

    在講心跳檢測之前,我們先看一下 BaseLoadBalancer 中的 setServersList 方法,有一段邏輯比較重要:

    Spring?Cloud負載均衡組件Ribbon原理是什么

    Spring?Cloud負載均衡組件Ribbon原理是什么

    這段邏輯我們看到,默認情況下,如果 Ping 的策略是 DummyPing,那么默認 upServerList = allServerList,而實際上,假如我們沒有進行進行特殊配置,其實默認的就是 DummyPing,這也是在 RibbonClientConfiguration 類中被加載的:

    Spring?Cloud負載均衡組件Ribbon原理是什么

    BaseLoadBalancer 初始化過程中,也會啟動一個 Scheduled 定時任務去定時更新任務,最終和 forceQuickPing() 方法一樣,調用一個默認策略來觸發心跳檢測,而默認策略就是 DummyPing,也就是默認所有服務都是可用的。

    Spring?Cloud負載均衡組件Ribbon原理是什么

    雖然默認不執行真正的心跳檢測操作,但是 Netflix 中提供了 PingUrl 等其他策略,PingUrl 其實就是發起一個 http 請求,如果有響應就認為服務可用,沒響應就認為服務不可用。

    修改心跳檢測策略可以通過如下配置切換(user-order-service 為客戶端的服務名),既然是可配置的,那么也可以自己實現一個策略,只需要實現 IPing 接口即可。

    user-order-service.ribbon.NFLoadBalancerPingClassName=com.netflix.loadbalancer.PingUrl

    Ribbon 的負載均衡算法

    當獲取到可用服務之后,那么最后應該選擇哪一個服務呢?這就需要使用到負載均衡策略,在 Ribbon 中,可以通過配置修改,也可以自定義負載均衡策略(實現 IRule 接口)。

    • RandomRule:隨機算法

    • RoundRobinRule:輪詢算法

    • ZoneAvoidanceRule:結合分區統計信息篩選出合適的分區(默認的負載均衡算法)

    • RetryRule:在 deadline 時間內,如果請求不成功,則重新發起請求知道找到一個可用的服務。

    • WeightedResponseTimeRule:根據服務器的響應時間計算權重值,服務器響應時間越長,這個服務器的權重就越小,會有定時任務對權重值進行更新。

    • AvailabilityFilteringRule:過濾掉短路(連續 3 次連接失敗)的服務和高并發的服務。

    • BestAvailableRule:選擇并發數最低的服務器

    負載均衡算法可通過以下配置進行修改:

    user-order-service.ribbon.NFLoadBalancerRuleClassName=Rule規則的類名

    關于“Spring Cloud負載均衡組件Ribbon原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    岳阳县| 化隆| 烟台市| 德令哈市| 锡林郭勒盟| 白银市| 扎囊县| 重庆市| 南城县| 大厂| 神农架林区| 竹山县| 饶阳县| 瑞丽市| 湖州市| 海城市| 凤阳县| 喀喇| 鄱阳县| 上饶市| 陕西省| 本溪市| 云浮市| 林周县| 朔州市| 闻喜县| 平泉县| 石景山区| 阿瓦提县| 油尖旺区| 中卫市| 正安县| 清流县| 蛟河市| 军事| 永福县| 南京市| 白河县| 太仓市| 邻水| 晋城|