目录
什么是负载均衡?
为什么使用负载均衡?
什么是ribbon?
ribbon与resttemplate整合
ribbon负载均衡源码跟踪
ribbon的常见配置
ribbon面试必问
总结
负载均衡是将用户的请求按照某种算法,动态的去选择某一个主机去处理,对于用户来说是透明的,在没有负载均衡之前我们可能是这么去请求一个数据的
有了负载均衡之后,用户请求过程可能是这样的
负载均衡分为客户端负载均衡和集中式负载均衡
客户端负载均衡:消费者在调用请求接口之前首先获取哪些资源是可用的,然后按照均衡算法选择一个资源进行请求,这个是客户端自己进行选择的,所以称为客户端负载均衡,常见的客户端负载均衡有ribbon
集中式负载均衡:在消费者和服务方中间使用独立的代理方式进行负载,有硬件的(F5),软件的(nginx)
这个问题其实很好回答,一个新的东西出来必定是为了解决某一个问题,不然他出现的就毫无意义,负载均衡也是一样,负载均衡是为了达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的,打个比方说,就相当于我们在火车站买票,如果只开了一个窗口,那么这个窗口排队的人肯定会很长,买票的效率会很低,如果我同时开通十个窗口,那么买票速度铁定会提升的,而且单个窗口的压力也会减少,响应素度也会更快的,这也就是为什么采用负载均衡的原因。
ribbon其实是netflix开源的一款用于客户端负载均衡的一个框架。
在springcloud提供了ribbon用来做客户端负载均衡,通过springcloud对ribbon的封装,我们可以很轻松的通过负载均衡去调用我们开发的rest服务,不需要手动去处理因负载均衡而出现的各种棘手情况,ribbon并不需要向eureka和网关那样单独部署,他是和每一个微服务耦合在一起的,这一节我们介绍的是resttemplate与ribbon整合去实现负载均衡的调用,这里负载均衡的启用方式是需要我们手动在resttemplate实例配置上面添加loadbalence注解的,关于resttemplate我上一节已经介绍过了,在这个后一节我会介绍feign工具的使用,使用它的时候,它自动帮我们开启了负载均衡。
关于resttemplate的整合,上一节中,我已经介绍过了,这一节主要介绍resttemplate的api使用
GET请求
(1) public
url:请求的接口地址,例“http://192.168.31.168:8081/getOrder?name={1}”
responseType:响应数据类型,例“String.class”,返回string类型数据,如果希望返回某一个实体对象也可以这样,User.class
uriVariables:填充地址栏里面的占位符
(2) public
url:url:请求的接口地址,例“http://192.168.31.168:8081/getOrder”
responseType:响应数据类型,例“String.class”,返回string类型数据,如果希望返回某一个实体对象也可以这样,User.class
uriVariables:参数的键值对
(3) public
url:请求的接口地址,例“http://192.168.31.168:8081/getOrder?name={1}”
responseType:响应数据类型,例“String.class”,返回string类型数据,与上面不同的是,这个返回结果封装了http的响应头等信息,,如果希望返回某一个实体对象也可以这样,User.class
uriVariables:填充地址栏里面的占位符
(4) public
url:url:请求的接口地址,例“http://192.168.31.168:8081/getOrder”
responseType:响应数据类型,例“String.class”,返回string类型数据,与上面不同的是,这个返回结果封装了http的响应代码等数据,如果希望返回某一个实体对象也可以这样,User.class,
uriVariables:参数的键值对
POST请求
(1) public
url:请求的接口地址,例“http://192.168.31.168:8081/getOrder?name={1}”
responseType:响应数据类型,例“String.class”,返回string类型数据,如果希望返回某一个实体对象也可以这样,User.class
uriVariables:填充地址栏里面的占位符
(2) public
url:url:请求的接口地址,例“http://192.168.31.168:8081/getOrder”
responseType:响应数据类型,例“String.class”,返回string类型数据,如果希望返回某一个实体对象也可以这样,User.class
uriVariables:参数的键值对
(3) public
url:请求的接口地址,例“http://192.168.31.168:8081/getOrder?name={1}”
responseType:响应数据类型,例“String.class”,返回string类型数据,与上面不同的是,这个返回结果封装了http的响应头等信息,,如果希望返回某一个实体对象也可以这样,User.class
uriVariables:填充地址栏里面的占位符
(4) public
url:url:请求的接口地址,例“http://192.168.31.168:8081/getOrder”
responseType:响应数据类型,例“String.class”,返回string类型数据,与上面不同的是,这个返回结果封装了http的响应代码等数据,如果希望返回某一个实体对象也可以这样,User.class,
uriVariables:参数的键值对
设想:ribbon是如何实现负载均衡的呢?根据我们前面说ribbon实现的的客户端负载均衡,所以他自己肯定有一个可用的服务列表,服务列表里面存储的是可用服务的地址,这是第一个条件;第二个是我们在resttemplate上面添加了注解后,他就自动实现了负载均衡,这个我们肯定会想到他拦截了我们的请求,所以这里肯定会有一个拦截器在帮助我们实现此功能,第三个是为什么我们加上了注解后,也可以使用服务名直接去调用了呢,肯定也有组件帮我们实现了替换的,让我们带着者三个疑问一起去追踪源码吧。
再看源码之前,我们都会从一个入口出发,然后进行调试。研究springmvc源码时,我们都知道肯定都是从dispatcherservlet出发,然后往下走,这里也是一样,通过我们上面的分析,发现实现了负载均衡时和这个注解有关的,所以我们先看看这个注解是如何定义的
/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
进入了LoadBalanced 注解后,我们看到他是这么定义的,并且注意到了上面的一行注释,通过注解标记resttemplate使用LoadBalancerClient去代理使用,这个我们先不关注,我们都知道,在springboot中,有一个申明式的注解,必定在其同名的包下面会有一个这样的(xxxAutoConfiguration)配置类,去配置这个注解,我们定位到该包下面,看到
同名的包下面确实有这样一个配置类LoadBalancerAutoConfiguration
我们看下这个类的注解信息
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
这里会涉及到spring的以注解使用,我挑几个常用的简单说下
@Configuration:注解可以用java代码的形式实现spring中xml配置文件配置的效果。
@ConditionalOnClass:其用途是判断当前classpath下是否存在指定类,若是则将当前的配置装载入spring容器
@ConditionalOnBean:当给定的在bean存在时,则实例化当前Bean
@EnableConfigurationProperties:如果该类只使用了@ConfigurationProperties注解,然后该类没有在扫描路径下或者没有使用@Component等注解,导致无法被扫描为bean,那么就必须在配置类上使用@EnableConfigurationProperties注解去指定这个类,这个时候就会让该类上的@ConfigurationProperties生效,然后作为bean添加进spring容器中
@ConditionalOnMissingBean:容器中不存在指定bean
介绍完几个基本注解之后,我们继续分析,看到
@LoadBalanced
@Autowired(required = false)
private List
这里面会注入只有被loadbalence注解的所有的resttemplate实例,我们再点击resttemplate进去,看到此类继承了Intecepting,看来真的有拦截器在起作用,我们可以看到InterceptingHttpAccessor类中有一个setInterceptors方法,这里标记下
,
然后我们看看他是在哪里调用这份方法的,我们再回到LoadBalancerAutoConfiguration配置类中
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
看到这里再往resttemplate中添加拦截器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);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
这里就是做拦截作用了,实现真正的逻辑实在execute方法中,我们点进去看看
public
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
Server server = this.getServer(loadBalancer);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
} else {
RibbonLoadBalancerClient.RibbonServer ribbOnServer= new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
return this.execute(serviceId, ribbonServer, request);
}
}
第一行通过serviceID找到对应的服务实例均衡器,serviceId其实就是每个微服务的应用名称("")
loadBalancer 这里面保存了所有的服务实例,可用的,宕机的都在里面
然后通过调用getServer方法,使用均衡算法拿到可用的服务实例(从几个中选择一个,达到均衡的作用),返回server,然后拿到这个server就可以继续执行目标请求,我们在这里也打一个断点,到目前位置,我们的第一步已经完成,我们现在要做的就是模拟一个请求,跟踪请求来调试源码,
源码调试
我们还是使用上一节的项目来调试。如果需要获取项目,请关注我的公众号“乐哉码农”,回复“eureka”获取资料
启动项目,在启动过程中,我们来验证下,ribbon是否拿到所有被loadbalence注解的resttemplate
我们可以看到resttemplates里面有两个resttemplate,我们在restteplate的定义中是否有两个被loadbalence注解
确实有两个,最上面一个没有被loadbalenced注解,这里写两个是为了让我们理解的更加清晰点,没有实际作用,
我们放掉断点,看到他到了这里
在这里,他为我们的template添加了拦截器,我们跳过,让项目启动完成
访问接口http://localhost:8082/getOrderForLoadBalence,进入断点
往下走,看到进入了拦截器(LoadBalancerInterceptor)里面的断点
在这里面获取到了我们的servicename,然后通过servicename去均衡器里面获取服务地址,继续往下走,
我们根据serviceid获取到了一个均衡器,我们看看这里面存的什么
可以看到upserverlist里面保存了两个地址,这个地址我们应该很熟悉,就是我们启动的两个服务实例,所以说明
getLoadBalancer方法根据serviceid拿到了可用的服务实例,将我们的服务名转换成了具体的实例地址,因为我们最终肯定只调用一个服务地址,所以我们继续往下面看,
往下走一步发现得到了一个server,这个server绑定的地址是8085端口,所以可以看出这里使用了均衡算法,返回一个服务实例给我们,后面肯定是拿着这个地址去替换我们现有的请求地址去发送请求,后的的不是我们这边关注的,所以整个源码分析也就到这里结束了。
1.禁用eureka
当我们在resttemplate上面添加loadbalence注解后,就可以使用服务名去调用,如果我们向关闭这个功能,可以使用ribbon.eureka.enable=false
2.配置接口地址列表
如果我们关闭了eureka之后,我们还想用服务名去调用,就需要手动配置服务配置列表
服务名.ribbon.listOfServers=IP:PORT1,IP:PORT2
3.配置负载均衡策略
服务名.ribbon.NFLoadBalencerRuleClassName=策略class全类名
4.超时时间
ribbon中有两种和超时时间相关的配置
ribbon.COnnectTimeout=2000 请求连接的超时时间
ribbon.ReadTimeout=5000 请求处理的超时时间
可以在前面加上具体的服务名,为指定的服务配置
5.并发参数
ribbon.MaxTotalCOnnections=500 最大连接数
ribbon,MaxCOnnectionsPerHost=500 每个host最大连接数
5.重试机制1
在集群环境中,用多个节点来提供相同的服务,当一个节点出现故障时,nginx会继续请求另外一个节点,而在eureka中,牺牲了数据一致性,保证了AP原则,有可能在某一个节点出现故障时,他短时间内还不会将这个节点去移除,他会在一段时间内等待这个节点重启,所以在这个时间段内,eurekaui一直将这个注册表保存在可用的服务列表中,导致我们去请求时,请求失败,这是我们客户端发现请求失败后就必须进行重试操作,在ribbon可以通过服务名.ribbon.NFLoadBalencerRuleClassName=xxx来指定重试机制
6.重试机制2
除了使用ribbon自带的重试机制,还可以集成spring-retry,
ribbon.maxAutoRetries=1 当前实例重试次数
ribbon.maxAutoRetriesNextServer=3 切换节点的重试次数
ribbon.okToRetryOnAllOperations=true,对所有请求进行重试
ribbon.retryableStatusCodes=500,402 对指定错误代码进行重试
1.ribbon是什么?
答: Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。
2.ribbon是什么类型的负载均衡?
答:ribbon实现的是客户端负载均衡
3.常见的实现了负载均衡有哪些?
nginx(集中式),ribbon(客户端),F5(集中式)
4.负载均衡的算法都有哪些?
IPhash,轮询,权重
通过本章的学习,对ribbon和负载均衡有了一个整体的学习,通过源码跟踪,了解了其负载均衡实现的原理,最后通过讲解一些ribbon的配置,对ribbon的常见自定义配置有了大概的理解,后面一章将会介绍feign的使用,使用feign更加快速高效的调用远程服务。
欢迎大家关注我的公众号,回复“资料领取”,获取人工智能、大数据等更多学习视频资料,如有侵权,请联系作者立即删除!