Ribbon简介
什么是Ribbon?
Ribbon是Netflix发布的负载均衡器,它可以帮我们控制HTTP和TCP客户端的行为。只需为Ribbon配置服务提供者地址列表,Ribbon就可基于负载均衡算法计算出要请求的目标服务地址。
Ribbon默认为我们提供了很多的负载均衡算法,例如轮询、随机、响应时间加权等——当然,为Ribbon自定义负载均衡算法也非常容易,只需实现IRule
接口即可。
spring-cloud-starter-netflix-eureka-client
已经包含 spring-cloud-starter-netfilx-ribbon
,故而无需额外添加依赖。@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
只需在RestTemplate
上添加LoadBalanced
注解,即可让RestTemplate整合Ribbon!
调用
@GetMapping("/users/{id}") public User findById(@PathVariable Long id) { // 这里用到了RestTemplate的占位符能力 User user = this.restTemplate.getForObject( "http://microservice-provider-user/users/{id}", User.class, id ); // ...业务... return user; }
我们将请求的目标服务改成了http://microservice-provider-user/users/{id}
,也就是http://{目标服务名称}/{目标服务端点}
的形式,Ribbon会自动在实际调用时,将目标服务名替换为该服务的IP和端口。
WARNING
事实上,这里的目标服务名称,在Ribbon里叫虚拟主机名
,主机名是不能包含_
等特殊字符的——这意味着,一般不建议配置spring.application.name = xxx_xxx
,如果你的应用名称一定带有下划线这种字符,
那么请额外配置eureka.instance.virtual-host-name = 一个合法的主机名
,否则Ribbon将会提示虚拟主机名不合法的异常(在早期的版本则是报空指针)!
什么时候用Ribbon,用在哪?
上面知道Ribbon实现在客户端的负载均衡,所以当我们需要调用多台部署了相同项目的服务器时,就可以使用Ribbon。既然时客户端的负载均衡,那自然是在调用方使用了。
比如服务调用方A调用服务提供方B/C/D,就可以再A使用Ribbon,负载均衡的调用BCD。
深入
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { }
可以看到类里面是空的。那想到在springboot中,有一个申明式的注解,必定在其同名的包下面会有一个这样的(xxxAutoConfiguration)配置类,去配置这个注解,
我们定位到该包下面,看到同名的包下面确实有这样一个配置类LoadBalancerAutoConfiguration
点进去是这样的:
@Configuration(//@Configuration:注解可以用java代码的形式实现spring中xml配置文件配置的效果。 proxyBeanMethods = false ) @ConditionalOnClass({RestTemplate.class})//@ConditionalOnClass:其用途是判断当前classpath下是否存在指定类,若是则将当前的配置装载入spring容器 @ConditionalOnBean({LoadBalancerClient.class})//@ConditionalOnBean:当给定的在bean存在时,则实例化当前Bean @EnableConfigurationProperties({LoadBalancerRetryProperties.class}) public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired( required = false )
//这里面会注入有@loadbalence注解的所有的restTemplate实例 private List<RestTemplate> restTemplates = Collections.emptyList(); @Autowired( required = false ) //还有很多。。。 }
@EnableConfigurationProperties:如果该类只使用了@ConfigurationProperties注解,然后该类没有在扫描路径下或者没有使用@Component等注解,导致无法被扫描为bean,那么就必须在配置类上使用@EnableConfigurationProperties注解去指定这个类,这个时候就会让该类上的@ConfigurationProperties生效,然后作为bean添加进spring容器中
InterceptingHttpAccessor
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations { //。。。。 }
看来真的有拦截器在起作用,我们可以看到InterceptingHttpAccessor类中有一个setInterceptors方法
public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) { Assert.noNullElements(interceptors, "'interceptors' must not contain null elements"); if (this.interceptors != interceptors) { this.interceptors.clear(); this.interceptors.addAll(interceptors); AnnotationAwareOrderComparator.sort(this.interceptors); } }
然后我们看看他是在哪里调用这份方法的,我们再回到LoadBalancerAutoConfiguration配置类中,看到这段方法:
@Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) { return (restTemplate) -> { List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; }
看到这里在往resttemplate中添加拦截器loadBalancerInterceptor,我们点击这个拦截器进去,主要看这段代码
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { URI originalUri = request.getURI();//获取URI String serviceName = originalUri.getHost();//获取serviceName 就是每个微服务的应用名称 Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); LoadBalancedRetryPolicy retryPolicy = this.lbRetryFactory.createRetryPolicy(serviceName, this.loadBalancer); RetryTemplate template = this.createRetryTemplate(serviceName, request, retryPolicy); return (ClientHttpResponse)template.execute((context) -> { ServiceInstance serviceInstance = null; if (context instanceof LoadBalancedRetryContext) { LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext)context; serviceInstance = lbContext.getServiceInstance(); } if (serviceInstance == null) { serviceInstance = this.loadBalancer.choose(serviceName);//查看这个choose方法,这里通过负载均衡选择一个server } ClientHttpResponse response = (ClientHttpResponse)this.loadBalancer.execute(serviceName, serviceInstance, this.requestFactory.createRequest(request, body, execution)); int statusCode = response.getRawStatusCode(); if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) { byte[] bodyCopy = StreamUtils.copyToByteArray(response.getBody()); response.close(); throw new ClientHttpResponseStatusCodeException(serviceName, response, bodyCopy); } else { return response; } }, new LoadBalancedRecoveryCallback<ClientHttpResponse, ClientHttpResponse>() { protected ClientHttpResponse createResponse(ClientHttpResponse response, URI uri) { return response; } });
查看choose方法:
public ServiceInstance choose(String serviceId, Object hint) { Server server = this.getServer(this.getLoadBalancer(serviceId), hint);//通过serviceID找到对应的服务实例均衡器,loadBalancer 这里面保存了所有的服务实例,可用的,宕机的都在里面 return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); }
使用均衡算法拿到可用的服务实例(从几个中选择一个,达到均衡的作用),返回server,然后拿到这个server就可以继续执行目标请求。
均衡器里面会保存allServerList,里面会有upServeLlist里面是我们启动的服务实例,然后使用负载均衡算法选择一个服务。
ribbon的常见配置
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最大连接数
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?