Spring Cloud Ribbon(一)

1、RestTemplate

    1.1简介

spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通讯方式,统一了RESTful的标准,封装了http连接, 咱们只须要传入url及返回值类型便可。相较于以前经常使用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。html

  在Spring应用程序中访问第三方REST服务与使用Spring RestTemplate类有关。RestTemplate类的设计原则与许多其余Spring *模板类(例如JdbcTemplate、JmsTemplate)相同,为执行复杂任务提供了一种具备默认行为的简化方法。java

  RestTemplate默认依赖JDK提供http链接的能力(HttpURLConnection),若是有须要的话也能够经过setRequestFactory方法替换为例如 Apache HttpComponents、Netty或OkHttp等其它HTTP library。nginx

  考虑到RestTemplate类是为调用REST服务而设计的,所以它的主要方法与REST的基础紧密相连就不足为奇了,后者是HTTP协议的方法:HEAD、GET、POST、PUT、DELETE和OPTIONS。例如,RestTemplate类具备headForHeaders()、getForObject()、postForObject()、put()和delete()等方法。git

1.二、实现

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

首先建两个项目web

 

 

RestTemplate包含如下几个部分:算法

    • HttpMessageConverter 对象转换器
    • ClientHttpRequestFactory 默认是JDK的HttpURLConnection
    • ResponseErrorHandler 异常处理
    • ClientHttpRequestInterceptor 请求拦截器

 spring-cloud-server的配置spring

 

 <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.propertiesapi

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了

2、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

相关文章
相关标签/搜索