码迷,mamicode.com
首页 > 编程语言 > 详细

微服务之路(七)spring cloud netflix ribbon

时间:2020-06-04 01:05:31      阅读:170      评论:0      收藏:0      [点我收藏+]

标签:结果   高可用   间隔   nali   上下   适合   端口映射   介绍   rri   

前言

⒈Ribbon是什么?

  Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具。

  Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。我们在配置文件中列出负载均衡所有的机器,Ribbon会自动的帮助我们基于某种规则(如简单轮询、随机连接等等)去连接这些机器。Ribbon客户端组件提供了一列完善的配置项(如连接超时、重试等等),我们也能很容易的使用Ribbon实现自定义的负载均衡算法。

⒉负载均衡概念

  负载均衡(Load Balance,简称LB),在微服务或分布式集群中经常用到的一种功能,就是将用户的请求以某种规则平摊到多个服务器上,从而达到系统的高可用。

  常见的负载均衡有软件例如Nginx、LVS等等,硬件F5等等。

  相应的在中间件,例如Dubbo和Spring Cloud中均给我们提供了负载均衡,Spring Cloud的负载均衡算法可以自定义。

  集中式负载均衡:即在服务的消费方和提供方之间使用独立的负载均衡设施(可以是硬件,如F5。也可以是软件,如Nginx),由该设施负责把请求通过某种策略转发至服务的提供方。

  进程内负载均衡:将负载均衡逻辑集成到服务消费方,由消费方从服务注册中心获取有那些服务地址可用,然后消费方从这些地址中选择一个合适的服务器。(Ribbon属于进程内负载均衡,它只是一个类库,集成于服务消费方进程,消费方通过它来获取到服务提供方的地址)

主要议题

  • Eureka高可用
  • RestTemplate
  • 整合Netflix Ribbon
  • 问题总结

主体内容

一、Eureka高可用

  • Eureka客户端高可用
    • 高可用注册中心集群
    • 获取注册信息时间间隔
    • 实例信息复制时间间隔
    • 实例Id
    • 实例端点映射
  • Eureka服务端高可用

1.Eureka客户端-高可用注册中心集群

(1)我们现在想要启动多个Eureka服务,这里调整一下启动参数。IDEA如下图:

技术图片

(2)那么其实我们客户端的注册地址也可以变成两个,这是允许的,这就是高可用注册中心集群。

#原来的
#eureka.client.serviceUrl.defaultZone=http://localhost:${eureka.server.port}/eureka
#现在的
eureka.client.serviceUrl.defaultZone=http://localhost:9090/eureka,http://localhost:9091/eureka

但是最好的方式是用域名服务器,举个例子,nginx方向代理。一个端口映射多个端口。这样只要配置一个东西就好了。

(3)这时候我们启动客户端项目,你会发现Eureka 服务端9090注册了一个服务,但是9091却没有。是这样的,Eureka是先来先服务。如果Eureka客户端应用配置了多个Eureka注册服务器,那么默认情况只有第一台可用的服务器,存在注册信息;如果第一台可用的Eureka服务器Down掉了,那么Eureka客户端应用将会选择下一台可用的Eureka服务器。

技术图片

技术图片

我现在就把第一台Down掉,也就是我们第一台Eureka项目启动类关掉。你会发现客户端报了些错误,然后后面就不报错了。等待会儿发现9091端口竟然把这个服务自动注册上了,这个就是Eureka高可用。

技术图片

这里补充个小知识:

这个端口我们可以采用随机数生成:${random.int[7070,7079]}。

(4)此时,整个项目结构图如下:

技术图片

补充:这里补充一下Eureka客户端的配置源码,都在这个叫做EurekaClientConfigBean中,服务端则显而易见的在EurekaServerConfigBean中。

eureka.client.serviceUrl.defaultZone映射的其实就是EurekaClientConfigBean中的serviceUrl字段,它是一个HashMap类型,Key为自定义,默认值“defaultZone";Value就是需要配置的Eureka注册服务器URL。构造函数如下:

public EurekaClientConfigBean() {
    this.serviceUrl.put("defaultZone", "http://localhost:8761/eureka/");
    ...
}

value可以是多值字段,通过“,”分割。源码如下:

public List<String> getEurekaServerServiceUrls(String myZone) {
    String serviceUrls = (String)this.serviceUrl.get(myZone);
    if (serviceUrls == null || serviceUrls.isEmpty()) {
        serviceUrls = (String)this.serviceUrl.get("defaultZone");
    }

    if (!StringUtils.isEmpty(serviceUrls)) {
        String[] serviceUrlsSplit = StringUtils.commaDelimitedListToStringArray(serviceUrls);
        List<String> eurekaServiceUrls = new ArrayList(serviceUrlsSplit.length);
        String[] var5 = serviceUrlsSplit;
        int var6 = serviceUrlsSplit.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            String eurekaServiceUrl = var5[var7];
            if (!this.endsWithSlash(eurekaServiceUrl)) {
                eurekaServiceUrl = eurekaServiceUrl + "/";
            }

            eurekaServiceUrls.add(eurekaServiceUrl.trim());
        }

        return eurekaServiceUrls;
    } else {
        return new ArrayList();
    }
}

2.Eureka客户端-获取注册信息时间间隔

还记不记得上面配置项中提到的eureka.client.fetch-registry=false这里面的fetch-registry。Eureka客户端需要获取Eureka服务器注册信息,这个方便服务调用。我们看一下上一章写的例子中的UserServiceProxy类,它里面的:

private  static final String PROVIDER_SERVER_URL_PREFIX="http://user-service-provider/";

为啥写个名称就能直接调用呢?其实我这个名称对应的是一个集群。

EurekaClient这个类关联着许多应用集合(Applications)->单个应用信息(Application)又关联着多个实例(InstanceInfos)->单个应用实例(InstanceInfo)

场景解释:那么当Eureka客户端需要调用具体某个服务时,比如user-service-consumer调用user-service-provider,user-service-provider实际对用对象是Application,关联了许多应用实例(InstanceInfo)。如果应用user-service-provider的应用实例发生变化时,那么user-service-consumer是需要感知的。比如:user-service-provider的机器从10台降到了5台,那么作为调用方的user-service-consumer需要知道这个变化的情况。课时这个变化过程,可能存在一定的延迟,可以通过调整注册信息时间间隔来减少错误。

(1)配置项 EurekaClientBean#registryFetchIntervalSeconds(默认30秒)

那么根据以上场景,我们就需要在consumer的application.properties文件中添加获取注册信息时间间隔配置项。

#调整注册信息的获取周期,源码中默认值30秒
eureka.client.registry-fetch-interval-seconds=5

3.Eureka客户端-实例信息复制时间间隔

具体就是客户单信息的上报到Eureka服务器的时间。当Eureka客户端应用上报频率越高,Eureka服务器的应用状态管理一直性就越高。

(1)配置项 EurekaClientBean#instanceInfoReplicationIntervalSeconds(默认30秒)

同样的,我们就需要在consumer的application.properties文件中添加获取注册信息时间间隔配置项。

#调整客户端应用状态信息上报的周期
eureka.client.instanceInfoReplicationIntervalSeconds=5

Eureka的应用信息同步是:拉的模式。

Eureka的应用信息上报的方式:推的模式。

4.Eureka客户端-实例Id

从Eureka Server Dashboard里面可以看到具体某个应用中的实例信息,比如:

技术图片

其中,他们的命名模式:${hostname}:{spring.application.name}??{server.port}

(1)配置项EurekaInstanceConfigBean#instanceId

#Eureka应用实例的id
eureka.instance.instanceid=${spring.application.name}:${server.port}

Eureka这里有个bug,那就是更改实例名后,以前的实例名仍然在注册中心会存在。当我将在实例后加上xxx:aaaa,重启客户端consumer,发现它确实被改成xxx:aaa。当我再次将其aaa改成bbb,结果重启后两个居然都在,这里就是Eureka一致性不强的原因:

技术图片

5.Eureka客户端-实例端点映射

我们可以配置Eureka客户端应用实例状态URL。当点击注册中心界面的Status地址时候,我们会发现它会跳转到我们配置的地址中去。

(1)配置项EurekaInstanceConfigBean#statuspageurlpath(示例,默认为/info)

#配置Eureka客户端应用实例状态URL
eureka.instance.status-page-url-path=/actuator/health

6.Eureka服务端高可用

那么这里示例图中的两个Eureka服务就需要进行双向同步,如图所示:

技术图片

这里介绍一个概念,那就是peer,端点的意思。比如图中两个Eureka服务器就是两个端点。我们之前配置的自我注册和检索服务配置如下:

#取消服务器自我注册
eureka.client.register-with-eureka=false
#注册中心的服务器,没必要再去检索服务
eureka.client.fetch-registry=false

现在我们需要对其作出更改,这种更改就用Spring的profile来实现吧。

(1)在服务端resources下创建application-peer1.properties,内容复制于application.properties,对配置稍作修改。修改的注释如下:

#Eureka Server 应用名称
spring.application.name = spring-cloud-eureka-server
#实例hostname
eureka.instance.hostname=peer1
#Eureka Server服务端口
server.port=9090
#取消服务器自我注册
eureka.client.register-with-eureka=true
#注册中心的服务器,没必要再去检索服务
eureka.client.fetch-registry=true
#Eureka Server服务URL,用于客户端注册和服务发现
#当前Eureka 服务器向9091(Eureka服务器)复制数据
eureka.client.serviceUrl.defaultZone=http://localhost:9091/eureka

(2)同样的,我们需要在服务端resources创建另一个application-peer2.propertie,用于第二台Eureka服务器的配置。

#Eureka Server 应用名称
spring.application.name = spring-cloud-eureka-server
#实例hostname
eureka.instance.hostname=peer2
#Eureka Server服务端口
server.port=9091
#取消服务器自我注册
eureka.client.register-with-eureka=true
#注册中心的服务器,没必要再去检索服务
eureka.client.fetch-registry=true
#Eureka Server服务URL,用于客户端注册和服务发现
#当前Eureka 服务器向9090(Eureka服务器)复制数据
eureka.client.serviceUrl.defaultZone=http://localhost:9090/eureka

(3)现在如何启动项目呢,Idea右上角绿色run 旁边的Edit Configurations中,作出如图配置,采用spring的profile启动。这样就可以通过--spring.profiles.active=peer1和--spring.profiles.active=peer2分别激活Eureka Server1和Eureka Server2。

技术图片

技术图片

然后你会发现这里红框显示他们会互相同步信息:

技术图片

技术图片

二、RestTemplate

还记得上一章我们在客户端启动类加了一个这个东西:

@LoadBalanced//负载均衡
@Bean
public RestTemplate restTemplate(){
    return new RestTemplate();
}

其实这个东西我们已经在consumer的代理类UserServiceProxy类中体现了,我们为啥能够通过这个地址:

//user-service-provider应用前缀
private  static final String PROVIDER_SERVER_URL_PREFIX="http://user-service-provider/";

能够感知到下面这个服务调用呢?

@Override
public boolean save(User user) {
   User returnValue = restTemplate.postForObject(PROVIDER_SERVER_URL_PREFIX+"/user/save",user,User.class);
   return returnValue==null?false:true;
}

HTTP消息转换器 HttpMessageConvertor

自定义实现,请参考spring boot rest,也就是第一章。

HTTP Client适配工厂:ClientHttpRequestFactory

这个方面主要考虑大家使用HttpClient偏好:

  • Spring实现
    • SimpleClientHttpRequestFactory
  • HttpClient实现
    • HttpComponentsClientHttpRequestFactory
  • OkHttp实现
    • OkHttp3ClientHttpRequestFactory

......

这边就是简单列举一些代码来演示。

那么先认识一个接口ClientHttpRequestFactory,进入这个接口,你会发现

@FunctionalInterface
public interface ClientHttpRequestFactory {
    ClientHttpRequest createRequest(URI var1, HttpMethod var2) throws IOException;
}

看看它都被谁实现了呢?(对着接口名IDEA中 Ctrl+Alt+B),发现OkHttpClient、RibbonClient,HttpClient等等都在其中。

技术图片

那么怎么去做呢?就拿Http Client中HttpComponentsClientHttpRequestFactory类举例:在客户端consumer的test中写一个java示例:

import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
 * @ClassName Http Client实现,这里比如使用Http Client ,那它对应的接口就是HttpComponentsClientHttpRequestFactory
 * @Describe TODO
 * @Author 66477
 * @Date 2020/6/321:32
 * @Version 1.0
 */
public class RestTemplateDemo {
    public static void main(String[] args) {
        RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());

        System.out.println(restTemplate.getForObject("http://localhost:8080/actuator/health", String.class));
    }
}

跑起来控制台如下:

21:48:19.770 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Connection: keep-alive[\r][\n]"
21:48:19.770 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
21:48:19.770 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "f[\r][\n]"
21:48:19.770 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "{"status":"UP"}[\r][\n]"
21:48:19.773 [main] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200 
21:48:19.773 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Type: application/json;charset=UTF-8
21:48:19.773 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Transfer-Encoding: chunked
21:48:19.773 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Wed, 03 Jun 2020 13:48:19 GMT
21:48:19.773 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Keep-Alive: timeout=60
21:48:19.773 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Connection: keep-alive
21:48:19.779 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Connection can be kept alive for 60000 MILLISECONDS
21:48:19.783 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
21:48:19.785 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json;charset=UTF-8"
21:48:19.786 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "0[\r][\n]"
21:48:19.786 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
21:48:19.788 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {}->http://localhost:8080] can be kept alive for 60.0 seconds
21:48:19.788 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
21:48:19.788 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {}->http://localhost:8080][total available: 1; route allocated: 1 of 5; total allocated: 1 of 10]
{"status":"UP"}

Process finished with exit code 0

以后根据习惯和团队习惯来使用适合自己的Http Client。

HTTP请求拦截器 ClientHttpRequestInterceptor

首先我们先找到ClientHttpRequestInterceptor这个接口,然后同样的Ctril+Alt+B看它的实现类,其中一个叫做LoadBalancerInterceptor,其中有个LoadBalancerClient,Ctril+Alt+B查看LoadBalancerClient的实现类RibbonLoadBalancerClient。我在这个类的下面图中方法处打了个断点:

技术图片

然后我们重启下Eureka server,还是启动两个端口:9090和9091。(这里启动两是为了之前配置的server互相注册实现高可用控制台不报错)

接着启动客户端的provider(启动两个端口),第一个端口采用7070,第二个端口采用7071.具体操作还是使用--sever.port= 7071启动操作。

然后以debug启动客户端consumer。

此时3个服务状态如下:

技术图片

那么这时候我们postman访问http://localhost:8080/user/save接口,保存一个用户至内存。

技术图片

这时会触发断点,我们先给予放行,然后(IDEA F9)(Eclipse F8)直接全部放行。

这时浏览器输入http://localhost:8080/user/list,这时又会跳到断点,这时我们F8一步步向下,到图中位置发现这里红框里调用的是provider端口为7070端口;

技术图片

到此为止直接(IDEA F9)(Eclipse F8)放行。然后再次刷新http://localhost:8080/user/list,重复上面的断点调试,到红框处,你会发现它调用的是provider的7071端口服务。

技术图片

这就是负载均衡的源码过程。那么此时架构图如下:

技术图片

三、整合Netflix Ribbon

实际请求客户端

  • LoadBalanceClient
    • RibbonLoadBalanceClient

负载均衡上下文

  • LoadBalancerContext
    • RibbonLoadBalancerContext

负载均衡器

  • ILoadBalancer
    • BaseLoadBalancer
    • DynamicServerListLoadBalancer
    • ZoneAwareLoadBalancer
    • NoOpLoadBalancer

负载均衡规则

核心规则接口

  • IRule
    • 随机规则:RandomRule
    • 最可用规则:BestAvailableRule
    • 轮询规则:RoundRobinRule
    • 重试实现:RetryRule
    • 客户端配置:ClientConfigEnabledRoundRobinRule
    • 可用性过滤规则:AvailabilityFilterRule
    • RT权重规则:WeightedResponseTimeRule
    • 规避区域规则:ZoneAvoidanceRule

PING策略

核心策略接口

  • IPingStrategy

PING接口

  • IPing
    • NoOpPing
    • DummyPing
    • PingConstant
    • PingUrl

Discovery Client实现

  • NIWSDiscoveryPing

四、问题总结

1.为什么要用Eureka?

解答:目前业界比较稳定云计算的开发中间件,虽然有一些不足,基本上可用。

2.使用Eureka的话,每个服务api都需要eureka插件?

解答:需要使用Eureka客户端

3.eureka主要功能为啥不能用浮动ip代替呢?

解答:如果要使用浮动ip也是可以的,不过需要扩展。

4.这章内容是不是用eureka来解释负载均衡原理、转发规则计算?

解答:是的。

5.eureka可以替换为zookeeper和consul,那么这几个使用有什么差异?

解答:可以参考:https://www.consul.io/intro/vs

6.服务器高可用配置时,是两个eureka节点互相注册,为啥要把register-with-eureka和fetch-register都改为true?

解答:无解释。一般都是这么配置的。

7.通讯不是指注册到defaultZone配置的那个么?

解答:默认情况是往defaultZone注册。

8.如果服务注册中心都挂了,服务还是能够运行吧?

解答:服务调用还是可以运行,有可能数据会不及时,不一致。

9.Spring Cloud日志收集有解决方案吗?

解答:一般用HBase,或者TSDB.

10.Spring Cloud 提供了链路跟踪的方法吗?

解答:http://cloud.spring.io/spring-cloud-static/Dalston.SR4/single/spring-cloud.html#_spring_cloud_sleuth,如果404,按照这个路径凭借感觉找。

微服务之路(七)spring cloud netflix ribbon

标签:结果   高可用   间隔   nali   上下   适合   端口映射   介绍   rri   

原文地址:https://www.cnblogs.com/jmy520/p/13040936.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!