主頁 > 後端開發 > Spring Cloud Ribbon(一)

Spring Cloud Ribbon(一)

2020-12-16 07:51:18 後端開發

一、RestTemplate

    1.1簡介

spring框架提供的RestTemplate類可用于在應用中呼叫rest服務,它簡化了與http服務的通信方式,統一了RESTful的標準,封裝了http鏈接, 我們只需要傳入url及回傳值型別即可,相較于之前常用的HttpClient,RestTemplate是一種更優雅的呼叫RESTful服務的方式,

  在Spring應用程式中訪問第三方REST服務與使用Spring RestTemplate類有關,RestTemplate類的設計原則與許多其他Spring *模板類(例如JdbcTemplate、JmsTemplate)相同,為執行復雜任務提供了一種具有默認行為的簡化方法,

  RestTemplate默認依賴JDK提供http連接的能力(HttpURLConnection),如果有需要的話也可以通過setRequestFactory方法替換為例如 Apache HttpComponents、Netty或OkHttp等其它HTTP library,

  考慮到RestTemplate類是為呼叫REST服務而設計的,因此它的主要方法與REST的基礎緊密相連就不足為奇了,后者是HTTP協議的方法:HEAD、GET、POST、PUT、DELETE和OPTIONS,例如,RestTemplate類具有headForHeaders()、getForObject()、postForObject()、put()和delete()等方法,

1.2、實作

 最新api地址:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html

首先建兩個專案

 

 

RestTemplate包含以下幾個部分:

    • HttpMessageConverter 物件轉換器
    • ClientHttpRequestFactory 默認是JDK的HttpURLConnection
    • ResponseErrorHandler 例外處理
    • ClientHttpRequestInterceptor 請求攔截器

 spring-cloud-server的配置

 

 <dependencies>
        <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>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

application.properties

spring.application.name=spring-cloud-server
server.port=8080

RestTemplateServer.class

@RestController
public class RestTemplateServer {
    @Value("${server.port}")
    private int port;

    @GetMapping("/orders")
    public String getAllOrder(){
        System.out.println("port:"+port);
        return "測驗成功";
    }
}

啟動專案訪問結果如下

 

 

 spring-cloud-user的組態檔

<dependencies>
        <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>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
server.port=8088

業務代碼RestTemplateUser.class

@RestController
public class RestTemplateUser {

    @Autowired
    RestTemplate restTemplate;

    //因為RestTemplate不存在所以要注入
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    @GetMapping("/user")
    public String findById(){
        return restTemplate.getForObject("http://localhost:8080/orders",String.class);
    }
}

啟動專案訪問可得到8080服務的結果

 

 

 這樣我們初步完成了兩個獨立專案的通信,如果不想在通過new的方式創建RestTemplate那也可以通過build()方法創建,修改后如下

@RestController
public class RestTemplateUser {

    @Autowired
    RestTemplate restTemplate;

    //因為RestTemplate不存在所以要注入
//    @Bean
//    public RestTemplate restTemplate(){
//        return new RestTemplate();
//    }
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
        return restTemplateBuilder.build();
    }

    @GetMapping("/user")
    public String findById(){

        return restTemplate.getForObject("http://localhost:8080/orders",String.class);
    }
}

但是現在很多服務架構都是多節點的,那么我們就要考慮多節點負載均衡的問題,這時最先想到的是Ribbon,修改代碼

修改cloud-cloud-user的pom.xml檔案,增加

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

為演示負載均衡,啟動兩個spring-cloud-server節點,再配置一個節點并啟動

 

 

 修改完后,再修改spring-cloud-user組態檔

server.port=8088

spring-cloud-server.ribbon.listOfServers=\
  localhost:8080,localhost:8081

這樣玩后有心的人就發現了,業務再用return restTemplate.getForObject("http://localhost:8080/orders",String.class);訪問另一個專案就不合適了,更改RestTemplateUser.class類

@RestController
public class RestTemplateUser {

    @Autowired
    RestTemplate restTemplate;

    //因為RestTemplate不存在所以要注入
//    @Bean
//    public RestTemplate restTemplate(){
//        return new RestTemplate();
//    }
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
        return restTemplateBuilder.build();
    }
    @Autowired
    LoadBalancerClient loadBalancerClient;


    @GetMapping("/user")
    public String findById(){

        ServiceInstance serviceInstance=loadBalancerClient.choose("spring-cloud-server");
        String url=String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort()+"/orders");
        return restTemplate.getForObject(url,String.class);
        //通過服務名稱在組態檔中選擇埠呼叫
       // return restTemplate.getForObject("http://localhost:8080/orders",String.class);
    }
}

訪問下面地址,多點幾次

 

 

 

 

 

 說到 了這里那我們現在就要來看下Ribbon了

二、Ribbon簡介

需要解決的問題: ① 如何在配置Eureka Client注冊中心時不去硬編碼Eureka Server的地址? ② 在微服務不同模塊間進行通信時,如何不去硬編碼服務提供者的地址?
③ 當部署多個相同微服務時,如何實作請求時的負載均衡? 
實作負載均衡方式1:通過服務器端實作負載均衡(nginx)

 

 實作負載均衡方式2:通過客戶端實作負載均衡

 

 

Ribbon是什么? Ribbon是Netflix發布的云中間層服務開源專案,其主要功能是提供客戶端實作負載均衡演算法,Ribbon客戶端組件提供一系列完善的配置項如連接超時,重試等,簡單的說,Ribbon是一個客戶端負載均衡器,我們可以在組態檔中Load Balancer后面的所有機器,Ribbon會自動的幫助你基于某種規則(如簡單輪詢,隨機連接等)去連接這些機器,我們也很容易使用Ribbon實作自定義的負載均衡演算法, 下圖展示了Eureka使用Ribbon時的大致架構: 

 

 Ribbon作業時分為兩步:第一步選擇Eureka Server,它優先選擇在同一個Zone且負載較少的Server;第二步再根據用戶指定的策略,再從Server取到的服務注冊串列中選擇一個地址,其中Ribbon提供了很多策略,例如輪詢round robin、隨機Random、根據回應時間加權等,

為了更好的了解Ribbon后面肯定是要進入原始碼,在進入原始碼之前做個鋪墊,我再來改造上面的代碼,引入@LoadBalanced注解,修改下

@RestController
public class RestTemplateUser {

    @Autowired
    RestTemplate restTemplate;

    //因為RestTemplate不存在所以要注入
//    @Bean
//    public RestTemplate restTemplate(){
//        return new RestTemplate();
//    }
//    @Bean
//    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
//        return restTemplateBuilder.build();
//    }
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
        return restTemplateBuilder.build();
    }

//    @Autowired
//    LoadBalancerClient loadBalancerClient;


    @GetMapping("/user")
    public String findById(){

//        ServiceInstance serviceInstance=loadBalancerClient.choose("spring-cloud-server");
//        String url=String.format("http://%s:%s",serviceInstance.getHost(),serviceInstance.getPort()+"/orders");
//        return restTemplate.getForObject(url,String.class);
        //通過服務名稱在組態檔中選擇埠呼叫
       return restTemplate.getForObject("http://spring-cloud-server/orders",String.class);
    }
}

啟動專案后會發現@LoadBalanced也能實作負載均衡,這里面我們就應該進入看下@LoadBalanced到底做了啥,在沒用@LoadBalanced之前getForObject只能識別ip的路徑,并不能識別服務名進行負載均衡,所以我們要看下@LoadBalanced是怎么實作的負載均衡

 

 在看碼源前先劇透下,之前某人說我寫的東西不好看懂,那我這次多花點時間畫圖,restTemplate.getForObject("http://spring-cloud-server/orders",String.class);這個方法他呼叫的是一個服務器名稱,我們知道,如果要訪問一個服務器我們一個具體的路徑才能訪問,那么@LoadBalanced是怎么做到的由一個服務名得到一個具體的路徑呢,這就要說到攔截器,他在呼叫真實路徑前會有攔截器攔截服務器名,然后拿到服務器去決議然后拼接得到一個真實的路徑名稱,然后拿真實路徑去訪問服務,詳細的步驟在原始碼講解中具體分析,

我們點擊@LoadBalanced進入如下圖

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {

}

我們會發現有一個叫@Qualifier的東西,其實這玩意就是一個標記的作用,但為了后面的原始碼分析,這里還是說明下@Qualifiler的用法

我們在spring-cloud-user專案中新建一個Qualifier包,在包中建三個類

public class QualifierTest {
    private String name;

    public QualifierTest(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
//@Configuration用于定義配置類,可替換xml組態檔,
// 被注解的類內部包含有一個或多個被@Bean注解的方法,
// 這些方法將會被AnnotationConfigApplicationContext或
// AnnotationConfigWebApplicationContext類進行掃描,
// 并用于構建bean定義,初始化Spring容器,
@Configuration
public class QualifierConfiguration {
    @Qualifier
    @Bean("QualifierTest1")
    QualifierTest QualifierTest1(){
        return new QualifierTest("QualifierTest1");
    }

    @Qualifier
    @Bean("QualifierTest2")
    QualifierTest QualifierTest2(){
        return new QualifierTest("QualifierTest2");
    }
}
@RestController
public class QualifierController {
    //@Qualifier作用是找到所有申明@Qualifier標記的實體
    @Qualifier
    @Autowired
    List<QualifierTest> testClassList= Collections.emptyList();

    @GetMapping("/qualifier")
    public Object test(){
        return testClassList;
    }
}

啟動專案訪問介面結果如下

 

 

除掉QualifierConfiguration.class中其中一個@Qualifier后重繪介面,會發現結果如下,這兩個結果對比可以證明@Qualifier其實就是一個標記的作用

 

 有了這個概念后我們進入LoadBalancerAutoConfiguration.class這個自動裝配類中會發現有和我剛剛演示一樣的代碼,其實我就是從這個裝配類中抄的,哈哈;

 

 看到這里相信大家就明白了,因為紅框的內容加了@LoadBalanced注解就能使RestTemplate生效是因為@Qualifier注解,有了這個概念接著往下走,在上圖這個自動裝配類中會加載注入所有加了@LoadBalanced注解的RestTemplate,這一步很關鍵,因為后面的攔截器加載跟這一步有關聯;竟然我們來到了LoadBalancerAutoConfiguration,這個自動裝配類來了,那就聊聊這里面的Bean裝配,下面這個圖是Bean的自動裝配程序

 

 

首先看自動裝配類攔截器LoadBalancerInterceptor

@Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    static class LoadBalancerInterceptorConfig {
        //定義一個Bean
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }
        //將定義的Bean作為引數傳入
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
//設定攔截器 list.add(loadBalancerInterceptor);
//設定到restTemplate中去 restTemplate.setInterceptors(list); }; } }

 

@Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
//對restTemplates進行for回圈,對每一個restTemplate加一個包裝叫RestTemplateCustomizer
//這個包裝的意義是可以對restTemplate再加一個自定義的攔截
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); }

有了上面的包裝,才有下面的攔截的加強

 

        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }

說到這里再將時序圖畫一下,我最初是通過@LoadBalanced注解進入到他的裝配類LoadBalancerAutoConfiguration,然后在LoadBalancerAutoConfiguration裝配類中找到攔截器的加載和增強的,根據這個邏輯畫出的時序圖如下

 

 之前在開篇中還講到過用下面這種方式進行負載均衡訪問,其實針對LoadBalancerClient是一樣的,他里面有一個RibbonAutoConfiguration

   @Autowired
    LoadBalancerClient loadBalancerClient;

在RibbonAutoConfiguration裝配類中會找到一個代碼如果下,他在裝配類中對LoadBalancerClient進行初始化

@Bean
    @ConditionalOnMissingBean(LoadBalancerClient.class)
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(springClientFactory());
    }

我們看頭檔案,會發現加載了LoadBalancerAutoConfiguration

 

 這時補充下時序圖如下,這就是Bean的加載程序,經過這一程序攔截器就算是加載進去了

 

 

 有了攔截器后,下一步要看的話肯定就是來看下攔截器到底做了啥,進入LoadBalancerInterceptor攔截器,會發現他會最終進入如下方法

@Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        final URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null,
                "Request URI does not contain a valid hostname: " + originalUri);
//將攔截委托給loadBalancer進行實作
return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); }

跟進loadBalancer看下做了啥(LoadBalancerClient注入是在RibbonAutoConfiguration配置類中完成的),跟蹤進去發現最侄訓是呼叫了RibbonLoadBalancerClient

 

進入execute方法,會發現里面只做了兩件事

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
            throws IOException {
//獲得負載均衡器 ILoadBalancer loadBalancer
= getLoadBalancer(serviceId);
//根據負載均衡器回傳Server,這個Server回傳是指定的某一個地址,其實負載的決議在這里就完成了 Server server
= getServer(loadBalancer, hint); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); return execute(serviceId, ribbonServer, request); }

 

進入getLoadBalancer看看他做了啥,在看之前先看下他的類關系圖

 ILoadBalancer介面:定義添加服務,選擇服務,獲取可用服務,獲取所有服務方法

AbstractLoadBalancer抽像類:定義了一個關于服務實體的分組列舉,包含了三種型別的服務:ALL表示所有服務,STATUS_UP表示正常運行的服務,STATUS_NOT_UP表示下線的服務,

 BaseLoadBalancer:

1):類中有兩個List集合,一個List集合用來保存所有的服務實體,還有一個List集合用來保存當前有效的服務實體

2):定義了一個IPingStrategy,用來描述服務檢查策略,IPingStrategy默認實作采用了SerialPingStrategy實作

3):chooseServer方法中(負載均衡的核心方法),呼叫IRule中的choose方法來找到一個具體的服務實體,默認實作是RoundRobinRule

4):PingTask用來檢查Server是否有效,默認執行時間間隔為10秒

5):markServerDown方法用來標記一個服務是否有效,標記方式為呼叫Server物件的setAlive方法設定isAliveFlag屬性為false

6):getReachableServers方法用來獲取所有有效的服務實體串列

7):getAllServers方法用來獲取所有服務的實體串列

8):addServers方法表示向負載均衡器中添加一個新的服務實體串列

 DynamicServerListLoadBalancer:主要是實作了服務實體清單在運行期間的動態更新能力,同時提供了對服務實體清單的過濾功能,

 ZoneAwareLoadBalancer:主要是重寫DynamicServerListLoadBalancer中的chooseServer方法,由于DynamicServerListLoadBalancer中負責均衡的策略依然是BaseLoadBalancer中的線性輪詢策略,這種策略不具備區域感知功能

 NoOpLoadBalancer:不做任何事的負載均衡實作,一般用于占位(然而貌似從沒被用到過),

有了這個概念后我們下面就來重點看BaseLoadBalancer,在嘮嘮之前先補充下時序圖

 

 點擊getLoadBalancer進入如下代碼

 

 在向下寫前,先提前說下ILoadBalancer這個類里面會幫我們做一件事,他會根據負載均衡的一個演算法進行一個負載的選擇,但是在負載之前他會有一個類的初始化程序,在選擇完成后ILoadBalancer實作回傳,然后將ILoadBalancer做為引數傳給Server server = getServer(loadBalancer, hint);在ILoadBalancer中他有一個實作會去呼叫BaseLoadBalancer.chooseServer,它會呼叫rule.choose(),rule的初始化是在ZoneAvoidanceRule中完成的,所以接下來看要分兩部分,ILoadBalancer做為一個負載均衡器,然后getServer會把這個負載均衡器會傳過去后進行一個負載的計算,這個流程說完后可能很多人還在懵逼狀態,那接下來我們就通過代碼來看他的實作,首先看ILoadBalancer的實作是誰

接著上圖來,點擊getLoadBalancer

 

 然后點擊getInstance

 

 

    @Override
    public <C> C getInstance(String name, Class<C> type) {
//這里面通過傳送一個name和一個type得到一個實體,這里面是一個工廠模式,我們點擊getInstance選擇它的NamedContextFactory實作進去 C instance
= super.getInstance(name, type); if (instance != null) { return instance; } IClientConfig config = getInstance(name, IClientConfig.class); return instantiateWithConfig(getContext(name), type, config); }

    public <T> T getInstance(String name, Class<T> type) {
//工廠模式會加載一個context AnnotationConfigApplicationContext context
= getContext(name); if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) { return context.getBean(type); } return null; }

 

 

getContext方法里面是用spring寫的,比較復雜,點擊getContext后如下圖,這里面是有個默認快取的,如果沒有會用createContext(name)根據名稱創建一個快取

 

回退到AnnotationConfigApplicationContext context = getContext(name);

 

public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = getContext(name);
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                type).length > 0) {
通過type得到一個Bean
return context.getBean(type); } return null; }

再回退到C instance = super.getInstance(name, type);進行打debug看下他回傳的是什么型別的ILoadBalancer

 

 

 從上圖可以看到回傳的是一個ZoneAwareLoadBalancer的ILoadBalancer,然后就拿著ILoadBalancer傳入getServer(loadBalancer, hint);中,這時的時序圖就如下了

 

 

 

到了這一步獲取負載均衡器這一程序就完成了,下面就是來完成程序2.通過負載均衡器中配置的默認負載均衡演算法選一個合適的Server,我們進入

Server server = getServer(loadBalancer, hint);的getServer方法,點擊進去如下,這里面其實進行的就是針對一個服務節點的選擇,其中loadBalancer.chooseServer(hint != null ? hint : "default");就是一種演算法的選擇,我們這里面沒有選擇演算法,所以采用默認演算法BaseLoadBalancer

進入默認演算法截圖如下

 

 然后他會呼叫rule.choose(key);方法,我們可以在進入方法前先看下IRule是啥,通過下圖我們可以很清楚的看到IRule里面所有的實作,之所以在這里提到IRule是因為IRule是Ribbon中實作負載均衡的一個很重要的規則,他實作了重置規則、輪詢規則、隨機規則及客戶端是否啟動輪詢的規則;在后面我看機會說其中一到兩種比較常用的演算法說明下

 

 

 我們這里rule.choose(key);采用的是輪詢演算法,選擇PredicateBasedRule,進去后截圖如下

 

 

    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
//根據我們的過濾規則過濾之后會根據輪詢去進行篩選,其中lb.getAllServers是獲取一個靜態的服務串列 Optional
<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key); if (server.isPresent()) { return server.get(); } else { return null; } } }

我們進入chooseRoundRobinAfterFiltering,下面的輪詢比較簡單,他先把節點數量eligible.size()傳進去,然后通過incrementAndGetModulo方法獲取一個下標

public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
//得到我們所有的配置資訊 List
<Server> eligible = getEligibleServers(servers, loadBalancerKey);
//配置數量
if (eligible.size() == 0) { return Optional.absent(); }
//進行輪詢計算
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size()))); }

可以進入incrementAndGetModulo方法看下

private int incrementAndGetModulo(int modulo) {
        for (;;) {
//獲取下一個節點的當前值
int current = nextIndex.get();
//根據這個值進行取模運算
int next = (current + 1) % modulo;
//設定下一個值
if (nextIndex.compareAndSet(current, next) && current < modulo) return current; } }

上面就是輪詢演算法的實作,這個演算法的實作比較簡單,下面再來看一個隨機演算法的實作

 

 

 

  

    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; } //傳入節點數量,然后隨機取值,如果有人想看怎么取的點擊這個chooseRandomInt就可以看到,它實作就一句話,就是把數量傳進去得到一個隨機值 int index = chooseRandomInt(serverCount); server = upList.get(index); 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; }

隨機實作聊完后,再回到我們跟蹤的代碼 return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));通過演算法得到具體的節點后eligible.get就可以得到 對應下標的服務串列,這時就得到了什么localhost:8082的具體埠號了,這一步完成后其實Server server = getServer(loadBalancer, hint);的活就做完了,下面的活就是拿著具體埠去重構了,更新下時序圖

 

 

 

 

 專案中所有例子原始碼:https://github.com/ljx958720/spring-cloud-Ribbon-1-.git

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/235261.html

標籤:其他

上一篇:Java基礎之:OOP——介面

下一篇:SpringBoot集成Swagger2

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more