爲什麼需要負載均衡?
爲了提供併發量,有時同一個服務提供者可以部署多個(商品服務)。這個客戶端在調用時要根據一定的負責均衡策略完成負載調用
步驟
首先先複製一份之前的 服務提供者的代碼 端口號改爲8002
application.yml
server:
port: 8002
spring:
application:
name: PRODUCT-SERVICE
eureka:
client:
service-url:
#defaultZone: http://localhost:7001/eureka #告訴服務提供者要把服務註冊到哪兒
#集羣配置,每一個註冊中心都要配置
defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka
instance:
prefer-ip-address: true # 當調用getHostname獲取實例的hostname時,返回ip而不是host名稱
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的話會自己尋找
啓動
服務啓動後就會訪問localhost:7001
就會得到
常見負載均衡實現技術
- Ribbon
- feign
Ribbon負載均衡調用
Ribbon是Netflix發佈的雲中間層服務開源項目,主要功能是提供客戶端負載均衡算法。Ribbon客戶端組件提供一系列完善的配置項,如,連接超時,重試等。簡單的說,Ribbon是一個客戶端負載均衡器,我們可以在配置文件中列出load Balancer後面所有的機器,Ribbon會自動的幫助你基於某種規則(如簡單輪詢,隨機連接等)去連接這些機器,我們也很容易使用Ribbon實現自定義的負載均衡算法。
Ribbon是一個客戶端負載均衡器,它可以按照一定規則來完成多服務器負載均衡調用,這些規則還支持自定義
集成原理
負載均衡測試
創建子模塊 order-service-ribbon-9002 服務消費端
導入相關依賴
<dependencies>
<!--公共代碼依賴-->
<dependency>
<groupId>org.leryoo</groupId>
<artifactId>User_interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--springboot支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--eureka客戶端,服務消費者也要從註冊中心獲取可用服務列表-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--客戶端負載均衡實現 ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>
配置文件 application.yml
server:
port: 9002
spring:
application:
name: ORDER-SERVICE-RIBBON
eureka:
client:
service-url:
# defaultZone: http://localhost:7001/eureka #告訴服務提供者要把服務註冊到哪兒
#集羣配置,每一個註冊中心都要配置
defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka
instance:
prefer-ip-address: true # 當調用getHostname獲取實例的hostname時,返回ip而不是host名稱
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的話會自己尋找
配置類
@LoadBalanced
開啓負載均衡調用 我們都是通過這個工具來完成服務提供者的調用
@Configuration
public class CfgBean {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
啓動類:
@SpringBootApplication
@EnableEurekaClient //Eureka客戶端
public class OrderService9002Application {
public static void main(String[] args) {
SpringApplication.run(OrderService9002Application.class,args);
}
}
通過不斷刷新就會出現輪詢效果
負載均衡策略
ribbon通過服務名獲取到服務列表後,要根據一定規則來選擇一個服務實例來完成調用.這個規則就叫負載均衡策略
IRule
通過配置不同IRule的子類,可以選擇不同負載均衡策略,也就是從服務列表以特定策略選擇一個服務來完成調用。當然也可以自定義。所以負載均衡策略可以分爲內置和自定義
內置負載均衡策略
內置負載均衡規則類 | 規則描述 |
---|---|
RoundRobinRule(默認) | 簡單輪詢服務列表來選擇服務器。它是Ribbon默認的負載均衡規則 |
AvailabilityFilteringRule | 對以下兩種服務器進行忽略: (1)在默認情況下,這臺服務器如果3次連接失敗,這臺服務器就會被設置爲“短路”狀態。短路狀態將持續30秒,如果再次連接失敗,短路的持續時間就會幾何級地增加。注意:可以通過修改配置loadbalancer..connectionFailureCountThreshold來修改連接失敗多少次之後被設置爲短路狀態。默認是3次。 (2)併發數過高的服務器。如果一個服務器的併發連接數過高,配置了AvailabilityFilteringRule規則的客戶端也會將其忽略。併發連接數的上線,可以由客戶端的..ActiveConnectionsLimit屬性進行配置。 |
WeightedResponseTimeRule | 爲每一個服務器賦予一個權重值。服務器響應時間越長,這個服務器的權重就越小 這個規則會隨機選擇服務器,這個權重值會影響服務器的選擇 |
ZoneAvoidanceRule | 以區域可用的服務器爲基礎進行服務器的選擇。使用Zone對服務器進行分類,這個Zone可以理解爲一個機房、一個機架等 |
BestAvailableRule | 忽略哪些短路的服務器,並選擇併發數較低的服務器 |
RandomRule | 隨機選擇一個可用的服務器 |
Retry | 重試機制的選擇邏輯 |
我們如果要自定義這些策略 需要在配置類中加上
@Bean
public IRule myRule(){
return new RandomRule(); //隨機
}
服務消費者獲取服務列表後,要通過特定負載均衡策略,選擇其中一種來完成調用。在ribbon負載均衡策略分爲以下兩種
1 內置 輪詢(默認) 可用性過濾 權重 隨機
2 自定義 IRule
修改負載均衡策略-只需要把負載均衡策略納入spring管理
Feign 負載策略
前面的可以發現當我們通過RestTemplate調用其它服務的API時,所需要的參數須在請求的URL中進行拼接,如果參數少的話或許我們還可以忍受,一旦有多個參數的話,這時拼接請求字符串就會效率低下
Feign是一個聲明式的Web Service客戶端,它的目的就是讓Web Service調用更加簡單。Feign提供了HTTP請求的模板,通過編寫簡單的接口和插入註解,就可以定義好HTTP請求的參數、格式、地址等信息。而Feign則會完全代理HTTP請求,我們只需要像調用方法一樣調用它就可以完成服務請求及相關處理。Feign整合了Ribbon和Hystrix(關於Hystrix我們後面再講),可以讓我們不再需要顯式地使用這兩個組件
Feign具有的特性
可插拔的註解支持,包括Feign註解和JAX-RS註解;
支持可插拔的HTTP編碼器和解碼器;
支持Hystrix和它的Fallback;
支持Ribbon的負載均衡;
支持HTTP請求和響應的壓縮。
這看起來有點像我們springmvc模式的Controller層的RequestMapping映射。這種模式是我們非常喜歡的。Feign是用@FeignClient來映射服務的
Feign是以接口方式進行調用,而不是通過RestTemplate來調用,feign底層還是ribbon,它進行了封裝,讓我們調用起來更加happy
步驟
創建子模塊 order-service-feign-9003 服務消費端
導入相關的依賴
<dependencies>
<!--公共代碼依賴-->
<dependency>
<groupId>org.leryoo</groupId>
<artifactId>product-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--springboot支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--eureka客戶端,服務消費者也要從註冊中心獲取可用服務列表-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--客戶端負載均衡實現 ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--feign的支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
配置文件 application.yml
server:
port: 9003
spring:
application:
name: ORDER-SERVICE-FEIGN
eureka:
client:
service-url:
# defaultZone: http://localhost:7001/eureka #告訴服務提供者要把服務註冊到哪兒
#集羣配置,每一個註冊中心都要配置
defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka
instance:
prefer-ip-address: true # 當調用getHostname獲取實例的hostname時,返回ip而不是host名稱
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的話會自己尋找
啓動類
@EnableFeignClients
如果你的client就在該類的子子孫孫包,否則可以指定包名@EnableFeignClients(basePackages = "xxx.xxx.xxx")
加了它以後,就會掃描加了@FeignClient
這個註解的接口,並且爲這些接口產生代理對象
並且把這些代理對象納入spring管理,我們要使用時直接獲取就可以完成遠程調用
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderService9003Application {
public static void main(String[] args) {
SpringApplication.run(OrderService9003Application.class,args);
}
}
@FeignClient(value = “PRODUCT-SERVICE”)
裏面所有的方法都是調用PRODUCT-SERVICE這個服務裏面的方法所以
所以 這個位置的參數要和服務提供者裏面的參數保持一致
@FeignClient(value = "PRODUCT-SERVICE")
@RequestMapping("/order")
public interface ProductClient {
@GetMapping("/{id}") //回調託底
public Product getProductById(@PathVariable(name = "id")Long id);
}
controller層
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private ProductClient productClient;
@GetMapping("/product/{id}")
public Product getProductById(@PathVariable("id")Long id) {
return productClient.getProductById(id);
}
}
需要啓動服務
輸出:
成功