我们通常所说的负载均衡都指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡
硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,比如F5等;软件负载均衡则是通过在服务器上安装一些具有均衡负载功能或模块的软件来完成请求分发工作,比如Nginx等
硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端的地址,然后进行转发
而客户端负载均衡和服务端负载均衡最大的不同点在于服务清单所存储的位置。在客户端负载均衡中,所有客户端节点都维护着自己要访问的服务端清单,而这些服务端的清单来自于服务注册中心。在客户端负载均衡中也需要心跳去维护服务端清单的健康性,这个步骤需要与服务注册中心配合完成
通过SpringCloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用只需要如下两步:
在上一篇博客中,已经通过引入Ribbon实现了服务消费者的客户端负载均衡功能,其中使用了RestTemplate对象。这个对象会使用Ribbon的自动化配置,同时通过配置@LoadBalanced还能够开启客户端负载均衡。下面将介绍一下RestTemplate针对几种不同请求类型和参数类型的服务调用实现
getForEntity方法返回的是ResponseEntity,该对象是Spring对HTTP请求响应的封装,其中主要存储了HTTP的几个重要元素,比如HTTP请求状态码的枚举对象HttpStatus、在它的父类HttpEntity中还存储着HTTP请求的头信息对象HttpHeaders以及泛型类型的请求体对象
getForEntity函数提供了以下三种不同的重载实现
getForEntity(String url, Class responseType, Object... uriVariables)
:该方法提供了三个参数,其中url为请求的地址,responseType为请求响应体body的包装类型,uriVariables为url中的参数绑定访问HELLO-SERVICE服务的/hello请求,同时最后一个参数tom会替换url中的{1}占位符,而返回的ResponseEntity对象中的body内容类型会根据第二个参数转换为String类型
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/hello?name={1}", String.class, "tom");
String body = responseEntity.getBody();
如果希望返回的body是一个User对象类型
ResponseEntity<User> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/hello?name={1}", User.class, "tom");
User user = responseEntity.getBody();
getForEntity(String url, Class responseType, Map uriVariables)
:使用该方法进行参数绑定时需要在占位符中指定Map中参数的key值 Map<String, String> params = new HashMap<>();
params.put("name", "tom");
String body = restTemplate.getForEntity("http://HELLO-SERVICE/hello?name={name}", String.class, params).getBody();
getForEntity(URI url, Class responseType)
:该方法使用URI对象替代之前的url和uriVariables参数来指定访问地址和参数绑定。java.net.URI
表示一个统一资源标识符引用 UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/hello?name={name}").build().expand("tom").encode();
URI uri = uriComponents.toUri();
String body = restTemplate.getForEntity(uri, String.class).getBody();
getForObject方法是对getForEntity的进一步封装,通过HttpMessageConverterExtractor对HTTP的请求响应体body内容进行对象转换,实现请求直接返回包装好的对象内容。比如:
UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/hello?name={name}").build().expand("tom").encode();
URI uri = uriComponents.toUri();
String body = restTemplate.getForObject(uri, String.class);
getForObject的三种重载方法的参数与getForEntity的相同
postForEntity方法同GET请求中的getForEntity类似。如下使用postForEntity提交POST请求到HELLO-SERVICE服务的/hello接口,提交的body内容为user对象,请求响应返回的body类型为String
User user = new User(1, "tom", 22);
ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://HELLO-SERVICE/hello", user, String.class);
String body = responseEntity.getBody();
或者采用HttpEntity的方式:
HttpEntity<User> request = new HttpEntity<>(new User(1, "tom", 22));
ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://HELLO-SERVICE/hello", request, String.class);
String body = responseEntity.getBody();
postForObject函数也跟getForObject类似
User user = new User(1, "tom", 22);
String body = restTemplate.postForObject("http://HELLO-SERVICE/hello", user, String.class);
postForLocation函数实现了以POST请求提交资源,并返回新资源的URI
User user = new User(1, "tom", 22);
URI uri = restTemplate.postForLocation("http://HELLO-SERVICE/hello", user);
由于postForLocation函数会返回新资源的URI,该URI就相当于指定了返回类型,所以此方法实现的POST请求不需要像postForEntity和postForObject那样指定responseType
IRule:负载均衡策略
IPing:探测服务实例是否存活的策略
ServerList:负载均衡使用的服务器列表。这个列表会缓存在负载均衡器中,并定期更新。当Ribbon与Eureka 结合使用时,ServerList的实现类就是DiscoveryEnabledNIWSServerList,它会保存Eureka Server中注册的服务实例表
ServerListFilter:服务器列表过滤器。主要用于对服务消费者获取到的服务器列表进行预过滤
ILoadBalancer:负载均衡器。上层代码通过调用其API进行服务调用的负载均衡选择,一般ILoadBalancer的实现类中会引用一个IRule
RestClient:服务调用器,Ribbon向服务提供者发起REST请求的工具
Ribbon的主要工作:
public interface IRule{
//选择哪个服务处理请求
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
AbstractLoadBalancerRule是负载均衡策略的抽象类,在该抽象类中定义了负载均衡器ILoadBalancer对象,该对象能够在具体实现选择服务策略时,获取到一些负载均衡器中维护的信息来作为分配依据,并以此设计一些算法来实现针对特定场景的高效策略
public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {
private ILoadBalancer lb;
@Override
public void setLoadBalancer(ILoadBalancer lb){
this.lb = lb;
}
@Override
public ILoadBalancer getLoadBalancer(){
return lb;
}
}
RandomRule:随机
RoundRobinRule:轮询
RetryRule:先按照轮询的策略获取服务,如果获取服务失败则在指定的时间内会进行重试,获取可用的服务
WeightedResponseTimeRule:根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越大。刚启动时如果同统计信息不足,则使用轮询的策略,等统计信息足够会切换到自身规则
BestAvailableRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务
AvailabilityFilteringRule:先过滤掉由于多次访问故障处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对于剩余的服务列表按照轮询的策略进行访问
ZoneAvoidanceRule:以ZoneAvoidancePredicate(服务所在的Zone是否可用)为主过滤条件,AvailabilityPredicate(服务连接数量是否超过阈值)为次过滤条件的组合过滤条件CompositePredicate进行过滤,然后对于剩余的服务列表按照轮询的策略进行访问
在配置类中指定负载均衡策略
@Bean
public IRule rule() {
//随机策略
return new RandomRule();
}
Ribbon的参数配置有两种方式:全局配置以及指定客户端配置
格式:ribbon.
代表Ribbon客户端配置的参数名,
代表了对应参数的值
全局配置Ribbon创建连接的超时时间
ribbon.ConnectTimeout=3000
格式:
和
的含义同全局配置相同,
代表了客户端的名称
为客户端指定具体的实例清单
hello-service.ribbon.listOfServers=localhost:8000,localhost:8001
当在SpringCloud的应用中同时引入SpringCloud Ribbon和SpringCloud Eureka依赖时,会触发Eureka中实现的对Ribbon的自动化配置。ServerList的维护机制将被com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
的实例所覆盖,该实现会将服务清单列表交给Eureka的服务治理机制来维护;IPing的实现将被com.netflix.niws.loadbalancer.NIWSDiscoveryPing
的实例所覆盖,该实现也将实例检查的任务交给了服务治理框架来进行维护
由于SpringCloud Ribbon默认实现了区域亲和策略,可以通过Eureka实例的元数据配置来实现区域化的实例配置方案。比如,可以将处于不同机房的实例配置成不同的区域值,以作为跨区域的容错机制实现,只需在服务实例的元数据中增加zone参数来指定自己所在的区域,比如:
eureka.instance.metadata-map.zone=shanghai
在SpringCloud Ribbon和SpringCloud Eureka结合的工程中,可以通过如下的参数配置来禁用Eureka对Ribbon服务实例的维护实现。这时对于服务实例的维护又回归到使用
参数配置的方式来实现
ribbon.eureka.enabled=false
spring.cloud.loadbalancer.retry.enabled=true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
hello-service.ribbon.ConnectTimeout=250
hello-service.ribbon.ReadTimeout=1000
hello-service.ribbon.OkToRetryOnAllOperations=true
hello-service.ribbon.MaxAutoRetriesNextServer=2
hello.ribbon.MaxAutoRetries=1
spring.cloud.loadbalancer.retry.enabled
:该参数用来开启重试机制hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
:断路器的超时时间需要大于Ribbon的超时时间,不然不会触发重试hello-service.ribbon.ConnectTimeout
:请求连接的超时时间hello-service.ribbon.ReadTimeout
:请求处理的超时时间hello-service.ribbon.OkToRetryOnAllOperations
:对所有操作请求都进行重试hello-service.ribbon.MaxAutoRetriesNextServer
:切换实例的重试次数hello.ribbon.MaxAutoRetries
:对当前实例的重试次数根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由MaxAutoRetries配置),如果不行,就换一个实例进行访问,如果还是不行,再换一次实例访问(更换次数由MaxAutoRetriesNextServer配置),如果依然不行,返回失败信息