Ribbon整合Eureka組件,以實現負載均衡

1總體框架的說明

    在本案例的框架裏,咱們將配置一個Eureka服務器,搭建三個提供相同服務的Eureka服務提供者,同時在Eureka服務調用者裏引入Ribbon組件,這樣,當有多個url向服務調用者發起調用請求時,整個框架能按配置在IRule和IPing中的「負載均衡策略「和「判斷服務器是否可用的策略」,把這些url請求合理地分攤到多臺機器上。java

    在下圖裏,咱們能看到本系統的結構圖,在其中,三個服務提供者向Eureka服務器註冊服務,而基於Ribbon的負載均衡器能有效地把請求分攤到不一樣的服務器上。spring

    爲了讓你們更方便地跑通這個案例,咱們將講解所有的服務器、服務提供者和服務調用者部分的代碼。在下表裏,列出了本架構中的全部項目。瀏覽器

項目名服務器

說明架構

EurekaRibbonDemo-Serverapp

Eureka服務器負載均衡

EurekaRibbonDemo-ServiceProviderOne框架

EurekaRibbonDemo-ServiceProviderTwodom

EurekaRibbonDemo-ServiceProviderThreeide

在這三個項目裏,分別部署着一個相同的服務提供者

EurekaRibbonDemo-ServiceCaller

服務調用者

    

2 編寫Eureka服務器    

    第一,在pom.xml裏編寫本項目須要用到的依賴包,在其中,是經過以下的代碼引入了Eureka服務器所必需的包,關鍵代碼以下。

1        <dependency>
2            <groupId>org.springframework.cloud</groupId>        
3    <artifactId>spring-cloud-starter-eureka-server</artifactId>
4        </dependency>

    第二,在application.yml這個文件裏,指定了針對Eureka服務器的配置,關鍵代碼以下。   

1    server:
2      port: 8888
3    eureka:
4      instance:
5        hostname: localhost
6      client:
7        serviceUrl:
8          defaultZone: http://localhost:8888/eureka/

    在第2和第5行裏,指定了本服務器所在的主機地址和端口號是localhost:8888,在第8行裏,指定了默認的url是http://localhost:8888/eureka/。

    第三步,在RegisterCenterApp這個服務啓動程序裏編寫啓動代碼。    

1    //省略必要的package和import代碼
2    @EnableEurekaServer
3    @SpringBoot
4    Application
5    public class RegisterCenterApp 
6    {
7        public static void main( String[] args )
8        { SpringApplication.run(RegisterCenterApp.class, args); }
9    }

    啓動該程序後,能在http://localhost:8888/看到該服務器的相關信息。

3 編寫Eureka服務提供者

    這裏有三個服務提供者,它們均是根據以前博文中的案例EurekaBasicDemo-ServiceProvider改寫而來。咱們就拿EurekaRibbonDemo-ServiceProviderOne來舉例,看下其中包含的關鍵要素。

    第一,一樣是在pom.xml裏,引入了服務提供者程序所需的jar包,不過在其中須要適當地修改項目名。

    第二,一樣是在ServiceProviderApp.java裏,編寫了啓動程序,代碼不變。

    第三,在application.yml裏,編寫了針對這個服務提供者的配置信息,關鍵代碼以下。    

1    server:
2      port: 1111
3    spring:
4      application:
5        name: sayHello
6    eureka:  
7      client:
8        serviceUrl:
9          defaultZone: http://localhost:8888/eureka/

    在第2行裏,指定了本服務是運行在1111端口上,在另外的兩個服務提供者程序裏,咱們分別指定了它們的工做端口是2222和3333。

    在第5行裏,咱們指定了服務提供者的名字是sayHello,另外兩個服務器提供者的名字一樣是sayHello,正由於它們的名字都同樣,因此服務調用者在請求服務時,負載均衡組件纔能有效地分攤流量。

    第四,在Controller這個控制器類裏,編寫了處理url請求的邏輯,關鍵代碼以下。    

1    //省略了必要的package和import的代碼
2    @RestController
3    public class Controller {    
4        @RequestMapping(value = "/sayHello/{username}", method = RequestMethod.GET    )
5        public String hello(@PathVariable("username") String username) {
6            System.out.println("This is ServerProvider1");        
7            return "Hello Ribbon, this is Server1, my name is:" + username;
8        }
9    }

    在第2行裏,咱們經過@RestController註解來講明本類承擔着「控制器」的角色。在第4行裏,咱們定義了觸發hello方法的url格式和Http請求的方式。在第5到第8行的hello方法裏咱們返回了一個字符串。請你們注意,在第6行和第7行的代碼裏,咱們能明顯看出輸出和返回信息是來自於1號服務提供者。

    EurekaRibbonDemo-ServiceProviderTwo和EurekaRibbonDemo-ServiceProviderOne項目很類似,改動點有以下三個。

    第一,在pom.xml裏,把項目名修改爲EurekaRibbonDemo-ServiceProviderTwo。

    第二,在application.yml裏,把端口號修改爲2222,關鍵代碼以下所示。

          1      server:

          2        port: 2222

    第三,在Controller.java的hello方法裏,在輸出和返回信息裏,打上出「Server2「的標記,關鍵代碼以下。    

1    @RequestMapping(value = "/sayHello/{username}", method = RequestMethod.GET    )
2        public String hello(@PathVariable("username") String username) {        
3            System.out.println("This is ServerProvider2");        
4            return "Hello Ribbon, this is Server2, my name is:" + username;
5        }

    在EurekaRibbonDemo-ServiceProviderThree裏,一樣在EurekaRibbonDemo-ServiceProviderOne基礎上作上述3個改動。這裏須要在application.yml裏,把端口號修改爲3333,在Controller類中,一樣須要在輸出和返回信息中打上「Server3」的標記。    

4 在Eureka服務調用者裏引入Ribbon

    EurekaRibbonDemo-ServiceCaller項目是根據第三章的EurekaBasicDemo-ServiceCaller改寫而來,其中的關鍵代碼以下。

    第一,在pom.xml裏,只是適當地修改項目名字,沒有修改其它代碼。

    第二,沒有修改啓動類ServiceCallerApp.java裏的代碼。

    第三,在application.yml了, 添加了描述服務器列表的listOfServers屬性,代碼以下。     

1    spring:
2      application:
3        name: callHello
4    server:
5      port: 8080
6    eureka: 
7      client:
8        serviceUrl:
9          defaultZone: http://localhost:8888/eureka/
10    sayHello:
11      ribbon:
12        listOfServers: 
13    http://localhost:1111/,http://localhost:2222/,http://localhost:3333

    在第3行,咱們指定了服務調用者自己的服務名是callHello,在第5裏,指定了這個微服務的是運行在8080端口上。因爲服務調用者自己也能對外界提供服務,因此外部程序能根據這個服務名和端口號,以url的形式調用其中的hello方法。

    這裏的關鍵是在第12和13行,咱們經過ribbon.listOfServers,指定了該服務調用者能得到服務的三個url地址,請注意,這裏的三個地址和上文裏服務提供者發佈服務的三個地址是一致的。

    第四,在控制器類裏,用RestTemplate對象,以負載均衡的方式調用服務,代碼以下。    

1    //省略必要的package和import的代碼
2    @RestController
3    @Configuration
4    public class Controller {        
5        @Bean
6        @LoadBalanced
7        public RestTemplate getRestTemplate()
8        { return new RestTemplate();  }
9        //提供服務的hello方法     
10        @RequestMapping(value = "/hello", method = RequestMethod.GET    )
11        public String hello() {        
12            RestTemplate template = getRestTemplate();
13            String retVal = template.getForEntity("http://sayHello/sayHello/Eureka", String.class).getBody();
14            return "In Caller, " + retVal;
15        }
16    }

    在這個控制器類的第7行裏,咱們經過getRestTemplate方法返回一個RestTemplate類型對象。

    RestTemplate是Spring提供的能以Rest形式訪問服務的對象,自己不具有負載均衡的能力,因此咱們須要在第6行經過@LoadBalanced註解賦予它這個能力。

    在第11行的hello方法裏,咱們首先在第12行經過getRestTemplate方法獲得了template對象,隨後經過第13行的代碼,用template對象提供的getForEntity方法,訪問以前Eureka服務提供者提供的「http://sayHello/sayHello/Eureka「服務,並獲得String類型的結果,最後在第14行,根據調用結果返回一個字符串。

    因爲在框架裏,咱們模擬了在三臺機器上部署服務的場景,而在上述服務調用者的代碼裏,咱們又在template對象上加入了@LoadBalanced註解,因此在上述第13行代碼裏發起的請求會被均攤到三臺服務器上。

    須要注意的是,這裏咱們沒有重寫IRule和IPing接口,因此這裏是採用了默認的RoundRobbin(也就是輪詢)的訪問策略,同時將默認全部的服務器都處於可用狀態。

    依次啓動本框架中的Eureka服務器,三臺服務提供者和服務器調用者的服務以後,在瀏覽器裏輸入http://localhost:8888/,咱們能看到以下圖所示的效果。

    在其中,咱們能看到有三個提供服務的SAYHELLO應用實例,它們分別運行在1111,2222和3333端口上,同時,服務調用者CALLHELLO則運行在8080端口上。

    

    若是咱們不斷在瀏覽器裏輸入http://localhost:8080/hello,那麼能依次看到以下所示的輸出。    

1    In Caller, Hello Ribbon, this is Server2, my name is:Eureka
2    In Caller, Hello Ribbon, this is Server1, my name is:Eureka
3    In Caller, Hello Ribbon, this is Server3, my name is:Eureka
4    In Caller, Hello Ribbon, this is Server2, my name is:Eureka
5    In Caller, Hello Ribbon, this is Server1, my name is:Eureka
6    In Caller, Hello Ribbon, this is Server3, my name is:Eureka
7    …

    從上述輸出來看,請求是以Server2,Server1和Server3的次序被均攤到三臺服務器上。在每次啓動服務後,可能承接請求的服務器次序會有所變化,可能下次是按Server1,Server2和Server3的次序,但每次都能看到「負載均衡」的效果。

5 重寫IRule和IPing接口

    這裏,咱們將在上述案例的基礎上,重寫IRule和IPing接口裏的方法,從而實現自定義負載均衡和判斷服務器是否可用的規則。

    請注意,因爲咱們是在客戶端,也就是EurekaRibbonDemo-ServiceCaller這個項目調用服務,因此本部分的全部代碼是寫在這個項目裏的。

    步驟一,編寫包含負載均衡規則的MyRule.java,代碼以下。    

1    package com.controller; //請注意這個package路徑
2    省略必要的import語句
3    public class MyRule implements IRule {//實現IRule類
4        private ILoadBalancer lb;
5         //必需要重寫這個choose方法
6        public Server choose(Object key) {
7            //獲得0到3的一個隨機數,但不包括3
8            int number = (int)(Math.random() * 3);
9            System.out.println("Choose the number is:" + number);
10            //獲得全部的服務器對象
11            List<Server> servers = lb.getAllServers();
12            //根據隨機數,返回一個服務器
13            return servers.get(number);        
14        }
15         省略必要的get和set方法
16    }

    在上述代碼的第3行裏,咱們實現了IRule類,並在其中得第6行裏,重寫了choose方法。

    在這個方法裏,咱們在第8行經過Math.random方法,獲得了0到3之間的一個隨機數,包括0,但不包括3,並用這個隨機數在第13行返回了一個Server對象,以此實現隨機選擇的效果。在實際的項目裏,還能夠根據具體的業務邏輯choose方法,以實現其它的「選擇服務器」的策略。

    第二步,編寫判斷服務器是否可用的MyPing.java,代碼以下。   

1    package com.controller; //也請注意這個package的路徑
2    省略import語句
3    public class MyPing implements IPing { //這裏是實現IPing類
4        //重寫了判斷服務器是否可用的isAlive方法 
5        public boolean isAlive(Server server) {
6            //這裏是生成一個隨機數,以此來判斷該服務器是否可用 
7            //還能夠根據服務器的相應時間等依據,判斷服務器是否可用
8            double data = Math.random();
9            if (data > 0.6) {
10                System.out.println("Current Server is available, Name:" + server.getHost() + ", Port is:" + server.getHostPort());
11                return true;
12            } else {
13                System.out.println("Current Server is not available, Name:" + server.getHost() + ", Port is:" + server.getHostPort());
14                return false;
15            }
16        }
17    }

    在第3行裏,咱們是實現了IPing這個接口,並在第5行重寫了其中的isAlive方法。

    在這個方法裏,咱們是根據一個隨機數,來判斷該服務器是否可用,若是可用,則返回true,如反之則返回false。請注意,這僅僅是個演示的案例,在實際項目裏,咱們基本上是不會重寫isAlive方法的。

    第三步,改寫application.yml,在其中添加關於MyPing和MyRule的配置,代碼以下。

    

1    spring:
2      application:
3        name: callHello
4    server:
5      port: 8080
6    eureka: 
7      client:
8        serviceUrl:
9          defaultZone: http://localhost:8888/eureka/
10    sayHello:
11      ribbon:
12        NFLoadBalancerRuleClassName: com.controller.MyRule
13        NFLoadBalancerPingClassName: com.controller.MyPing
14        listOfServers: 
15    http://localhost:1111/,http://localhost:2222/,http://localhost:3333

    改動點是從第10行到第13行,請注意這裏的SayHello須要和服務提供者給出的「服務名」一致,在第12和13行裏,分別定義了本程序(也就是服務調用者)所用到的IRule和IPing類,配置時須要包含包名和文件名。

    第四步,改寫Controller.java和這個控制器類,代碼以下。    

1    省略必要的package和import代碼
2    @RestController
3    @Configuration
4    public class Controller {
5        //以Autowired的方式引入loadBalanceerClient對象    
6        @Autowired  
7        private LoadBalancerClient loadBalancerClient; 
8        //給RestTemplate對象加入@LoadBalanced註解
9        //以此賦予該對象負載均衡的能力 
10        @Bean
11        @LoadBalanced
12        public RestTemplate getRestTemplate()
13        { return new RestTemplate();   }
14            @Bean //引入MyRule
15        public IRule ribbonRule()
16        { return new MyRule();}
17        @Bean //引入MyPing
18        public IPing ribbonpIng() 
19        {  return new MyPing();}
20        //編寫提供服務的hello方法
21        @RequestMapping(value = "/hello", method = RequestMethod.GET    )
22        public String hello() {
23             //引入策略,這裏的sayHello須要和application.yml
24             //第10行的sayHello一致,這樣才能引入MyPing和MyRule
25            loadBalancerClient.choose("sayHello");
26            RestTemplate template = getRestTemplate();
27            String retVal = template.getForEntity("http://sayHello/sayHello/Eureka", String.class).getBody();
28            return "In Caller, " + retVal;
29        }
30    }

    和以前的代碼相比,咱們添加了第15行和第18行的兩個方法,以此引入自定義的MyRule和MyPing兩個方法。

    並且,在hello方法的第15行裏,咱們經過choose方法,爲loadBalancerClient這個負載均衡對象選擇了MyRule和MyPing這兩個規則。

    若是依次啓動Eureka服務器,註冊在Eureka裏的三個服務提供者和服務調用者以後,在瀏覽器裏輸入http://localhost:8080/hello,那麼能在EurekaRibbonDemo-ServiceCaller的控制檯裏看到相似於以下的輸出。    

1    Choose the number is:1
2    Choose the number is:0
3    Current Server is not available, Name:192.168.42.1, Port is:192.168.42.1:2222
4    Current Server is available, Name:192.168.42.1, Port is:192.168.42.1:3333
5    Current Server is not available, Name:192.168.42.1, Port is:192.168.42.1:1111

    第1和第2行是MyRule裏的輸出,而第3到第5行是MyPing裏的輸出,因爲這些輸出是和隨機數有關,因此每次輸出的內容未必一致,但至少能說明,咱們在MyRule和MyPing裏配置的相關策略是生效的,服務調用者(EurekaRibbonDemo-ServiceCaller)的屢次請求在以「負載均衡」的方式分發到各服務提供者時,會引入咱們定義在上述兩個類裏的策略。

 

本文所給出的代碼都可運行,此外,本文內容摘自本人所寫的專業書籍,轉載時請同時引入該版權申明,請勿用於商業用途。

相關文章
相關標籤/搜索