文章目錄
目前Spring Cloud中服務之間通過Rest方式調用有兩種方式:
- feign
- RestTemplate+Ribbon
feign的方式更優雅,使用更簡單(內部也使用的ribbon做的負載均衡),但是我們這使用的第二種方案,因爲Ribbon可以負載均衡並且可以自定義負載均衡算法
1. 是什麼
是Netflix發佈的開源項目,主要功能是提供客戶端的軟件負載均衡算法,將Netflix的中間層服務連接在一起。在配置文件中列出Load Balancer(簡稱LB)後面的所有的機器,Ribbon後自動幫助你基於某種規則(如簡單輪詢,隨機連接等)去連接這些機器,也可實現自定義的負載均衡算法。
Dubbo和SpringCloud中均提供了負載均衡,但SpringCloud的負載均衡算法可以自定義
2. LB 相關知識
LB負載均衡 (Load Balance),在微服務或分佈式集羣中經常用到的一種應用,將用戶的請求平攤到多個服務上,從而達到系統的高可用HA(High Available),常用的負載均衡有軟件NGINX,LVS,硬件F5等。
LB分爲:
- 集中式LB
即在服務的消費方和提供方之間使用獨立的LB設施(硬件如F5,軟件如nginx),由該設施負責把訪問請求通過某種策略轉發至服務提供方 - 進程內LB
即將LB邏輯繼承到服務消費方,消費方從服務註冊中心獲取哪些地址可用,然後自己在從這些地址中選擇出一個合適的服務器。Ribbon就屬於進程內LB,它只是一個類庫,集成於消費方進程,消費方通過它來獲取服務提供方的地址
3. Ribbon中LB的思路
- 先選擇EurekaServer,優先選擇在同一區域負載較少的EurekaServer
- 再根據用戶指定的策略,從EurekaServer取到的服務註冊列表中選擇一個地址
4. 核心組件IRule
IRule:根據特定算法中從服務列表中選取一個要訪問的服務,提供了7種策略:
- RoundRobinRule: 輪詢
- RandomRule: 隨機
- AvailabilityFilteringRule: 會先過濾掉由於多次訪問故障而處於斷路器跳閘狀態的服務,還有併發的連接數量超過閾值的服務,然後對剩餘的服務列表按照輪詢策略進行訪問
- WeightedResponseTimeRule: 根據平均響應時間計算所有服務的權重,響應時間越快服務權重越大被選中的概率越高。剛啓動時如果統計信息不足,則使用RoundRobinRule策略,等統計信息足夠時,會切換到WeightedResponseTimeRule、
- RetryRule: 先按照RoundRobinRule的策略獲取服務,如果獲取服務失敗則在指定時間內進行重試,獲取可用服務
- BestAvailableRule: 會先過濾掉由於多次訪問故障而處於短路器跳閘狀態的服務,然後選擇一個併發量最小的服務
- ZoneAvoidanceRule: 複合判斷server所在區域的性能和server的可用性選擇服務器
也可以自定義策略,步驟爲:
-
自定義返回類型爲IRule的Bean的配置類
@Configuration public class MySelfRule { @Bean public IRule myRule() { //return new RandomRule();// Ribbon默認是輪詢,我自定義爲隨機 //return new RoundRobinRule();// Ribbon默認是輪詢,我自定義爲隨機 return new RandomRule_ZY();// 我自定義爲每臺機器5次 } } //----------------------自定義路由策略類--------------- public class RandomRule_ZY extends AbstractLoadBalancerRule { // total = 0 // 當total==5以後,我們指針才能往下走, // index = 0 // 當前對外提供服務的服務器地址, // total需要重新置爲零,但是已經達到過一個5次,我們的index = 1 // 分析:我們5次,但是微服務只有8001 8002 8003 三臺,OK? // private int total = 0; // 總共被調用的次數,目前要求每臺被調用5次 private int currentIndex = 0; // 當前提供服務的機器號 public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } List<Server> upList = lb.getReachableServers(); List<Server> allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0) { /* * No servers. End regardless of pass, because subsequent passes only get more * restrictive. */ return null; } // int index = rand.nextInt(serverCount);// java.util.Random().nextInt(3); // server = upList.get(index); // private int total = 0; // 總共被調用的次數,目前要求每臺被調用5次 // private int currentIndex = 0; // 當前提供服務的機器號 if(total < 5) { server = upList.get(currentIndex); total++; }else { total = 0; currentIndex++; if(currentIndex >= upList.size()) { currentIndex = 0; } } if (server == null) { /* * The only time this should happen is if the server list were somehow trimmed. * This is a transient condition. Retry after yielding. */ Thread.yield(); continue; } if (server.isAlive()) { return (server); } // Shouldn't actually happen.. but must be transient or a bug. server = null; Thread.yield(); } return server; } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { // TODO Auto-generated method stub }
}
-
在啓動類上配置自定義的路由策略@RibbonClient,使其啓動時就加載配置
@SpringBootApplication @EnableEurekaClient //在啓動該微服務的時候就能去加載我們的自定義Ribbon配置類,從而使配置生效 //@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class) @RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class) public class DeptConsumer80_App { public static void main(String[] args) { SpringApplication.run(DeptConsumer80_App.class, args); } }
注:自定義配置類不能放在@ComponentScan所掃描的當前包下及子包下,否則我們自定義的配置類就會被所有的Ribbon客戶端鎖共享,也就是說達不到特殊化定製的目的了
5. 項目實戰
5.1 引依賴
<!-- Ribbon相關 -->
<!-- Eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- Ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!-- Eureka相關 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
5.2 配置文件
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
5.3 在消費者(客戶端)的啓動項配置
@SpringBootApplication
@EnableEurekaClient
// name:要調用服務的名稱,該服務使用的LB策略,默認是輪詢
@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=RetryRule.class)
public class DeptConsumer80_App
{
public static void main(String[] args)
{
SpringApplication.run(DeptConsumer80_App.class, args);
}
@Bean
@LoadBalanced//Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端 , 負載均衡的工具。
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
RestTemplate 也可以寫在統一配置中心上,這樣不用再在每個客戶端都寫了
@Bean
@LoadBalanced//Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端 , 負載均衡的工具。
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
5.4 service層調用服務
// 注意,這些的是註冊到Eureka上服務的名稱地址,不是通過ip調用的。
// 直接調用服務而不用在關心地址和端口號
//private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";
/**
* 使用 使用restTemplate訪問restful接口非常的簡單粗暴無腦。
* (url, requestMap, ResponseBean.class)這三個參數分別代表 REST請求地址、請求參數、HTTP響應轉換被轉換成的對象類型。
*/
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/consumer/dept/add")
public boolean add(Dept dept)
{
。。。邏輯代碼
// 請求地址:服務地址+方法路徑,參數,返回類型,這樣就實現了調用別的服務
// 完成真正的通過微服務名字從Eureka薩汗找到服務並訪問
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
。。。邏輯代碼
}