如何使用原生的Ribbon

子月生發表於2021-10-30

什麼是Ribbon

之前分析了如何使用原生的Feign,今天我們來研究 Netflix 團隊開發的另外一個類庫--Ribbon。
Ribbon 和 Feign 有很多相似的地方,首先,它們本質上都是 HTTP client,其次,它們都具備重試、整合斷路器等功能。最大的區別在於,Ribbon 內建了一個負載均衡器,而 Feign 沒有。

本文將介紹如何使用原生的 Ribbon,注意是原生的,而不是被 Spring 層層封裝的 Ribbon。

為什麼要使用Ribbon

這裡我們需要回答兩個問題:

  1. 為什麼要使用 HTTP client?
  2. 為什麼要在 HTTP client 裡內建負載均衡器?

其中,第一個問題在如何使用原生的Feign中已經講過,這裡就不囉嗦了,我們直接看第二個問題。

我們知道,Apache HTTP client、Feign 並沒有內建負載均衡器,也就是說,HTTP client 並不一定要內建負載均衡器,那為什麼 Ribbon 要搞特殊呢?

其實,我們可以想想,Ribbon 更多地被用在內部呼叫,而這種場景有一個比較大的特點--目標服務為叢集部署。通常情況下,在呼叫目標服務時,我們希望請求儘可能平均地分發到每個例項。通過內建的負載均衡器,Ribbon 可以很好地滿足要求,而 Apache HTTP client、Feign 就無法做到。

所以,在 HTTP client 裡內建負載均衡器是為了能夠在目標服務為叢集部署時提供負載均衡支援。

zzs_ribbon_003

有的人可能會說,你單獨部署一臺負載均衡器就行了嘛,搞那麼複雜幹嘛。當然,你可以這麼做。但是你要考慮很重要的一點,mid-tier services 的請求量要遠大於 edge services,所以你需要一臺效能極高的負載均衡器。從這個角度來說,Ribbon 的方案幫你省下了獨立部署負載均衡器的開銷。

zzs_ribbon_002

如何使用Ribbon

專案中我用 RxNettty 寫了一個簡單的 HTTP 介面(見cn.zzs.ribbon.RxUserServer)供後面的例子呼叫,這個介面執行在本機的 8080、8081、8082 介面,用來模擬三臺不同的例項。所以,如果你想要測試專案中的例子,要先把這三臺例項先啟動好。

http://127.0.0.1:8080/user/getUserById?userId={userId}
request:userId=1
response:User [id=1, name=zzs001, age=18]

這裡提醒一下,Ribbon 的 API 用到了很多 RxJava 程式碼,如果之前沒接觸過,最好先了解下。

專案環境

os:win 10

jdk:1.8.0_231

maven:3.6.3

IDE:Spring Tool Suite 4.6.1.RELEASE

Ribbon:2.7.17

作為HTTP client的用法

和 Feign 一樣,Ribbon 支援使用註解方式定義 HTTP 介面,除此之外,Ribbon 還支援使用HttpRequestTemplateHttpClientRequest等方式定義,這部分的例子我也提供了,感興趣可以移步專案原始碼。

服務例項的列表通過ConfigurationManager設定。當你看到ConfigurationManager時,會不會覺得很熟悉呢?我們之前在Eureka詳解系列(三)--探索Eureka強大的配置體系中詳細介紹過,沒錯,Ribbon 用的還是這套配置體系。需要強調下,Netflix 團隊開發的這套配置體系提供了動態配置支援(當然,你要會用才行),正是基於這一點,整合了 eureka 的應用才能夠實現服務例項的動態調整

// 使用註解定義HTTP API
@ClientProperties(properties = {
        @Property(name="ReadTimeout", value="2000"),
        @Property(name="ConnectTimeout", value="1000"),
        @Property(name="MaxAutoRetries", value="1"),
        @Property(name="MaxAutoRetriesNextServer", value="2")
}, exportToArchaius = true)
interface UserService {
    @TemplateName("getUserById")
    @Http(
            method = HttpMethod.GET,
            uri = "/user/getUserById?userId={userId}",
            headers = {
                    @Header(name = "X-Platform-Version", value = "xyz"),
                    @Header(name = "X-Auth-Token", value = "abc")
            })
    RibbonRequest<ByteBuf> getUserById(@Var("userId") String userId);
}

public class RxUserProxyTest {
    @Test
    public void testBase() throws InterruptedException {
        // 指定服務例項的地址
        // key:服務+“.ribbon.”+配置項名稱(見com.netflix.client.config.CommonClientConfigKey)
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.listOfServers", "127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");
        
        UserService userService = Ribbon.from(UserService.class);
        
        userService.getUserById("1")
            .toObservable()
            .subscribe(new Subscriber<Object>() {
                @Override
                public void onCompleted() {
                    LOG.info("onCompleted");
                }
                
                @Override
                public void onError(Throwable e) {
                    e.printStackTrace();
                }
                
                @Override
                public void onNext(Object t) {
                    LOG.info("onNext:{}", t);
                    if(t != null && t instanceof ByteBuf) {
                        LOG.info(ByteBuf.class.cast(t).toString(Charset.defaultCharset()));
                    }
                }
            });
        // 因為請求HTTP介面是非同步的,這裡要讓測試主執行緒先睡一會
        Thread.sleep(10000);
    }
}

預設的負載均衡規則

為了觀察多次請求在三臺例項的分配情況,現在我們更改下程式碼,試著發起 6 次請求。

    @Test
    public void test01() throws InterruptedException {
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.listOfServers", "127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");
            
        UserService userService = Ribbon.from(UserService.class);
        // 發起多次請求
        Observable<ByteBuf>[] requestList = new Observable[]{
                userService.getUserById("1").toObservable(),
                userService.getUserById("2").toObservable(),
                userService.getUserById("3").toObservable(),
                userService.getUserById("4").toObservable(),
                userService.getUserById("5").toObservable(),
                userService.getUserById("6").toObservable()
        };
        Observable.concat(Observable.from(requestList))
                .subscribe(subscriber);
        Thread.sleep(10000);
    }

執行測試,可以看到,6 次請求被平均地分配到了 3 臺例項。

zzs_ribbon_004

在日誌中,可以看到了預設的負載均衡規則。

zzs_ribbon_005

通過原始碼可以看到,這個預設的規則本質上採用的是輪詢策略RoundRobinRule。除此之外,Ribbon 還定義了RandomRuleRetryRule等規則供我們選擇。

public class AvailabilityFilteringRule {
    RoundRobinRule roundRobinRule = new RoundRobinRule();
}

自定義負載均衡規則

自定義負載均衡規則需要繼承com.netflix.loadbalancer.AbstractLoadBalancerRule,並實現 choose 方法。這裡我定義的規則是:不管有多少例項,預設訪問第一臺。

public class MyLoadBalancerRule extends AbstractLoadBalancerRule {
    @Override
    public Server choose(Object key) {
        
        ILoadBalancer lb = getLoadBalancer();
        
        List<Server> allServers = lb.getAllServers();
        
        return allServers.stream().findFirst().orElse(null);
    }
}

接著,只需要通過ConfigurationManager配置自定義規則就行。

    @Test
    public void test01() throws InterruptedException {
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.listOfServers", "127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082");
        // 配置自定義規則
        ConfigurationManager.getConfigInstance().setProperty(
                "UserService.ribbon.NFLoadBalancerRuleClassName", "cn.zzs.ribbon.MyLoadBalancerRule");
            
        UserService userService = Ribbon.from(UserService.class);
        
        Observable<ByteBuf>[] requestList = new Observable[]{
                userService.getUserById("1").toObservable(),
                userService.getUserById("2").toObservable(),
                userService.getUserById("3").toObservable(),
                userService.getUserById("1").toObservable(),
                userService.getUserById("2").toObservable(),
                userService.getUserById("3").toObservable()
        };
        Observable.concat(Observable.from(requestList))
                .subscribe(subscriber);
        Thread.sleep(10000);
    }

執行測試,可以看到,所有請求都被分配到了第一臺例項。自定義負載均衡規則生效。

zzs_ribbon_006

結語

以上,基本講完 Ribbon 的使用方法,其實 Ribbon 還有其他可以擴充套件的東西,例如,斷路器、重試等等。感興趣的話,可以自行分析。

最後,感謝閱讀。

參考資料

ribbon github

相關原始碼請移步:https://github.com/ZhangZiSheng001/ribbon-demo

本文為原創文章,轉載請附上原文出處連結:https://www.cnblogs.com/ZhangZiSheng001/p/15484505.html

相關文章