您好,登录后才能下订单哦!
这篇“Ribbon如何使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Ribbon如何使用”文章吧。
Ribbon是一个客户端负载均衡工具,封装Netflix Ribbon组件,能够提供客户端负载均衡能力。
理解Ribbon最重要的就是理解客户端这个概念,所谓客户端负载均衡工具不同于Nginx(服务端负载均衡),Ribbon和应用程序绑定,本身不是独立的服务,也不存储服务列表,需要负载均衡的时候,会通过应用程序获取注册服务列表,然后通过列表进行负载均衡和调用。
Nginx独立进程做负载均衡,通过负载均衡策略,将请求转发到不同的服务上
客户端负载均衡,通过在客户端保存服务列表信息,然后自己调用负载均衡策略,分摊调用不同的服务
Ribbon的负载均衡有两种方式
和 RestTemplate 结合 Ribbon+RestTemplate
和 OpenFeign 结合
Ribbon的核心子模块
ribbon-loadbalancer:可以独立使用或者和其他模块一起使用的负载均衡API
ribbon-core:Ribbon的核心API
订单服务调用商品服务
配置过程 分两步
在订单服务中导入ribbon的依赖
<!--ribbon--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
配置 RestTemplate
订单服务调用商品服务
订单服务调用商品服务的链接 不能写成ip+端口号,需要写成商品服务的服务名称
重启 订单服务 测试负载均衡
Ribbon负载均衡简单版实现的流程
RestTemplate发送的请求是服务名称http://nacos-product/product/getProductById/1
获取
@LoadBalanced
注解标记的RestTemplate
RestTemplate
添加一个拦截器,当使用RestTemplate
发起http调用时进行拦截根据url中的服务名称 以及自身的负载均衡策略 去订单服务的服务列表中找到一个要调用的ip+端口号 localhost:8802
访问该目标服务,并获取返回结果
服务列表实际上是个map
Ribbon将所有标记@LoadBalanced
注解的RestTemplate
保存到一个List集合当中,具体源码如下:
@LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList();
具体源码位置是在LoadBalancerAutoConfiguration
中。
拦截器不是Ribbon的功能
RestTemplate添加拦截器需要有两个步骤,首先是定义一个拦截器,其次是将定义的拦截器添加到RestTemplate中。
实现ClientHttpRequestInterceptor
接口就具备了拦截请求的功能,该接口源码如下:
public interface ClientHttpRequestInterceptor { /** *实现该方法,在该方法内完成拦截请求后的逻辑内容。 *对于ribbon而言,在该方法内完成了根据具体规则从 *服务集群中选取一个服务,并向该服务发起请求的操作。 */ ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException; }
ribbon中对应的实现类是LoadBalancerInterceptor
具体源码如下:
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { private LoadBalancerClient loadBalancer; private LoadBalancerRequestFactory requestFactory; //省略构造器代码... @Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); /** *拦截请求,并调用loadBalancer.execute()方法 *在该方法内部完成server的选取。向选取的server *发起请求,并获得返回结果。 */ return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution)); } }
RestTemplate
继承了InterceptingHttpAccessor
,在InterceptingHttpAccessor
中提供了获取以及添加拦截器的方法,具体源码如下:
public abstract class InterceptingHttpAccessor extends HttpAccessor { /** * 所有的拦截器是以一个List集合形式进行保存。 */ private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(); /** * 设置拦截器。 */ public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) { this.interceptors = interceptors; } /** * 获取当前的拦截器。 */ public List<ClientHttpRequestInterceptor> getInterceptors() { return interceptors; } //省略部分代码... }
通过这两个方法我们就可以将刚才定义的LoadBalancerInterceptor
添加到有@LoadBalanced
注解标识的RestTemplate
中。具体的源码如下(LoadBalancerAutoConfiguration)省略部分代码:
public class LoadBalancerAutoConfiguration { /** * 获取所有带有@LoadBalanced注解的restTemplate */ @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); /** * 创建SmartInitializingSingleton接口的实现类。Spring会在所有 * 单例Bean初始化完成后回调该实现类的afterSingletonsInstantiated() * 方法。在这个方法中会为所有被@LoadBalanced注解标识的 * RestTemplate添加ribbon的自定义拦截器LoadBalancerInterceptor。 */ @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer( final List<RestTemplateCustomizer> customizers) { return new SmartInitializingSingleton() { @Override public void afterSingletonsInstantiated() { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } } }; } /** * 创建Ribbon自定义拦截器LoadBalancerInterceptor * 创建前提是当前classpath下不存在spring-retry。 * 所以LoadBalancerInterceptor是默认的Ribbon拦截 * 请求的拦截器。 */ @Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } /** * 添加拦截器具体方法。首先获取当前拦截器集合(List) * 然后将loadBalancerInterceptor添加到当前集合中 * 最后将新的集合放回到restTemplate中。 */ @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return new RestTemplateCustomizer() { @Override public void customize(RestTemplate restTemplate) { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } }
至此知道了ribbon拦截请求的基本原理,接下来我们看看Ribbon是怎样选取server的。
通过上面的介绍我们知道了当发起请求时ribbon会用LoadBalancerInterceptor
这个拦截器进行拦截。在该拦截器中会调用LoadBalancerClient.execute()
方法,该方法具体代码如下:
@Override public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { /** *创建loadBalancer的过程可以理解为组装选取服务的规则(IRule)、 *服务集群的列表(ServerList)、检验服务是否存活(IPing)等特性 *的过程(加载RibbonClientConfiguration这个配置类),需要注意 *的是这个过程并不是在启动时进行的,而是当有请求到来时才会处理。 */ ILoadBalancer loadBalancer = getLoadBalancer(serviceId); /** * 根据ILoadBalancer来选取具体的一个Server。 * 选取的过程是根据IRule、IPing、ServerList * 作为参照。 */ Server server = getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); return execute(serviceId, ribbonServer, request); }
通过代码我们可知,首先创建一个ILoadBalancer
,这个ILoadBalancer
是Ribbon的核心类。可以理解成它包含了选取服务的规则(IRule
)、服务集群的列表(ServerList
)、检验服务是否存活(IPing
)等特性,同时它也具有了根据这些特性从服务集群中选取具体一个服务的能力。 Server server = getServer(loadBalancer);
这行代码就是选取举一个具体server。 最终调用了内部的execute
方法,该方法代码如下(只保留了核心代码):
@Override public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException { try { //发起调用 T returnVal = request.apply(serviceInstance); statsRecorder.recordStats(returnVal); return returnVal; } catch (IOException ex) { statsRecorder.recordStats(ex); throw ex; } catch (Exception ex) { statsRecorder.recordStats(ex); ReflectionUtils.rethrowRuntimeException(ex); } return null; }
接下来看下request.apply(serviceInstance)
方法的具体做了那些事情(LoadBalancerRequestFactory中):
@Override public ClientHttpResponse apply(final ServiceInstance instance) throws Exception { HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer); //省略部分代码... /** * 发起真正请求。 */ return execution.execute(serviceRequest, body); }
看到这里整体流程的原理就说完了,接下来我们结合一张图来回顾下整个过程:
首先获取所有标识@LoadBalanced
注解的RestTemplate
(可以理解成获取那些开启了Ribbon负载均衡功能的RestTemplate
),然后将Ribbon默认的拦截器LoadBalancerInterceptor
添加到RestTemplate
中,这样当使用RestTemplate
发起http请求时就会起到拦截的作用。当有请求发起时,ribbon默认的拦截器首先会创建ILoadBalancer
(里面包含了选取服务的规则(IRule
)、服务集群的列表(ServerList
)、检验服务是否存活(IPing
)等特性)。在代码层面的含义是加载RibbonClientConfiguration
配置类)。然后使用ILoadBalancer
从服务集群中选择一个服务,最后向这个服务发送请求。
参考资料:https://www.jianshu.com/p/79b9cf0d0519
根据上述Ribbon的原理,可以知道IRule接口负责负载均衡的实现,具体如下:
规则名称 特点 AvailabilityFilteringRule 过滤掉一直连接失败的被标记为circuit tripped的后端Server,并 过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate 来包含过滤server的逻辑,其实就是检查status里记录的各个server 的运行状态 BestAvailableRule 选择一个最小的并发请求的server,逐个考察server, 如果Server被tripped了,则跳过 RandomRule 随机选择一个Server ResponseTimeWeightedRule 已废弃,作用同WeightedResponseTimeRule WeightedResponseTimeRule 权重根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低 RetryRule 对选定的负载均衡策略加上重试机制,在一个配置时间段内当 选择Server不成功,则一直尝试使用subRule的方式选择一个 可用的Server RoundRobinRule 轮询选择,轮询index,选择index对应位置的Server ZoneAvoidanceRule 默认的负载均衡策略,即复合判断Server所在区域的性能和Server的可用性 选择Server,在没有区域的环境下,类似于轮询(RandomRule) 其中RandomRule表示随机策略、RoundRobinRule表示轮询策略、WeightedResponseTimeRule表示加权策略、BestAvailableRule表示请求数最少策略等等
随机源码:
轮询源码:
默认是轮询 可以修改为任意的规则
修改为随机算法
创建具有负载均衡功能的RestTemplate实例
[@Bean](https://my.oschina.net/bean) @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }
使用RestTemplate进行rest操作的时候,会自动使用负载均衡策略,它内部会在RestTemplate中加入LoadBalancerInterceptor这个拦截器,这个拦截器的作用就是使用负载均衡。
默认情况下会采用轮询策略,如果希望采用其它策略,则指定IRule实现,如:
[@Bean](https://my.oschina.net/bean) public IRule ribbonRule() { return new BestAvailableRule(); }
这种方式对OpenFeign也有效。
修改为按照Nacos配置的权重进行负载均衡
在nacos中对集群进行权重的配置
在项目中,选择使用 NacosRule
Ribbon默认懒加载,意味着只有在发起调用的时候才会创建客户端
ribbon: eager-load: # 开启ribbon饥饿加载 enabled: true # 配置user-center使用ribbon饥饿加载,多个使用逗号分隔 clients: user-center
主要调整请求的超时时间,是否重试
如果业务没有做幂等性的话建议把重试关掉:ribbon.MaxAutoRetriesNextServer=0
# 从注册中心刷新servelist的时间 默认30秒,单位ms ribbon.ServerListRefreshInterval=15000 # 请求连接的超时时间 默认1秒,单位ms ribbon.ConnectTimeout=30000 # 请求处理的超时时间 默认1秒,单位ms ribbon.ReadTimeout=30000 # 对所有操作请求都进行重试,不配置这个MaxAutoRetries不起作用 默认false #ribbon.OkToRetryOnAllOperations=true # 对当前实例的重试次数 默认0 # ribbon.MaxAutoRetries=1 # 切换实例的重试次数 默认1 ribbon.MaxAutoRetriesNextServer=0
如果
MaxAutoRetries=1
和MaxAutoRetriesNextServer=1
请求在1s内响应,超过1秒先同一个服务器上重试1次,如果还是超时或失败,向其他服务上请求重试1次。那么整个ribbon请求过程的超时时间为:ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1)
以上就是关于“Ribbon如何使用”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。