前言
⒈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}