1. 程式人生 > 實用技巧 >1.1 自定義負載均衡器

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呼叫的遠端服務. 其原理和這個是類似, 但功能實現要比這個複雜得多.