1.1 自定義負載均衡器
1. 模擬呼叫一個服務的多個例項
我們現在有兩個服務, 一個getway服務, 另一個是order服務. 使用的是nacos管理配置檔案和服務註冊中心
假如我現在product服務要呼叫order服務. product服務有1臺, order服務有3臺. 那麼是如何實現負載均衡的呢?
下面我們來模擬一下負載均衡的實現.
package com.lxl.www.gateway.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class UserController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("get/order")
public String getOrder() {
// 獲取要呼叫的服務例項列表
List<ServiceInstance> userServices = discoveryClient.getInstances("order");
if (null == userServices || 0 == userServices.size()) {
return "使用者微服務沒有對應的例項可用";
}
// 獲取列表第一個服務例項---這裡可以設定一個負載均衡演算法,輪詢,隨機等
String targetUri = userServices.get(0).getUri().toString();
// 傳送請求到第一個服務例項
String forObject = restTemplate.getForObject(targetUri + "/config", String.class);
System.out.println(forObject);
return forObject;
}
}
這裡服務註冊發現使用的是誰呢? 使用的是nacos, nacos提供了自己的open api. 也封裝了介面. 通過DiscoveryClient就可以呼叫介面
驗證啟動效果
啟動一個getway服務, 埠號是 8080
啟動3臺order服務. 8081, 8082, 8083
啟動order服務的時候有一個技巧, 之前都是使用的動態埠號, 這次使用另一種方式, 更簡單
開啟配置->選擇要啟動的Application(這裡選擇的是order服務)->勾選右上角的Share共享, 就可以給這個應用啟動多個客戶端了. 注意埠不能一樣哈.
如上圖, 看到啟動按鈕下標有個3麼?表示當前服務啟動了3臺
下面來看看nacos的服務註冊情況
我們看到, 一個由兩個服務, 一個是order, 共有3個例項; 另一個是gateway
order三臺例項的具體詳情如下: 埠號分別是8081,8082,8083
接下來,我們訪問gateway的介面. 在接口裡面模擬呼叫order服務的例項, 請求的是獲取的第一個服務例項
http://localhost:8080/get/order
傳送了五次請求,流量全部打到了第二個服務例項上
總結: 以上是沒有使用任何元件, 我們純手工自己寫了一個實現服務呼叫的方法. 這個還是比較簡單的. 真實環境肯定不用我們自己寫, 因為有現成的元件. 這個元件就Ribbon
2. 讓RestTemplate實現自動實現負載均衡
上面這個方法的簡單模擬瞭如何在一個服務的多個例項中完成呼叫. 那麼最終使用的是RestTemplate. 那麼接下來我們來看一看RestTemplate的原始碼
我們看到在RestTemplate中有各種各樣的方法呼叫, get, post ,put,delete等等. 他們最終呼叫的是this.execute(....)方法, 那麼我們來看看this.execute(...)方法的實現.
我們發現在execute(...)方法裡直接呼叫了doExecute(...)
最終實現跳轉的url是在doExecute方法裡. 而我們可以在這裡對url進行一個包裝. 如何包裝了, 前端傳過來的是服務名, 我根據服務名查詢對應的服務列表. 然後通過負載均衡演算法, 確定要定位的伺服器.
我們可以來重寫一下RestTemplate方法
package com.lxl.www.order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
@Slf4j
@Component
public class LxlRestTemplate extends RestTemplate {
@Autowired
private DiscoveryClient discoveryClient;
@Override
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
// 替換url
try {
url = replaceUrl(url);
} catch (URISyntaxException e) {
e.printStackTrace();
}
return super.doExecute(url, method, requestCallback, responseExtractor);
}
private URI replaceUrl(URI url) throws URISyntaxException {
// 通過URI獲取服務名
String serviceName = url.getHost();
log.info("呼叫的服務名是:{}", serviceName);
// 獲取請求路徑path
String path = url.getPath();
log.info("[呼叫的服務路徑是:{}]", path);
// 呼叫nacos查詢服務例項列表
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
String targetHost = instances.get(0).getUri().toString();
String source = targetHost + path;
return new URI(source);
}
}
第一: LxlRestTemplate繼承自RestTemplate
第二: 重寫了doExecute方法. 在裡面重新包裝了url, 根據服務名找到對應的服務例項列表, 然後選擇一臺伺服器, 重新構建一個新的URI,
第三: 呼叫父類方法doExecute();
接下來使用我們自定義的RestTemplate
這樣就實現了根據服務名+負載均衡策略 定向到指定服務了
後面要學習的ribbon最終也是通過RestTemplate呼叫的遠端服務. 其原理和這個是類似, 但功能實現要比這個複雜得多.