本例將會運行兩個服務器,兩個服務提供者實例和一個服務調用者實例,通過服務調用者請求服務,實現集群部署並通過HttpClient的REST客戶端訪問服務調用者發布的服務看到負載均衡的效果。
1.本例集群結構圖
由於本例的開發環境只有一台電腦,操作系統為Windows,如果要構建集群,需要修改hosts文件,為其添加主機名的映射,修改C:\Windows\System32\drivers\etc\hosts文件,添加以下內容:
127.0.0.1 slave1 slave2
2.構建服務器端
新建一個maven項目first-cloud-server,項目的目錄結構如下圖
引入相關依賴
pom.xml代碼清單

1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 7 <groupId>com.triheart</groupId> 8 <artifactId>first-cloud-server</artifactId> 9 <version>1.0-SNAPSHOT</version> 10 11 <dependencyManagement> 12 <dependencies> 13 <dependency> 14 <groupId>org.springframework.cloud</groupId> 15 <artifactId>spring-cloud-dependencies</artifactId> 16 <version>Dalston.SR1</version> 17 <type>pom</type> 18 <scope>import</scope> 19 </dependency> 20 </dependencies> 21 </dependencyManagement> 22 23 <dependencies> 24 <dependency> 25 <groupId>org.springframework.cloud</groupId> 26 <artifactId>spring-cloud-starter-eureka-server</artifactId> 27 </dependency> 28 </dependencies> 29 30 </project>
編寫配置文件,由於對同一個應用程序需要啟動兩次(IDEA啟動多次應用程序),因此需要在配置文件中使用spring的profiles配置,配置文件的代碼清單如下

1 server: 2 port: 8761 3 spring: 4 application: 5 name: first-cloud-server 6 profiles: slave1 7 eureka: 8 instance: 9 hostname: slave1 10 client: 11 serviceUrl: 12 defaultZone: http://slave2:8762/eureka/ 13 --- 14 server: 15 port: 8762 16 spring: 17 application: 18 name: first-cloud-server 19 profiles: slave2 20 eureka: 21 instance: 22 hostname: slave2 23 client: 24 serviceUrl: 25 defaultZone: http://slave1:8761/eureka/
在配置文件中配置了兩個profiles,名稱分別為slave1和slave2。在slave1中,配置應用端口為8761,主機名為slave1,當實用slave1這個profiles來啟動服務器時,將會向http://slave2:8762/eureka/注冊自己;同理,使用slave2的profiles來啟動服務器時,會像slave1這台服務器注冊自己。簡單地說,就是兩個服務器啟動之后,他們會互相注冊。
編寫啟動類FirstServer.java,讓其在啟動時讀取控制台的輸入來決定使用哪個profiles來啟動相應的Eureka服務。
FirstServer.java代碼清單

1 package firstcloudprovider; 2 3 import org.springframework.boot.autoconfigure.SpringBootApplication; 4 import org.springframework.boot.builder.SpringApplicationBuilder; 5 import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 7 import java.util.Scanner; 8 9 /** 10 * @author 阿遠 11 * Date: 2018/8/24 12 * Time: 15:17 13 */ 14 @SpringBootApplication 15 @EnableEurekaServer 16 public class FirstServer { 17 18 public static void main(String[] args) { 19 Scanner scan = new Scanner(System.in); 20 String profiles = scan.nextLine(); 21 new SpringApplicationBuilder(FirstServer.class).profiles(profiles).run(args); 22 } 23 }
在啟動類中,先從控制台輸入profiles的值,再根據輸入的值啟動相應的Eureka服務器,需要注意的是,當我們啟動第一個服務器時會拋異常,這是因為我們第一個服務器會去找第二個服務器來注冊自己,而我們的第二個服務器還沒有啟動,我們把第二個服務器啟動之后,分別訪問http://slave1:8761和http://slave2:8762,可以在Eureka服務器的控制台上看到,兩個服務器已經相互注冊了 。
3.構建服務提供者
服務提供者也需要啟動兩個實例,其構建與服務器的構建類似。新建一個maven項目first-cloud-provider,項目的目錄結構如下
在pom.xml文件中引入相關依賴。
pom.xml代碼清單

1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 7 <groupId>com.triheart</groupId> 8 <artifactId>first-cloud-provider</artifactId> 9 <version>1.0-SNAPSHOT</version> 10 11 <dependencyManagement> 12 <dependencies> 13 <dependency> 14 <groupId>org.springframework.cloud</groupId> 15 <artifactId>spring-cloud-dependencies</artifactId> 16 <version>Dalston.SR1</version> 17 <type>pom</type> 18 <scope>import</scope> 19 </dependency> 20 </dependencies> 21 </dependencyManagement> 22 23 <dependencies> 24 <dependency> 25 <groupId>org.springframework.cloud</groupId> 26 <artifactId>spring-cloud-starter-config</artifactId> 27 </dependency> 28 <dependency> 29 <groupId>org.springframework.cloud</groupId> 30 <artifactId>spring-cloud-starter-eureka</artifactId> 31 </dependency> 32 </dependencies> 33 </project>
編寫配置文件,將Eureka客戶端的兩個服務提供者注冊到Eureka服務器。
配置文件application.yml代碼清單

1 spring: 2 application: 3 name: first-cloud-provider 4 eureka: 5 instance: 6 hostname: localhost 7 client: 8 serviceUrl: 9 defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
編寫啟動類,由於默認端口只有8080,而我們有兩個服務提供者,為了避免端口沖突,我們讓啟動類啟動時從控制台讀取輸入的端口來決定使用哪個端口提供服務。
啟動類FirstServiceProvider.java代碼清單

1 package com.triheart.firstcloudprivider; 2 3 import org.springframework.boot.autoconfigure.SpringBootApplication; 4 import org.springframework.boot.builder.SpringApplicationBuilder; 5 import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 6 7 import java.util.Scanner; 8 9 /** 10 * @author 阿遠 11 * Date: 2018/8/24 12 * Time: 16:42 13 */ 14 @SpringBootApplication 15 @EnableEurekaClient 16 public class FirstServiceProvider { 17 18 public static void main(String[] args) { 19 // 讀取控制台輸入的端口,避免端口沖突 20 Scanner scan = new Scanner(System.in); 21 String port = scan.nextLine(); 22 new SpringApplicationBuilder(FirstServiceProvider.class).properties("server.port=" + port).run(args); 23 } 24 }
為了看到服務提供者提供的服務,我們還需要編寫控制器來供后面的服務調用者調用。
實體類Person.java代碼清單

1 package com.triheart.firstcloudprivider.entity; 2 3 public class Person { 4 5 private Integer id; 6 7 private String name; 8 9 private Integer age; 10 11 private String message; 12 13 public Person() { 14 super(); 15 } 16 17 public Person(Integer id, String name, Integer age) { 18 super(); 19 this.id = id; 20 this.name = name; 21 this.age = age; 22 } 23 24 public Integer getId() { 25 return id; 26 } 27 28 public void setId(Integer id) { 29 this.id = id; 30 } 31 32 public String getName() { 33 return name; 34 } 35 36 public void setName(String name) { 37 this.name = name; 38 } 39 40 public Integer getAge() { 41 return age; 42 } 43 44 public void setAge(Integer age) { 45 this.age = age; 46 } 47 48 public String getMessage() { 49 return message; 50 } 51 52 public void setMessage(String message) { 53 this.message = message; 54 } 55 56 }
控制器FirstController.java代碼清單

1 package com.triheart.firstcloudprivider.controller; 2 3 import javax.servlet.http.HttpServletRequest; 4 5 import com.triheart.firstcloudprivider.entity.Person; 6 import org.springframework.http.MediaType; 7 import org.springframework.web.bind.annotation.PathVariable; 8 import org.springframework.web.bind.annotation.RequestMapping; 9 import org.springframework.web.bind.annotation.RequestMethod; 10 import org.springframework.web.bind.annotation.RestController; 11 12 @RestController 13 public class FirstController { 14 15 @RequestMapping(value = "/person/{personId}", method = RequestMethod.GET, 16 produces = MediaType.APPLICATION_JSON_VALUE) 17 public Person findPerson(@PathVariable("personId") Integer personId, HttpServletRequest request) { 18 Person person = new Person(personId, "Crazyit", 30); 19 // 為了查看結果,將請求的URL設置到Person實例中 20 person.setMessage(request.getRequestURL().toString()); 21 return person; 22 } 23 }
控制器的findPerson方法將請求的服務URL保存到Person對象的message屬性中,因此在調用服務后我們可以根據message屬性來查看我們請求的URL。
我們將啟動類啟動兩次,分別在控制台輸入端口8080和8081,然后分別訪問http://slave1:8761和http://slave1:8762,可以看到first-cloud-provider這個服務已經被注冊到slave1和slave2這兩個Eureka服務器里面了。
4.構建服務調用者
服務調用者只需啟動一個實例。新建一個maven項目first-cloud-invoker,項目的目錄結構如下
引入相關依賴
pom.xml代碼清單

1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 7 <groupId>com.triheart</groupId> 8 <artifactId>first-cloud-invoker</artifactId> 9 <version>1.0-SNAPSHOT</version> 10 11 <dependencyManagement> 12 <dependencies> 13 <dependency> 14 <groupId>org.springframework.cloud</groupId> 15 <artifactId>spring-cloud-dependencies</artifactId> 16 <version>Dalston.SR1</version> 17 <type>pom</type> 18 <scope>import</scope> 19 </dependency> 20 </dependencies> 21 </dependencyManagement> 22 23 <dependencies> 24 <dependency> 25 <groupId>org.springframework.cloud</groupId> 26 <artifactId>spring-cloud-starter-config</artifactId> 27 </dependency> 28 <dependency> 29 <groupId>org.springframework.cloud</groupId> 30 <artifactId>spring-cloud-starter-eureka</artifactId> 31 </dependency> 32 <dependency> 33 <groupId>org.springframework.cloud</groupId> 34 <artifactId>spring-cloud-starter-ribbon</artifactId> 35 </dependency> 36 </dependencies> 37 38 </project>
編寫配置文件,將Eureka客戶端的服務調用者注冊兩個Eureka服務器
application.yml代碼清單

1 server: 2 port: 9000 3 spring: 4 application: 5 name: first-cloud-invoker 6 eureka: 7 instance: 8 hostname: localhost 9 client: 10 service-url: 11 defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
編寫啟動類以及控制類,控制器通過RestTemplate類的對象來訪問服務提供者提供的服務
啟動類FirstInvoker.java代碼清單

1 package com.triheart.firstcloudinvoker; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 7 /** 8 * @author 阿遠 9 * Date: 2018/8/24 10 * Time: 20:04 11 */ 12 @SpringBootApplication 13 @EnableDiscoveryClient 14 public class FirstInvoker { 15 16 public static void main(String[] args) { 17 SpringApplication.run(FirstInvoker.class, args); 18 } 19 }
控制器InvokerController.java代碼清單

1 package com.triheart.firstcloudinvoker.controller; 2 3 import org.springframework.cloud.client.loadbalancer.LoadBalanced; 4 import org.springframework.context.annotation.Bean; 5 import org.springframework.context.annotation.Configuration; 6 import org.springframework.http.MediaType; 7 import org.springframework.web.bind.annotation.RequestMapping; 8 import org.springframework.web.bind.annotation.RequestMethod; 9 import org.springframework.web.bind.annotation.RestController; 10 import org.springframework.web.client.RestTemplate; 11 12 /** 13 * @author 阿遠 14 * Date: 2018/8/24 15 * Time: 20:07 16 */ 17 @RestController 18 @Configuration 19 public class InvokerController { 20 21 @Bean 22 @LoadBalanced 23 public RestTemplate getRestTemplate() { 24 return new RestTemplate(); 25 } 26 27 @RequestMapping(value = "/router", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) 28 public String router() { 29 RestTemplate restTpl = getRestTemplate(); 30 // 根據應用名稱調用服務 31 String json = restTpl.getForObject( 32 "http://first-cloud-provider/person/1", String.class); 33 return json; 34 } 35 }
在瀏覽器中分別打開兩個窗口訪問http://localhost:9000/router,可以看到下圖的效果
前后兩次分別訪問的是端口是8081和8080的服務提供者,此時已有負載均衡的效果,為了讓負載均衡的效果看得更明顯,我們編寫一個REST客戶端進行測試。
5.編寫REST客戶端進行測試
新建一個maven項目,項目的目錄結構如下
引入httpclient的依賴
pom.xml代碼清單

1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 7 <artifactId>first-cloud-rest-client</artifactId> 8 <groupId>com.triheart</groupId> 9 <version>1.0-SNAPSHOT</version> 10 11 <dependencies> 12 <dependency> 13 <groupId>org.apache.httpcomponents</groupId> 14 <artifactId>httpclient</artifactId> 15 <version>4.5.2</version> 16 </dependency> 17 </dependencies> 18 </project>
編寫啟動類TestHttpClient.java,在main方法編寫中調用REST服務的代碼
TestHttpClient.java代碼清單

1 package com.triheart.firstcloudrestclient; 2 3 import org.apache.http.HttpResponse; 4 import org.apache.http.client.methods.HttpGet; 5 import org.apache.http.impl.client.CloseableHttpClient; 6 import org.apache.http.impl.client.HttpClients; 7 import org.apache.http.util.EntityUtils; 8 9 /** 10 * @author 阿遠 11 * Date: 2018/8/24 12 * Time: 20:48 13 */ 14 public class TestHttpClient { 15 16 public static void main(String[] args) throws Exception { 17 // 創建默認的HttpClient 18 CloseableHttpClient httpclient = HttpClients.createDefault(); 19 // 調用6次服務並輸出結果 20 for(int i = 0; i < 6; i++) { 21 // 調用 GET 方法請求服務 22 HttpGet httpget = new HttpGet("http://localhost:9000/router"); 23 // 獲取響應 24 HttpResponse response = httpclient.execute(httpget); 25 // 根據 響應解析出字符串 26 System.out.println(EntityUtils.toString(response.getEntity())); 27 } 28 } 29 }
在main方法中,通過for循環調用了6次9000端口的route服務並輸出結果。我們按以下順序啟動各個組件:
a.啟動兩個服務器端,在控制台分別輸入slave1和slave2。
b.啟動兩個服務提供者,在控制台分別輸入8080和8081。
c.啟動服務調用者。
啟動了整個集群后,運行TestHttpClient,就可以看到控制台上輸出如下
此時,就有了明顯的負載均衡的效果。